Query Advanced
Persistence
Reference data an application reads on every visit can paint from IndexedDB before the network answers. Only endpoints that ask for it are stored, per signed-in user.
Markdown version of this page, for assistants (Markdown for AI)
Note ./persist is experimental: the API can still change in a minor release.
Stability per entry point: Entry points.
On this page
Setup
export const persistence = persist({
store: indexedDbStore(),
// One partition per signed-in user; null (signed out) reads and writes nothing.
session: () => (session.userId === null ? null : String(session.userId)),
buster: BUILD_ID, // a new build drops every record
maxAge: '24h',
eager: [listMarkets] // restored before `ready` resolves
});
export const client = createClient({
transport: http({ base: '/' }),
modules: [persistence]
});export const listMarkets = defineQuery({
get: 'api/markets',
result: type<Market[]>(),
entity: 'Market',
tags: ['Markets'],
fresh: '10m',
persist: true // stored by ./persist, restored on the next visit
});persist: true on the definition is the only opt-in; every other endpoint is never
written. persist: { maxAge: '1h' } sets its own age.
Restore
- Records are restored lazily: an entry reads its record when it is first used, unless its
endpoint is listed in
eager. - While an entry's record is read, its request waits (no longer than the eager restore may hold
ready; the binding showsloading). A record inside itsfreshwindow is shown and no request is sent; an older one is shown and refetched in the background, asrefreshing.refetch(),load()andep.fetch()never wait. - A record never replaces a newer value.
persistence.readyresolves when the eager restore is done; it never rejects.- When IndexedDB is unavailable (a private window), persistence is off for the session and queries use the network.
Measured (benchmark P7, a persisted list of 250 rows, reload): first data paint 115.2 ms after navigation start from IndexedDB, against 870 ms from the network without the module, where the test server answers after a fixed delay.
Write-behind
Commits mark entries dirty; they are written in batches at background priority, one transaction per flush, and only the changed records. Longest task while flushing a thousand dirty entries among ten thousand persisted: 4.104 ms (engine benchmark B12).
Sessions and sign-out
export async function signOut() {
await persistence.forget(); // delete this user's records
session.userId = null;
client.clear();
}Each session value has its own partition. When session() becomes null, nothing is read or written; forget() deletes the partition of the
current session.
Options
| Option | Type | Default | Effect |
|---|---|---|---|
store | PersistStore | required | Where records live: indexedDbStore(), or your own store. |
session | () => string | null | required | Reactive. The partition; only a hash of it is stored. null: read and write nothing. |
buster | string | – | A build or schema version; a different value drops every record. |
maxAge | Duration | '24h' | Older records are not restored; an endpoint’s persist.maxAge wins. |
maxBytes | number | 8 MiB of text | Stored text per partition; the oldest records are evicted first. |
eager | QueryEndpoint[] | – | Restored before ready resolves, at setup and on every session change. |
compress | number | false | 262144 | gzip values longer than this many characters. |
Persist JSON data only: a Date or a class instance in a value is reported in development
and restores as JSON.
Own store
A PersistStore has four methods: get, put (keeps the
newer updatedAt), delete and list. persistStoreSuite() from ./testing runs the contract's cases against your store, and memoryStore() is the in-memory store for tests.
Reference
- @svelte-lean/query/persist
- Server rendering: the other way to start with data