@lix-js/sdk 0.10.0 → 0.12.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.
Files changed (42) hide show
  1. package/README.md +49 -78
  2. package/dist/binding-types.d.ts +9 -18
  3. package/dist/binding.browser.js +18 -5
  4. package/dist/binding.node.js +5 -11
  5. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  6. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  7. package/dist/errors.d.ts +0 -2
  8. package/dist/errors.js +0 -13
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +1 -1
  11. package/dist/indexeddb-backend.d.ts +19 -0
  12. package/dist/indexeddb-backend.js +121 -0
  13. package/dist/lix.d.ts +3 -6
  14. package/dist/lix.js +28 -54
  15. package/dist/open-lix.d.ts +4 -13
  16. package/dist/open-lix.js +45 -152
  17. package/dist/remote/client.d.ts +2 -3
  18. package/dist/remote/client.js +50 -23
  19. package/dist/remote/{protocol.d.ts → server-protocol.d.ts} +40 -32
  20. package/dist/remote/{protocol.js → server-protocol.js} +36 -17
  21. package/dist/result.d.ts +2 -1
  22. package/dist/result.js +12 -0
  23. package/dist/storage-adapter.d.ts +30 -0
  24. package/dist/storage-adapter.js +12 -0
  25. package/dist/types.d.ts +25 -27
  26. package/dist/value.d.ts +2 -1
  27. package/dist/value.js +23 -10
  28. package/dist/wasm/lix_js_sdk.d.ts +5 -10
  29. package/dist/wasm/lix_js_sdk.js +33 -45
  30. package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
  31. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +3 -6
  32. package/dist/worker/client.d.ts +1 -12
  33. package/dist/worker/client.js +0 -161
  34. package/dist/worker/host.js +0 -23
  35. package/dist/worker/protocol.d.ts +1 -15
  36. package/package.json +8 -12
  37. package/dist/client-state.d.ts +0 -53
  38. package/dist/client-state.js +0 -318
  39. package/dist/local-storage-adapter.d.ts +0 -26
  40. package/dist/local-storage-adapter.js +0 -117
  41. package/dist/snapshot-persistence.d.ts +0 -7
  42. package/dist/snapshot-persistence.js +0 -26
package/README.md CHANGED
@@ -22,28 +22,28 @@ console.log(result.rows[0]?.get("message"));
22
22
  await lix.close();
23
23
  ```
24
24
 
25
- ## Remote workspaces
25
+ ## Remote repositories
26
26
 
27
- Use the same Lix client as a thin client against a hosted workspace:
27
+ Use the same Lix client as a thin client against a hosted repository:
28
28
 
29
29
  ```ts
30
30
  const lix = await openLix({
31
- server: {
32
- mode: "remote",
33
- url: "https://lixray.com/@namespace/workspace",
34
- headers: async () => ({
35
- Authorization: `Bearer ${await accessToken()}`,
36
- }),
37
- },
31
+ server: {
32
+ mode: "remote",
33
+ url: "https://example.com/repositories/acme",
34
+ headers: async () => ({
35
+ Authorization: `Bearer ${await accessToken()}`,
36
+ }),
37
+ },
38
38
  });
39
39
 
40
40
  const files = lix.observe("SELECT path FROM lix_file ORDER BY path");
41
41
  const initial = await files.next();
42
42
 
43
- await lix.execute(
44
- "INSERT INTO lix_file (path, content) VALUES ($1, $2)",
45
- ["/hello.txt", new TextEncoder().encode("hello")],
46
- );
43
+ await lix.execute("INSERT INTO lix_file (path, content) VALUES ($1, $2)", [
44
+ "/hello.txt",
45
+ new TextEncoder().encode("hello"),
46
+ ]);
47
47
  const update = await files.next();
48
48
 
49
49
  files.close();
@@ -55,59 +55,29 @@ open a local engine. Dynamic headers are resolved for every request and
55
55
  observation reconnect. An injected `fetch` can route requests through a service
56
56
  binding or another authorized server-side transport.
57
57
 
58
- Browser clients can opt into private, durable client state with the local
59
- storage adapter:
58
+ Remote server sessions are branch-pinned, so switching one client does not
59
+ switch another client. Browser-local application state belongs to the
60
+ application rather than the remote Lix handle.
60
61
 
61
- ```ts
62
- import { openLix } from "@lix-js/sdk";
63
- import { LocalStorage } from "@lix-js/sdk/local-storage-adapter";
64
-
65
- const lix = await openLix({
66
- server: {
67
- mode: "remote",
68
- url: "https://lixray.com/@namespace/workspace",
69
- },
70
- storage: new LocalStorage(),
71
- });
72
-
73
- const previousUiState = lix.clientState.get("atelier-ui");
74
- await lix.clientState.set("atelier-ui", { sidebar: "history" });
75
- ```
76
-
77
- `lix.clientState` is hydrated before `openLix()` resolves, so reads are
78
- synchronous. Its JSON values and the client's active branch are stored in a
79
- private local Lix snapshot; workspace SQL continues to execute only on the
80
- server. Reopening the same remote URL with the same storage restores both. Each
81
- remote server session is branch-pinned, so switching one client does not switch
82
- another client.
83
-
84
- After a remote branch switch succeeds, saving that branch as the next-reopen
85
- preference is best effort: a client-storage failure does not turn the completed
86
- server switch into a rejected operation. Explicit `lix.clientState.set()` and
87
- `.delete()` calls report durability failures to their caller. Because the local
88
- Rust transaction has already committed, `get()` continues to expose that live
89
- session value; a later successful snapshot save can make it durable.
90
-
91
- Filesystem sync and SQLite persistence use native Node.js dependencies:
62
+ Filesystem sync uses native Node.js dependencies:
92
63
 
93
64
  ```ts
94
- import { LocalFilesystem, openLix } from "@lix-js/sdk";
65
+ import { openLix } from "@lix-js/sdk";
66
+ import { FilesystemStorage } from "@lix-js/storage-filesystem";
95
67
 
96
68
  const lix = await openLix({
97
- storage: new LocalFilesystem({
98
- path: "./workspace",
99
- syncAllFiles: true,
100
- }),
69
+ storage: new FilesystemStorage({ path: "./repository" }),
101
70
  });
102
71
 
103
72
  await lix.execute(
104
- "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
105
- ["/hello.txt", new TextEncoder().encode("world")],
73
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
74
+ ["/hello.txt", new TextEncoder().encode("world")],
106
75
  );
107
76
 
108
- const result = await lix.execute("SELECT content FROM lix_file WHERE path = $1", [
109
- "/hello.txt",
110
- ]);
77
+ const result = await lix.execute(
78
+ "SELECT content FROM lix_file WHERE path = $1",
79
+ ["/hello.txt"],
80
+ );
111
81
  const bytes = result.rows[0]?.value("content").asBytes();
112
82
 
113
83
  console.log(bytes && new TextDecoder().decode(bytes));
@@ -140,8 +110,8 @@ const draft = await lix.createBranch({ name: "Draft" });
140
110
 
141
111
  await lix.switchBranch({ branchId: draft.id });
142
112
  await lix.execute(
143
- "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
144
- ["/status.txt", new TextEncoder().encode("draft")],
113
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
114
+ ["/status.txt", new TextEncoder().encode("draft")],
145
115
  );
146
116
 
147
117
  await lix.switchBranch({ branchId: main });
@@ -155,31 +125,31 @@ const merge = await lix.mergeBranch({ sourceBranchId: draft.id });
155
125
  const tx = await lix.beginTransaction();
156
126
 
157
127
  try {
158
- await tx.execute(
159
- "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
160
- ["/a.txt", new TextEncoder().encode("1")],
161
- );
162
- await tx.execute(
163
- "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
164
- ["/b.txt", new TextEncoder().encode("2")],
165
- );
166
- await tx.commit();
128
+ await tx.execute(
129
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
130
+ ["/a.txt", new TextEncoder().encode("1")],
131
+ );
132
+ await tx.execute(
133
+ "INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
134
+ ["/b.txt", new TextEncoder().encode("2")],
135
+ );
136
+ await tx.commit();
167
137
  } catch (error) {
168
- await tx.rollback();
169
- throw error;
138
+ await tx.rollback();
139
+ throw error;
170
140
  }
171
141
  ```
172
142
 
173
143
  ## Notes
174
144
 
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`.
176
- - Pass `new LocalFilesystem({ path, lixDir, syncAllFiles: true })` for filesystem sync with repository metadata in an external `.lix` directory and no workspace `.lix` directory.
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.
178
- - 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.
179
- - In browsers, local mode and remote mode with client storage load the Rust
180
- engine as WebAssembly. Supplying a snapshot storage adapter persists that
181
- local Lix; in remote mode, the local engine contains only client state.
182
- - `LocalFilesystem` and `SQLite` are Node.js-only. Constructing them is safe in
145
+ - `openLix()` opens a fresh in-memory Lix. Install `@lix-js/storage-filesystem` and pass `new FilesystemStorage({ path })` for a filesystem repository directory backed by `<path>/.lix/.internal/rocksdb`.
146
+ - In browsers, pass `new IndexedDbStorage({ name })` to persist a complete local Lix across reloads.
147
+ - Only one Lix handle may open an IndexedDB storage name at a time, including across browser tabs.
148
+ - Pass `syncAllFiles: false` to start filesystem sync with no regular repository files, then call `storage.importPaths(["notes/today.md"])` on the `FilesystemStorage` instance to sync selected files. Imported paths are exact repository-relative file paths, not directories or globs.
149
+ - In browsers, local mode and remote mode with IndexedDB storage load the Rust
150
+ engine as WebAssembly. In remote mode, the local engine contains only client
151
+ state.
152
+ - `FilesystemStorage` is Node.js-only. Constructing it is safe in
183
153
  shared code, but passing one to `openLix()` in a browser throws an error.
184
154
  - The package is ESM-only.
185
155
  - The package uses conditional ESM imports internally: Node.js resolves the
@@ -207,8 +177,9 @@ try {
207
177
  Hosts that apply one policy to every response can use
208
178
  `script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'` globally
209
179
  instead. Worker-scoped headers keep those permissions out of the page.
180
+
210
181
  - SQL parameters use normal JavaScript values: `string`, finite `number`, `boolean`, `Uint8Array`, `null`, JSON-compatible arrays, and JSON-compatible plain objects.
211
- - Use `Value.integer(...)`, `Value.real(...)`, `Value.text(...)`, `Value.json(...)`, or `Value.blob(...)` only when you need to pass an explicit native Lix value.
182
+ - Use `Value.integer(...)`, `Value.real(...)`, `Value.text(...)`, `Value.jsonb(...)`, `Value.timestamptz(...)`, or `Value.blob(...)` only when you need to pass an explicit native Lix value.
212
183
 
213
184
  ## Browser development
214
185
 
@@ -1,6 +1,9 @@
1
- import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, UndoReceipt, RedoReceipt, 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 } from "./types.js";
2
2
  import type { NativeLixValue } from "./value.js";
3
+ import type { LixStorageAdapterConfig } from "./storage-adapter.js";
3
4
  export type BindingExecuteResult = {
5
+ statementIndex?: number;
6
+ label?: string;
4
7
  columns: string[];
5
8
  rows: NativeLixValue[][];
6
9
  rowsAffected: number;
@@ -19,6 +22,7 @@ export type BindingParam = NativeLixValue;
19
22
  export type BindingBatchStatement = {
20
23
  sql: string;
21
24
  params: BindingParam[];
25
+ label?: string;
22
26
  };
23
27
  export type LixBinding = {
24
28
  execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
@@ -27,13 +31,6 @@ export type LixBinding = {
27
31
  beginTransaction(): Promise<LixTransactionBinding>;
28
32
  activeBranchId(): Promise<string>;
29
33
  activeAccountId(): Promise<string>;
30
- clientStateEntries?(): Promise<Array<{
31
- key: string;
32
- value: JsonValue;
33
- }>>;
34
- clientStateGet?(key: string): Promise<JsonValue | undefined>;
35
- clientStateSet?(key: string, value: JsonValue): Promise<void>;
36
- clientStateDelete?(key: string): Promise<void>;
37
34
  createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
38
35
  createCheckpoint(): Promise<CreateCheckpointReceipt>;
39
36
  undo(): Promise<UndoReceipt>;
@@ -43,7 +40,7 @@ export type LixBinding = {
43
40
  mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
44
41
  mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
45
42
  syncDiskToLix(): Promise<void>;
46
- /** Internal snapshot capability implemented by browser memory bindings. */
43
+ /** Explicit snapshot utility available on direct in-memory WASM bindings. */
47
44
  exportSnapshot?(): Promise<Uint8Array>;
48
45
  close(): Promise<void>;
49
46
  };
@@ -59,13 +56,7 @@ export type ObserveEventsBinding = {
59
56
  export type TelemetryDispatch = (span: LixTelemetrySpan) => void;
60
57
  export type LixStorageConfig = {
61
58
  kind: "memory";
62
- snapshot?: Uint8Array;
63
59
  } | {
64
- kind: "sqlite";
65
- path: string;
66
- } | {
67
- kind: "localFilesystem";
68
- path: string;
69
- lixDir?: string;
70
- syncAllFiles: boolean;
71
- };
60
+ kind: "indexedDb";
61
+ name: string;
62
+ } | LixStorageAdapterConfig;
@@ -1,14 +1,27 @@
1
+ import { IndexedDbBackend } from "./indexeddb-backend.js";
1
2
  // Generated before TypeScript compilation and emitted beside this module.
2
3
  // @ts-expect-error Generated by build:wasm.
3
- import initWasm, { openMemoryFromSnapshot } from "./wasm/lix_js_sdk.js";
4
+ import initWasm, { openIndexedDb, openMemory } from "./wasm/lix_js_sdk.js";
4
5
  let wasmInitialized;
5
6
  function initializeWasm() {
6
7
  return (wasmInitialized ??= initWasm());
7
8
  }
8
9
  export async function openLixBinding(storage, telemetry) {
9
- if (storage.kind !== "memory") {
10
- throw new Error(`${storage.kind === "localFilesystem" ? "LocalFilesystem" : "SQLite"} is only available in Node.js`);
11
- }
12
10
  await initializeWasm();
13
- return openMemoryFromSnapshot(telemetry, storage.snapshot);
11
+ switch (storage.kind) {
12
+ case "memory":
13
+ return openMemory(telemetry);
14
+ case "indexedDb": {
15
+ const backend = await IndexedDbBackend.open(storage.name);
16
+ try {
17
+ return (await openIndexedDb(backend, telemetry));
18
+ }
19
+ catch (error) {
20
+ await backend.close().catch(() => undefined);
21
+ throw error;
22
+ }
23
+ }
24
+ case "filesystem":
25
+ throw new Error("FilesystemStorage is only available in Node.js");
26
+ }
14
27
  }
@@ -45,21 +45,15 @@ export function openLixBinding(storage, telemetry) {
45
45
  : undefined;
46
46
  switch (storage.kind) {
47
47
  case "memory":
48
- if (storage.snapshot !== undefined) {
49
- throw new Error("Memory snapshots are only available in the browser binding");
50
- }
51
48
  if (nativeTelemetry)
52
49
  return addon.Lix.openMemory(nativeTelemetry);
53
50
  return addon.Lix.openMemory();
54
- case "sqlite":
55
- if (nativeTelemetry) {
56
- return addon.Lix.openSQLite(storage.path, nativeTelemetry);
57
- }
58
- return addon.Lix.openSQLite(storage.path);
59
- case "localFilesystem":
51
+ case "indexedDb":
52
+ throw new Error("IndexedDbStorage is only available in browsers");
53
+ case "filesystem":
60
54
  if (nativeTelemetry) {
61
- return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
55
+ return addon.Lix.openFilesystemStorage(storage.path, storage.syncAllFiles, nativeTelemetry);
62
56
  }
63
- return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles);
57
+ return addon.Lix.openFilesystemStorage(storage.path, storage.syncAllFiles);
64
58
  }
65
59
  }
package/dist/errors.d.ts CHANGED
@@ -5,5 +5,3 @@ export type LixJsError = Error & {
5
5
  };
6
6
  export declare function invalidArgument(operation: string, argument: string, expected: string, actual: string, receiver?: string): LixJsError;
7
7
  export declare function invalidParam(index: number, message: string, actual: string): LixJsError;
8
- export declare function localFilesystemNotOpen(operation: string): LixJsError;
9
- export declare function localFilesystemAlreadyOpen(): LixJsError;
package/dist/errors.js CHANGED
@@ -17,16 +17,3 @@ export function invalidParam(index, message, actual) {
17
17
  };
18
18
  return error;
19
19
  }
20
- export function localFilesystemNotOpen(operation) {
21
- const error = new Error(`LocalFilesystem.${operation}() requires the storage to be opened with openLix() first`);
22
- error.name = "LixError";
23
- error.code = "LIX_LOCAL_FILESYSTEM_NOT_OPEN";
24
- error.details = { operation };
25
- return error;
26
- }
27
- export function localFilesystemAlreadyOpen() {
28
- const error = new Error("openLix() LocalFilesystem is already open; close the existing Lix or create a new LocalFilesystem");
29
- error.name = "LixError";
30
- error.code = "LIX_LOCAL_FILESYSTEM_IN_USE";
31
- return error;
32
- }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, SQLite, } from "./open-lix.js";
1
+ export { IndexedDbStorage, Lix, LixTransaction, ObserveEvents, openLix, } from "./open-lix.js";
2
+ export type { LixStorage, LixStorageAdapterConfig, LixStorageConnection, } from "./storage-adapter.js";
2
3
  export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plugins.js";
3
4
  export { Row } from "./result.js";
4
5
  export { Value } from "./value.js";
5
- export type { LixClientState } from "./client-state.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, SQLiteOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
6
+ export type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, IndexedDbStorageOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, LixTelemetryOptions, LixTelemetrySpan, 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 { IndexedDbStorage, 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";
@@ -0,0 +1,19 @@
1
+ type IndexedDbEntry = {
2
+ key: Uint8Array;
3
+ value: Uint8Array;
4
+ };
5
+ type IndexedDbChanges = {
6
+ deletes: Uint8Array[];
7
+ puts: IndexedDbEntry[];
8
+ strictDurability: boolean;
9
+ };
10
+ /** Internal worker-local bridge used by the WASM IndexedDB storage adapter. */
11
+ export declare class IndexedDbBackend {
12
+ #private;
13
+ private constructor();
14
+ static open(name: string): Promise<IndexedDbBackend>;
15
+ loadEntries(): Promise<IndexedDbEntry[]>;
16
+ applyChanges(changes: IndexedDbChanges): Promise<void>;
17
+ close(): Promise<void>;
18
+ }
19
+ export {};
@@ -0,0 +1,121 @@
1
+ const DATABASE_VERSION = 1;
2
+ const ENTRY_STORE = "entries";
3
+ /** Internal worker-local bridge used by the WASM IndexedDB storage adapter. */
4
+ export class IndexedDbBackend {
5
+ #database;
6
+ #releaseLock;
7
+ #closed = false;
8
+ constructor(database, releaseLock) {
9
+ this.#database = database;
10
+ this.#releaseLock = releaseLock;
11
+ }
12
+ static async open(name) {
13
+ const releaseLock = await acquireDatabaseLock(name);
14
+ try {
15
+ const request = indexedDB.open(name, DATABASE_VERSION);
16
+ request.onupgradeneeded = () => {
17
+ if (!request.result.objectStoreNames.contains(ENTRY_STORE)) {
18
+ request.result.createObjectStore(ENTRY_STORE);
19
+ }
20
+ };
21
+ return new IndexedDbBackend(await openDatabase(request), releaseLock);
22
+ }
23
+ catch (error) {
24
+ releaseLock();
25
+ throw error;
26
+ }
27
+ }
28
+ async loadEntries() {
29
+ const transaction = this.#database.transaction(ENTRY_STORE, "readonly");
30
+ const store = transaction.objectStore(ENTRY_STORE);
31
+ const entries = [];
32
+ await new Promise((resolve, reject) => {
33
+ const request = store.openCursor();
34
+ request.onerror = () => reject(request.error ?? transaction.error);
35
+ request.onsuccess = () => {
36
+ const cursor = request.result;
37
+ if (!cursor) {
38
+ resolve();
39
+ return;
40
+ }
41
+ try {
42
+ entries.push({
43
+ key: copyBytes(cursor.key, "IndexedDB entry key"),
44
+ value: copyBytes(cursor.value, "IndexedDB entry value"),
45
+ });
46
+ }
47
+ catch (error) {
48
+ transaction.abort();
49
+ reject(error);
50
+ return;
51
+ }
52
+ cursor.continue();
53
+ };
54
+ });
55
+ await transactionDone(transaction);
56
+ return entries;
57
+ }
58
+ async applyChanges(changes) {
59
+ const transaction = this.#database.transaction(ENTRY_STORE, "readwrite", {
60
+ durability: changes.strictDurability ? "strict" : "default",
61
+ });
62
+ const store = transaction.objectStore(ENTRY_STORE);
63
+ for (const key of changes.deletes)
64
+ store.delete(binaryKey(key));
65
+ for (const entry of changes.puts) {
66
+ store.put(entry.value, binaryKey(entry.key));
67
+ }
68
+ await transactionDone(transaction);
69
+ }
70
+ async close() {
71
+ if (this.#closed)
72
+ return;
73
+ this.#closed = true;
74
+ this.#database.close();
75
+ this.#releaseLock();
76
+ }
77
+ }
78
+ function acquireDatabaseLock(name) {
79
+ let release;
80
+ const released = new Promise((resolve) => {
81
+ release = resolve;
82
+ });
83
+ return new Promise((resolve, reject) => {
84
+ void navigator.locks
85
+ .request(`lix:indexeddb:${name}`, { mode: "exclusive", ifAvailable: true }, async (lock) => {
86
+ if (!lock) {
87
+ reject(new Error(`IndexedDB storage '${name}' is already open`));
88
+ return;
89
+ }
90
+ resolve(release);
91
+ await released;
92
+ })
93
+ .catch(reject);
94
+ });
95
+ }
96
+ function openDatabase(request) {
97
+ return new Promise((resolve, reject) => {
98
+ request.onsuccess = () => resolve(request.result);
99
+ request.onerror = () => reject(request.error);
100
+ request.onblocked = () => reject(new Error("IndexedDB database open was blocked"));
101
+ });
102
+ }
103
+ function binaryKey(value) {
104
+ return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
105
+ }
106
+ function transactionDone(transaction) {
107
+ return new Promise((resolve, reject) => {
108
+ transaction.oncomplete = () => resolve();
109
+ transaction.onabort = () => reject(transaction.error);
110
+ transaction.onerror = () => reject(transaction.error);
111
+ });
112
+ }
113
+ function copyBytes(value, label) {
114
+ if (value instanceof ArrayBuffer) {
115
+ return new Uint8Array(value.slice(0));
116
+ }
117
+ if (ArrayBuffer.isView(value)) {
118
+ return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
119
+ }
120
+ throw new Error(`${label} is not binary data`);
121
+ }
package/dist/lix.d.ts CHANGED
@@ -1,15 +1,12 @@
1
- import { type LixClientState, type ManagedClientState } from "./client-state.js";
2
1
  import type { LixBinding, LixTransactionBinding, ObserveEventsBinding } from "./binding-types.js";
3
- import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, UndoReceipt } from "./types.js";
2
+ import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, ExecuteBatchResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, UndoReceipt } from "./types.js";
4
3
  export declare class Lix {
5
4
  #private;
6
5
  private readonly binding;
7
- private readonly managedClientState?;
8
6
  private closePromise;
9
- readonly clientState: LixClientState;
10
- constructor(binding: LixBinding, managedClientState?: ManagedClientState | undefined);
7
+ constructor(binding: LixBinding);
11
8
  execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
12
- executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteResult[]>;
9
+ executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteBatchResult[]>;
13
10
  observe(sql: string, params?: SqlParam[]): ObserveEvents;
14
11
  beginTransaction(): Promise<LixTransaction>;
15
12
  activeBranchId(): Promise<string>;