Save and restore SQLite checkpoints
Save a virtual filesystem snapshot under a name, then load it into a fresh volume later. The application owns the SQLite database and decides when to capture and restore each checkpoint.
This guide uses Bun and the supported @effect/sql-sqlite-bun provider. It assumes you can already create a core
volume. For an introduction to the available packages, see Choose a package.
If snapshots are new to you, first read Build and transport filesystem snapshots.
Install the persistence package
Install the persistence package and the matching Bun SQLite and crypto providers:
npm install @effect-vfs/persistence@latest
npm install "@effect/sql-sqlite-bun@$(npm view @effect-vfs/persistence peerDependencies.effect)" "@effect/platform-bun@$(npm view @effect-vfs/persistence peerDependencies.effect)"@effect-vfs/persistence includes @effect-vfs/core. The SQLite provider is a separate runtime dependency because
the persistence package does not choose or open a database for you.
Configure the checkpoint store
Choose limits for the encoded snapshot and the filesystem data it can contain. The store applies the same limits when it saves and loads a checkpoint.
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import { CheckpointError, CheckpointStore } from "@effect-vfs/persistence"
import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import * as SqliteClient from "@effect/sql-sqlite-bun/SqliteClient"
import { ByteSize, Effect, Layer } from "effect"
const limits = {
maxEncodedBytes: ByteSize.megabytes(10),
maxRecords: 10_000,
maxEntries: 20_000,
maxDecodedBytes: ByteSize.megabytes(5)
}
const Database = SqliteClient.layer({ filename: "checkpoints.sqlite" })
const Checkpoints = CheckpointStore.layer(limits).pipe(
Layer.provide(Layer.effectDiscard(CheckpointStore.migrate)),
Layer.provide(Database)
)Use a file-backed database when another process must load the checkpoint. An in-memory database such as :memory:
does not survive the process that created it.
CheckpointStore.migrate creates the package tables. Run it during application startup before serving checkpoint
work. It is safe to run again on later startups. CheckpointStore.layer does not run migrations by itself.
Save a named checkpoint
Capture a snapshot explicitly and pass it to save:
const save = Effect.gen(function* () {
const store = yield* CheckpointStore
const volume = yield* Vfs.fromFixture({
entries: [
{
kind: "file",
path: "/hello.txt",
bytes: new TextEncoder().encode("hello")
}
]
})
yield* store.save("run-42", yield* volume.snapshot)
})
await Effect.runPromise(
save.pipe(Effect.provide(Checkpoints), Effect.provide(BunCrypto.layer))
)Each name is create-only. Saving run-42 again fails with AlreadyExists and leaves the original checkpoint
unchanged. Later changes to volume are not written automatically; capture another snapshot and save it under a new
name.
Load and restore the checkpoint
Load the named snapshot and use it to create a fresh, independent volume:
const restore = Effect.gen(function* () {
const store = yield* CheckpointStore
const snapshot = yield* store.load("run-42")
const volume = yield* Vfs.fromSnapshot(snapshot, {
maxBytes: ByteSize.megabytes(5)
})
const caller = yield* volume.caller()
return new TextDecoder().decode(yield* caller.readFile("/hello.txt"))
})
const text = await Effect.runPromise(
restore.pipe(Effect.provide(Checkpoints), Effect.provide(BunCrypto.layer))
)
console.log(text)The program prints:
helloThe restore program can run in another process as long as it opens the same checkpoints.sqlite file and provides
the same checkpoint layer setup.
Handle checkpoint failures
CheckpointError identifies naming, lookup, and SQLite failures. Handle it by tag, then inspect its code:
const handleCheckpointError = (error: CheckpointError) => {
switch (error.code) {
case "InvalidName":
return Effect.logError(
"Use a nonempty checkpoint name of at most 255 UTF-8 bytes"
)
case "NotFound":
return Effect.logWarning("The checkpoint does not exist")
case "AlreadyExists":
return Effect.logWarning("The checkpoint name is already in use")
case "Storage":
return Effect.logError("SQLite checkpoint operation failed", error.cause)
}
}
const load = Effect.gen(function* () {
const store = yield* CheckpointStore
return yield* store.load("run-42")
}).pipe(Effect.catchTag("CheckpointError", handleCheckpointError))The error also records the operation (save, load, or migrate) and, when applicable, the checkpoint name.
Storage preserves the underlying failure in cause.
Snapshot validation failures remain ImageError. They include invalid limits, corrupt or
unsupported stored images, and images that exceed the configured budgets.
While snapshot version 1 is being solidified, a checkpoint written by an earlier schema revision may fail core validation. Regenerate that checkpoint. The persistence package stores snapshot bytes unchanged and does not migrate them.
Understand database ownership and commits
Keep the application-provided SQLite client alive for as long as the checkpoint store uses it. The persistence
package owns the effect_vfs_checkpoints and effect_vfs_checkpoint_migrations tables, while your application owns
the database file, driver configuration, migration coordination, backups, and connection lifetime.
save participates in an enclosing Effect SQL transaction. It does not commit that outer transaction. Without an
outer transaction, a successful save has completed its insert. SQLite uniqueness also ensures that only one of two
competing saves can claim a name.
An interruption can arrive after SQLite commits but before the caller observes success. If you retry the same name,
handle AlreadyExists as an uncertain result: verify that the existing checkpoint is the one you intended instead of
assuming that every duplicate is safe to ignore.
The package does not configure SQLite busy timeouts, journaling, synchronization, or power-loss durability. Choose those settings according to your application's requirements.
This release does not provide checkpoint listing, replacement, deletion, history, or automatic saving. For the full
service and error signatures, see the CheckpointStore API reference.