@lix-js/sdk 0.8.3 → 0.9.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 (47) hide show
  1. package/README.md +82 -16
  2. package/dist/binding-types.d.ts +18 -2
  3. package/dist/binding.browser.d.ts +2 -2
  4. package/dist/binding.browser.js +3 -3
  5. package/dist/binding.node.d.ts +2 -2
  6. package/dist/binding.node.js +10 -4
  7. package/dist/bundled-plugins/plugin_csv_v2.lixplugin +0 -0
  8. package/dist/bundled-plugins/plugin_markdown_incremental_v2.lixplugin +0 -0
  9. package/dist/bundled-plugins.js +4 -4
  10. package/dist/client-state.d.ts +40 -0
  11. package/dist/client-state.js +178 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/lix.d.ts +44 -0
  14. package/dist/lix.js +369 -0
  15. package/dist/local-storage-adapter.d.ts +26 -0
  16. package/dist/local-storage-adapter.js +117 -0
  17. package/dist/open-lix.d.ts +3 -34
  18. package/dist/open-lix.js +103 -170
  19. package/dist/remote/client.d.ts +8 -0
  20. package/dist/remote/client.js +1036 -0
  21. package/dist/remote/protocol.d.ts +166 -0
  22. package/dist/remote/protocol.js +362 -0
  23. package/dist/remote/sse.d.ts +12 -0
  24. package/dist/remote/sse.js +87 -0
  25. package/dist/snapshot-persistence.d.ts +7 -0
  26. package/dist/snapshot-persistence.js +26 -0
  27. package/dist/types.d.ts +58 -1
  28. package/dist/wasm/lix_js_sdk.d.ts +19 -4
  29. package/dist/wasm/lix_js_sdk.js +98 -17
  30. package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
  31. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +9 -2
  32. package/dist/worker/client.d.ts +23 -4
  33. package/dist/worker/client.js +287 -10
  34. package/dist/worker/host.js +30 -2
  35. package/dist/worker/protocol.d.ts +26 -2
  36. package/dist/workerd.d.ts +10 -0
  37. package/dist/workerd.js +7 -4
  38. package/package.json +19 -16
  39. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  40. package/dist/bundled-plugins/plugin_md_v2.lixplugin +0 -0
  41. package/dist/jco/js-component-bindgen-component.core.wasm +0 -0
  42. package/dist/jco/js-component-bindgen-component.core2.wasm +0 -0
  43. package/dist/jco/js-component-bindgen-component.js +0 -13662
  44. package/dist/jco-transpile.browser.d.ts +0 -14
  45. package/dist/jco-transpile.browser.js +0 -22
  46. package/dist/plugin-runtime.d.ts +0 -45
  47. package/dist/plugin-runtime.js +0 -124
package/README.md CHANGED
@@ -22,6 +22,72 @@ console.log(result.rows[0]?.get("message"));
22
22
  await lix.close();
23
23
  ```
24
24
 
25
+ ## Remote workspaces
26
+
27
+ Use the same Lix client as a thin client against a hosted workspace:
28
+
29
+ ```ts
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
+ },
38
+ });
39
+
40
+ const files = lix.observe("SELECT path FROM lix_file ORDER BY path");
41
+ const initial = await files.next();
42
+
43
+ await lix.execute(
44
+ "INSERT INTO lix_file (path, data) VALUES ($1, $2)",
45
+ ["/hello.txt", new TextEncoder().encode("hello")],
46
+ );
47
+ const update = await files.next();
48
+
49
+ files.close();
50
+ await lix.close();
51
+ ```
52
+
53
+ Without `storage`, remote mode uses the server for all persistence and does not
54
+ open a local engine. Dynamic headers are resolved for every request and
55
+ observation reconnect. An injected `fetch` can route requests through a service
56
+ binding or another authorized server-side transport.
57
+
58
+ Browser clients can opt into private, durable client state with the local
59
+ storage adapter:
60
+
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
+
25
91
  Filesystem sync and SQLite persistence use native Node.js dependencies:
26
92
 
27
93
  ```ts
@@ -93,36 +159,36 @@ try {
93
159
  - Pass `new LocalFilesystem({ path, lixDir, syncAllFiles: true })` for filesystem sync with repository metadata in an external `.lix` directory and no workspace `.lix` directory.
94
160
  - 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.
95
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.
96
- - In browsers, `openLix()` loads the Rust engine as WebAssembly and uses the
97
- in-memory storage.
162
+ - In browsers, local mode and remote mode with client storage load the Rust
163
+ engine as WebAssembly. Supplying a snapshot storage adapter persists that
164
+ local Lix; in remote mode, the local engine contains only client state.
98
165
  - `LocalFilesystem` and `SQLite` are Node.js-only. Constructing them is safe in
99
166
  shared code, but passing one to `openLix()` in a browser throws an error.
100
167
  - The package is ESM-only.
101
168
  - The package uses conditional ESM imports internally: Node.js resolves the
102
169
  native N-API binding, while browsers and other runtimes resolve the portable
103
170
  WebAssembly binding. Vite follows this split without consumer configuration.
104
- - Every `openLix()` owns one dedicated worker. The engine, storage, and
105
- installed WASM plugin components all run in that worker in both Node.js and
106
- browsers, so database and plugin work does not block the page's main thread.
107
- - Installed WASM plugin components are transpiled with JCO and executed by the
108
- worker's WebAssembly runtime in both environments. Plugin execution does not
109
- yet enforce the declared fuel, timeout, or memory limits, so only install
110
- trusted plugins.
171
+ - Every browser `openLix()` owns one dedicated worker, so database work does
172
+ 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
174
+ Wasmtime runtime. The browser and Workerd bindings currently open without a
175
+ 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.
111
177
  - A page Content Security Policy only needs to permit the package's same-origin
112
- worker. WebAssembly compilation and JCO's generated `data:` module imports
113
- happen inside that worker, so they can be scoped to the worker script's HTTP
114
- response instead of being allowed by the document:
178
+ worker. WebAssembly compilation happens inside that worker, so the required
179
+ permission can be scoped to the worker script's HTTP response instead of
180
+ being allowed by the document:
115
181
 
116
182
  ```http
117
183
  # HTML document response
118
184
  Content-Security-Policy: default-src 'self'; script-src 'self'; worker-src 'self'
119
185
 
120
186
  # Lix worker response (Vite emits assets/entry.browser-<hash>.js)
121
- Content-Security-Policy: default-src 'none'; script-src 'self' data: 'wasm-unsafe-eval'; connect-src 'self'
187
+ Content-Security-Policy: default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'
122
188
  ```
123
189
 
124
190
  Hosts that apply one policy to every response can use
125
- `script-src 'self' data: 'wasm-unsafe-eval'; worker-src 'self'` globally
191
+ `script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'` globally
126
192
  instead. Worker-scoped headers keep those permissions out of the page.
127
193
  - SQL parameters use normal JavaScript values: `string`, finite `number`, `boolean`, `Uint8Array`, `null`, JSON-compatible arrays, and JSON-compatible plain objects.
128
194
  - Use `Value.integer(...)`, `Value.real(...)`, `Value.text(...)`, `Value.json(...)`, or `Value.blob(...)` only when you need to pass an explicit native Lix value.
@@ -141,8 +207,8 @@ npm run test:browser
141
207
 
142
208
  `npm run test:browser:production` additionally packs the SDK, installs the
143
209
  tarball into a minimal Vite app, makes a production build, and exercises SQL
144
- plus both bundled plugins in Chromium. It runs with both worker-scoped and
145
- global strict CSP headers.
210
+ plus bundled-plugin archive loading in Chromium. It runs with both
211
+ worker-scoped and global strict CSP headers.
146
212
 
147
213
  Use `npm run build:wasm:dev` while iterating on the Rust bridge when release
148
214
  optimization is unnecessary.
@@ -1,4 +1,4 @@
1
- import type { CreateBranchOptions, CreateBranchReceipt, ExecuteOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
1
+ import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, 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
4
  columns: string[];
@@ -16,17 +16,32 @@ export type BindingObserveEvent = {
16
16
  rows: BindingExecuteResult;
17
17
  };
18
18
  export type BindingParam = NativeLixValue;
19
+ export type BindingBatchStatement = {
20
+ sql: string;
21
+ params: BindingParam[];
22
+ };
19
23
  export type LixBinding = {
20
24
  execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
25
+ executeBatch(statements: BindingBatchStatement[], options?: LixBatchOptions): Promise<BindingExecuteResult[]>;
21
26
  observe(sql: string, params: BindingParam[]): Promise<ObserveEventsBinding>;
22
27
  beginTransaction(): Promise<LixTransactionBinding>;
23
28
  activeBranchId(): Promise<string>;
29
+ clientStateEntries?(): Promise<Array<{
30
+ key: string;
31
+ value: JsonValue;
32
+ }>>;
33
+ clientStateGet?(key: string): Promise<JsonValue | undefined>;
34
+ clientStateSet?(key: string, value: JsonValue): Promise<void>;
35
+ clientStateDelete?(key: string): Promise<void>;
24
36
  createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
37
+ createCheckpoint(): Promise<CreateCheckpointReceipt>;
25
38
  switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
26
39
  importFilesystemPaths(paths: string[]): Promise<void>;
27
40
  mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
28
41
  mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
29
42
  syncDiskToLix(): Promise<void>;
43
+ /** Internal snapshot capability implemented by browser memory bindings. */
44
+ exportSnapshot?(): Promise<Uint8Array>;
30
45
  close(): Promise<void>;
31
46
  };
32
47
  export type LixTransactionBinding = {
@@ -38,9 +53,10 @@ export type ObserveEventsBinding = {
38
53
  next(): Promise<BindingObserveEvent | null | undefined>;
39
54
  close(): void;
40
55
  };
41
- export type PluginRuntimeDispatch = (request: unknown) => Promise<unknown>;
56
+ export type TelemetryDispatch = (span: LixTelemetrySpan) => void;
42
57
  export type LixStorageConfig = {
43
58
  kind: "memory";
59
+ snapshot?: Uint8Array;
44
60
  } | {
45
61
  kind: "sqlite";
46
62
  path: string;
@@ -1,2 +1,2 @@
1
- import type { LixStorageConfig, LixBinding, PluginRuntimeDispatch } from "./binding-types.js";
2
- export declare function openLixBinding(storage: LixStorageConfig, dispatch: PluginRuntimeDispatch): Promise<LixBinding>;
1
+ import type { LixStorageConfig, LixBinding, TelemetryDispatch } from "./binding-types.js";
2
+ export declare function openLixBinding(storage: LixStorageConfig, telemetry?: TelemetryDispatch): Promise<LixBinding>;
@@ -1,14 +1,14 @@
1
1
  // Generated before TypeScript compilation and emitted beside this module.
2
2
  // @ts-expect-error Generated by build:wasm.
3
- import initWasm, { openMemory } from "./wasm/lix_js_sdk.js";
3
+ import initWasm, { openMemoryFromSnapshot } from "./wasm/lix_js_sdk.js";
4
4
  let wasmInitialized;
5
5
  function initializeWasm() {
6
6
  return (wasmInitialized ??= initWasm());
7
7
  }
8
- export async function openLixBinding(storage, dispatch) {
8
+ export async function openLixBinding(storage, telemetry) {
9
9
  if (storage.kind !== "memory") {
10
10
  throw new Error(`${storage.kind === "localFilesystem" ? "LocalFilesystem" : "SQLite"} is only available in Node.js`);
11
11
  }
12
12
  await initializeWasm();
13
- return openMemory(dispatch);
13
+ return openMemoryFromSnapshot(telemetry, storage.snapshot);
14
14
  }
@@ -1,2 +1,2 @@
1
- import type { LixStorageConfig, LixBinding, PluginRuntimeDispatch } from "./binding-types.js";
2
- export declare function openLixBinding(storage: LixStorageConfig, dispatch: PluginRuntimeDispatch): Promise<LixBinding>;
1
+ import type { LixStorageConfig, LixBinding, TelemetryDispatch } from "./binding-types.js";
2
+ export declare function openLixBinding(storage: LixStorageConfig, telemetry?: TelemetryDispatch): Promise<LixBinding>;
@@ -39,13 +39,19 @@ catch (cause) {
39
39
  error.cause = cause;
40
40
  throw error;
41
41
  }
42
- export function openLixBinding(storage, dispatch) {
42
+ export function openLixBinding(storage, telemetry) {
43
+ const nativeTelemetry = telemetry
44
+ ? (spanJson) => telemetry(JSON.parse(spanJson))
45
+ : undefined;
43
46
  switch (storage.kind) {
44
47
  case "memory":
45
- return addon.Lix.openMemory(dispatch);
48
+ if (storage.snapshot !== undefined) {
49
+ throw new Error("Memory snapshots are only available in the browser binding");
50
+ }
51
+ return addon.Lix.openMemory(nativeTelemetry);
46
52
  case "sqlite":
47
- return addon.Lix.openSQLite(storage.path, dispatch);
53
+ return addon.Lix.openSQLite(storage.path, nativeTelemetry);
48
54
  case "localFilesystem":
49
- return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, dispatch);
55
+ return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
50
56
  }
51
57
  }
@@ -1,11 +1,11 @@
1
1
  const BUNDLED_PLUGIN_MANIFEST = [
2
2
  {
3
- key: "plugin_md_v2",
4
- fileName: "plugin_md_v2.lixplugin",
3
+ key: "plugin_markdown_incremental_v2",
4
+ fileName: "plugin_markdown_incremental_v2.lixplugin",
5
5
  },
6
6
  {
7
- key: "plugin_csv",
8
- fileName: "plugin_csv.lixplugin",
7
+ key: "plugin_csv_v2",
8
+ fileName: "plugin_csv_v2.lixplugin",
9
9
  },
10
10
  ];
11
11
  export async function bundledPluginArchives() {
@@ -0,0 +1,40 @@
1
+ import type { LixBinding } from "./binding-types.js";
2
+ import type { JsonValue } from "./types.js";
3
+ export declare const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
4
+ export type LixClientState = {
5
+ /** Returns the hydrated client-local value without a network round trip. */
6
+ get<T extends JsonValue = JsonValue>(key: string): T | undefined;
7
+ /** Commits the value through the local Rust Lix and then persists its snapshot. */
8
+ set(key: string, value: JsonValue): Promise<void>;
9
+ /** Deletes the value through the local Rust Lix and then persists its snapshot. */
10
+ delete(key: string): Promise<void>;
11
+ /** Subscribes to successful mutations made through this client-state handle. */
12
+ subscribe(listener: () => void): () => void;
13
+ };
14
+ export declare function unavailableClientState(): LixClientState;
15
+ type ClientStateBinding = LixBinding & {
16
+ exportSnapshot?: () => Promise<Uint8Array>;
17
+ };
18
+ export type OpenClientStateOptions = {
19
+ readonly binding: ClientStateBinding;
20
+ readonly saveSnapshot?: (snapshot: Uint8Array) => Promise<void>;
21
+ readonly closeBinding?: boolean;
22
+ };
23
+ /**
24
+ * Opens the typed client-state facade over a private local Rust Lix.
25
+ *
26
+ * Values are ordinary global, untracked `lix_key_value` rows. The physical
27
+ * prefix is intentionally private so built-in Lix key/value rows never leak
28
+ * through this small API.
29
+ */
30
+ export declare function openClientState(options: OpenClientStateOptions): Promise<ManagedLixClientState>;
31
+ export declare class ManagedLixClientState implements LixClientState {
32
+ #private;
33
+ constructor(options: OpenClientStateOptions, initial: Map<string, JsonValue>);
34
+ get<T extends JsonValue = JsonValue>(key: string): T | undefined;
35
+ set(key: string, value: JsonValue): Promise<void>;
36
+ delete(key: string): Promise<void>;
37
+ subscribe(listener: () => void): () => void;
38
+ close(): Promise<void>;
39
+ }
40
+ export {};
@@ -0,0 +1,178 @@
1
+ import { isSnapshotPersistenceAfterCommitError } from "./snapshot-persistence.js";
2
+ import { Value } from "./value.js";
3
+ export const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
4
+ export function unavailableClientState() {
5
+ const unavailable = () => {
6
+ const error = new Error("Lix client state requires client storage; pass storage to openLix()");
7
+ error.name = "LixError";
8
+ error.code = "LIX_CLIENT_STORAGE_REQUIRED";
9
+ return error;
10
+ };
11
+ return {
12
+ get: () => undefined,
13
+ set: async () => {
14
+ throw unavailable();
15
+ },
16
+ delete: async () => {
17
+ throw unavailable();
18
+ },
19
+ subscribe: () => () => undefined,
20
+ };
21
+ }
22
+ /**
23
+ * Opens the typed client-state facade over a private local Rust Lix.
24
+ *
25
+ * Values are ordinary global, untracked `lix_key_value` rows. The physical
26
+ * prefix is intentionally private so built-in Lix key/value rows never leak
27
+ * through this small API.
28
+ */
29
+ export async function openClientState(options) {
30
+ const entries = options.binding.clientStateEntries;
31
+ if (!entries) {
32
+ throw new Error("The selected Lix binding does not support typed client state");
33
+ }
34
+ const initial = new Map();
35
+ for (const entry of await entries.call(options.binding)) {
36
+ assertClientStateKey(entry.key);
37
+ assertJsonValue(entry.value);
38
+ initial.set(entry.key, cloneJsonValue(entry.value));
39
+ }
40
+ return new ManagedLixClientState(options, initial);
41
+ }
42
+ export class ManagedLixClientState {
43
+ #binding;
44
+ #saveSnapshot;
45
+ #closeBinding;
46
+ #values;
47
+ #listeners = new Set();
48
+ #operationQueue = Promise.resolve();
49
+ #closePromise;
50
+ #acceptingOperations = true;
51
+ constructor(options, initial) {
52
+ this.#binding = options.binding;
53
+ this.#saveSnapshot = options.saveSnapshot;
54
+ this.#closeBinding = options.closeBinding ?? false;
55
+ this.#values = initial;
56
+ }
57
+ get(key) {
58
+ assertClientStateKey(key);
59
+ const value = this.#values.get(key);
60
+ return value === undefined ? undefined : cloneJsonValue(value);
61
+ }
62
+ set(key, value) {
63
+ assertClientStateKey(key);
64
+ assertJsonValue(value);
65
+ this.#assertOpen();
66
+ const nextValue = cloneJsonValue(value);
67
+ return this.#enqueue(async () => {
68
+ const set = this.#binding.clientStateSet;
69
+ if (!set)
70
+ throw new Error("Typed Lix client state is unavailable");
71
+ try {
72
+ await set.call(this.#binding, key, nextValue);
73
+ }
74
+ catch (error) {
75
+ if (!isSnapshotPersistenceAfterCommitError(error))
76
+ throw error;
77
+ this.#commitSet(key, nextValue);
78
+ throw error;
79
+ }
80
+ this.#commitSet(key, nextValue);
81
+ await this.#persist();
82
+ });
83
+ }
84
+ delete(key) {
85
+ assertClientStateKey(key);
86
+ this.#assertOpen();
87
+ return this.#enqueue(async () => {
88
+ const deleteValue = this.#binding.clientStateDelete;
89
+ if (!deleteValue)
90
+ throw new Error("Typed Lix client state is unavailable");
91
+ try {
92
+ await deleteValue.call(this.#binding, key);
93
+ }
94
+ catch (error) {
95
+ if (!isSnapshotPersistenceAfterCommitError(error))
96
+ throw error;
97
+ this.#commitDelete(key);
98
+ throw error;
99
+ }
100
+ this.#commitDelete(key);
101
+ await this.#persist();
102
+ });
103
+ }
104
+ subscribe(listener) {
105
+ if (typeof listener !== "function") {
106
+ throw new TypeError("clientState.subscribe() requires a function");
107
+ }
108
+ this.#assertOpen();
109
+ this.#listeners.add(listener);
110
+ return () => this.#listeners.delete(listener);
111
+ }
112
+ async close() {
113
+ if (this.#closePromise)
114
+ return this.#closePromise;
115
+ this.#acceptingOperations = false;
116
+ this.#closePromise = (async () => {
117
+ await this.#operationQueue;
118
+ this.#listeners.clear();
119
+ if (this.#closeBinding)
120
+ await this.#binding.close();
121
+ })();
122
+ return this.#closePromise;
123
+ }
124
+ #enqueue(operation) {
125
+ const result = this.#operationQueue.then(operation, operation);
126
+ this.#operationQueue = result.then(() => undefined, () => undefined);
127
+ return result;
128
+ }
129
+ async #persist() {
130
+ if (!this.#saveSnapshot)
131
+ return;
132
+ if (!this.#binding.exportSnapshot) {
133
+ throw new Error("The selected Lix binding cannot export storage snapshots");
134
+ }
135
+ await this.#saveSnapshot(await this.#binding.exportSnapshot());
136
+ }
137
+ #commitSet(key, value) {
138
+ this.#values.set(key, value);
139
+ this.#publish();
140
+ }
141
+ #commitDelete(key) {
142
+ if (this.#values.delete(key))
143
+ this.#publish();
144
+ }
145
+ #publish() {
146
+ for (const listener of [...this.#listeners]) {
147
+ try {
148
+ listener();
149
+ }
150
+ catch {
151
+ // Subscribers do not participate in the completed local transaction.
152
+ }
153
+ }
154
+ }
155
+ #assertOpen() {
156
+ if (!this.#acceptingOperations) {
157
+ throw new Error("Lix client state is closed");
158
+ }
159
+ }
160
+ }
161
+ function assertClientStateKey(key) {
162
+ if (typeof key !== "string" || key.length === 0) {
163
+ throw new TypeError("clientState key must be a non-empty string");
164
+ }
165
+ }
166
+ function assertJsonValue(value) {
167
+ // Value.json owns the SDK's full JSON validation, including finite numbers,
168
+ // well-formed strings, plain objects, and cycle detection.
169
+ Value.json(value);
170
+ }
171
+ function cloneJsonValue(value) {
172
+ if (Array.isArray(value))
173
+ return value.map(cloneJsonValue);
174
+ if (value && typeof value === "object") {
175
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneJsonValue(entry)]));
176
+ }
177
+ return value;
178
+ }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, SQLite, }
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
- export type { CreateBranchOptions, CreateBranchReceipt, ExecuteOptions, ExecuteResult, LocalFilesystemOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, SqlParam, SQLiteOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
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";
package/dist/lix.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { type LixClientState, type ManagedLixClientState } from "./client-state.js";
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";
4
+ export declare class Lix {
5
+ #private;
6
+ private readonly binding;
7
+ private readonly managedClientState?;
8
+ private closePromise;
9
+ readonly clientState: LixClientState;
10
+ constructor(binding: LixBinding, managedClientState?: ManagedLixClientState | undefined);
11
+ execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
12
+ executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteResult[]>;
13
+ observe(sql: string, params?: SqlParam[]): ObserveEvents;
14
+ beginTransaction(): Promise<LixTransaction>;
15
+ activeBranchId(): Promise<string>;
16
+ /** Subscribes to successful branch switches made through this Lix handle. */
17
+ subscribeActiveBranch(listener: () => void): () => void;
18
+ createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
19
+ createCheckpoint(): Promise<CreateCheckpointReceipt>;
20
+ switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
21
+ mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
22
+ mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
23
+ close(): Promise<void>;
24
+ }
25
+ export declare class ObserveEvents {
26
+ private readonly onClose;
27
+ private readonly setup;
28
+ private closed;
29
+ private readonly observeBinding;
30
+ constructor(observeBinding: Promise<ObserveEventsBinding>, onClose?: () => void);
31
+ next(): Promise<ObserveEvent | undefined>;
32
+ close(): void;
33
+ }
34
+ export declare class LixTransaction {
35
+ private readonly binding;
36
+ private readonly onFinish;
37
+ private finishPromise;
38
+ private finished;
39
+ constructor(binding: LixTransactionBinding, onFinish?: () => void);
40
+ execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
41
+ commit(): Promise<void>;
42
+ rollback(): Promise<void>;
43
+ private finish;
44
+ }