Snapshots
Copy one committed version out as a portable file, and load it back into any store.
A snapshot is one committed version of a database, copied out as a single byte array and loadable into any block store. It is how you back a database up, seed a test fixture from real data, ship a prepared database as a static asset, or hand a colleague exactly what you were looking at.
From the database
The database copies itself out as the finished file, and loads one back:
const bytes = await db.exportSnapshot();
const restored = new MinnowDatabase(new MemoryBlockStore());
await restored.importSnapshot(bytes);Both take an onProgress callback, and both work the same way through the
worker client — there the file crosses the channel in slices, so the main
thread copies a few megabytes at a time instead of stalling on one clone of the whole database.
That is also what the devtools' Download database button does; see the devtools.
Everything below is the layer underneath, for when you want the records rather than the file, or a store that has no database in front of it.
import { encodeSnapshot, decodeSnapshot } from "@minnowdb/core/storage";
const bytes = await encodeSnapshot(await store.exportSnapshot());Loading
Into a fresh in-memory store:
import { MemoryBlockStore } from "@minnowdb/core/storage";
const db = new MinnowDatabase(MemoryBlockStore.fromSnapshot(await decodeSnapshot(bytes)));Or into IndexedDB, where it is durable and available on the next visit:
const store = await IndexedDbBlockStore.open({ name: "seeded" });
await store.importSnapshot(await decodeSnapshot(bytes), {
onProgress: ({ writtenBytes, totalBytes }) => {
setProgress(writtenBytes / totalBytes);
},
});The target store must be empty. Loading into a database that already holds one throws rather than
merging two histories. MemoryBlockStore.fromSnapshot builds a store around a snapshot instead,
which is the same thing for a store that was never used.
What it carries
Everything needed to read the data and to keep writing correctly afterwards:
- The block bytes the current manifest points at — verbatim, already compressed.
- One checkpoint manifest, the table catalog, the live segments, and the committed transactions that segment visibility resolves through.
- The row-id and auto-increment counters, so later writes continue past the high-water mark instead of colliding with rows that are there but hidden.
- Unique-key membership, so an insert that duplicates an existing key still conflicts.
- Full-text bases, when they already cover the exported version.
And what it deliberately drops: leases, query spill pages, garbage-collection and compaction job records, in-flight transactions, and version history. A database with ten thousand commits behind it loads as one clean version. Superseded blocks a compaction left behind are not copied, so a snapshot is usually smaller than the database it came from.
A full-text index that does not already cover the exported version is marked for rebuild rather than shipped stale. The index is a pruning accelerator that the scan re-verifies, so that costs a rebuild, never a wrong answer.
The container
A magic number, a format version, a gzipped JSON header, and the block payloads laid end to end. Blocks are already self-describing and doubly CRC-checked, so loading authenticates every block header without decompressing any payload — a corrupt file fails at load rather than mid-query.
Read the header alone when you only need to know what a file is:
import { readSnapshotSummary } from "@minnowdb/core/storage";
const summary = await readSnapshotSummary(bytes);
// { formatVersion, version, createdAt, tableCount, blockCount, payloadBytes, byteLength }Cheap enough to run against a large file before deciding whether to load it.
Exporting safely
MemoryBlockStore#exportSnapshot runs on the store's commit queue, so it always sees one
consistent version.
IndexedDbBlockStore#exportSnapshot reads across several transactions, so a concurrent commit
could move the version underneath it. Hold a backup lease across the call when another writer is
possible; a build script that is the only writer in its process does not need one.
MinnowDatabase#exportSnapshot delegates to whichever of those the database was built on, so it
inherits the same rule. A store that implements neither method says so rather than failing as a
missing property.
The snapshot format is versioned and validated, but like the block format it carries no compatibility promise while the library is version zero. Treat a snapshot as a copy of a database you can rebuild, not as an archival format.