JavaScript API Reference

@lix-js/sdk exports openLix(), the generic JavaScript storage protocol, Value and bundledPluginArchives. @lix-js/storage-opfs and @lix-js/storage-filesystem provide concrete storage implementations. openLix() returns a local repository, a thin remote client, or a synchronized local replica.

import { openLix } from "@lix-js/sdk";

const lix = await openLix();

openLix()

const lix = await openLix(options?);

Options:

OptionTypeDescription
storageLixStorageLocal storage selected by a provider package. Omit it for memory.
serverRemoteLixServerOptions | SyncLixServerOptionsConnect directly to a server or synchronize a local replica.
telemetryLixTelemetryOptionsOptional onSpan(span) callback that receives telemetry spans. Local and sync modes only.

Connect to a remote server:

const lix = await openLix({
  server: {
    mode: "remote",
    url: "https://example.com/lix/01936f4e-7b6c-7c3d-8f9a-123456789abc",
    headers: () => ({ Authorization: `Bearer ${token}` }),
  },
});

Remote file content, SQL rows, and branches live on the server. Use headers for authentication and fetch when you need a custom fetch implementation.

Open a synchronized local replica by combining storage with sync mode:

import { OpfsStorage } from "@lix-js/storage-opfs";

const lix = await openLix({
  storage: new OpfsStorage({ name: "atelier" }),
  server: {
    mode: "sync",
    url: "https://example.com/lix/01936f4e-7b6c-7c3d-8f9a-123456789abc",
    headers: () => ({ Authorization: `Bearer ${token}` }),
  },
});

In sync mode, execute() resolves when the local transaction commits. Server synchronization continues in the background. See Collaboration and Sync for the complete behavior.

Use OpfsStorage to persist a local browser Lix across reloads:

import { openLix } from "@lix-js/sdk";
import { OpfsStorage } from "@lix-js/storage-opfs";

const lix = await openLix({
  storage: new OpfsStorage({ name: "atelier" }),
});

Use FilesystemStorage for a repository directory backed by RocksDB at <repository>/.lix/.internal/rocksdb:

import { openLix } from "@lix-js/sdk";
import { FilesystemStorage } from "@lix-js/storage-filesystem";

const lix = await openLix({
  storage: new FilesystemStorage({ path: "./repository" }),
});

Use selective synchronization when only explicit paths should be imported:

const storage = new FilesystemStorage({
  path: "./repository",
  syncAllFiles: false,
});
const lix = await openLix({ storage });
await storage.importPaths(["notes/today.md"]);

Call storage.syncDiskToLix() to run one manual sync pass that imports pending disk changes into Lix. It returns Promise<void> and requires an open Lix instance.

await storage.syncDiskToLix();

Lix instance

execute()

const result = await lix.execute(sql, params?, options?);

Executes one PostgreSQL-dialect SQL statement against the active Lix session. Pass a single statement. To run several statements atomically, call executeBatch() with an array of { sql, params? } objects. Do not concatenate statements into one SQL string or parse a script on the host.

Parameters:

ParameterTypeDescription
sqlstringOne statement from Lix's PostgreSQL-dialect subset.
paramsSqlParam[]Optional positional parameters addressed as $1, $2, and so on.
optionsExecuteOptionsOptional execution options. See below.

SqlParam accepts JSON values, Uint8Array, or a Value:

type SqlParam = JsonValue | Uint8Array | Value;

ExecuteOptions:

OptionTypeDescription
originKeystringOptional origin label for the mutation.
idempotencyKeystringStable identity for one logical remote SQL mutation. This is the retry story: supply the same key when retrying after a lost response, and the server applies the mutation only once. Remote Lix generates one per call when omitted. Sent as Idempotency-Key, not SQL options.
rowMode"object" | "array"Return plain objects by default or positional arrays when duplicate column names must remain separately addressable.

Result:

type ExecuteResult<TRow = Record<string, unknown>> = {
  statementIndex?: number;
  label?: string;
  columns: { name: string; type: "null" | "boolean" | "integer" | "real" | "text" | "jsonb" | "timestamptz" | "blob" }[];
  rows: TRow[];
  rowsAffected: number;
  notices: { code: string; message: string; hint?: string }[];
};
FieldDescription
columnsColumn names and SQL value types in result order. Empty for statements that do not return rows.
rowsEnumerable plain objects by default. Property access, destructuring, spread, and JSON serialization work directly.
rowsAffectedNumber of rows affected by write statements.
noticesNon-fatal engine notices with { code, message, hint? }.

Example:

const result = await lix.execute(
  "SELECT path, content FROM lix_file WHERE path = $1",
  ["/hello.txt"],
);

const path = result.rows[0]?.path;
const content = result.rows[0]?.content as Uint8Array | undefined;

executeBatch()

const results = await lix.executeBatch(statements, options?);

Executes multiple statements atomically in one call. statements is a non-empty array of { sql, params?, label? } objects — one statement per entry, already split by the caller. Lix does not parse a multi-statement script. options accepts the same originKey and idempotencyKey as execute(). Results preserve input order and include a zero-based statementIndex. A supplied label is echoed unchanged; labels are opaque and may repeat. If a label is omitted, the result has no label property.

const results = await lix.executeBatch([
  {
    label: "create",
    sql: "INSERT INTO lix_file (path, content) VALUES ($1, $2)",
    params: ["/a.txt", bytes],
  },
  { sql: "SELECT count(*) AS n FROM lix_file" },
]);

console.log(results[0].statementIndex, results[0].label); // 0, "create"
console.log(results[1].statementIndex, results[1].label); // 1, undefined

const returning = await lix.executeBatch([
  {
    label: "update",
    sql: "UPDATE task SET done = true WHERE id = $1 RETURNING id, done",
    params: ["task-1"],
  },
]);
console.log(returning[0].rows[0]?.done);

observe()

const events = lix.observe(sql, params?);

Observes a SQL query. Returns an ObserveEvents handle. Call next() to await the next result; it resolves with { sequence, mutationSequence, result } for the initial result and after each change, or undefined after the observation is closed. Call close() to stop observing.

const events = lix.observe("SELECT path FROM lix_file");
const event = await events.next();
console.log(event?.result.rows.length);
events.close();

beginTransaction()

const tx = await lix.beginTransaction();

Starts a transaction. While it is open, execute statements on the transaction handle.

const tx = await lix.beginTransaction();
try {
  await tx.execute("INSERT INTO lix_file (path, content) VALUES ($1, $2)", [
    "/hello.txt",
    new TextEncoder().encode("hello"),
  ]);
  await tx.commit();
} catch (error) {
  await tx.rollback();
  throw error;
}

activeBranchId()

const branchId = await lix.activeBranchId();

Returns the id of the branch the Lix instance is currently reading and writing.

activeAccountId()

const accountId = await lix.activeAccountId();

Returns the id of the active account.

subscribeActiveBranch()

const unsubscribe = lix.subscribeActiveBranch(listener);

Subscribes to successful branch switches made through this Lix handle. The listener is a function with no arguments. Returns an unsubscribe function.

Checkpoints

Checkpointing uses the canonical SQL surface rather than a separate typed SDK method:

const result = await lix.execute(
  "SELECT commit_id FROM lix_create_checkpoint()",
);
const commitId = result.rows[0].commit_id;

See Checkpoints for scoped row-reference selections.

undo() / redo()

const undone = await lix.undo();
const redone = await lix.redo();

undo() reverts the latest change on the active branch by committing an inverse commit. redo() replays the last undone change.

Results:

type UndoReceipt = {
  branchId: string;
  targetCommitId: string;
  inverseCommitId: string;
};

type RedoReceipt = {
  branchId: string;
  targetCommitId: string;
  replayCommitId: string;
};

createBranch()

const branch = await lix.createBranch({
  name: "Explore",
});

Creates a branch.

Options:

OptionTypeDescription
namestringBranch name.
idstringOptional explicit branch id.
fromCommitIdstringOptional commit id to start from.

Result:

type CreateBranchReceipt = {
  id: string;
  name: string;
  hidden: boolean;
  commitId: string;
};

switchBranch()

await lix.switchBranch({ branchId });

Switches the Lix instance to another branch. Plain SQL tables read and write the active branch.

type SwitchBranchReceipt = { branchId: string };

mergeBranchPreview()

const preview = await lix.mergeBranchPreview({
  sourceBranchId: draft.id,
});

Computes the merge result from sourceBranchId into the active branch without applying it.

Result:

type MergeBranchPreview = {
  outcome: "alreadyUpToDate" | "fastForward" | "mergeCommitted";
  targetBranchId: string;
  sourceBranchId: string;
  baseCommitId: string;
  targetHeadCommitId: string;
  sourceHeadCommitId: string;
  changeStats: MergeChangeStats;
  conflicts: MergeConflict[];
};

mergeBranch()

const merge = await lix.mergeBranch({
  sourceBranchId: draft.id,
});

Merges sourceBranchId into the active branch.

Result:

type MergeBranchReceipt = {
  outcome: "alreadyUpToDate" | "fastForward" | "mergeCommitted";
  targetBranchId: string;
  sourceBranchId: string;
  baseCommitId: string;
  targetHeadBeforeCommitId: string;
  sourceHeadBeforeCommitId: string;
  targetHeadAfterCommitId: string;
  createdMergeCommitId: string | null;
  changeStats: MergeChangeStats;
};

MergeChangeStats:

type MergeChangeStats = {
  total: number;
  added: number;
  modified: number;
  removed: number;
};

MergeConflict:

type MergeConflict = {
  kind: "sameRowChanged";
  rowRef: string;
  fileId: string | null;
  target: MergeConflictSide;
  source: MergeConflictSide;
};

type MergeConflictSide = {
  kind: "added" | "modified" | "removed";
  beforeChangeId: string | null;
  afterChangeId: string | null;
};

close()

await lix.close();

Closes the Lix handle and its storage resources.

Transaction

Transactions expose:

MethodDescription
execute(sql, params?, options?)Execute SQL inside the transaction. Same ExecuteOptions as lix.execute().
commit()Commit the transaction and close the transaction handle.
rollback()Roll back the transaction and close the transaction handle.

Result rows

execute() returns ordinary JavaScript objects.

const row = result.rows[0]!;

Use row.column_name, row[dynamicColumn], destructuring, spread, or JSON.stringify(row) directly. Duplicate output names use the last value in object mode while every descriptor remains in columns; pass { rowMode: "array" } to execute() or executeBatch() when positional duplicates are required.

Value

Value constructs explicitly typed SQL parameters. Returned values are native JavaScript values and their SQL types are described by result.columns.

Accessors:

MethodReturn typeDescription
toJS()unknownReturns a defensive copy of the native JS value.
asBytes()Uint8Array | undefinedReturns a defensive copy for blob values.

Constructors:

MethodDescription
Value.null()Create a SQL null value.
Value.integer(value)Create an integer value.
Value.boolean(value)Create a boolean value.
Value.real(value)Create a real number value.
Value.text(value)Create a text value.
Value.jsonb(value)Create a JSONB value.
Value.timestamptz(value)Create a timestamptz value from an RFC 3339 string.
Value.blob(value)Create a blob value from Uint8Array.
Value.from(raw)Convert a JSON-compatible JS value, Uint8Array, or Value into a Value.