# Concepts
Source: https://selfstore.dev/docs/concepts
The selfstore mental model - the simple store, snapshots, durable homes, backups, sync and the headless status - in one page.
selfstore has one mental model, and once it clicks the whole API reads
naturally.
There is no server in this picture. The cloud (if you use one) holds only an opaque encrypted ZIP; every merge runs on your devices.
## The simple store
`const store = await selfstore('app')` is the front door. It OWNS the data:
you `put` and `all` through it, and it handles saving, syncing and merging.
Convention over configuration, every default overridable:
- an IndexedDB cache named after the app (in-memory where IndexedDB does not
exist, so tests and SSR just work),
- schema version 1,
- debounced auto-save on every mutation,
- the browser sync moments (tab focus, network return, an interval, tab hide)
wired automatically (`autoSync: false` to take over).
Everything deeper lives on `store.advanced` - the full `createLocalStore`
store, the same instance. Start simple; reach down without rewiring. That
deeper surface is the [advanced guide](https://selfstore.dev/docs/advanced).
## Snapshot
The unit the backup and merge layers exchange:
```ts
type Snapshot = {
collections: Record; // named arrays of plain JSON records
files: { id: string; name: string; mime: string; bytes: Uint8Array }[];
};
```
You rarely build one by hand with the simple store, but it is what a backup
file contains and what `gather()`/`apply()` move on the advanced store. Two
constraints: every record carries a **string id** (or you map one via the
[sync config](https://selfstore.dev/docs/sync)), and collection names starting with `__` are
reserved for the library's own bookkeeping.
## Working copy
The IndexedDB cache that makes offline the normal case. Writes land there
(debounced); `selfstore(app)` restores from it on open. It is **encrypted at
rest**: your records and file blobs are AES-256-GCM envelopes under a
non-extractable per-device key, unsealed only in memory as your app reads them.
That defeats casual inspection, partial exfiltration and disk forensics; it
does not stop code running in your own origin. For data where even a full copy
of the browser profile must stay sealed, [cacheLock](https://selfstore.dev/docs/security#cachelock)
keeps the key in memory only.
## Durable home
Where the data survives a cleared browser: a disk file, the user's Google
Drive, any WebDAV server, an S3-compatible bucket. Attached with one call and
one user gesture (`store.connectFile()` / `connectDrive(auth)` /
`connectWebdav(config)` / `connectS3(config)`), rebuilt silently on the next
open. The home receives an encrypted ZIP; the cloud only ever holds opaque
bytes.
Connecting resolves to an honest [`ConnectOutcome`](https://selfstore.dev/docs/quick-start):
`merged`, `started`, `manual` or `cancelled`. A home is any object
implementing `BackupTarget`; the built-ins are conveniences, not privileges
([write your own](https://selfstore.dev/docs/advanced)).
## Backup
A portable, self-describing ZIP of one snapshot. `store.downloadBackup()` and
`store.exportBackup()` produce one; `store.importBackup(file)` reads one in.
Unencrypted it opens in any archive tool; encrypted (while `protect()` is on)
it is AES-256-GCM over an Argon2id-derived key with the parameters stored per
file. The [format](https://selfstore.dev/docs/format) is specified independently of the library.
## Sync
The same home, connected from a second device, becomes a meeting point. A
Hybrid Logical Clock plus per-collection strategies converge replicas
deterministically, on-device; the storage stays dumb. Conflicts are journaled
with both values instead of silently dropped. Details: [sync](https://selfstore.dev/docs/sync) and
[peers](https://selfstore.dev/docs/peers) for sharing between people.
## Headless status
One descriptor drives your persistence UI: `store.status`
(`{ state, severity, actionable, action?, labelKey }`) and `store.error`
(`{ code, labelKey, message } | null`). No colours, no copy: you map
`labelKey` through your i18n and `severity` through your design tokens.
The `action` field is load-bearing. A transient failure (an offline blip, a
cold-starting server) is **not** attention-worthy: the store retries silently.
Only a genuine loss of access sets `action`, and only to one of two gestures:
`unlock` (`store.unlock(password)` for a locked encrypted home) or `reconnect`
(`store.reconnect()` after real auth loss).
## Schema versions
`schema` versions your **data shape**, not your app release. Bump it when the
shape changes and pass `migrate(fromVersion, snap)`; older backups then
upgrade on read. Data written by a **newer** schema than the running app
refuses loudly with `SCHEMA_TOO_NEW` instead of corrupting silently.
Map of this site for a model: https://selfstore.dev/llms.txt
Every page in one file: https://selfstore.dev/llms-full.txt