@lix-js/sdk 0.14.0 → 0.15.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
@@ -85,6 +85,20 @@ Remote server sessions are branch-pinned, so switching one client does not
85
85
  switch another client. Browser-local application state belongs to the
86
86
  application rather than the remote Lix handle.
87
87
 
88
+ Remote handles support branch creation, merge preview and merge through the
89
+ server's Rust engine. `openAnotherSession()` inherits the active branch unless
90
+ one is supplied, retains the authenticated account, and returns an independent
91
+ handle with the same operations—including streaming `exportSnapshot()`. Snapshot
92
+ export covers the complete repository, not only the active branch. Cancelling
93
+ the returned stream releases the HTTP request.
94
+
95
+ Internally, local and connected-sync bindings call `Lix`; remote bindings call
96
+ the Rust protocol client. Both implement the required Rust session operation
97
+ contract, and the WASM bindings share SQL and branch-operation forwarding and
98
+ value conversion. Host-specific storage, telemetry, stream, and actor cleanup
99
+ remain in the bindings. This does not make network availability or authentication
100
+ constraints identical to an offline local engine.
101
+
88
102
  Filesystem sync uses native Node.js dependencies:
89
103
 
90
104
  ```ts
@@ -58,7 +58,7 @@ export type LixBinding = {
58
58
  mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
59
59
  mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
60
60
  syncDiskToLix(): Promise<void>;
61
- exportSnapshot?(): SnapshotExportBinding;
61
+ exportSnapshot(): SnapshotExportBinding;
62
62
  close(): Promise<void>;
63
63
  };
64
64
  export type LixTransactionBinding = {
@@ -1,16 +1,8 @@
1
- import { initializeBrowserWasm } from "./browser-wasm-init.js";
1
+ import { initializeWasm } from "./wasm-init.js";
2
2
  import { restoreSnapshot } from "./snapshot-restore.js";
3
3
  // Generated before TypeScript compilation and emitted beside this module.
4
4
  // @ts-ignore Generated by build:wasm and absent in source-only checks.
5
- import initWasm, { openJsStorage, openJsStorageFromSnapshot, openMemory, openMemoryFromSnapshot, } from "./wasm/lix_js_sdk.js";
6
- let wasmInitialized;
7
- function initializeWasm() {
8
- if (wasmInitialized !== undefined)
9
- return wasmInitialized;
10
- const initialization = initializeBrowserWasm(initWasm, new URL("./wasm/lix_js_sdk_bg.wasm", import.meta.url));
11
- wasmInitialized = initialization;
12
- return wasmInitialized;
13
- }
5
+ import { openJsStorage, openJsStorageFromSnapshot, openMemory, openMemoryFromSnapshot, } from "./wasm/lix_js_sdk.js";
14
6
  export async function openLixBinding(storage, telemetry, telemetryParent, server, openProgress, snapshot) {
15
7
  await initializeWasm();
16
8
  switch (storage.kind) {
@@ -1,14 +1,8 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { restoreSnapshot } from "./snapshot-restore.js";
2
+ import { initializeWasm } from "./wasm-init.js";
3
3
  // Generated before TypeScript compilation and emitted beside this module.
4
4
  // @ts-ignore Generated by build:wasm and absent in source-only checks.
5
- import initWasm, { openMemory, openMemoryFromSnapshot } from "./wasm/lix_js_sdk.js";
6
- let wasmInitialized;
7
- function initializeWasm() {
8
- return (wasmInitialized ??= initWasm({
9
- module_or_path: readFile(new URL("./wasm/lix_js_sdk_bg.wasm", import.meta.url)),
10
- }));
11
- }
5
+ import { openMemory, openMemoryFromSnapshot } from "./wasm/lix_js_sdk.js";
12
6
  export async function openMemoryWasmBinding(telemetry, telemetryParent, openProgress, snapshot) {
13
7
  await initializeWasm();
14
8
  if (snapshot) {
package/dist/lix.js CHANGED
@@ -118,14 +118,7 @@ export class Lix {
118
118
  });
119
119
  const operation = this.#runOperation(async () => {
120
120
  try {
121
- const exportSnapshot = this.binding.exportSnapshot;
122
- if (!exportSnapshot) {
123
- const error = new Error("snapshot export is not available for remote Lix handles");
124
- error.name = "LixError";
125
- error.code = "LIX_UNSUPPORTED_STORAGE";
126
- throw error;
127
- }
128
- resolveBinding(exportSnapshot.call(this.binding));
121
+ resolveBinding(this.binding.exportSnapshot());
129
122
  await completed;
130
123
  }
131
124
  catch (error) {
package/dist/open-lix.js CHANGED
@@ -59,7 +59,8 @@ async function openLixInternal(options, snapshot) {
59
59
  }
60
60
  const { openLixWorkerBinding } = await import("./worker/client.js");
61
61
  if (options.storage === undefined) {
62
- return new Lix(await openLixWorkerBinding({ kind: "memory" }, undefined, options.telemetry, syncServer, options.onProgress, snapshot));
62
+ const binding = await openLixWorkerBinding({ kind: "memory" }, undefined, options.telemetry, syncServer, options.onProgress, snapshot);
63
+ return new Lix(binding);
63
64
  }
64
65
  if (isJsProviderLixStorage(options.storage)) {
65
66
  return openJsProviderStorage(options.storage, options.telemetry, syncServer, options.onProgress, snapshot);
@@ -1,26 +1,7 @@
1
- import { initializeBrowserWasm } from "../browser-wasm-init.js";
1
+ import { initializeWasm } from "../wasm-init.js";
2
2
  // Generated before TypeScript compilation and emitted beside the JS SDK.
3
3
  // @ts-ignore Generated by build:wasm and absent in source-only checks.
4
- import initWasm, { openRemote } from "../wasm/lix_js_sdk.js";
5
- let wasmInitialized;
6
- function initializeWasm() {
7
- if (wasmInitialized !== undefined)
8
- return wasmInitialized;
9
- let initialization;
10
- if (typeof process !== "undefined" && process.versions?.node) {
11
- initialization = import("node:fs/promises").then(({ readFile }) => initWasm({
12
- module_or_path: readFile(new URL("../wasm/lix_js_sdk_bg.wasm", import.meta.url)),
13
- }), (error) => {
14
- wasmInitialized = undefined;
15
- throw error;
16
- });
17
- }
18
- else {
19
- initialization = initializeBrowserWasm(initWasm, new URL("../wasm/lix_js_sdk_bg.wasm", import.meta.url));
20
- }
21
- wasmInitialized = initialization;
22
- return wasmInitialized;
23
- }
4
+ import { openRemote } from "../wasm/lix_js_sdk.js";
24
5
  export async function openRemoteLixBinding(options, clientOptions = {}) {
25
6
  if (!options || typeof options !== "object") {
26
7
  throw new TypeError("openLix() remote server must be an object");
@@ -41,9 +22,10 @@ export async function openRemoteLixBinding(options, clientOptions = {}) {
41
22
  clientOptions.initialActiveBranchId.length === 0) {
42
23
  throw new TypeError("initialActiveBranchId must be a non-empty string");
43
24
  }
44
- const protocolLocator = connectionUrl(options.url).toString();
25
+ const locator = connectionUrl(options.url);
26
+ const protocolLocator = locator.toString();
45
27
  await initializeWasm();
46
- return openRemote(protocolLocator, remoteFetch, options.headers, clientOptions.initialActiveBranchId);
28
+ return await openRemote(protocolLocator, remoteFetch, options.headers, clientOptions.initialActiveBranchId);
47
29
  }
48
30
  function connectionUrl(value) {
49
31
  let locator;
package/dist/types.d.ts CHANGED
@@ -184,9 +184,9 @@ export type ObserveEvent = {
184
184
  sequence: number;
185
185
  mutationSequence: number;
186
186
  /**
187
- * The current result of the observed query. Remote observations reconcile
188
- * the first frame of every stream through execute before publishing it, so
189
- * reconnects cannot expose a stale server snapshot to consumers.
187
+ * The current result of the observed query. Remote rows and
188
+ * `mutationSequence` come from the same authoritative observation frame;
189
+ * reconnect frames are coalesced when their rows are unchanged.
190
190
  */
191
191
  result: ExecuteResult;
192
192
  };
@@ -55,9 +55,10 @@ export class WasmRemoteLix {
55
55
  createBranch(options: any): Promise<any>;
56
56
  execute(sql: string, params: any, options?: any | null): Promise<any>;
57
57
  executeBatch(statements: any, options?: any | null): Promise<any>;
58
+ exportSnapshot(): WasmSnapshotExport;
58
59
  importFilesystemPaths(_paths: any): Promise<void>;
59
- mergeBranch(_options: any): Promise<any>;
60
- mergeBranchPreview(_options: any): Promise<any>;
60
+ mergeBranch(options: any): Promise<any>;
61
+ mergeBranchPreview(options: any): Promise<any>;
61
62
  observe(sql: string, params: any): Promise<WasmRemoteObserveEvents>;
62
63
  openAnotherSession(options: any): Promise<WasmRemoteLix>;
63
64
  redo(): Promise<any>;
@@ -162,6 +163,7 @@ export interface InitOutput {
162
163
  readonly wasmremotelix_createBranch: (a: number, b: number) => number;
163
164
  readonly wasmremotelix_execute: (a: number, b: number, c: number, d: number, e: number) => number;
164
165
  readonly wasmremotelix_executeBatch: (a: number, b: number, c: number) => number;
166
+ readonly wasmremotelix_exportSnapshot: (a: number) => number;
165
167
  readonly wasmremotelix_importFilesystemPaths: (a: number, b: number) => number;
166
168
  readonly wasmremotelix_mergeBranch: (a: number, b: number) => number;
167
169
  readonly wasmremotelix_mergeBranchPreview: (a: number, b: number) => number;
@@ -184,9 +186,9 @@ export interface InitOutput {
184
186
  readonly wasmsnapshotrestore_finish: (a: number) => number;
185
187
  readonly wasmsnapshotrestore_isComplete: (a: number) => number;
186
188
  readonly wasmsnapshotrestore_write: (a: number, b: number, c: number) => number;
187
- readonly __wasm_bindgen_func_elem_92059: (a: number, b: number, c: number, d: number) => void;
188
- readonly __wasm_bindgen_func_elem_92061: (a: number, b: number, c: number, d: number) => void;
189
- readonly __wasm_bindgen_func_elem_16459: (a: number, b: number) => void;
189
+ readonly __wasm_bindgen_func_elem_92635: (a: number, b: number, c: number, d: number) => void;
190
+ readonly __wasm_bindgen_func_elem_92637: (a: number, b: number, c: number, d: number) => void;
191
+ readonly __wasm_bindgen_func_elem_16813: (a: number, b: number) => void;
190
192
  readonly __wbindgen_export: (a: number, b: number) => number;
191
193
  readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
192
194
  readonly __wbindgen_export3: (a: number) => void;
@@ -356,6 +356,13 @@ export class WasmRemoteLix {
356
356
  const ret = wasm.wasmremotelix_executeBatch(this.__wbg_ptr, addHeapObject(statements), isLikeNone(options) ? 0 : addHeapObject(options));
357
357
  return takeObject(ret);
358
358
  }
359
+ /**
360
+ * @returns {WasmSnapshotExport}
361
+ */
362
+ exportSnapshot() {
363
+ const ret = wasm.wasmremotelix_exportSnapshot(this.__wbg_ptr);
364
+ return WasmSnapshotExport.__wrap(ret);
365
+ }
359
366
  /**
360
367
  * @param {any} _paths
361
368
  * @returns {Promise<void>}
@@ -365,19 +372,19 @@ export class WasmRemoteLix {
365
372
  return takeObject(ret);
366
373
  }
367
374
  /**
368
- * @param {any} _options
375
+ * @param {any} options
369
376
  * @returns {Promise<any>}
370
377
  */
371
- mergeBranch(_options) {
372
- const ret = wasm.wasmremotelix_mergeBranch(this.__wbg_ptr, addHeapObject(_options));
378
+ mergeBranch(options) {
379
+ const ret = wasm.wasmremotelix_mergeBranch(this.__wbg_ptr, addHeapObject(options));
373
380
  return takeObject(ret);
374
381
  }
375
382
  /**
376
- * @param {any} _options
383
+ * @param {any} options
377
384
  * @returns {Promise<any>}
378
385
  */
379
- mergeBranchPreview(_options) {
380
- const ret = wasm.wasmremotelix_mergeBranchPreview(this.__wbg_ptr, addHeapObject(_options));
386
+ mergeBranchPreview(options) {
387
+ const ret = wasm.wasmremotelix_mergeBranchPreview(this.__wbg_ptr, addHeapObject(options));
381
388
  return takeObject(ret);
382
389
  }
383
390
  /**
@@ -760,19 +767,19 @@ function __wbg_get_imports() {
760
767
  __wbg__wbg_cb_unref_61db23ac97f16c31: function(arg0) {
761
768
  getObject(arg0)._wbg_cb_unref();
762
769
  },
763
- __wbg_acquireSession_fbd9953aa76fbd06: function(arg0) {
770
+ __wbg_acquireSession_57f1f2cd5f681c2d: function(arg0) {
764
771
  const ret = getObject(arg0).acquireSession();
765
772
  return addHeapObject(ret);
766
773
  },
767
- __wbg_beginRead_114dd35fcf6a9a3d: function(arg0, arg1) {
774
+ __wbg_beginRead_1138c26c0b47afd3: function(arg0, arg1) {
768
775
  const ret = getObject(arg0).beginRead(takeObject(arg1));
769
776
  return addHeapObject(ret);
770
777
  },
771
- __wbg_beginScan_87c5d513513b2fc1: function(arg0, arg1, arg2, arg3) {
778
+ __wbg_beginScan_d935bfe04519622e: function(arg0, arg1, arg2, arg3) {
772
779
  const ret = getObject(arg0).beginScan(takeObject(arg1), takeObject(arg2), takeObject(arg3));
773
780
  return addHeapObject(ret);
774
781
  },
775
- __wbg_beginWrite_ad3177b3fd6be002: function(arg0, arg1) {
782
+ __wbg_beginWrite_4d84cc1cbfc72fa9: function(arg0, arg1) {
776
783
  const ret = getObject(arg0).beginWrite(takeObject(arg1));
777
784
  return addHeapObject(ret);
778
785
  },
@@ -788,18 +795,18 @@ function __wbg_get_imports() {
788
795
  const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
789
796
  return addHeapObject(ret);
790
797
  }, arguments); },
791
- __wbg_changed_8b774e9361ffc108: function(arg0) {
798
+ __wbg_changed_8b3c117efdd37fbf: function(arg0) {
792
799
  const ret = getObject(arg0).changed();
793
800
  return addHeapObject(ret);
794
801
  },
795
- __wbg_close_2ff8ceedc96b8813: function(arg0) {
796
- getObject(arg0).close();
797
- },
798
- __wbg_close_c35854fb9094c0b5: function(arg0) {
802
+ __wbg_close_70069a5308f6a3d8: function(arg0) {
799
803
  const ret = getObject(arg0).close();
800
804
  return addHeapObject(ret);
801
805
  },
802
- __wbg_commit_d64c4bd2c40c0b47: function(arg0) {
806
+ __wbg_close_eb44b04eda5115d4: function(arg0) {
807
+ getObject(arg0).close();
808
+ },
809
+ __wbg_commit_9dc6a608a8f559f7: function(arg0) {
803
810
  const ret = getObject(arg0).commit();
804
811
  return addHeapObject(ret);
805
812
  },
@@ -807,11 +814,11 @@ function __wbg_get_imports() {
807
814
  const ret = Reflect.construct(getObject(arg0), getObject(arg1));
808
815
  return addHeapObject(ret);
809
816
  }, arguments); },
810
- __wbg_deleteMany_4e9717e44a5ce975: function(arg0, arg1, arg2) {
817
+ __wbg_deleteMany_15f48563982d361e: function(arg0, arg1, arg2) {
811
818
  const ret = getObject(arg0).deleteMany(takeObject(arg1), takeObject(arg2));
812
819
  return addHeapObject(ret);
813
820
  },
814
- __wbg_deleteRange_c80634c355468a31: function(arg0, arg1, arg2) {
821
+ __wbg_deleteRange_29b8dd1d129b1f8c: function(arg0, arg1, arg2) {
815
822
  const ret = getObject(arg0).deleteRange(takeObject(arg1), takeObject(arg2));
816
823
  return addHeapObject(ret);
817
824
  },
@@ -838,7 +845,7 @@ function __wbg_get_imports() {
838
845
  const ret = Array.from(getObject(arg0));
839
846
  return addHeapObject(ret);
840
847
  },
841
- __wbg_getMany_a58fdfcbaeefb016: function(arg0, arg1) {
848
+ __wbg_getMany_946bf213edc127f3: function(arg0, arg1) {
842
849
  const ret = getObject(arg0).getMany(takeObject(arg1));
843
850
  return addHeapObject(ret);
844
851
  },
@@ -963,7 +970,7 @@ function __wbg_get_imports() {
963
970
  const a = state0.a;
964
971
  state0.a = 0;
965
972
  try {
966
- return __wasm_bindgen_func_elem_92061(a, state0.b, arg0, arg1);
973
+ return __wasm_bindgen_func_elem_92637(a, state0.b, arg0, arg1);
967
974
  } finally {
968
975
  state0.a = a;
969
976
  }
@@ -993,7 +1000,7 @@ function __wbg_get_imports() {
993
1000
  const a = state0.a;
994
1001
  state0.a = 0;
995
1002
  try {
996
- return __wasm_bindgen_func_elem_92061(a, state0.b, arg0, arg1);
1003
+ return __wasm_bindgen_func_elem_92637(a, state0.b, arg0, arg1);
997
1004
  } finally {
998
1005
  state0.a = a;
999
1006
  }
@@ -1004,7 +1011,7 @@ function __wbg_get_imports() {
1004
1011
  state0.a = 0;
1005
1012
  }
1006
1013
  },
1007
- __wbg_nextPage_ae3f298cfa6ac6c7: function(arg0, arg1) {
1014
+ __wbg_nextPage_6902443bcbff10d3: function(arg0, arg1) {
1008
1015
  const ret = getObject(arg0).nextPage(arg1 >>> 0);
1009
1016
  return addHeapObject(ret);
1010
1017
  },
@@ -1043,7 +1050,7 @@ function __wbg_get_imports() {
1043
1050
  const ret = getObject(arg0).push(getObject(arg1));
1044
1051
  return ret;
1045
1052
  },
1046
- __wbg_putMany_59c7c5f2646a2cd3: function(arg0, arg1, arg2) {
1053
+ __wbg_putMany_168141e3afba203c: function(arg0, arg1, arg2) {
1047
1054
  const ret = getObject(arg0).putMany(takeObject(arg1), takeObject(arg2));
1048
1055
  return addHeapObject(ret);
1049
1056
  },
@@ -1054,7 +1061,7 @@ function __wbg_get_imports() {
1054
1061
  const ret = getObject(arg0).queueMicrotask;
1055
1062
  return addHeapObject(ret);
1056
1063
  },
1057
- __wbg_replaceMany_31899cff00668d04: function(arg0, arg1, arg2) {
1064
+ __wbg_replaceMany_b2dc402a4ff6b772: function(arg0, arg1, arg2) {
1058
1065
  const ret = getObject(arg0).replaceMany(takeObject(arg1), takeObject(arg2));
1059
1066
  return addHeapObject(ret);
1060
1067
  },
@@ -1062,7 +1069,7 @@ function __wbg_get_imports() {
1062
1069
  const ret = Promise.resolve(getObject(arg0));
1063
1070
  return addHeapObject(ret);
1064
1071
  },
1065
- __wbg_rollback_d6c4b2947a68cd53: function(arg0) {
1072
+ __wbg_rollback_5632fb7c3f6f9b08: function(arg0) {
1066
1073
  const ret = getObject(arg0).rollback();
1067
1074
  return addHeapObject(ret);
1068
1075
  },
@@ -1146,18 +1153,18 @@ function __wbg_get_imports() {
1146
1153
  const ret = WasmRemoteObserveEvents.__wrap(arg0);
1147
1154
  return addHeapObject(ret);
1148
1155
  },
1149
- __wbg_watchForChanges_c00fffbd08c7f090: function(arg0) {
1156
+ __wbg_watchForChanges_465d4036436d28da: function(arg0) {
1150
1157
  const ret = getObject(arg0).watchForChanges();
1151
1158
  return addHeapObject(ret);
1152
1159
  },
1153
1160
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
1154
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 21300, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
1155
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_92059);
1161
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 21338, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
1162
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_92635);
1156
1163
  return addHeapObject(ret);
1157
1164
  },
1158
1165
  __wbindgen_cast_0000000000000002: function(arg0, arg1) {
1159
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1612, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1160
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_16459);
1166
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1647, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1167
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_16813);
1161
1168
  return addHeapObject(ret);
1162
1169
  },
1163
1170
  __wbindgen_cast_0000000000000003: function(arg0) {
@@ -1206,14 +1213,14 @@ function __wbg_get_imports() {
1206
1213
  };
1207
1214
  }
1208
1215
 
1209
- function __wasm_bindgen_func_elem_16459(arg0, arg1) {
1210
- wasm.__wasm_bindgen_func_elem_16459(arg0, arg1);
1216
+ function __wasm_bindgen_func_elem_16813(arg0, arg1) {
1217
+ wasm.__wasm_bindgen_func_elem_16813(arg0, arg1);
1211
1218
  }
1212
1219
 
1213
- function __wasm_bindgen_func_elem_92059(arg0, arg1, arg2) {
1220
+ function __wasm_bindgen_func_elem_92635(arg0, arg1, arg2) {
1214
1221
  try {
1215
1222
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1216
- wasm.__wasm_bindgen_func_elem_92059(retptr, arg0, arg1, addHeapObject(arg2));
1223
+ wasm.__wasm_bindgen_func_elem_92635(retptr, arg0, arg1, addHeapObject(arg2));
1217
1224
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
1218
1225
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
1219
1226
  if (r1) {
@@ -1224,8 +1231,8 @@ function __wasm_bindgen_func_elem_92059(arg0, arg1, arg2) {
1224
1231
  }
1225
1232
  }
1226
1233
 
1227
- function __wasm_bindgen_func_elem_92061(arg0, arg1, arg2, arg3) {
1228
- wasm.__wasm_bindgen_func_elem_92061(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
1234
+ function __wasm_bindgen_func_elem_92637(arg0, arg1, arg2, arg3) {
1235
+ wasm.__wasm_bindgen_func_elem_92637(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
1229
1236
  }
1230
1237
 
1231
1238
  const WasmLixFinalization = (typeof FinalizationRegistry === 'undefined')
Binary file
@@ -46,6 +46,7 @@ export const wasmremotelix_close: (a: number) => number;
46
46
  export const wasmremotelix_createBranch: (a: number, b: number) => number;
47
47
  export const wasmremotelix_execute: (a: number, b: number, c: number, d: number, e: number) => number;
48
48
  export const wasmremotelix_executeBatch: (a: number, b: number, c: number) => number;
49
+ export const wasmremotelix_exportSnapshot: (a: number) => number;
49
50
  export const wasmremotelix_importFilesystemPaths: (a: number, b: number) => number;
50
51
  export const wasmremotelix_mergeBranch: (a: number, b: number) => number;
51
52
  export const wasmremotelix_mergeBranchPreview: (a: number, b: number) => number;
@@ -68,9 +69,9 @@ export const wasmsnapshotrestore_cancel: (a: number) => number;
68
69
  export const wasmsnapshotrestore_finish: (a: number) => number;
69
70
  export const wasmsnapshotrestore_isComplete: (a: number) => number;
70
71
  export const wasmsnapshotrestore_write: (a: number, b: number, c: number) => number;
71
- export const __wasm_bindgen_func_elem_92059: (a: number, b: number, c: number, d: number) => void;
72
- export const __wasm_bindgen_func_elem_92061: (a: number, b: number, c: number, d: number) => void;
73
- export const __wasm_bindgen_func_elem_16459: (a: number, b: number) => void;
72
+ export const __wasm_bindgen_func_elem_92635: (a: number, b: number, c: number, d: number) => void;
73
+ export const __wasm_bindgen_func_elem_92637: (a: number, b: number, c: number, d: number) => void;
74
+ export const __wasm_bindgen_func_elem_16813: (a: number, b: number) => void;
74
75
  export const __wbindgen_export: (a: number, b: number) => number;
75
76
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
76
77
  export const __wbindgen_export3: (a: number) => void;
@@ -0,0 +1,2 @@
1
+ /** All local and remote bindings share the same Wasm module in this realm. */
2
+ export declare function initializeWasm(): Promise<unknown>;
@@ -0,0 +1,23 @@
1
+ // Generated before TypeScript compilation and emitted beside this module.
2
+ // @ts-ignore Generated by build:wasm and absent in source-only checks.
3
+ import initWasm from "./wasm/lix_js_sdk.js";
4
+ let wasmInitialized;
5
+ /** All local and remote bindings share the same Wasm module in this realm. */
6
+ export function initializeWasm() {
7
+ return (wasmInitialized ??= loadWasm());
8
+ }
9
+ async function loadWasm() {
10
+ const moduleUrl = new URL("./wasm/lix_js_sdk_bg.wasm", import.meta.url);
11
+ if (typeof process !== "undefined" && process.versions?.node) {
12
+ const { readFile } = await import("node:fs/promises");
13
+ return initWasm({ module_or_path: await readFile(moduleUrl) });
14
+ }
15
+ // Chromium can abort a consumer when workers cold-load the same large
16
+ // response concurrently. Hold the cross-realm lock until compilation ends
17
+ // so later workers can consume the completed immutable HTTP cache entry.
18
+ const lockManager = globalThis.navigator?.locks;
19
+ const run = () => initWasm({ module_or_path: moduleUrl });
20
+ if (!lockManager)
21
+ return run();
22
+ return lockManager.request(`lix:browser-wasm:${moduleUrl.href}`, { mode: "exclusive" }, run);
23
+ }
@@ -29,6 +29,7 @@ export declare class LixWorkerClient {
29
29
  private onProgress?;
30
30
  openReport: LixOpenReport | undefined;
31
31
  private readonly syncFetchControllers;
32
+ private readonly syncFetchStreams;
32
33
  constructor(connection?: WorkerConnection);
33
34
  get isDisposed(): boolean;
34
35
  allocateSnapshotInputId(): number;
@@ -41,6 +42,9 @@ export declare class LixWorkerClient {
41
42
  private handleWorkerEvent;
42
43
  private resolveSyncHeaders;
43
44
  private resolveSyncFetch;
45
+ private resolveSyncFetchStreamPull;
46
+ private cancelSyncFetch;
47
+ private finishSyncFetchStream;
44
48
  private handleFatal;
45
49
  private rejectPending;
46
50
  }
@@ -368,6 +368,7 @@ export class LixWorkerClient {
368
368
  onProgress;
369
369
  openReport;
370
370
  syncFetchControllers = new Map();
371
+ syncFetchStreams = new Map();
371
372
  constructor(connection = createWorkerConnection()) {
372
373
  this.connection = connection;
373
374
  connection.onMessage((message) => this.handleMessage(message));
@@ -401,6 +402,10 @@ export class LixWorkerClient {
401
402
  for (const controller of this.syncFetchControllers.values())
402
403
  controller.abort();
403
404
  this.syncFetchControllers.clear();
405
+ for (const reader of this.syncFetchStreams.values()) {
406
+ void reader?.cancel().catch(() => undefined);
407
+ }
408
+ this.syncFetchStreams.clear();
404
409
  onDisposed?.();
405
410
  }
406
411
  request(operation, sessionId = 0) {
@@ -493,9 +498,11 @@ export class LixWorkerClient {
493
498
  case "sync.fetch":
494
499
  void this.resolveSyncFetch(message.requestId, message.request);
495
500
  break;
501
+ case "sync.fetch.stream.pull":
502
+ void this.resolveSyncFetchStreamPull(message.requestId);
503
+ break;
496
504
  case "sync.fetch.cancel":
497
- this.syncFetchControllers.get(message.requestId)?.abort();
498
- this.syncFetchControllers.delete(message.requestId);
505
+ this.cancelSyncFetch(message.requestId);
499
506
  break;
500
507
  }
501
508
  }
@@ -532,6 +539,7 @@ export class LixWorkerClient {
532
539
  }
533
540
  const controller = new AbortController();
534
541
  this.syncFetchControllers.set(requestId, controller);
542
+ let retainedStream = false;
535
543
  try {
536
544
  const response = await fetcher(request.url, {
537
545
  method: request.method,
@@ -542,6 +550,29 @@ export class LixWorkerClient {
542
550
  credentials: request.credentials,
543
551
  signal: controller.signal,
544
552
  });
553
+ if (this.syncFetchControllers.get(requestId) !== controller ||
554
+ controller.signal.aborted) {
555
+ await response.body?.cancel().catch(() => undefined);
556
+ return;
557
+ }
558
+ if (request.responseMode === "stream") {
559
+ this.syncFetchStreams.set(requestId, response.body?.getReader());
560
+ retainedStream = true;
561
+ this.notify({
562
+ kind: "sync.fetch.result",
563
+ requestId,
564
+ result: {
565
+ ok: true,
566
+ response: {
567
+ status: response.status,
568
+ statusText: response.statusText,
569
+ headers: headerEntries(response.headers),
570
+ streaming: true,
571
+ },
572
+ },
573
+ });
574
+ return;
575
+ }
545
576
  const body = await readSyncResponseBody(response, request.responseLimit, controller);
546
577
  this.notify({
547
578
  kind: "sync.fetch.result",
@@ -567,9 +598,61 @@ export class LixWorkerClient {
567
598
  }
568
599
  }
569
600
  finally {
570
- this.syncFetchControllers.delete(requestId);
601
+ if (!retainedStream)
602
+ this.syncFetchControllers.delete(requestId);
571
603
  }
572
604
  }
605
+ async resolveSyncFetchStreamPull(requestId) {
606
+ if (!this.syncFetchStreams.has(requestId))
607
+ return;
608
+ const reader = this.syncFetchStreams.get(requestId);
609
+ try {
610
+ const result = reader ? await reader.read() : { done: true };
611
+ if (!this.syncFetchStreams.has(requestId) ||
612
+ this.syncFetchStreams.get(requestId) !== reader ||
613
+ this.syncFetchControllers.get(requestId)?.signal.aborted) {
614
+ return;
615
+ }
616
+ this.notify({
617
+ kind: "sync.fetch.stream.result",
618
+ requestId,
619
+ result: result.done
620
+ ? { ok: true, done: true }
621
+ : { ok: true, done: false, chunk: result.value.slice() },
622
+ });
623
+ if (result.done) {
624
+ reader?.releaseLock();
625
+ this.finishSyncFetchStream(requestId);
626
+ }
627
+ }
628
+ catch (error) {
629
+ const controller = this.syncFetchControllers.get(requestId);
630
+ if (!controller?.signal.aborted) {
631
+ this.notify({
632
+ kind: "sync.fetch.stream.result",
633
+ requestId,
634
+ result: { ok: false, error: serializeWorkerError(error) },
635
+ });
636
+ }
637
+ try {
638
+ reader?.releaseLock();
639
+ }
640
+ catch {
641
+ // A cancellation can leave the read pending until its rejection settles.
642
+ }
643
+ this.finishSyncFetchStream(requestId);
644
+ }
645
+ }
646
+ cancelSyncFetch(requestId) {
647
+ this.syncFetchControllers.get(requestId)?.abort();
648
+ const reader = this.syncFetchStreams.get(requestId);
649
+ void reader?.cancel().catch(() => undefined);
650
+ this.finishSyncFetchStream(requestId);
651
+ }
652
+ finishSyncFetchStream(requestId) {
653
+ this.syncFetchStreams.delete(requestId);
654
+ this.syncFetchControllers.delete(requestId);
655
+ }
573
656
  handleFatal(error) {
574
657
  if (this.disposed)
575
658
  return;
@@ -1,3 +1,4 @@
1
+ import { openLixBinding } from "#binding";
1
2
  import { type WorkerHostEndpoint, type WorkerSyncFetchResponse } from "./protocol.js";
2
- export declare function startWorkerHost(endpoint: WorkerHostEndpoint): void;
3
+ export declare function startWorkerHost(endpoint: WorkerHostEndpoint, openBinding?: typeof openLixBinding): void;
3
4
  export declare function responseFromSyncFetch(resolved: WorkerSyncFetchResponse): Response;
@@ -1,6 +1,6 @@
1
1
  import { openLixBinding } from "#binding";
2
2
  import { deserializeWorkerError, serializeWorkerError, } from "./protocol.js";
3
- export function startWorkerHost(endpoint) {
3
+ export function startWorkerHost(endpoint, openBinding = openLixBinding) {
4
4
  const sessions = new Map();
5
5
  let nextSessionId = 1;
6
6
  let nextTransactionId = 1;
@@ -13,6 +13,8 @@ export function startWorkerHost(endpoint) {
13
13
  let nextSyncRequestId = 1;
14
14
  const pendingSyncHeaders = new Map();
15
15
  const pendingSyncFetch = new Map();
16
+ const pendingSyncStreamPulls = new Map();
17
+ const syncStreamCleanup = new Map();
16
18
  let finiteQueue = Promise.resolve();
17
19
  endpoint.onMessage((message) => {
18
20
  if (!("id" in message)) {
@@ -46,6 +48,15 @@ export function startWorkerHost(endpoint) {
46
48
  }
47
49
  return;
48
50
  }
51
+ if (message.operation.kind === "observe") {
52
+ const operation = message.operation;
53
+ // Observation setup is metadata-only. Keeping it behind the global
54
+ // finite-operation queue lets an authority mutation that is waiting for
55
+ // local publication block a newly mounted server-first History query.
56
+ // The live `next()` lane is already independent for the same reason.
57
+ void respond(message, () => handleObserveRegistration(message.sessionId, operation.sql, operation.params));
58
+ return;
59
+ }
49
60
  finiteQueue = finiteQueue.then(async () => {
50
61
  try {
51
62
  await respond(message, async () => {
@@ -108,8 +119,35 @@ export function startWorkerHost(endpoint) {
108
119
  pending.reject(deserializeWorkerError(message.result.error));
109
120
  break;
110
121
  }
122
+ case "sync.fetch.stream.result": {
123
+ const pending = pendingSyncStreamPulls.get(message.requestId);
124
+ pendingSyncStreamPulls.delete(message.requestId);
125
+ if (!pending)
126
+ break;
127
+ if (!message.result.ok) {
128
+ const error = deserializeWorkerError(message.result.error);
129
+ pending.controller.error(error);
130
+ pending.reject(error);
131
+ finishSyncStream(message.requestId);
132
+ }
133
+ else if (message.result.done) {
134
+ pending.controller.close();
135
+ pending.resolve();
136
+ finishSyncStream(message.requestId);
137
+ }
138
+ else {
139
+ pending.controller.enqueue(message.result.chunk);
140
+ pending.resolve();
141
+ }
142
+ break;
143
+ }
111
144
  }
112
145
  }
146
+ function finishSyncStream(requestId) {
147
+ const cleanup = syncStreamCleanup.get(requestId);
148
+ syncStreamCleanup.delete(requestId);
149
+ cleanup?.();
150
+ }
113
151
  async function respond(request, operation) {
114
152
  try {
115
153
  const value = await operation();
@@ -133,7 +171,7 @@ export function startWorkerHost(endpoint) {
133
171
  ? undefined
134
172
  : requiredSnapshotInput(operation.snapshotId).readable;
135
173
  try {
136
- const opened = await openLixBinding(operation.storage, operation.telemetryEnabled
174
+ const opened = await openBinding(operation.storage, operation.telemetryEnabled
137
175
  ? (span) => endpoint.postMessage({ kind: "telemetry", span })
138
176
  : undefined, telemetryParent, createSyncServerBridge(operation.server), operation.progressEnabled
139
177
  ? (progress) => endpoint.postMessage({ kind: "open.progress", progress })
@@ -216,12 +254,8 @@ export function startWorkerHost(endpoint) {
216
254
  throw workerStateError("snapshot pulls bypass the finite operation queue");
217
255
  case "exportSnapshot.cancel":
218
256
  throw workerStateError("snapshot cancellation bypasses the finite operation queue");
219
- case "observe": {
220
- const events = await requiredLix(sessionId).observe(operation.sql, operation.params);
221
- const observeId = nextObserveId++;
222
- observations.set(observeId, events);
223
- return observeId;
224
- }
257
+ case "observe":
258
+ throw workerStateError("observe must use the observation lane");
225
259
  case "close": {
226
260
  const openLix = requiredLix(sessionId);
227
261
  await openLix.close();
@@ -296,14 +330,17 @@ export function startWorkerHost(endpoint) {
296
330
  };
297
331
  }
298
332
  async function bridgeFetch(input, init) {
299
- const responseLimit = init?.lixResponseLimit;
300
- if (typeof responseLimit !== "number" ||
301
- !Number.isSafeInteger(responseLimit) ||
302
- responseLimit <= 0) {
333
+ const extension = init;
334
+ const streaming = extension?.lixResponseStream === true;
335
+ const responseLimit = extension?.lixResponseLimit;
336
+ if (!streaming &&
337
+ (typeof responseLimit !== "number" ||
338
+ !Number.isSafeInteger(responseLimit) ||
339
+ responseLimit <= 0)) {
303
340
  throw new TypeError("Browser sync fetch has no valid response limit");
304
341
  }
305
342
  const requestId = nextSyncRequestId++;
306
- const request = {
343
+ const requestBase = {
307
344
  url: typeof input === "string"
308
345
  ? input
309
346
  : input instanceof URL
@@ -313,8 +350,14 @@ export function startWorkerHost(endpoint) {
313
350
  headers: headerEntries(init?.headers),
314
351
  body: serializableBody(init?.body),
315
352
  credentials: init?.credentials,
316
- responseLimit,
317
353
  };
354
+ const request = streaming
355
+ ? { ...requestBase, responseMode: "stream" }
356
+ : {
357
+ ...requestBase,
358
+ responseMode: "buffered",
359
+ responseLimit: responseLimit,
360
+ };
318
361
  const response = new Promise((resolve, reject) => {
319
362
  pendingSyncFetch.set(requestId, { resolve, reject });
320
363
  endpoint.postMessage({ kind: "sync.fetch", requestId, request });
@@ -324,20 +367,77 @@ export function startWorkerHost(endpoint) {
324
367
  pendingSyncFetch.delete(requestId);
325
368
  if (pending) {
326
369
  pending.reject(new DOMException("The operation was aborted", "AbortError"));
327
- endpoint.postMessage({ kind: "sync.fetch.cancel", requestId });
328
370
  }
371
+ const pull = pendingSyncStreamPulls.get(requestId);
372
+ pendingSyncStreamPulls.delete(requestId);
373
+ if (pull) {
374
+ const error = new DOMException("The operation was aborted", "AbortError");
375
+ pull.controller.error(error);
376
+ pull.reject(error);
377
+ }
378
+ endpoint.postMessage({ kind: "sync.fetch.cancel", requestId });
379
+ finishSyncStream(requestId);
329
380
  };
330
381
  if (init?.signal?.aborted)
331
382
  abort();
332
383
  else
333
384
  init?.signal?.addEventListener("abort", abort, { once: true });
385
+ let streamEstablished = false;
334
386
  try {
335
387
  const resolved = await response;
388
+ if (resolved.streaming) {
389
+ const signal = init?.signal;
390
+ if (signal?.aborted) {
391
+ abort();
392
+ throw new DOMException("The operation was aborted", "AbortError");
393
+ }
394
+ if (resolved.status === 204 ||
395
+ resolved.status === 205 ||
396
+ resolved.status === 304) {
397
+ endpoint.postMessage({ kind: "sync.fetch.cancel", requestId });
398
+ return new Response(null, {
399
+ status: resolved.status,
400
+ statusText: resolved.statusText,
401
+ headers: resolved.headers,
402
+ });
403
+ }
404
+ syncStreamCleanup.set(requestId, () => signal?.removeEventListener("abort", abort));
405
+ const body = new ReadableStream({
406
+ pull: (controller) => new Promise((resolve, reject) => {
407
+ pendingSyncStreamPulls.set(requestId, {
408
+ controller,
409
+ resolve,
410
+ reject,
411
+ });
412
+ endpoint.postMessage({
413
+ kind: "sync.fetch.stream.pull",
414
+ requestId,
415
+ });
416
+ }),
417
+ cancel: abort,
418
+ });
419
+ const streamedResponse = new Response(body, {
420
+ status: resolved.status,
421
+ statusText: resolved.statusText,
422
+ headers: resolved.headers,
423
+ });
424
+ streamEstablished = true;
425
+ return streamedResponse;
426
+ }
336
427
  return responseFromSyncFetch(resolved);
337
428
  }
429
+ catch (error) {
430
+ if (streaming && !streamEstablished) {
431
+ endpoint.postMessage({ kind: "sync.fetch.cancel", requestId });
432
+ finishSyncStream(requestId);
433
+ }
434
+ throw error;
435
+ }
338
436
  finally {
339
- init?.signal?.removeEventListener("abort", abort);
340
437
  pendingSyncFetch.delete(requestId);
438
+ if (!streamEstablished) {
439
+ init?.signal?.removeEventListener("abort", abort);
440
+ }
341
441
  }
342
442
  }
343
443
  async function handleObserveNext(observeId, telemetryParent) {
@@ -347,6 +447,15 @@ export function startWorkerHost(endpoint) {
347
447
  events.setTelemetryParent(telemetryParent);
348
448
  return events.next();
349
449
  }
450
+ async function handleObserveRegistration(sessionId, sql, params) {
451
+ // Do not touch the mutable Lix telemetry carrier here: it belongs to the
452
+ // serialized finite lane. Each `observe.next` supplies telemetry directly
453
+ // to its observation binding.
454
+ const events = await requiredLix(sessionId).observe(sql, params);
455
+ const observeId = nextObserveId++;
456
+ observations.set(observeId, events);
457
+ return observeId;
458
+ }
350
459
  function requiredLix(sessionId) {
351
460
  const lix = sessions.get(sessionId);
352
461
  if (!lix)
@@ -364,6 +473,9 @@ export function startWorkerHost(endpoint) {
364
473
  }
365
474
  }
366
475
  export function responseFromSyncFetch(resolved) {
476
+ if (resolved.streaming) {
477
+ throw new TypeError("Streaming sync responses require the worker stream bridge");
478
+ }
367
479
  const body = resolved.status === 204 ||
368
480
  resolved.status === 205 ||
369
481
  resolved.status === 304
@@ -12,14 +12,23 @@ export type WorkerSyncFetchRequest = {
12
12
  headers: [string, string][];
13
13
  body?: string | Uint8Array;
14
14
  credentials?: RequestCredentials;
15
+ } & ({
16
+ responseMode: "buffered";
15
17
  responseLimit: number;
16
- };
17
- export type WorkerSyncFetchResponse = {
18
+ } | {
19
+ responseMode: "stream";
20
+ });
21
+ type WorkerSyncFetchResponseHead = {
18
22
  status: number;
19
23
  statusText: string;
20
24
  headers: [string, string][];
21
- body: Uint8Array;
22
25
  };
26
+ export type WorkerSyncFetchResponse = WorkerSyncFetchResponseHead & ({
27
+ streaming: true;
28
+ } | {
29
+ streaming?: false;
30
+ body: Uint8Array;
31
+ });
23
32
  export type WorkerRequest = {
24
33
  id: number;
25
34
  sessionId: number;
@@ -138,6 +147,20 @@ export type WorkerNotification = {
138
147
  ok: false;
139
148
  error: SerializedWorkerError;
140
149
  };
150
+ } | {
151
+ kind: "sync.fetch.stream.result";
152
+ requestId: number;
153
+ result: {
154
+ ok: true;
155
+ done: true;
156
+ } | {
157
+ ok: true;
158
+ done: false;
159
+ chunk: Uint8Array;
160
+ } | {
161
+ ok: false;
162
+ error: SerializedWorkerError;
163
+ };
141
164
  };
142
165
  export type WorkerInput = WorkerRequest | WorkerNotification;
143
166
  export type WorkerConnection = {
@@ -181,9 +204,13 @@ export type WorkerResponse = {
181
204
  kind: "sync.fetch";
182
205
  requestId: number;
183
206
  request: WorkerSyncFetchRequest;
207
+ } | {
208
+ kind: "sync.fetch.stream.pull";
209
+ requestId: number;
184
210
  } | {
185
211
  kind: "sync.fetch.cancel";
186
212
  requestId: number;
187
213
  };
188
214
  export declare function serializeWorkerError(error: unknown): SerializedWorkerError;
189
215
  export declare function deserializeWorkerError(error: SerializedWorkerError): Error;
216
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lix-js/sdk",
3
3
  "type": "module",
4
- "version": "0.14.0",
4
+ "version": "0.15.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -49,10 +49,10 @@
49
49
  "typecheck": "tsc -p tsconfig.test.json --noEmit"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@lix-js/sdk-darwin-arm64": "0.14.0",
53
- "@lix-js/sdk-linux-arm64": "0.14.0",
54
- "@lix-js/sdk-linux-x64": "0.14.0",
55
- "@lix-js/sdk-win32-x64": "0.14.0"
52
+ "@lix-js/sdk-darwin-arm64": "0.15.0",
53
+ "@lix-js/sdk-linux-arm64": "0.15.0",
54
+ "@lix-js/sdk-linux-x64": "0.15.0",
55
+ "@lix-js/sdk-win32-x64": "0.15.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@vitest/browser-playwright": "4.1.10",
@@ -1,14 +0,0 @@
1
- type WasmInitializer = (options: {
2
- module_or_path: URL;
3
- }) => Promise<unknown>;
4
- /**
5
- * Initializes one fingerprinted Wasm asset without racing the browser's shared
6
- * HTTP cache across tabs.
7
- *
8
- * Chromium can abort one consumer when separate workers cold-load the same
9
- * large response concurrently. The lock lasts only for the initial streaming
10
- * compilation. Once it releases, later workers consume the completed immutable
11
- * cache entry and compile independently.
12
- */
13
- export declare function initializeBrowserWasm(initialize: WasmInitializer, moduleUrl: URL): Promise<unknown>;
14
- export {};
@@ -1,16 +0,0 @@
1
- /**
2
- * Initializes one fingerprinted Wasm asset without racing the browser's shared
3
- * HTTP cache across tabs.
4
- *
5
- * Chromium can abort one consumer when separate workers cold-load the same
6
- * large response concurrently. The lock lasts only for the initial streaming
7
- * compilation. Once it releases, later workers consume the completed immutable
8
- * cache entry and compile independently.
9
- */
10
- export function initializeBrowserWasm(initialize, moduleUrl) {
11
- const lockManager = globalThis.navigator.locks;
12
- const run = () => initialize({ module_or_path: moduleUrl });
13
- if (!lockManager)
14
- return run();
15
- return lockManager.request(`lix:browser-wasm:${moduleUrl.href}`, { mode: "exclusive" }, run);
16
- }