From SWR FULL

SWR and data-query have a lot in common. A URL is the key, revalidation is declared, and the API stays small. Most of the translation is mechanical. The declaration sits outside the component, and writes belong to the same primitive instead of a separate hook.

The mechanical part

// SWR
const { data, error, isLoading } = useSWR('/api/articles', fetcher, {
    revalidateOnFocus: true,
    refreshInterval: 30000
});

// WildflowerJS
wildflower.query('articles', {
    from: '/api/articles',
    key: 'id',
    refresh: ['focus', 30]        // SECONDS, not milliseconds
});
<ul data-query="articles">
    <template><li data-bind="title"></li></template>
</ul>
<p data-show="$articles.isLoading">Loading…</p>

Where SWR returns { data, error, isLoading } to a component, a query publishes the same state on a named entity that markup binds directly: $articles.rows, $articles.error, $articles.isLoading, plus isStale, lastSync, count, and pendingWrites.

SWRWildflowerJSNotes
the key argumentfromA URL string, or a function.
fetchernothing, or a function fromThe engine fetches and parses JSON itself.
global fetcher in SWRConfigwildflower.config({ headers })Scoped by origin; 'self' is this page's own.
revalidateOnFocus'focus' rung
revalidateOnReconnect'reconnect' rung
refreshIntervala bare number rungSeconds here, milliseconds there.
dedupingInterval'fresh:N' rungGates event rungs rather than deduping calls.
fallbackDatainitial
keepPreviousDataalways onRows are never blanked by a refresh; isStale marks the gap.
conditional key (null)an unresolved :tokenWaits rather than fetching. Nothing to write.
mutate()write(), patch(), invalidate()See below.

Conditional fetching

SWR's idiom is a null key: useSWR(slug ? `/api/articles/${slug}` : null, fetcher). The equivalent here is to put the value in the URL and let the query wait for it.

wildflower.query('article', {
    from: '/api/articles/:slug',
    key: 'slug',
    params: () => ({ slug: wildflower.getStore('route').slug })
});

Until route.slug has a value, nothing is requested and nothing is reported as an error. The query reports isLoading, since there is no data yet and nothing has failed, and it loads when the router sets the value and calls refresh().

Writes

SWR splits reading and writing. mutate() manipulates the cache, useSWRMutation handles remote writes, and the optimistic option asks you to supply both the optimistic data and the rollback behavior. Here the write side is part of the same declaration.

// SWR
await mutate('/api/articles', updateArticle(article), {
    optimisticData: (current) => current.map(a => a.id === article.id ? article : a),
    rollbackOnError: true,
    revalidate: true
});

// WildflowerJS
wildflower.query('articles', {
    from: '/api/articles',
    key: 'id',
    to: '/api/articles/:id',
    body: item => item
});

wildflower.getQuery('articles').write({ id: 42, title: 'Renamed' });

You do not write the optimistic projection because the key and the field merge determine it, and rollback is field-level rather than whole-list: only the fields that write still owns revert, so a second edit in flight on the same row survives the first one's failure.

mutate() used purely as a local cache write, with no request, maps to patch(). mutate() used as "just revalidate" maps to invalidate().

Structural differences

Declarations are global. SWR queries are declared inside the component that uses them, and two components asking for the same key share one cache entry automatically. Here you declare a query once, by name, and any number of elements bind it. That is less ceremony for shared data and less convenient for a one-off fetch that belongs to a single component, where a plain fetch() in init() is still the right tool.

The cache is not addressable. SWR keys by URL, and so does a query's result cache, so switching back to a URL you have already loaded paints from memory in both. What SWR gives you on top is the key itself. mutate(key) can rewrite or revalidate any entry from anywhere, and every entry stays live at once. A query's cache is a repaint source for one result set rather than a set of addressable entries, so two views needing different slices at the same time are two named queries. For stacking pages, append accumulates instead.