@lix-js/sdk 0.16.0 → 0.17.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 (73) hide show
  1. package/README.md +187 -22
  2. package/dist/binding-types.d.ts +20 -6
  3. package/dist/binding.browser.d.ts +2 -0
  4. package/dist/binding.browser.js +14 -4
  5. package/dist/binding.node-wasm.d.ts +1 -1
  6. package/dist/binding.node-wasm.js +5 -3
  7. package/dist/binding.node.d.ts +2 -0
  8. package/dist/binding.node.js +34 -8
  9. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  10. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  11. package/dist/compatibility.d.ts +6 -0
  12. package/dist/compatibility.js +6 -0
  13. package/dist/component-host/dispatch.d.ts +12 -0
  14. package/dist/component-host/dispatch.js +364 -0
  15. package/dist/component-host/index.d.ts +15 -0
  16. package/dist/component-host/index.js +84 -0
  17. package/dist/component-host/instrument.d.ts +6 -0
  18. package/dist/component-host/instrument.js +260 -0
  19. package/dist/conversion-provider.d.ts +4 -0
  20. package/dist/conversion-provider.js +20 -0
  21. package/dist/hosted-lix.js +1 -1
  22. package/dist/http-transport.d.ts +25 -0
  23. package/dist/http-transport.js +162 -0
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +1 -0
  26. package/dist/lix.d.ts +20 -10
  27. package/dist/lix.js +68 -23
  28. package/dist/migration-binding.browser.d.ts +33 -0
  29. package/dist/migration-binding.browser.js +46 -0
  30. package/dist/migration-binding.node.d.ts +6 -0
  31. package/dist/migration-binding.node.js +5 -0
  32. package/dist/migration-wasm/lix_js_sdk.d.ts +271 -0
  33. package/dist/migration-wasm/lix_js_sdk.js +1852 -0
  34. package/dist/migration-wasm/lix_js_sdk_bg.wasm +4 -0
  35. package/dist/migration-wasm/lix_js_sdk_bg.wasm.d.ts +101 -0
  36. package/dist/migration.d.ts +23 -0
  37. package/dist/migration.js +55 -0
  38. package/dist/open-lix.js +29 -18
  39. package/dist/open-progress.d.ts +10 -0
  40. package/dist/open-progress.js +59 -0
  41. package/dist/remote/client.d.ts +2 -1
  42. package/dist/remote/client.js +11 -1
  43. package/dist/result.d.ts +4 -3
  44. package/dist/result.js +3 -4
  45. package/dist/storage-adapter.d.ts +7 -5
  46. package/dist/storage-ownership.d.ts +5 -0
  47. package/dist/storage-ownership.js +4 -0
  48. package/dist/types.d.ts +132 -22
  49. package/dist/wasm/lix_js_sdk.d.ts +57 -16
  50. package/dist/wasm/lix_js_sdk.js +268 -60
  51. package/dist/wasm/lix_js_sdk_bg.wasm +2 -2
  52. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +23 -9
  53. package/dist/worker/client.d.ts +12 -3
  54. package/dist/worker/client.js +98 -81
  55. package/dist/worker/durable-local-admission.d.ts +20 -0
  56. package/dist/worker/durable-local-admission.js +87 -0
  57. package/dist/worker/entry.shared.browser.d.ts +1 -0
  58. package/dist/worker/entry.shared.browser.js +211 -0
  59. package/dist/worker/factory.browser.d.ts +2 -0
  60. package/dist/worker/factory.browser.js +65 -1
  61. package/dist/worker/factory.node.d.ts +1 -0
  62. package/dist/worker/factory.node.js +3 -0
  63. package/dist/worker/host.d.ts +4 -2
  64. package/dist/worker/host.js +92 -32
  65. package/dist/worker/protocol.d.ts +33 -8
  66. package/dist/worker/protocol.js +25 -7
  67. package/dist/worker/shared-admission.d.ts +26 -0
  68. package/dist/worker/shared-admission.js +116 -0
  69. package/dist/worker/shared-engine.d.ts +34 -0
  70. package/dist/worker/shared-engine.js +194 -0
  71. package/package.json +21 -13
  72. package/dist/remote/server-protocol.d.ts +0 -185
  73. package/dist/remote/server-protocol.js +0 -417
@@ -1,6 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const __wbg_browsercomponenthost_free: (a: number, b: number) => void;
4
5
  export const __wbg_wasmlix_free: (a: number, b: number) => void;
5
6
  export const __wbg_wasmlixtransaction_free: (a: number, b: number) => void;
6
7
  export const __wbg_wasmobserveevents_free: (a: number, b: number) => void;
@@ -9,13 +10,16 @@ export const __wbg_wasmremotelixtransaction_free: (a: number, b: number) => void
9
10
  export const __wbg_wasmremoteobserveevents_free: (a: number, b: number) => void;
10
11
  export const __wbg_wasmsnapshotexport_free: (a: number, b: number) => void;
11
12
  export const __wbg_wasmsnapshotrestore_free: (a: number, b: number) => void;
13
+ export const browsercomponenthost_call: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
12
14
  export const createHosted: (a: number) => number;
13
15
  export const deleteHosted: (a: number) => number;
14
- export const openJsStorage: (a: number, b: number, c: number, d: number, e: number) => number;
15
- export const openJsStorageFromSnapshot: (a: number, b: number, c: number, d: number) => number;
16
- export const openMemory: (a: number, b: number, c: number, d: number) => number;
17
- export const openMemoryFromSnapshot: (a: number, b: number, c: number) => number;
16
+ export const inspectJsStorageRepository: (a: number) => number;
17
+ export const openJsStorage: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
18
+ export const openJsStorageFromSnapshot: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => number;
19
+ export const openMemory: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => number;
20
+ export const openMemoryFromSnapshot: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
18
21
  export const openRemote: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
22
+ export const parseSqlScript: (a: number, b: number, c: number, d: number) => void;
19
23
  export const wasmlix_activeAccountId: (a: number) => number;
20
24
  export const wasmlix_activeBranchId: (a: number) => number;
21
25
  export const wasmlix_beginTransaction: (a: number) => number;
@@ -24,17 +28,22 @@ export const wasmlix_createBranch: (a: number, b: number) => number;
24
28
  export const wasmlix_createHosted: (a: number, b: number) => number;
25
29
  export const wasmlix_execute: (a: number, b: number, c: number, d: number, e: number) => number;
26
30
  export const wasmlix_executeBatch: (a: number, b: number, c: number) => number;
31
+ export const wasmlix_exportReplicaRecovery: (a: number, b: number, c: number) => number;
27
32
  export const wasmlix_exportSnapshot: (a: number) => number;
28
33
  export const wasmlix_importFilesystemPaths: (a: number, b: number) => number;
29
34
  export const wasmlix_mergeBranch: (a: number, b: number) => number;
30
35
  export const wasmlix_mergeBranchPreview: (a: number, b: number) => number;
31
36
  export const wasmlix_observe: (a: number, b: number, c: number, d: number) => number;
32
- export const wasmlix_openAnotherSession: (a: number, b: number) => number;
37
+ export const wasmlix_openAnotherSession: (a: number, b: number, c: number) => number;
33
38
  export const wasmlix_openReport: (a: number, b: number) => void;
39
+ export const wasmlix_recoverReplica: (a: number, b: number, c: number) => number;
40
+ export const wasmlix_recoverReplicaWithServer: (a: number, b: number, c: number, d: number) => number;
34
41
  export const wasmlix_redo: (a: number) => number;
42
+ export const wasmlix_replicaRecoverySources: (a: number) => number;
35
43
  export const wasmlix_setTelemetryParent: (a: number, b: number, c: number) => void;
36
44
  export const wasmlix_switchBranch: (a: number, b: number) => number;
37
45
  export const wasmlix_syncDiskToLix: (a: number) => number;
46
+ export const wasmlix_syncHealth: (a: number) => number;
38
47
  export const wasmlix_undo: (a: number) => number;
39
48
  export const wasmlixtransaction_commit: (a: number) => number;
40
49
  export const wasmlixtransaction_execute: (a: number, b: number, c: number, d: number, e: number) => number;
@@ -49,16 +58,21 @@ export const wasmremotelix_close: (a: number) => number;
49
58
  export const wasmremotelix_createBranch: (a: number, b: number) => number;
50
59
  export const wasmremotelix_execute: (a: number, b: number, c: number, d: number, e: number) => number;
51
60
  export const wasmremotelix_executeBatch: (a: number, b: number, c: number) => number;
61
+ export const wasmremotelix_exportReplicaRecovery: (a: number, b: number, c: number) => number;
52
62
  export const wasmremotelix_exportSnapshot: (a: number) => number;
53
63
  export const wasmremotelix_importFilesystemPaths: (a: number, b: number) => number;
54
64
  export const wasmremotelix_mergeBranch: (a: number, b: number) => number;
55
65
  export const wasmremotelix_mergeBranchPreview: (a: number, b: number) => number;
56
66
  export const wasmremotelix_observe: (a: number, b: number, c: number, d: number) => number;
57
67
  export const wasmremotelix_openAnotherSession: (a: number, b: number) => number;
68
+ export const wasmremotelix_recoverReplica: (a: number, b: number, c: number) => number;
69
+ export const wasmremotelix_recoverReplicaWithServer: (a: number, b: number, c: number, d: number) => number;
58
70
  export const wasmremotelix_redo: (a: number) => number;
71
+ export const wasmremotelix_replicaRecoverySources: (a: number) => number;
59
72
  export const wasmremotelix_setTelemetryParent: (a: number, b: number) => void;
60
73
  export const wasmremotelix_switchBranch: (a: number, b: number) => number;
61
74
  export const wasmremotelix_syncDiskToLix: (a: number) => number;
75
+ export const wasmremotelix_syncHealth: (a: number) => number;
62
76
  export const wasmremotelix_undo: (a: number) => number;
63
77
  export const wasmremotelixtransaction_commit: (a: number) => number;
64
78
  export const wasmremotelixtransaction_execute: (a: number, b: number, c: number, d: number, e: number) => number;
@@ -72,10 +86,10 @@ export const wasmsnapshotrestore_cancel: (a: number) => number;
72
86
  export const wasmsnapshotrestore_finish: (a: number) => number;
73
87
  export const wasmsnapshotrestore_isComplete: (a: number) => number;
74
88
  export const wasmsnapshotrestore_write: (a: number, b: number, c: number) => number;
75
- export const __wasm_bindgen_func_elem_95597: (a: number, b: number, c: number, d: number) => void;
76
- export const __wasm_bindgen_func_elem_95599: (a: number, b: number, c: number, d: number) => void;
77
- export const __wasm_bindgen_func_elem_17533: (a: number, b: number, c: number) => number;
78
- export const __wasm_bindgen_func_elem_17532: (a: number, b: number) => void;
89
+ export const __wasm_bindgen_func_elem_99617: (a: number, b: number, c: number, d: number) => void;
90
+ export const __wasm_bindgen_func_elem_99619: (a: number, b: number, c: number, d: number) => void;
91
+ export const __wasm_bindgen_func_elem_20812: (a: number, b: number, c: number) => number;
92
+ export const __wasm_bindgen_func_elem_20811: (a: number, b: number) => void;
79
93
  export const __wbindgen_export: (a: number, b: number) => number;
80
94
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
81
95
  export const __wbindgen_export3: (a: number) => void;
@@ -1,7 +1,10 @@
1
+ import { type HttpTransport } from "../http-transport.js";
1
2
  import type { LixBinding, LixStorageConfig } from "../binding-types.js";
2
3
  import type { LixTelemetryOptions, LixOpenProgress, LixOpenReport, LixServerOptions } from "../types.js";
3
- import { type WorkerConnection, type WorkerNotification, type WorkerOperation } from "./protocol.js";
4
- type SyncServerRuntimeOptions = LixServerOptions;
4
+ import { type WorkerConnection, type WorkerNotification, type WorkerOperation, type WorkerSyncServerOptions } from "./protocol.js";
5
+ type SyncServerRuntimeOptions = LixServerOptions & {
6
+ transport?: HttpTransport;
7
+ };
5
8
  export declare function openLixWorker(storage: LixStorageConfig, onDisposed?: () => void, telemetry?: LixTelemetryOptions, server?: SyncServerRuntimeOptions, onProgress?: (progress: LixOpenProgress) => void, snapshot?: ReadableStream<Uint8Array>): Promise<LixWorkerClient>;
6
9
  export declare function pumpSnapshotToWorker(client: LixWorkerClient, reader: ReadableStreamDefaultReader<Uint8Array>, snapshotId: number, open: Promise<unknown>): Promise<void>;
7
10
  /** Opens the local worker transport behind the semantic Lix binding. */
@@ -18,6 +21,7 @@ export declare class BindingLease {
18
21
  export declare function workerBinding(client: LixWorkerClient, lease: BindingLease, sessionId: number): LixBinding;
19
22
  export declare class LixWorkerClient {
20
23
  private readonly connection;
24
+ readonly reusable: boolean;
21
25
  private nextRequestId;
22
26
  private nextSnapshotInputId;
23
27
  private readonly pending;
@@ -26,11 +30,14 @@ export declare class LixWorkerClient {
26
30
  private onDisposed?;
27
31
  private telemetry?;
28
32
  private syncServer?;
33
+ private scopedServers;
34
+ private nextTransportScope;
35
+ withRecoveryServer<T>(server: import("../binding-types.js").SyncServerBindingOptions, operation: (scope: number, server: WorkerSyncServerOptions) => Promise<T>): Promise<T>;
29
36
  private onProgress?;
30
37
  openReport: LixOpenReport | undefined;
31
38
  private readonly syncFetchControllers;
32
39
  private readonly syncFetchStreams;
33
- constructor(connection?: WorkerConnection);
40
+ constructor(connection?: WorkerConnection, reusable?: boolean);
34
41
  get isDisposed(): boolean;
35
42
  allocateSnapshotInputId(): number;
36
43
  beginLease(onDisposed?: () => void, telemetry?: LixTelemetryOptions, syncServer?: SyncServerRuntimeOptions, onProgress?: (progress: LixOpenProgress) => void): void;
@@ -51,4 +58,6 @@ export declare class LixWorkerClient {
51
58
  export declare function hostedLixWorkerOperation<T>(operation: Extract<WorkerOperation, {
52
59
  kind: "hosted.create" | "hosted.delete";
53
60
  }>): Promise<T>;
61
+ export declare function convertReplicaWorkerOperation(storage: LixStorageConfig, server: SyncServerRuntimeOptions, branchId?: string): Promise<void>;
62
+ export declare function retryReplicaMigrationCleanupWorkerOperation(storage: LixStorageConfig, server: SyncServerRuntimeOptions): Promise<number>;
54
63
  export {};
@@ -1,4 +1,7 @@
1
- import { createWorkerConnection, openDirectLixBinding } from "#worker-factory";
1
+ import { ADMISSION_PROTOCOL_EPOCH, ADMISSION_STORAGE_EPOCH } from "./shared-admission.js";
2
+ import { observeOpenProgress, emitOpenProgress } from "../open-progress.js";
3
+ import { fetchTransport } from "../http-transport.js";
4
+ import { createWorkerConnection, createSharedWorkerConnection, openDirectLixBinding } from "#worker-factory";
2
5
  import { ownedSnapshotRestoreChunks } from "../snapshot-restore.js";
3
6
  import { deserializeWorkerError, serializeWorkerError, } from "./protocol.js";
4
7
  const MAX_IDLE_WORKERS = 1;
@@ -6,7 +9,12 @@ const MAX_IDLE_WORKERS = 1;
6
9
  // survives close(). Concurrent opens still receive isolated workers.
7
10
  const idleWorkers = [];
8
11
  export async function openLixWorker(storage, onDisposed, telemetry, server, onProgress, snapshot) {
9
- let client = idleWorkers.pop();
12
+ const providerOptions = storage.kind === "jsStorage" ? storage.options : undefined;
13
+ const sharedKey = !snapshot && server && providerOptions && typeof providerOptions === "object"
14
+ && "sharedEngineKey" in providerOptions && typeof providerOptions.sharedEngineKey === "string"
15
+ && providerOptions.sharedEngineKey.startsWith("lix:opfs:") ? providerOptions.sharedEngineKey : undefined;
16
+ const sharedConnection = sharedKey ? createSharedWorkerConnection(`${sharedKey}:protocol-${ADMISSION_PROTOCOL_EPOCH}:storage-${ADMISSION_STORAGE_EPOCH}`) : undefined;
17
+ let client = sharedConnection ? new LixWorkerClient(sharedConnection, false) : idleWorkers.pop();
10
18
  while (client?.isDisposed)
11
19
  client = idleWorkers.pop();
12
20
  client ??= new LixWorkerClient();
@@ -104,6 +112,20 @@ export async function pumpSnapshotToWorker(client, reader, snapshotId, open) {
104
112
  }
105
113
  /** Opens the local worker transport behind the semantic Lix binding. */
106
114
  export async function openLixWorkerBinding(storage, onDisposed, telemetry, server, onProgress, snapshot) {
115
+ const progress = observeOpenProgress(onProgress);
116
+ const routed = server && onProgress ? { ...server,
117
+ transport: progress.transport(server.transport ?? fetchTransport(server.fetch)),
118
+ } : server;
119
+ try {
120
+ const binding = await openLixWorkerBindingInner(storage, onDisposed, telemetry, routed, onProgress ? value => emitOpenProgress(onProgress, value) : undefined, snapshot);
121
+ progress.complete();
122
+ return binding;
123
+ }
124
+ finally {
125
+ progress.stop();
126
+ }
127
+ }
128
+ async function openLixWorkerBindingInner(storage, onDisposed, telemetry, server, onProgress, snapshot) {
107
129
  if (openDirectLixBinding) {
108
130
  const telemetryDispatch = telemetry
109
131
  ? (span) => {
@@ -274,6 +296,11 @@ export function workerBinding(client, lease, sessionId) {
274
296
  });
275
297
  return workerTransactionBinding(request, transactionId);
276
298
  },
299
+ replicaRecoverySources: () => request({ kind: "replicaRecoverySources" }),
300
+ exportReplicaRecovery: (id) => request({ kind: "exportReplicaRecovery", id }),
301
+ recoverReplica: (id) => request({ kind: "recoverReplica", id }),
302
+ recoverReplicaWithServer: (id, server) => client.withRecoveryServer(server, (transportScope, serialized) => request({ kind: "recoverReplicaWithServer", id, server: serialized, transportScope })),
303
+ syncHealth: () => request({ kind: "syncHealth" }),
277
304
  activeBranchId: () => request({ kind: "activeBranchId" }),
278
305
  activeAccountId: () => request({ kind: "activeAccountId" }),
279
306
  createBranch: (options) => request({ kind: "createBranch", options }),
@@ -350,7 +377,7 @@ function workerObserveBinding(request, notify, observeId) {
350
377
  }
351
378
  async function releaseWorker(client) {
352
379
  client.endLease();
353
- if (!client.isDisposed && idleWorkers.length < MAX_IDLE_WORKERS) {
380
+ if (client.reusable && !client.isDisposed && idleWorkers.length < MAX_IDLE_WORKERS) {
354
381
  idleWorkers.push(client);
355
382
  return;
356
383
  }
@@ -358,6 +385,7 @@ async function releaseWorker(client) {
358
385
  }
359
386
  export class LixWorkerClient {
360
387
  connection;
388
+ reusable;
361
389
  nextRequestId = 1;
362
390
  nextSnapshotInputId = 1;
363
391
  pending = new Map();
@@ -366,12 +394,26 @@ export class LixWorkerClient {
366
394
  onDisposed;
367
395
  telemetry;
368
396
  syncServer;
397
+ scopedServers = new Map();
398
+ nextTransportScope = 1;
399
+ async withRecoveryServer(server, operation) {
400
+ const scope = this.nextTransportScope++;
401
+ const runtime = { url: server.url, headers: server.headerProvider ?? server.headers, transport: server.transport };
402
+ this.scopedServers.set(scope, runtime);
403
+ try {
404
+ return await operation(scope, serializeSyncServer(runtime));
405
+ }
406
+ finally {
407
+ this.scopedServers.delete(scope);
408
+ }
409
+ }
369
410
  onProgress;
370
411
  openReport;
371
412
  syncFetchControllers = new Map();
372
413
  syncFetchStreams = new Map();
373
- constructor(connection = createWorkerConnection()) {
414
+ constructor(connection = createWorkerConnection(), reusable = true) {
374
415
  this.connection = connection;
416
+ this.reusable = reusable;
375
417
  connection.onMessage((message) => this.handleMessage(message));
376
418
  connection.onFatal((error) => this.handleFatal(error));
377
419
  }
@@ -494,10 +536,10 @@ export class LixWorkerClient {
494
536
  }
495
537
  break;
496
538
  case "sync.headers":
497
- void this.resolveSyncHeaders(message.requestId);
539
+ void this.resolveSyncHeaders(message.requestId, message.transportScope);
498
540
  break;
499
541
  case "sync.fetch":
500
- void this.resolveSyncFetch(message.requestId, message.request);
542
+ void this.resolveSyncFetch(message.requestId, message.request, message.transportScope);
501
543
  break;
502
544
  case "sync.fetch.stream.pull":
503
545
  void this.resolveSyncFetchStreamPull(message.requestId);
@@ -507,9 +549,11 @@ export class LixWorkerClient {
507
549
  break;
508
550
  }
509
551
  }
510
- async resolveSyncHeaders(requestId) {
552
+ async resolveSyncHeaders(requestId, transportScope) {
511
553
  try {
512
- const source = this.syncServer?.headers;
554
+ if (transportScope !== undefined && !this.scopedServers.has(transportScope))
555
+ throw workerClosedError();
556
+ const source = (transportScope === undefined ? this.syncServer : this.scopedServers.get(transportScope))?.headers;
513
557
  const headers = typeof source === "function" ? await source() : source;
514
558
  this.notify({
515
559
  kind: "sync.headers.result",
@@ -525,38 +569,33 @@ export class LixWorkerClient {
525
569
  });
526
570
  }
527
571
  }
528
- async resolveSyncFetch(requestId, request) {
529
- const fetcher = this.syncServer?.fetch;
530
- if (!fetcher) {
531
- this.notify({
532
- kind: "sync.fetch.result",
533
- requestId,
534
- result: {
535
- ok: false,
536
- error: serializeWorkerError(new Error("Sync fetch bridge is unavailable")),
537
- },
538
- });
572
+ async resolveSyncFetch(requestId, request, transportScope) {
573
+ const server = transportScope === undefined ? this.syncServer : this.scopedServers.get(transportScope);
574
+ if (!server) {
575
+ this.notify({ kind: "sync.fetch.result", requestId, result: { ok: false, error: serializeWorkerError(workerClosedError()) } });
539
576
  return;
540
577
  }
578
+ const transport = server.transport ?? fetchTransport(server.fetch);
541
579
  const controller = new AbortController();
542
580
  this.syncFetchControllers.set(requestId, controller);
543
581
  let retainedStream = false;
544
582
  try {
545
- const response = await fetcher(request.url, {
546
- method: request.method,
547
- headers: request.headers,
548
- body: typeof request.body === "string"
549
- ? request.body
550
- : request.body?.slice().buffer,
551
- credentials: request.credentials,
552
- signal: controller.signal,
553
- });
583
+ const response = await transport({ url: request.url, response: request.response, init: {
584
+ method: request.method,
585
+ headers: request.headers,
586
+ body: typeof request.body === "string"
587
+ ? request.body
588
+ : request.body?.slice().buffer,
589
+ credentials: request.credentials,
590
+ signal: controller.signal,
591
+ cache: request.cache, redirect: request.redirect,
592
+ } });
554
593
  if (this.syncFetchControllers.get(requestId) !== controller ||
555
594
  controller.signal.aborted) {
556
595
  await response.body?.cancel().catch(() => undefined);
557
596
  return;
558
597
  }
559
- if (request.responseMode === "stream") {
598
+ if (request.response.mode === "streaming") {
560
599
  this.syncFetchStreams.set(requestId, response.body?.getReader());
561
600
  retainedStream = true;
562
601
  this.notify({
@@ -574,7 +613,7 @@ export class LixWorkerClient {
574
613
  });
575
614
  return;
576
615
  }
577
- const body = await readSyncResponseBody(response, request.responseLimit, controller);
616
+ const body = new Uint8Array(await response.arrayBuffer());
578
617
  this.notify({
579
618
  kind: "sync.fetch.result",
580
619
  requestId,
@@ -590,6 +629,8 @@ export class LixWorkerClient {
590
629
  });
591
630
  }
592
631
  catch (error) {
632
+ if (isSyncResponseTooLarge(error))
633
+ controller.abort(error);
593
634
  if (!controller.signal.aborted || isSyncResponseTooLarge(error)) {
594
635
  this.notify({
595
636
  kind: "sync.fetch.result",
@@ -671,58 +712,10 @@ export class LixWorkerClient {
671
712
  this.connection.unref();
672
713
  }
673
714
  }
674
- async function readSyncResponseBody(response, limit, controller) {
675
- if (!Number.isSafeInteger(limit) || limit <= 0) {
676
- throw new TypeError("Browser sync fetch has no valid response limit");
677
- }
678
- const declaredLength = Number(response.headers.get("content-length"));
679
- if (Number.isFinite(declaredLength) && declaredLength > limit) {
680
- const error = syncResponseTooLarge(limit);
681
- controller.abort(error);
682
- throw error;
683
- }
684
- const stream = response.body;
685
- if (!stream)
686
- return new Uint8Array();
687
- const reader = stream.getReader();
688
- const chunks = [];
689
- let total = 0;
690
- try {
691
- while (true) {
692
- const { done, value } = await reader.read();
693
- if (done)
694
- break;
695
- total += value.byteLength;
696
- if (total > limit) {
697
- const error = syncResponseTooLarge(limit);
698
- controller.abort(error);
699
- await reader.cancel(error).catch(() => undefined);
700
- throw error;
701
- }
702
- chunks.push(value);
703
- }
704
- }
705
- finally {
706
- reader.releaseLock();
707
- }
708
- const body = new Uint8Array(total);
709
- let offset = 0;
710
- for (const chunk of chunks) {
711
- body.set(chunk, offset);
712
- offset += chunk.byteLength;
713
- }
714
- return body;
715
- }
716
- function syncResponseTooLarge(limit) {
717
- const error = new Error(`sync fetch response exceeds ${limit} bytes`);
718
- error.name = "LixError";
719
- error.code = "LIX_ERROR_SYNC_RESPONSE_TOO_LARGE";
720
- return error;
721
- }
722
715
  function isSyncResponseTooLarge(error) {
723
716
  return (error instanceof Error &&
724
717
  error.code ===
725
- "LIX_ERROR_SYNC_RESPONSE_TOO_LARGE");
718
+ "LIX_TRANSPORT_RESPONSE_LIMIT");
726
719
  }
727
720
  function workerClosedError() {
728
721
  const error = new Error("Lix worker is closed");
@@ -739,7 +732,6 @@ function serializeSyncServer(server) {
739
732
  ? undefined
740
733
  : headerEntries(server.headers),
741
734
  dynamicHeaders: typeof server.headers === "function",
742
- customFetch: server.fetch !== undefined,
743
735
  };
744
736
  }
745
737
  function headerEntries(headers) {
@@ -755,7 +747,7 @@ async function resolveDirectSyncServer(server) {
755
747
  return {
756
748
  url: new URL(server.url).toString(),
757
749
  headers: headerEntries(headers),
758
- fetch: server.fetch,
750
+ transport: server.transport ?? (server.fetch ? fetchTransport(server.fetch) : undefined),
759
751
  };
760
752
  }
761
753
  export async function hostedLixWorkerOperation(operation) {
@@ -768,3 +760,28 @@ export async function hostedLixWorkerOperation(operation) {
768
760
  await client.terminate();
769
761
  }
770
762
  }
763
+ export async function convertReplicaWorkerOperation(storage, server, branchId) {
764
+ const providerOptions = storage.kind === "jsStorage" ? storage.options : undefined;
765
+ const sharedKey = providerOptions && typeof providerOptions === "object"
766
+ && "sharedEngineKey" in providerOptions && typeof providerOptions.sharedEngineKey === "string"
767
+ && providerOptions.sharedEngineKey.startsWith("lix:opfs:") ? providerOptions.sharedEngineKey : undefined;
768
+ const connection = sharedKey ? createSharedWorkerConnection(`${sharedKey}:protocol-${ADMISSION_PROTOCOL_EPOCH}:storage-${ADMISSION_STORAGE_EPOCH}`) : undefined;
769
+ const client = connection ? new LixWorkerClient(connection, false) : new LixWorkerClient();
770
+ client.beginLease(undefined, undefined, server);
771
+ try {
772
+ await client.request({ kind: "replica.convert", storage, server: serializeSyncServer(server), branchId }, 0);
773
+ }
774
+ finally {
775
+ await client.terminate();
776
+ }
777
+ }
778
+ export async function retryReplicaMigrationCleanupWorkerOperation(storage, server) {
779
+ const client = new LixWorkerClient();
780
+ client.beginLease(undefined, undefined, server);
781
+ try {
782
+ return await client.request({ kind: "replica.cleanup", storage, server: serializeSyncServer(server) }, 0);
783
+ }
784
+ finally {
785
+ await client.terminate();
786
+ }
787
+ }
@@ -0,0 +1,20 @@
1
+ import { type AdmissionIdentity } from "./shared-admission.js";
2
+ export interface DurableLocalAdmissionStore {
3
+ get(key: string): Promise<unknown>;
4
+ put(key: string, value: unknown): Promise<void>;
5
+ delete(key: string): Promise<void>;
6
+ }
7
+ /** Durable local routing evidence only. Never grants or restores a remote lease. */
8
+ export declare class DurableLocalAdmission {
9
+ private readonly scope;
10
+ private readonly store;
11
+ private readonly url;
12
+ private readonly repositoryId;
13
+ constructor(scope: string, url: string, store?: DurableLocalAdmissionStore);
14
+ private key;
15
+ private validIdentity;
16
+ read(headers: [string, string][]): Promise<AdmissionIdentity | undefined>;
17
+ /** Call only after actual storage/account validation, never after metadata admission alone. */
18
+ record(headers: [string, string][], identity: AdmissionIdentity): Promise<void>;
19
+ remove(headers: [string, string][]): Promise<void>;
20
+ }
@@ -0,0 +1,87 @@
1
+ import { ADMISSION_PROTOCOL_EPOCH, ADMISSION_STORAGE_EPOCH } from "./shared-admission.js";
2
+ const anonymous = "00000000-0000-7000-8000-000000000002";
3
+ const databaseName = "lix-local-admission-v1";
4
+ const storeName = "proofs";
5
+ /** Each operation closes its connection and resolves only after transaction commit. */
6
+ class IndexedDbProofStore {
7
+ async operation(key, mode, value) {
8
+ const db = await new Promise((resolve, reject) => {
9
+ const request = indexedDB.open(databaseName, 1);
10
+ request.onupgradeneeded = () => request.result.createObjectStore(storeName);
11
+ request.onsuccess = () => resolve(request.result);
12
+ request.onerror = () => reject(request.error);
13
+ });
14
+ try {
15
+ return await new Promise((resolve, reject) => {
16
+ const transaction = db.transaction(storeName, mode === "get" ? "readonly" : "readwrite");
17
+ const store = transaction.objectStore(storeName);
18
+ const request = mode === "get" ? store.get(key) : mode === "put" ? store.put(value, key) : store.delete(key);
19
+ transaction.oncomplete = () => resolve(request.result);
20
+ transaction.onabort = () => reject(transaction.error ?? new Error("Local admission transaction aborted"));
21
+ transaction.onerror = () => reject(transaction.error ?? new Error("Local admission transaction failed"));
22
+ });
23
+ }
24
+ finally {
25
+ db.close();
26
+ }
27
+ }
28
+ get(key) { return this.operation(key, "get"); }
29
+ async put(key, value) { await this.operation(key, "put", value); }
30
+ async delete(key) { await this.operation(key, "delete"); }
31
+ }
32
+ /** Durable local routing evidence only. Never grants or restores a remote lease. */
33
+ export class DurableLocalAdmission {
34
+ scope;
35
+ store;
36
+ url;
37
+ repositoryId;
38
+ constructor(scope, url, store = new IndexedDbProofStore()) {
39
+ this.scope = scope;
40
+ this.store = store;
41
+ const parsed = new URL(url);
42
+ const id = parsed.pathname.match(/^\/lix\/([0-9a-f-]{36})\/?$/i)?.[1];
43
+ if (!scope || scope.length > 4096 || !id || !["https:", "http:"].includes(parsed.protocol) || parsed.search || parsed.hash || parsed.username || parsed.password) {
44
+ throw new Error("Invalid durable local admission scope");
45
+ }
46
+ parsed.pathname = `/lix/${id}`;
47
+ this.url = parsed.toString();
48
+ this.repositoryId = id;
49
+ }
50
+ async key(headers) {
51
+ const entries = [];
52
+ new Headers(headers).forEach((value, name) => entries.push([name, value]));
53
+ const bytes = new TextEncoder().encode(JSON.stringify([1, this.scope, this.url, entries, ADMISSION_PROTOCOL_EPOCH, ADMISSION_STORAGE_EPOCH]));
54
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
55
+ return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, "0")).join("");
56
+ }
57
+ validIdentity(value) {
58
+ if (!value || typeof value !== "object" || Array.isArray(value))
59
+ return false;
60
+ const v = value;
61
+ return Object.keys(v).sort().join(",") === "principalId,protocolEpoch,repositoryId,storageEpoch" &&
62
+ v.repositoryId === this.repositoryId && typeof v.principalId === "string" && /^[\x21-\x7e]{1,255}$/.test(v.principalId) &&
63
+ v.protocolEpoch === ADMISSION_PROTOCOL_EPOCH && v.storageEpoch === ADMISSION_STORAGE_EPOCH;
64
+ }
65
+ async read(headers) {
66
+ const key = await this.key(headers);
67
+ const value = await this.store.get(key);
68
+ if (!value || typeof value !== "object" || Array.isArray(value))
69
+ return undefined;
70
+ const record = value;
71
+ if (Object.keys(record).sort().join(",") !== "identity,key,schemaVersion" || record.schemaVersion !== 1 || record.key !== key || !this.validIdentity(record.identity))
72
+ return undefined;
73
+ if (!new Headers(headers).get("authorization") && record.identity.principalId !== anonymous)
74
+ return undefined;
75
+ return { ...record.identity };
76
+ }
77
+ /** Call only after actual storage/account validation, never after metadata admission alone. */
78
+ async record(headers, identity) {
79
+ if (!this.validIdentity(identity))
80
+ throw new Error("Invalid durable local admission identity");
81
+ if (!new Headers(headers).get("authorization") && identity.principalId !== anonymous)
82
+ return;
83
+ const key = await this.key(headers);
84
+ await this.store.put(key, { schemaVersion: 1, key, identity: { ...identity } });
85
+ }
86
+ async remove(headers) { await this.store.delete(await this.key(headers)); }
87
+ }
@@ -0,0 +1 @@
1
+ export {};