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.

Lix runs on one device with a choice of memory, filesystem, or browser OPFS storage adapter. No server is required.

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.

A machine or sandbox runs Replica Lix with FilesystemStorage. Agents and tools read and write ordinary project files. The replica synchronizes with Authoritative Lix on a server backed by SlateDB and S3.
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.

The client uses the Lix SDK to make API calls to Authoritative Lix on the server, with no local storage. The server uses a SlateDB storage adapter backed by S3.
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.

A browser runs Replica Lix with a SQLite storage adapter backed by OPFS. It synchronizes with Authoritative Lix on a server, whose SlateDB storage adapter uses S3.
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.

AdapterAvailable inStores data in
Memory (default)JavaScript, RustTemporary in-memory data
FilesystemStorageJavaScript (Node.js), RustFiles and repository state on disk
OpfsStorageJavaScript (browser)Browser OPFS through SQLite Wasm
RocksDBRustLocal disk for native embedded persistence
SlateDBRustS3-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:

  1. Space isolation. Keys in different spaces never collide.
  2. Coherent read views. A read handle observes one coherent view for its lifetime.
  3. Ordered scans. Scans return keys in ascending byte order.
  4. Atomic commits. A commit publishes all staged mutations or none.
  5. Persistence. Persistent implementations define their durability boundary. Memory is 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.

Sponsor
SponsoredKunjungi sekarang
Promo