sveltelean Query
Version v0.2.0

Setup

src/lib/query.ts
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]
});
src/lib/api/markets.ts
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 shows loading). A record inside its fresh window is shown and no request is sent; an older one is shown and refetched in the background, as refreshing. refetch(), load() and ep.fetch() never wait.
  • A record never replaces a newer value.
  • persistence.ready resolves 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

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

OptionTypeDefaultEffect
storePersistStorerequiredWhere records live: indexedDbStore(), or your own store.
session() => string | nullrequiredReactive. The partition; only a hash of it is stored. null: read and write nothing.
busterstring–A build or schema version; a different value drops every record.
maxAgeDuration'24h'Older records are not restored; an endpoint’s persist.maxAge wins.
maxBytesnumber8 MiB of textStored text per partition; the oldest records are evicted first.
eagerQueryEndpoint[]–Restored before ready resolves, at setup and on every session change.
compressnumber | false262144gzip 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