Storage
Lix runs in memory by default. Choose a storage adapter when you need to keep data across restarts. Files, SQL, and version control use the same API.
In-memory (default)
Start with openLix() and no options. Data lives in memory for the lifetime of
the instance, making this useful for tests and trying Lix:
import { openLix } from "@lix-js/sdk";
const lix = await openLix();
// ... use it ...
await lix.close();
Local filesystem
Use @lix-js/storage-filesystem
in Node.js to persist a directory. Agents and tools can read and write its
ordinary files:
import { openLix } from "@lix-js/sdk";
import { FilesystemStorage } from "@lix-js/storage-filesystem";
const lix = await openLix({
storage: new FilesystemStorage({ path: "./repository" }),
});
Lix stores repository state in <repository>/.lix/.internal. Keep that state
with the directory to reopen it. Only regular files synchronize; symlinks and
special entries are excluded.
For selective sync, pass syncAllFiles: false and import paths with
storage.importPaths(paths). See Rust usage below.
Filesystem sync
A partial replica with on-demand sync runs locally alongside the authoritative server.
Use FilesystemStorage for project directories or mounted sandbox volumes.
It keeps ordinary files synchronized with the replica, which exchanges commits
with the server in the background.
import { openLix } from "@lix-js/sdk";
import { FilesystemStorage } from "@lix-js/storage-filesystem";
const lix = await openLix({
storage: new FilesystemStorage({ path: "/workspace/project" }),
server: {
mode: "partial_replica",
url: "https://example.com/lix/01936f4e-7b6c-7c3d-8f9a-123456789abc",
},
});
Point path at the directory your infrastructure mounts into the sandbox.
Each machine keeps its own replica. Share a branch to exchange changes, or
use separate branches for review. See
opening and reconnecting
for on-demand loading and offline behavior.
Remote mode
A classic client-server setup: your app sends requests through the Lix SDK,
and the server executes them against its repository. Pass server without storage.
Storage is managed on the server. Use LixRay or
your own host, and replace the example URL with your Lix
connection URL.
import { openLix } from "@lix-js/sdk";
const lix = await openLix({
server: {
url: "https://example.com/lix/01936f4e-7b6c-7c3d-8f9a-123456789abc",
},
});
Each operation requires a network round trip; successful writes are accepted by the server. For ordinary files on disk, use filesystem sync.
Browser OPFS
OpfsStorage persists Lix in the browser across reloads. Add
server: { url: repositoryUrl, mode: "partial_replica" } alongside storage
to create a partial replica with on-demand sync.
Opening loads bounded metadata. SQL fetches missing native inputs and retains them locally. Reads and writes whose dependencies are resident run locally, including offline; local commits upload in the background. Background synchronization advances the local state without making warm foreground operations wait for the server.
import { openLix } from "@lix-js/sdk";
import { OpfsStorage } from "@lix-js/storage-opfs";
const lix = await openLix({
storage: new OpfsStorage({ name: "acme" }),
server: {
mode: "partial_replica",
url: "https://example.com/lix/01936f4e-7b6c-7c3d-8f9a-123456789abc",
},
});
Install @lix-js/storage-opfs.
SQLite Wasm persists the replica in the browser's Origin Private File System
(OPFS). Reuse name within the same browser origin to reopen it after reloads.
Omit server for a browser-only repository. Workers and tabs can share the
same name through the package's storage worker and cross-tab Web Lock.
These configurations create a partial replica with on-demand sync. Opening
loads bounded metadata; SQL fetches missing native inputs and caches them locally.
Reads and writes whose dependencies are resident execute locally, including offline. Local
commits upload in the background. server.mode defaults to "remote", which
rejects storage; the partial-replica opt-in is required. See
opening and reconnecting.
How storage adapters fit
The client configures its local adapter with storage. The host configures
server storage. In the diagram, SlateDB runs inside the server process and
uses S3 as its external backing store.
| Adapter | Available in | Stores data in |
|---|---|---|
Memory (default) | JavaScript, Rust | Temporary in-memory data |
FilesystemStorage | JavaScript (Node.js), Rust | Files and repository state on disk |
OpfsStorage | JavaScript (browser) | Browser OPFS through SQLite Wasm |
RocksDB | Rust | Local disk for native embedded persistence |
SlateDB | Rust | S3-compatible object storage |
The reference server
uses SlateDB; custom hosts can choose another adapter. The separate server
option controls remote execution or replica synchronization. See the
connection reference.
JavaScript sync clients require a durable adapter, such as OpfsStorage or
FilesystemStorage. Use Snapshots to export or restore a
complete repository.
Rust filesystem adapter
In Rust, start directory synchronization explicitly:
use lix::open_lix;
use lix_storage_filesystem::FilesystemStorage;
let storage = FilesystemStorage::new("./repository").open()?;
let lix = open_lix().with_storage(storage.clone()).await?;
storage.start_sync(&lix).await?;
storage.sync_disk_to_lix().await?;
storage.stop_sync().await?;
The adapter owns directory synchronization after start_sync(). Stop it before
immediately reopening the directory; dropping the final instance attempts
shutdown.
Automatic format upgrades
Opening a supported older format copies it into an inactive storage epoch,
validates it, then atomically publishes it. No separate migration call is
needed. Report progress with Rust's OpenProgressSink or JavaScript's
onProgress.
Lix retains the previous generation for rollback. Budget roughly 2× the live repository size plus WAL, compaction, and temporary-write space. Upgrade time depends on data size, storage, and hardware; available capacity is the practical limit. Later upgrades reuse the inactive epoch and reclaim legacy storage asynchronously.
Run the RocksDB capacity profile against a released-v75 repository:
LIX_MIGRATION_PROFILE_MIB=256 cargo test -p lix-storage-rocksdb \
--features storage-benches --test migration_profile --release -- \
--ignored --nocapture
Closing
Always await lix.close() in scripts and tests. Long-lived servers can hold one Lix instance for the process lifetime.
Custom storage (Rust)
Adapters implement ordered transactional key-value storage, without parsing Lix SQL or interpreting branches and changes.
Implement three asynchronous traits from lix::storage: Storage, StorageRead, and StorageWrite. An implementation must guarantee:
- Space isolation. Keys in different spaces never collide.
- Coherent read views. A read handle observes one coherent view for its lifetime.
- Ordered scans. Scans return keys in ascending byte order.
- Atomic commits. A commit publishes all staged mutations or none.
- Persistence. Persistent implementations define their durability boundary.
Memoryis ephemeral.
Validate an implementation with the public conformance suite:
use lix::storage::conformance::run_storage_conformance;
let report = run_storage_conformance(&factory).await;
report.assert_no_failures();
Backends without an existing adapter, such as PostgreSQL or Cloudflare D1, need such an implementation.