# Advanced (the pull-model store) Source: https://selfstore.dev/docs/advanced When your state lives in its own reactive model, or you are writing a destination - createLocalStore, custom BackupTargets and the subpath imports. The simple store owns your data and covers most apps. Reach for `selfstore/advanced` in two cases: your state already lives in **its own reactive model** (Svelte runes, Redux, signals) and you want selfstore to follow it rather than own it, or you are **writing your own destination**. Everything here is the same install, one import away, and `store.advanced` on a simple store IS this store, the same instance, so the two styles compose instead of competing. ## The pull-model store You keep ownership of the data; selfstore pulls a snapshot to save and pushes one back to load. ```ts import { createLocalStore, indexedDbCache } from 'selfstore/advanced'; const store = createLocalStore({ app: 'my-app', schemaVersion: 1, gather: () => myApp.toSnapshot(), // your state -> Snapshot apply: (snap) => myApp.load(snap), // Snapshot -> your state cache: indexedDbCache('my-app'), // or memoryCache() in tests / SSR }); await store.init(); // hydrate from cache, restore target, converge myApp.onEveryChange(() => store.schedule()); // debounced auto-save window.addEventListener('pagehide', () => store.flush()); ``` The store logs a one-time warning for a mis-keyed record here rather than throwing, because, unlike the simple store, it cannot know which of your collections you consider mergeable. ## Destinations, and silent reboot ```ts import { driveTarget, fileTarget, webdavTarget, s3Target, gisDriveAuth } from 'selfstore/advanced'; const cache = indexedDbCache('my-app'); const auth = gisDriveAuth({ clientId: GOOGLE_CLIENT_ID }); const drive = () => driveTarget.connect({ auth, kv: cache.kv, fileName: 'my-app.zip' }); const store = createLocalStore({ app: 'my-app', schemaVersion: 1, gather, apply, cache, restoreTarget: async (kind) => (kind === 'drive' ? drive() : null), // silent reboot }); await store.init(); // On a user click (opens Google consent the first time): await store.attachTarget(await drive(), { strategy: 'merge', password }); // Disk file (Chromium) and WebDAV work alike: const f = await fileTarget.connect({ kv: cache.kv, fileName: 'my-app.zip' }); const w = await webdavTarget.connect({ kv: cache.kv, config: { url, username, password } }); ``` `strategy` is `'merge'` (default, multi-device), `'replace-local'` (load the target over local) or `'replace-remote'` (push local over the target). ## Write your own destination A `BackupTarget` is about six methods. The error protocol is the whole contract: throw `AuthExpiredError` for a **genuine** loss of access (the store raises the reconnect gate); anything else you throw reads as **transient** and is retried silently. ```ts import { AuthExpiredError, type BackupTarget } from 'selfstore/advanced'; const bucket: BackupTarget = { kind: 's3', // any string except 'device' / 'file-manual' label: 'my bucket', async save(blob) { const res = await put(KEY, blob); if (res.status === 401 || res.status === 403) throw new AuthExpiredError(); if (!res.ok) throw new Error(`upload failed: ${res.status}`); // transient return res.headers.get('etag'); // or null }, async load() { return (await getObject(KEY)) ?? null; }, async isReady() { return true; }, // false = transient blip; throw AuthExpiredError = genuine async reconnect() { return true; }, // user gesture; true if access re-established async disconnect() {}, // forget locally; never delete remote data abortInFlight() { controller.abort(); }, // optional (1.1.0): cut in-flight work now }; await store.connectTarget(bucket); // on a simple store // or advancedStore.attachTarget(bucket, { strategy: 'merge' }); ``` Get the `isReady()` split right - return `false` for "not right now", throw `AuthExpiredError` only for "the user must act" - and your users never see a false-alarm reconnect dialog. A complete, typechecked version ships as `examples/custom-target.ts` in the repository. The optional `abortInFlight()` (added in 1.1.0) lets the store cut your target's in-flight request the instant the user detaches it, instead of the detach waiting behind a request stuck on a woken-from-sleep radio. Wire it to an `AbortController` your `save`/`load` fetches share; skip it and detaching simply runs those calls to their own deadlines first, as before. ## Peers, groups and the merge engine Three deeper capabilities have their own pages, all built on this store: - **[Peers and groups](https://selfstore.dev/docs/peers):** share a store between people over read-only links (`store.attachPeer`), including passwordless groups from `selfstore/groups` (per-member keys, signed manifests). - **[Multi-device sync](https://selfstore.dev/docs/sync):** the strategies, the conflict journal and the honest limits; the bare merge engine is `selfstore/sync`. - **[Testing](https://selfstore.dev/docs/testing):** `memoryCache()` plus a fifteen-line in-memory target drive the whole loop in plain vitest, no browser. ## The subpaths | Import | What it is | | --- | --- | | `selfstore` | the simple store + the fluent backup-file API. Start here. | | `selfstore/advanced` | the pull-model store, caches, targets, status derivation, functional codec | | `selfstore/groups` | passwordless group encryption: identities, sealed envelopes, signed manifests | | `selfstore/sync` | the bare merge engine (HLC + strategies), for embedding in your own persistence | Map of this site for a model: https://selfstore.dev/llms.txt Every page in one file: https://selfstore.dev/llms-full.txt