Freshness and Live Data FULL
Every query answers one question in its declaration. When should this data refresh? The answer is a ladder of options, from a single fetch to a server push stream, all sharing the same state surface and the same safety rules.
The Refresh Ladder
| Rung | Declaration | Behavior |
|---|---|---|
| Once | 'once' (default) | Fetch when first observed. Refresh only when asked. |
| Poll | 30 | Refetch every N seconds while the query is on screen. |
| Conditional poll | 'etag:60' | Re-check every N seconds with a conditional request. The server answers 304 when nothing changed, and no data moves. |
| Focus | 'focus' | Refresh when the tab regains focus. |
| Reconnect | 'reconnect' | Refresh when the browser comes back online. |
| Server push | 'sse' | Hold an EventSource open and apply what the server sends. |
Rungs combine as an array. A typical dashboard query reads refresh: ['focus', 'etag:60']. Fresh when the user returns, cheap in the background.
Conditional Requests
When a response carries an ETag header, the query remembers it. Every conditional re-check sends If-None-Match, and a 304 answer costs a round trip with no body, no parsing, and no DOM work. Static file hosts and most API servers do this without any configuration on your part.
A Live Feed Without a Server
Liveness does not require SSE. A function source plus invalidate() turns any event-producing transport into a live query. Here the transport is a simulated in-page feed; in your app it could be a WebSocket or a sync engine:
The pattern:
wildflower.query('prices', {
from: () => feed.snapshot(), // any transport, any shape source
key: 'id'
});
// Each transport event invalidates; the engine re-runs the source
// and patches only the rows that changed.
feed.onTick(() => wildflower.getQuery('prices').invalidate());
Server-Sent Events
The 'sse' rung holds a browser-native EventSource open and treats the server as the source of truth for when data changes. Two kinds of message are understood:
- A message with a JSON body is the new result set. It applies directly, and any fetch already in flight is discarded in its favor.
- An empty message is an invalidation signal. The query re-checks its
fromURL conditionally.
wildflower.query('liveItems', {
from: '/api/items', // initial load and catch-up fetches
refresh: 'sse',
stream: '/api/items/stream' // dedicated event-stream endpoint
});
The stream option exists because routing a fetch and an event stream through one URL tends to fall apart at proxies and gateways. When your setup does serve both from one URL, omit stream and the query connects to from.
isStale and syncError, keeps every row on screen, and lets EventSource reconnect on its own. The reopen triggers one conditional fetch to catch up on anything missed.
Lifecycle: Active While Observed
A query is active while an element bound to it is in the document. When the last one leaves, the query waits about five seconds, then stands down. Timers stop, listeners detach, streams close, and any request in flight is aborted. The data and the remembered ETag stay put, so if the query is observed again it shows its last results instantly and catches up with a single conditional fetch.
The grace window makes this invisible. Toggling a panel with data-show, switching tabs, and list churn all pass through it without tearing anything down. You do not manage any of this, and there is nothing to configure.