@lix-js/sdk 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,7 +41,7 @@ const files = lix.observe("SELECT path FROM lix_file ORDER BY path");
41
41
  const initial = await files.next();
42
42
 
43
43
  await lix.execute(
44
- "INSERT INTO lix_file (path, data) VALUES ($1, $2)",
44
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2)",
45
45
  ["/hello.txt", new TextEncoder().encode("hello")],
46
46
  );
47
47
  const update = await files.next();
@@ -88,7 +88,7 @@ server switch into a rejected operation. Explicit `lix.clientState.set()` and
88
88
  Rust transaction has already committed, `get()` continues to expose that live
89
89
  session value; a later successful snapshot save can make it durable.
90
90
 
91
- Filesystem sync and SQLite persistence use native Node.js dependencies:
91
+ Filesystem sync uses native Node.js dependencies:
92
92
 
93
93
  ```ts
94
94
  import { LocalFilesystem, openLix } from "@lix-js/sdk";
@@ -101,20 +101,37 @@ const lix = await openLix({
101
101
  });
102
102
 
103
103
  await lix.execute(
104
- "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
104
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
105
105
  ["/hello.txt", new TextEncoder().encode("world")],
106
106
  );
107
107
 
108
- const result = await lix.execute("SELECT data FROM lix_file WHERE path = $1", [
108
+ const result = await lix.execute("SELECT content FROM lix_file WHERE path = $1", [
109
109
  "/hello.txt",
110
110
  ]);
111
- const bytes = result.rows[0]?.value("data").asBytes();
111
+ const bytes = result.rows[0]?.value("content").asBytes();
112
112
 
113
113
  console.log(bytes && new TextDecoder().decode(bytes));
114
114
 
115
115
  await lix.close();
116
116
  ```
117
117
 
118
+ ## Discover the SQL contract
119
+
120
+ Lix extends the standard `information_schema.columns` relation with
121
+ `lix_value_kind` and `lix_insert_policy`. Inspect it before generating writes:
122
+
123
+ ```sql
124
+ SELECT table_name, column_name, data_type, is_nullable, column_default,
125
+ lix_value_kind, lix_insert_policy
126
+ FROM information_schema.columns
127
+ WHERE table_name = 'lix_file'
128
+ ORDER BY ordinal_position;
129
+ ```
130
+
131
+ `lix_insert_policy` distinguishes `REQUIRED`, `DEFAULT`, `CONDITIONAL`, and
132
+ `READ_ONLY` columns. For the complete table and history-function map, see
133
+ [SQL Surfaces](https://lix.dev/docs/surfaces).
134
+
118
135
  ## Branches
119
136
 
120
137
  ```ts
@@ -123,7 +140,7 @@ const draft = await lix.createBranch({ name: "Draft" });
123
140
 
124
141
  await lix.switchBranch({ branchId: draft.id });
125
142
  await lix.execute(
126
- "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
143
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
127
144
  ["/status.txt", new TextEncoder().encode("draft")],
128
145
  );
129
146
 
@@ -139,11 +156,11 @@ const tx = await lix.beginTransaction();
139
156
 
140
157
  try {
141
158
  await tx.execute(
142
- "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
159
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
143
160
  ["/a.txt", new TextEncoder().encode("1")],
144
161
  );
145
162
  await tx.execute(
146
- "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
163
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
147
164
  ["/b.txt", new TextEncoder().encode("2")],
148
165
  );
149
166
  await tx.commit();
@@ -158,11 +175,10 @@ try {
158
175
  - `openLix()` opens a fresh in-memory Lix. Pass `new LocalFilesystem({ path, syncAllFiles: true })` for a filesystem workspace directory backed by `<path>/.lix/.internal/rocksdb`.
159
176
  - Pass `new LocalFilesystem({ path, lixDir, syncAllFiles: true })` for filesystem sync with repository metadata in an external `.lix` directory and no workspace `.lix` directory.
160
177
  - Pass `syncAllFiles: false` to start filesystem sync with no regular workspace files, then call `storage.importPaths(["notes/today.md"])` on the `LocalFilesystem` instance to sync selected files. Imported paths are exact workspace-relative file paths, not directories or globs.
161
- - Use `new SQLite({ path })` when a single SQLite-backed `.lix` file is the application document itself, for example when defining a new file format and using Lix as the application's file format.
162
178
  - In browsers, local mode and remote mode with client storage load the Rust
163
179
  engine as WebAssembly. Supplying a snapshot storage adapter persists that
164
180
  local Lix; in remote mode, the local engine contains only client state.
165
- - `LocalFilesystem` and `SQLite` are Node.js-only. Constructing them is safe in
181
+ - `LocalFilesystem` is Node.js-only. Constructing it is safe in
166
182
  shared code, but passing one to `openLix()` in a browser throws an error.
167
183
  - The package is ESM-only.
168
184
  - The package uses conditional ESM imports internally: Node.js resolves the
@@ -170,10 +186,10 @@ try {
170
186
  WebAssembly binding. Vite follows this split without consumer configuration.
171
187
  - Every browser `openLix()` owns one dedicated worker, so database work does
172
188
  not block the page's main thread. Node.js uses the native binding's actor.
173
- - Node.js executes installed Component API v2 plugins with the Rust SDK's
189
+ - Node.js executes installed Component API v1 plugins with the Rust SDK's
174
190
  Wasmtime runtime. The browser and Workerd bindings currently open without a
175
191
  component runtime: they can use ordinary Lix storage and SQL, but do not
176
- execute installed plugins. A browser V2 host is a separate follow-up.
192
+ execute installed plugins. A browser Component host is a separate follow-up.
177
193
  - A page Content Security Policy only needs to permit the package's same-origin
178
194
  worker. WebAssembly compilation happens inside that worker, so the required
179
195
  permission can be scoped to the worker script's HTTP response instead of
@@ -1,6 +1,8 @@
1
- import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, LixBatchOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt, LixTelemetrySpan, JsonValue } from "./types.js";
1
+ import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, UndoReceipt, RedoReceipt, ExecuteOptions, LixBatchOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt, LixTelemetrySpan, JsonValue } from "./types.js";
2
2
  import type { NativeLixValue } from "./value.js";
3
3
  export type BindingExecuteResult = {
4
+ statementIndex?: number;
5
+ label?: string;
4
6
  columns: string[];
5
7
  rows: NativeLixValue[][];
6
8
  rowsAffected: number;
@@ -19,6 +21,7 @@ export type BindingParam = NativeLixValue;
19
21
  export type BindingBatchStatement = {
20
22
  sql: string;
21
23
  params: BindingParam[];
24
+ label?: string;
22
25
  };
23
26
  export type LixBinding = {
24
27
  execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
@@ -26,6 +29,7 @@ export type LixBinding = {
26
29
  observe(sql: string, params: BindingParam[]): Promise<ObserveEventsBinding>;
27
30
  beginTransaction(): Promise<LixTransactionBinding>;
28
31
  activeBranchId(): Promise<string>;
32
+ activeAccountId(): Promise<string>;
29
33
  clientStateEntries?(): Promise<Array<{
30
34
  key: string;
31
35
  value: JsonValue;
@@ -35,6 +39,8 @@ export type LixBinding = {
35
39
  clientStateDelete?(key: string): Promise<void>;
36
40
  createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
37
41
  createCheckpoint(): Promise<CreateCheckpointReceipt>;
42
+ undo(): Promise<UndoReceipt>;
43
+ redo(): Promise<RedoReceipt>;
38
44
  switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
39
45
  importFilesystemPaths(paths: string[]): Promise<void>;
40
46
  mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
@@ -57,9 +63,6 @@ export type TelemetryDispatch = (span: LixTelemetrySpan) => void;
57
63
  export type LixStorageConfig = {
58
64
  kind: "memory";
59
65
  snapshot?: Uint8Array;
60
- } | {
61
- kind: "sqlite";
62
- path: string;
63
66
  } | {
64
67
  kind: "localFilesystem";
65
68
  path: string;
@@ -7,7 +7,7 @@ function initializeWasm() {
7
7
  }
8
8
  export async function openLixBinding(storage, telemetry) {
9
9
  if (storage.kind !== "memory") {
10
- throw new Error(`${storage.kind === "localFilesystem" ? "LocalFilesystem" : "SQLite"} is only available in Node.js`);
10
+ throw new Error("LocalFilesystem is only available in Node.js");
11
11
  }
12
12
  await initializeWasm();
13
13
  return openMemoryFromSnapshot(telemetry, storage.snapshot);
@@ -48,10 +48,13 @@ export function openLixBinding(storage, telemetry) {
48
48
  if (storage.snapshot !== undefined) {
49
49
  throw new Error("Memory snapshots are only available in the browser binding");
50
50
  }
51
- return addon.Lix.openMemory(nativeTelemetry);
52
- case "sqlite":
53
- return addon.Lix.openSQLite(storage.path, nativeTelemetry);
51
+ if (nativeTelemetry)
52
+ return addon.Lix.openMemory(nativeTelemetry);
53
+ return addon.Lix.openMemory();
54
54
  case "localFilesystem":
55
- return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
55
+ if (nativeTelemetry) {
56
+ return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
57
+ }
58
+ return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles);
56
59
  }
57
60
  }
@@ -1,11 +1,11 @@
1
1
  const BUNDLED_PLUGIN_MANIFEST = [
2
2
  {
3
- key: "plugin_markdown_incremental_v2",
4
- fileName: "plugin_markdown_incremental_v2.lixplugin",
3
+ key: "plugin_markdown",
4
+ fileName: "plugin_markdown.lixplugin",
5
5
  },
6
6
  {
7
- key: "plugin_csv_v2",
8
- fileName: "plugin_csv_v2.lixplugin",
7
+ key: "plugin_csv",
8
+ fileName: "plugin_csv.lixplugin",
9
9
  },
10
10
  ];
11
11
  export async function bundledPluginArchives() {
@@ -1,16 +1,20 @@
1
1
  import type { LixBinding } from "./binding-types.js";
2
- import type { JsonValue } from "./types.js";
2
+ import type { JsonValue, LixSnapshotStorage } from "./types.js";
3
3
  export declare const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
4
+ export declare const ACTIVE_ACCOUNT_CLIENT_STATE_KEY = "lix_active_account_id";
4
5
  export type LixClientState = {
5
6
  /** Returns the hydrated client-local value without a network round trip. */
6
7
  get<T extends JsonValue = JsonValue>(key: string): T | undefined;
7
- /** Commits the value through the local Rust Lix and then persists its snapshot. */
8
+ /** Persists a client-local value in the configured client storage. */
8
9
  set(key: string, value: JsonValue): Promise<void>;
9
- /** Deletes the value through the local Rust Lix and then persists its snapshot. */
10
+ /** Deletes a client-local value from the configured client storage. */
10
11
  delete(key: string): Promise<void>;
11
12
  /** Subscribes to successful mutations made through this client-state handle. */
12
13
  subscribe(listener: () => void): () => void;
13
14
  };
15
+ export type ManagedClientState = LixClientState & {
16
+ close(): Promise<void>;
17
+ };
14
18
  export declare function unavailableClientState(): LixClientState;
15
19
  type ClientStateBinding = LixBinding & {
16
20
  exportSnapshot?: () => Promise<Uint8Array>;
@@ -37,4 +41,13 @@ export declare class ManagedLixClientState implements LixClientState {
37
41
  subscribe(listener: () => void): () => void;
38
42
  close(): Promise<void>;
39
43
  }
44
+ /**
45
+ * Opens client state directly over snapshot storage without starting a local
46
+ * Lix runtime. This is used by remote Lix connections, where the storage
47
+ * option persists client-local state rather than the remote workspace.
48
+ */
49
+ export declare function openStoredClientState(options: {
50
+ readonly storage: LixSnapshotStorage;
51
+ readonly namespace: string;
52
+ }): Promise<ManagedClientState>;
40
53
  export {};
@@ -1,6 +1,8 @@
1
1
  import { isSnapshotPersistenceAfterCommitError } from "./snapshot-persistence.js";
2
2
  import { Value } from "./value.js";
3
3
  export const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
4
+ export const ACTIVE_ACCOUNT_CLIENT_STATE_KEY = "lix_active_account_id";
5
+ const STORED_CLIENT_STATE_HEADER = "lix-client-state-v1\n";
4
6
  export function unavailableClientState() {
5
7
  const unavailable = () => {
6
8
  const error = new Error("Lix client state requires client storage; pass storage to openLix()");
@@ -158,6 +160,141 @@ export class ManagedLixClientState {
158
160
  }
159
161
  }
160
162
  }
163
+ /**
164
+ * Opens client state directly over snapshot storage without starting a local
165
+ * Lix runtime. This is used by remote Lix connections, where the storage
166
+ * option persists client-local state rather than the remote workspace.
167
+ */
168
+ export async function openStoredClientState(options) {
169
+ const snapshot = await options.storage.load(options.namespace);
170
+ if (snapshot !== undefined && !(snapshot instanceof Uint8Array)) {
171
+ throw new TypeError("Client-state storage load() must return a Uint8Array");
172
+ }
173
+ return new StoredClientState(options.storage, options.namespace, decodeStoredClientState(snapshot));
174
+ }
175
+ class StoredClientState {
176
+ #storage;
177
+ #namespace;
178
+ #values;
179
+ #listeners = new Set();
180
+ #operationQueue = Promise.resolve();
181
+ #closePromise;
182
+ #acceptingOperations = true;
183
+ #dirty = false;
184
+ constructor(storage, namespace, values) {
185
+ this.#storage = storage;
186
+ this.#namespace = namespace;
187
+ this.#values = values;
188
+ }
189
+ get(key) {
190
+ assertClientStateKey(key);
191
+ const value = this.#values.get(key);
192
+ return value === undefined ? undefined : cloneJsonValue(value);
193
+ }
194
+ set(key, value) {
195
+ assertClientStateKey(key);
196
+ assertJsonValue(value);
197
+ this.#assertOpen();
198
+ const nextValue = cloneJsonValue(value);
199
+ return this.#enqueue(async () => {
200
+ this.#values.set(key, nextValue);
201
+ this.#dirty = true;
202
+ this.#publish();
203
+ await this.#persist();
204
+ });
205
+ }
206
+ delete(key) {
207
+ assertClientStateKey(key);
208
+ this.#assertOpen();
209
+ return this.#enqueue(async () => {
210
+ if (this.#values.delete(key))
211
+ this.#publish();
212
+ this.#dirty = true;
213
+ await this.#persist();
214
+ });
215
+ }
216
+ subscribe(listener) {
217
+ if (typeof listener !== "function") {
218
+ throw new TypeError("clientState.subscribe() requires a function");
219
+ }
220
+ this.#assertOpen();
221
+ this.#listeners.add(listener);
222
+ return () => this.#listeners.delete(listener);
223
+ }
224
+ async close() {
225
+ if (this.#closePromise)
226
+ return this.#closePromise;
227
+ this.#acceptingOperations = false;
228
+ this.#closePromise = (async () => {
229
+ await this.#operationQueue;
230
+ if (this.#dirty)
231
+ await this.#persist();
232
+ this.#listeners.clear();
233
+ })();
234
+ return this.#closePromise;
235
+ }
236
+ #enqueue(operation) {
237
+ const result = this.#operationQueue.then(operation, operation);
238
+ this.#operationQueue = result.then(() => undefined, () => undefined);
239
+ return result;
240
+ }
241
+ async #persist() {
242
+ await this.#storage.save(this.#namespace, encodeStoredClientState(this.#values));
243
+ this.#dirty = false;
244
+ }
245
+ #publish() {
246
+ for (const listener of [...this.#listeners]) {
247
+ try {
248
+ listener();
249
+ }
250
+ catch {
251
+ // Subscribers do not participate in the completed mutation.
252
+ }
253
+ }
254
+ }
255
+ #assertOpen() {
256
+ if (!this.#acceptingOperations) {
257
+ throw new Error("Lix client state is closed");
258
+ }
259
+ }
260
+ }
261
+ function encodeStoredClientState(values) {
262
+ const entries = [...values].map(([key, value]) => [
263
+ key,
264
+ cloneJsonValue(value),
265
+ ]);
266
+ return new TextEncoder().encode(`${STORED_CLIENT_STATE_HEADER}${JSON.stringify(entries)}`);
267
+ }
268
+ function decodeStoredClientState(snapshot) {
269
+ if (snapshot === undefined)
270
+ return new Map();
271
+ const header = new TextEncoder().encode(STORED_CLIENT_STATE_HEADER);
272
+ if (snapshot.length < header.length ||
273
+ header.some((byte, index) => snapshot[index] !== byte)) {
274
+ throw new Error("Stored Lix client state header is invalid");
275
+ }
276
+ let parsed;
277
+ try {
278
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(snapshot.subarray(header.length)));
279
+ }
280
+ catch (error) {
281
+ throw new Error("Stored Lix client state is invalid", { cause: error });
282
+ }
283
+ if (!Array.isArray(parsed)) {
284
+ throw new Error("Stored Lix client state entries must be an array");
285
+ }
286
+ const values = new Map();
287
+ for (const entry of parsed) {
288
+ if (!Array.isArray(entry) || entry.length !== 2) {
289
+ throw new Error("Stored Lix client state entry is invalid");
290
+ }
291
+ const [key, value] = entry;
292
+ assertClientStateKey(key);
293
+ assertJsonValue(value);
294
+ values.set(key, cloneJsonValue(value));
295
+ }
296
+ return values;
297
+ }
161
298
  function assertClientStateKey(key) {
162
299
  if (typeof key !== "string" || key.length === 0) {
163
300
  throw new TypeError("clientState key must be a non-empty string");
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, SQLite, } from "./open-lix.js";
1
+ export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, } from "./open-lix.js";
2
2
  export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plugins.js";
3
3
  export { Row } from "./result.js";
4
4
  export { Value } from "./value.js";
5
5
  export type { LixClientState } from "./client-state.js";
6
- export type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, LocalFilesystemOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, LixTelemetryOptions, LixTelemetrySpan, LixSnapshotStorage, RemoteLixFetch, RemoteLixServerOptions, SqlParam, SQLiteOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
6
+ export type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, LocalFilesystemOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, LixTelemetryOptions, LixTelemetrySpan, LixSnapshotStorage, RemoteLixFetch, RemoteLixServerOptions, UndoReceipt, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, SQLite, } from "./open-lix.js";
1
+ export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, } from "./open-lix.js";
2
2
  export { bundledPluginArchives, } from "./bundled-plugins.js";
3
3
  export { Row } from "./result.js";
4
4
  export { Value } from "./value.js";
package/dist/lix.d.ts CHANGED
@@ -1,22 +1,25 @@
1
- import { type LixClientState, type ManagedLixClientState } from "./client-state.js";
1
+ import { type LixClientState, type ManagedClientState } from "./client-state.js";
2
2
  import type { LixBinding, LixTransactionBinding, ObserveEventsBinding } from "./binding-types.js";
3
- import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, SqlParam, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
3
+ import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, ExecuteBatchResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, UndoReceipt } from "./types.js";
4
4
  export declare class Lix {
5
5
  #private;
6
6
  private readonly binding;
7
7
  private readonly managedClientState?;
8
8
  private closePromise;
9
9
  readonly clientState: LixClientState;
10
- constructor(binding: LixBinding, managedClientState?: ManagedLixClientState | undefined);
10
+ constructor(binding: LixBinding, managedClientState?: ManagedClientState | undefined);
11
11
  execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
12
- executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteResult[]>;
12
+ executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteBatchResult[]>;
13
13
  observe(sql: string, params?: SqlParam[]): ObserveEvents;
14
14
  beginTransaction(): Promise<LixTransaction>;
15
15
  activeBranchId(): Promise<string>;
16
+ activeAccountId(): Promise<string>;
16
17
  /** Subscribes to successful branch switches made through this Lix handle. */
17
18
  subscribeActiveBranch(listener: () => void): () => void;
18
19
  createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
19
20
  createCheckpoint(): Promise<CreateCheckpointReceipt>;
21
+ undo(): Promise<UndoReceipt>;
22
+ redo(): Promise<RedoReceipt>;
20
23
  switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
21
24
  mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
22
25
  mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
package/dist/lix.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { invalidArgument } from "./errors.js";
2
2
  import { ACTIVE_BRANCH_CLIENT_STATE_KEY, unavailableClientState, } from "./client-state.js";
3
- import { normalizeOptionals, wrapExecuteResult } from "./result.js";
3
+ import { normalizeOptionals, wrapExecuteBatchResult, wrapExecuteResult, } from "./result.js";
4
4
  import { isSnapshotPersistenceAfterCommitError } from "./snapshot-persistence.js";
5
5
  import { normalizeParam, toNativeValue } from "./value.js";
6
6
  const transactionFinalizer = new FinalizationRegistry(({ transaction, onFinish }) => {
@@ -50,7 +50,7 @@ export class Lix {
50
50
  const normalizedStatements = normalizeBatchStatements(statements, options);
51
51
  return this.#runOperation(async () => {
52
52
  const results = await this.binding.executeBatch(normalizedStatements, options);
53
- return results.map(wrapExecuteResult);
53
+ return results.map(wrapExecuteBatchResult);
54
54
  });
55
55
  }
56
56
  observe(sql, params = []) {
@@ -83,6 +83,9 @@ export class Lix {
83
83
  async activeBranchId() {
84
84
  return this.#runOperation(() => this.binding.activeBranchId());
85
85
  }
86
+ async activeAccountId() {
87
+ return this.#runOperation(() => this.binding.activeAccountId());
88
+ }
86
89
  /** Subscribes to successful branch switches made through this Lix handle. */
87
90
  subscribeActiveBranch(listener) {
88
91
  if (typeof listener !== "function") {
@@ -98,6 +101,12 @@ export class Lix {
98
101
  async createCheckpoint() {
99
102
  return this.#runOperation(() => this.binding.createCheckpoint());
100
103
  }
104
+ async undo() {
105
+ return this.#runOperation(() => this.binding.undo());
106
+ }
107
+ async redo() {
108
+ return this.#runOperation(() => this.binding.redo());
109
+ }
101
110
  async switchBranch(options) {
102
111
  return this.#runOperation(async () => {
103
112
  const receipt = await this.binding.switchBranch(options);
@@ -327,9 +336,14 @@ function normalizeBatchStatements(statements, options) {
327
336
  if (!Array.isArray(params)) {
328
337
  throw invalidArgument("executeBatch", `statements[${statementIndex}].params`, "array", typeof params);
329
338
  }
339
+ if (statement.label !== undefined &&
340
+ typeof statement.label !== "string") {
341
+ throw invalidArgument("executeBatch", `statements[${statementIndex}].label`, "string", typeof statement.label);
342
+ }
330
343
  return {
331
344
  sql: statement.sql,
332
345
  params: params.map((param, parameterIndex) => toNativeValue(normalizeParam(param, parameterIndex))),
346
+ ...(statement.label === undefined ? {} : { label: statement.label }),
333
347
  };
334
348
  }
335
349
  catch (error) {
@@ -1,10 +1,6 @@
1
1
  import { Lix } from "./lix.js";
2
- import type { LocalFilesystemOptions, OpenLixOptions, SQLiteOptions } from "./types.js";
2
+ import type { LocalFilesystemOptions, OpenLixOptions } from "./types.js";
3
3
  export { Lix, LixTransaction, ObserveEvents } from "./lix.js";
4
- export declare class SQLite {
5
- readonly path: string;
6
- constructor(options: SQLiteOptions);
7
- }
8
4
  export declare class LocalFilesystem {
9
5
  readonly path: string;
10
6
  readonly lixDir: string | undefined;
package/dist/open-lix.js CHANGED
@@ -1,18 +1,7 @@
1
1
  import { localFilesystemAlreadyOpen, localFilesystemNotOpen, } from "./errors.js";
2
- import { ACTIVE_BRANCH_CLIENT_STATE_KEY, openClientState, } from "./client-state.js";
2
+ import { ACTIVE_ACCOUNT_CLIENT_STATE_KEY, ACTIVE_BRANCH_CLIENT_STATE_KEY, openClientState, openStoredClientState, } from "./client-state.js";
3
3
  import { Lix } from "./lix.js";
4
4
  export { Lix, LixTransaction, ObserveEvents } from "./lix.js";
5
- export class SQLite {
6
- path;
7
- constructor(options) {
8
- if (!options ||
9
- typeof options.path !== "string" ||
10
- options.path.length === 0) {
11
- throw new TypeError("SQLite requires a non-empty path");
12
- }
13
- this.path = options.path;
14
- }
15
- }
16
5
  const openLocalFilesystems = new WeakMap();
17
6
  export class LocalFilesystem {
18
7
  path;
@@ -78,39 +67,35 @@ export async function openLix(options = {}) {
78
67
  return new Lix(await openRemoteLixBinding(options.server));
79
68
  }
80
69
  assertSnapshotStorage(options.storage);
81
- const { openPersistentLixWorkerBinding } = await import("./worker/client.js");
82
- const clientBinding = await openPersistentLixWorkerBinding({
70
+ const clientState = await openStoredClientState({
83
71
  storage: options.storage,
84
72
  namespace: remoteClientStateNamespace(options.server.url),
85
73
  });
86
- let clientState;
87
- try {
88
- clientState = await openClientState({
89
- binding: clientBinding,
90
- closeBinding: true,
91
- });
92
- }
93
- catch (error) {
94
- await clientBinding.close().catch(() => undefined);
95
- throw error;
96
- }
97
74
  const restoredBranchId = clientState.get(ACTIVE_BRANCH_CLIENT_STATE_KEY);
75
+ const restoredAccountId = clientState.get(ACTIVE_ACCOUNT_CLIENT_STATE_KEY);
98
76
  let remoteBinding;
99
77
  try {
100
78
  try {
101
79
  remoteBinding = await openRemoteLixBinding(options.server, {
102
80
  initialActiveBranchId: restoredBranchId,
81
+ initialActiveAccountId: restoredAccountId,
103
82
  });
104
83
  }
105
84
  catch (error) {
106
85
  if (!restoredBranchId || !isBranchNotFoundError(error))
107
86
  throw error;
108
- remoteBinding = await openRemoteLixBinding(options.server);
87
+ remoteBinding = await openRemoteLixBinding(options.server, {
88
+ initialActiveAccountId: restoredAccountId,
89
+ });
109
90
  }
110
91
  const activeBranchId = await remoteBinding.activeBranchId();
92
+ const activeAccountId = await remoteBinding.activeAccountId();
111
93
  if (activeBranchId !== restoredBranchId) {
112
94
  await clientState.set(ACTIVE_BRANCH_CLIENT_STATE_KEY, activeBranchId);
113
95
  }
96
+ if (activeAccountId !== restoredAccountId) {
97
+ await clientState.set(ACTIVE_ACCOUNT_CLIENT_STATE_KEY, activeAccountId);
98
+ }
114
99
  return new Lix(remoteBinding, clientState);
115
100
  }
116
101
  catch (error) {
@@ -123,12 +108,6 @@ export async function openLix(options = {}) {
123
108
  if (options.storage === undefined) {
124
109
  return new Lix(await openLixWorkerBinding({ kind: "memory" }, undefined, options.telemetry));
125
110
  }
126
- if (options.storage instanceof SQLite) {
127
- return new Lix(await openLixWorkerBinding({
128
- kind: "sqlite",
129
- path: options.storage.path,
130
- }, undefined, options.telemetry));
131
- }
132
111
  if (options.storage instanceof LocalFilesystem) {
133
112
  const storage = options.storage;
134
113
  if (openLocalFilesystems.has(storage)) {
@@ -166,7 +145,7 @@ export async function openLix(options = {}) {
166
145
  throw error;
167
146
  }
168
147
  }
169
- throw new TypeError("openLix() requires storage to be SQLite, LocalFilesystem, or a Lix snapshot storage adapter");
148
+ throw new TypeError("openLix() requires storage to be LocalFilesystem or a Lix snapshot storage adapter");
170
149
  }
171
150
  function isSnapshotStorage(value) {
172
151
  return (typeof value === "object" &&
@@ -3,6 +3,7 @@ import type { RemoteLixServerOptions } from "../types.js";
3
3
  import { type RemoteHandshakeRequest } from "./protocol.js";
4
4
  type RemoteLixClientOptions = {
5
5
  initialActiveBranchId?: RemoteHandshakeRequest["activeBranchId"];
6
+ initialActiveAccountId?: RemoteHandshakeRequest["activeAccountId"];
6
7
  };
7
8
  export declare function openRemoteLixBinding(options: RemoteLixServerOptions, clientOptions?: RemoteLixClientOptions): Promise<LixBinding>;
8
9
  export {};