The snapshot utility captures a directory (or single file) from any fs-like
filesystem into a portable value, and restores it later --- recursively, and
preserving symlinks and binary content. It comes in three encodings:
a plain POJO, a compact CBOR Uint8Array, and a JSON Uint8Array.
It is published as its own package, which memfs already depends on:
import * as snapshot from '@jsonjoy.com/fs-snapshot';
For the simple "string/Buffer contents only" case, a `Volume` has built-in
[`toJSON` / `fromJSON`](/libs/memfs/volumes). Reach for snapshots when you need
to preserve symlinks, round-trip binary data faithfully, or serialize to bytes
(CBOR / JSON) for storage or transport.
Every function takes a target descriptor. The synchronous functions want a
synchronous fs; the async ones want a promises API:
// sync functions
{fs: FsSynchronousApi, path?: string, separator?: '/' | '\\'}
// async functions
{fs: FsPromisesApi, path?: string, separator?: '/' | '\\'}
path defaults to '/'. Any fs-like object works --- memfs, the real
fs, or an adapter.
toSnapshot* returns a SnapshotNode (a nested tuple, see below);
fromSnapshot* writes it back into a filesystem.
const snap = snapshot.toSnapshotSync({ fs, path: '/app' });
snapshot.fromSnapshotSync(snap, { fs: fs2, path: '/restored' });
const snap = await snapshot.toSnapshot({ fs: fs.promises, path: '/app' });
await snapshot.fromSnapshot(snap, { fs: fs2.promises, path: '/restored' });
Encoded as a CBOR Uint8Array --- compact and binary-safe, good for storing or
sending a whole tree over the wire.
const bytes = snapshot.toBinarySnapshotSync({ fs, path: '/app' }); // Uint8Array
snapshot.fromBinarySnapshotSync(bytes, { fs: fs2, path: '/app' });
const bytes = await snapshot.toBinarySnapshot({ fs: fs.promises, path: '/app' });
await snapshot.fromBinarySnapshot(bytes, { fs: fs2.promises, path: '/app' });
Same idea, JSON-encoded into a Uint8Array. Binary file contents are carried as
Base64 data-URL strings, so the result is valid JSON yet still round-trips
binary data.
const bytes = snapshot.toJsonSnapshotSync({ fs, path: '/app' }); // Uint8Array
snapshot.fromJsonSnapshotSync(bytes, { fs: fs2, path: '/app' });
| Format | To snapshot | From snapshot | Returns |
|---|---|---|---|
| POJO | toSnapshotSync / toSnapshot |
fromSnapshotSync / fromSnapshot |
SnapshotNode |
| CBOR | toBinarySnapshotSync / toBinarySnapshot |
fromBinarySnapshotSync / fromBinarySnapshot |
Uint8Array |
| JSON | toJsonSnapshotSync / toJsonSnapshot |
fromJsonSnapshotSync / fromJsonSnapshot |
Uint8Array |
The *Sync variants take a synchronous fs; the others take fs.promises and
return a Promise.
A snapshot follows the Compact JSON scheme: each node is a tuple whose first element is its type.
const enum SnapshotNodeType {
Folder = 0,
File = 1,
Symlink = 2,
}
Folder --- type, metadata, and a map of children:
[
0,
{},
{
'file.bin': [1, {}, new Uint8Array([1, 2, 3])],
},
];
File --- type, metadata, and contents as a Uint8Array:
[1, {}, new Uint8Array([1, 2, 3])];
Symlink --- type and metadata carrying the link target:
[2, { target: 'file.bin' }];
Because the format is structural and stable, a snapshot taken, restored into a fresh filesystem, and snapshotted again is deep-equal to the original.