@lix-js/sdk 0.12.3 → 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.
Files changed (50) hide show
  1. package/README.md +54 -10
  2. package/dist/binding-types.d.ts +37 -9
  3. package/dist/binding.browser.d.ts +2 -2
  4. package/dist/binding.browser.js +20 -13
  5. package/dist/binding.node-wasm.d.ts +2 -2
  6. package/dist/binding.node-wasm.js +9 -11
  7. package/dist/binding.node.d.ts +3 -3
  8. package/dist/binding.node.js +70 -13
  9. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  10. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +2 -2
  13. package/dist/lix.d.ts +27 -5
  14. package/dist/lix.js +125 -6
  15. package/dist/open-lix.d.ts +4 -5
  16. package/dist/open-lix.js +174 -33
  17. package/dist/remote/client.d.ts +1 -2
  18. package/dist/remote/client.js +40 -1151
  19. package/dist/remote/server-protocol.d.ts +5 -6
  20. package/dist/remote/server-protocol.js +40 -6
  21. package/dist/result.d.ts +6 -13
  22. package/dist/result.js +9 -35
  23. package/dist/snapshot-restore.d.ts +6 -0
  24. package/dist/snapshot-restore.js +101 -0
  25. package/dist/storage-adapter.d.ts +175 -19
  26. package/dist/storage-adapter.js +21 -0
  27. package/dist/types.d.ts +104 -34
  28. package/dist/value.d.ts +1 -0
  29. package/dist/value.js +8 -0
  30. package/dist/wasm/lix_js_sdk.d.ts +121 -24
  31. package/dist/wasm/lix_js_sdk.js +606 -78
  32. package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
  33. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +48 -6
  34. package/dist/wasm-init.d.ts +2 -0
  35. package/dist/wasm-init.js +23 -0
  36. package/dist/worker/client.d.ts +31 -5
  37. package/dist/worker/client.js +541 -45
  38. package/dist/worker/factory.browser.d.ts +2 -2
  39. package/dist/worker/factory.node.d.ts +2 -2
  40. package/dist/worker/factory.node.js +3 -10
  41. package/dist/worker/host.d.ts +4 -2
  42. package/dist/worker/host.js +401 -43
  43. package/dist/worker/protocol.d.ts +107 -3
  44. package/package.json +6 -10
  45. package/dist/indexeddb-backend.d.ts +0 -19
  46. package/dist/indexeddb-backend.js +0 -121
  47. package/dist/remote/sse.d.ts +0 -12
  48. package/dist/remote/sse.js +0 -87
  49. package/dist/workerd.d.ts +0 -25
  50. package/dist/workerd.js +0 -35
@@ -1,4 +1,4 @@
1
- import type { LixBinding, LixStorageConfig, TelemetryDispatch } from "../binding-types.js";
1
+ import type { LixBinding, LixStorageConfig, TelemetryDispatch, TelemetryParentContext, OpenProgressDispatch, SyncServerBindingOptions } from "../binding-types.js";
2
2
  import type { WorkerConnection } from "./protocol.js";
3
- export declare const openDirectLixBinding: undefined | ((storage: LixStorageConfig, telemetry?: TelemetryDispatch) => Promise<LixBinding>);
3
+ export declare const openDirectLixBinding: undefined | ((storage: LixStorageConfig, telemetry?: TelemetryDispatch, telemetryParent?: TelemetryParentContext, server?: SyncServerBindingOptions, openProgress?: OpenProgressDispatch, snapshot?: ReadableStream<Uint8Array>) => Promise<LixBinding>);
4
4
  export declare function createWorkerConnection(): WorkerConnection;
@@ -1,5 +1,5 @@
1
- import type { LixBinding, LixStorageConfig, TelemetryDispatch } from "../binding-types.js";
1
+ import type { LixBinding, LixStorageConfig, SyncServerBindingOptions, TelemetryDispatch, TelemetryParentContext, OpenProgressDispatch } from "../binding-types.js";
2
2
  import type { WorkerConnection } from "./protocol.js";
3
3
  export declare function createWorkerConnection(): WorkerConnection;
4
4
  export declare function workerExecArgv(execArgv: readonly string[]): string[];
5
- export declare const openDirectLixBinding: (storage: LixStorageConfig, telemetry?: TelemetryDispatch) => Promise<LixBinding | undefined>;
5
+ export declare const openDirectLixBinding: (storage: LixStorageConfig, telemetry?: TelemetryDispatch, telemetryParent?: TelemetryParentContext, server?: SyncServerBindingOptions, openProgress?: OpenProgressDispatch, snapshot?: ReadableStream<Uint8Array>) => Promise<LixBinding | undefined>;
@@ -1,5 +1,5 @@
1
1
  import { Worker } from "node:worker_threads";
2
- import { openNativeLixBinding } from "../binding.node.js";
2
+ import { openLixBinding } from "../binding.node.js";
3
3
  export function createWorkerConnection() {
4
4
  const worker = new Worker(new URL("./entry.node.js", import.meta.url), {
5
5
  name: "lix",
@@ -52,13 +52,6 @@ export function workerExecArgv(execArgv) {
52
52
  /// Native Lix already owns a dedicated serialized engine actor. Routing it
53
53
  /// through a second JavaScript worker adds two message-port hops per query
54
54
  /// without adding isolation or concurrency.
55
- export const openDirectLixBinding = async (storage, telemetry) => {
56
- try {
57
- return await openNativeLixBinding(storage, telemetry);
58
- }
59
- catch (error) {
60
- if (storage.kind === "memory")
61
- return undefined;
62
- throw error;
63
- }
55
+ export const openDirectLixBinding = async (storage, telemetry, telemetryParent, server, openProgress, snapshot) => {
56
+ return openLixBinding(storage, telemetry, telemetryParent, server, openProgress, snapshot);
64
57
  };
@@ -1,2 +1,4 @@
1
- import { type WorkerHostEndpoint } from "./protocol.js";
2
- export declare function startWorkerHost(endpoint: WorkerHostEndpoint): void;
1
+ import { openLixBinding } from "#binding";
2
+ import { type WorkerHostEndpoint, type WorkerSyncFetchResponse } from "./protocol.js";
3
+ export declare function startWorkerHost(endpoint: WorkerHostEndpoint, openBinding?: typeof openLixBinding): void;
4
+ export declare function responseFromSyncFetch(resolved: WorkerSyncFetchResponse): Response;
@@ -1,24 +1,77 @@
1
1
  import { openLixBinding } from "#binding";
2
- import { serializeWorkerError, } from "./protocol.js";
3
- export function startWorkerHost(endpoint) {
4
- let lix;
2
+ import { deserializeWorkerError, serializeWorkerError, } from "./protocol.js";
3
+ export function startWorkerHost(endpoint, openBinding = openLixBinding) {
4
+ const sessions = new Map();
5
+ let nextSessionId = 1;
5
6
  let nextTransactionId = 1;
6
7
  let nextObserveId = 1;
7
8
  const transactions = new Map();
8
9
  const observations = new Map();
10
+ let nextSnapshotExportId = 1;
11
+ const snapshotExports = new Map();
12
+ const snapshotInputs = new Map();
13
+ let nextSyncRequestId = 1;
14
+ const pendingSyncHeaders = new Map();
15
+ const pendingSyncFetch = new Map();
16
+ const pendingSyncStreamPulls = new Map();
17
+ const syncStreamCleanup = new Map();
9
18
  let finiteQueue = Promise.resolve();
10
19
  endpoint.onMessage((message) => {
11
20
  if (!("id" in message)) {
12
21
  handleNotification(message);
13
22
  return;
14
23
  }
15
- if (message.operation.kind === "observe.next") {
16
- const observeId = message.operation.observeId;
17
- void respond(message, () => handleObserveNext(observeId));
24
+ if (message.operation.kind === "openSnapshot.write" ||
25
+ message.operation.kind === "openSnapshot.finish") {
26
+ const operation = message.operation;
27
+ void respond(message, () => handleSnapshotInput(operation));
28
+ return;
29
+ }
30
+ if (message.operation.kind === "open" &&
31
+ message.operation.snapshotId !== undefined) {
32
+ ensureSnapshotInput(message.operation.snapshotId);
33
+ }
34
+ if (message.operation.kind === "observe.next" ||
35
+ message.operation.kind === "exportSnapshot.next" ||
36
+ message.operation.kind === "exportSnapshot.cancel") {
37
+ if (message.operation.kind === "observe.next") {
38
+ const observeId = message.operation.observeId;
39
+ void respond(message, () => handleObserveNext(observeId, message.telemetryParent));
40
+ }
41
+ else if (message.operation.kind === "exportSnapshot.next") {
42
+ const exportId = message.operation.exportId;
43
+ void respond(message, () => handleSnapshotNext(exportId));
44
+ }
45
+ else {
46
+ const exportId = message.operation.exportId;
47
+ void respond(message, () => handleSnapshotCancel(exportId));
48
+ }
49
+ return;
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));
18
58
  return;
19
59
  }
20
60
  finiteQueue = finiteQueue.then(async () => {
21
- await respond(message, () => handleFiniteOperation(message.operation));
61
+ try {
62
+ await respond(message, async () => {
63
+ if (message.operation.kind !== "open") {
64
+ requiredLix(message.sessionId).setTelemetryParent(message.telemetryParent);
65
+ }
66
+ return handleFiniteOperation(message.sessionId, message.operation, message.telemetryParent);
67
+ });
68
+ }
69
+ finally {
70
+ // The mutable FFI carrier is safe only within this serialized
71
+ // operation. Clear it before another request or background task
72
+ // can accidentally inherit a stale remote parent.
73
+ (sessions.get(message.sessionId) ?? sessions.get(0))?.setTelemetryParent();
74
+ }
22
75
  });
23
76
  });
24
77
  function handleNotification(message) {
@@ -29,6 +82,13 @@ export function startWorkerHost(endpoint) {
29
82
  events?.close();
30
83
  break;
31
84
  }
85
+ case "openSnapshot.cancel": {
86
+ const input = snapshotInputs.get(message.snapshotId);
87
+ snapshotInputs.delete(message.snapshotId);
88
+ if (input)
89
+ void input.writer.abort().catch(() => undefined);
90
+ break;
91
+ }
32
92
  case "transaction.abandon":
33
93
  finiteQueue = finiteQueue.then(async () => {
34
94
  const transaction = transactions.get(message.transactionId);
@@ -37,8 +97,57 @@ export function startWorkerHost(endpoint) {
37
97
  await transaction.rollback().catch(() => undefined);
38
98
  });
39
99
  break;
100
+ case "sync.headers.result": {
101
+ const pending = pendingSyncHeaders.get(message.requestId);
102
+ pendingSyncHeaders.delete(message.requestId);
103
+ if (!pending)
104
+ break;
105
+ if (message.result.ok)
106
+ pending.resolve(message.result.headers);
107
+ else
108
+ pending.reject(deserializeWorkerError(message.result.error));
109
+ break;
110
+ }
111
+ case "sync.fetch.result": {
112
+ const pending = pendingSyncFetch.get(message.requestId);
113
+ pendingSyncFetch.delete(message.requestId);
114
+ if (!pending)
115
+ break;
116
+ if (message.result.ok)
117
+ pending.resolve(message.result.response);
118
+ else
119
+ pending.reject(deserializeWorkerError(message.result.error));
120
+ break;
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
+ }
40
144
  }
41
145
  }
146
+ function finishSyncStream(requestId) {
147
+ const cleanup = syncStreamCleanup.get(requestId);
148
+ syncStreamCleanup.delete(requestId);
149
+ cleanup?.();
150
+ }
42
151
  async function respond(request, operation) {
43
152
  try {
44
153
  const value = await operation();
@@ -52,21 +161,45 @@ export function startWorkerHost(endpoint) {
52
161
  });
53
162
  }
54
163
  }
55
- async function handleFiniteOperation(operation) {
164
+ async function handleFiniteOperation(sessionId, operation, telemetryParent) {
56
165
  switch (operation.kind) {
57
166
  case "open":
58
- if (lix)
167
+ if (sessions.size > 0)
59
168
  throw workerStateError("Lix worker is already open");
60
- lix = await openLixBinding(operation.storage, operation.telemetryEnabled
61
- ? (span) => endpoint.postMessage({ kind: "telemetry", span })
62
- : undefined);
63
- return undefined;
169
+ {
170
+ const snapshot = operation.snapshotId === undefined
171
+ ? undefined
172
+ : requiredSnapshotInput(operation.snapshotId).readable;
173
+ try {
174
+ const opened = await openBinding(operation.storage, operation.telemetryEnabled
175
+ ? (span) => endpoint.postMessage({ kind: "telemetry", span })
176
+ : undefined, telemetryParent, createSyncServerBridge(operation.server), operation.progressEnabled
177
+ ? (progress) => endpoint.postMessage({ kind: "open.progress", progress })
178
+ : undefined, snapshot);
179
+ sessions.set(0, opened);
180
+ return opened.openReport?.();
181
+ }
182
+ finally {
183
+ if (operation.snapshotId !== undefined) {
184
+ snapshotInputs.delete(operation.snapshotId);
185
+ }
186
+ }
187
+ }
188
+ case "openSnapshot.write":
189
+ case "openSnapshot.finish":
190
+ throw workerStateError("snapshot input uses the restore lane");
191
+ case "openAnotherSession": {
192
+ const opened = await requiredLix(sessionId).openAnotherSession(operation.options);
193
+ const openedSessionId = nextSessionId++;
194
+ sessions.set(openedSessionId, opened);
195
+ return openedSessionId;
196
+ }
64
197
  case "execute":
65
- return requiredLix().execute(operation.sql, operation.params, operation.options);
198
+ return requiredLix(sessionId).execute(operation.sql, operation.params, operation.options);
66
199
  case "executeBatch":
67
- return requiredLix().executeBatch(operation.statements, operation.options);
200
+ return requiredLix(sessionId).executeBatch(operation.statements, operation.options);
68
201
  case "beginTransaction": {
69
- const transaction = await requiredLix().beginTransaction();
202
+ const transaction = await requiredLix(sessionId).beginTransaction();
70
203
  const transactionId = nextTransactionId++;
71
204
  transactions.set(transactionId, transaction);
72
205
  return transactionId;
@@ -86,56 +219,247 @@ export function startWorkerHost(endpoint) {
86
219
  return undefined;
87
220
  }
88
221
  case "activeBranchId":
89
- return requiredLix().activeBranchId();
222
+ return requiredLix(sessionId).activeBranchId();
90
223
  case "activeAccountId":
91
- return requiredLix().activeAccountId();
224
+ return requiredLix(sessionId).activeAccountId();
92
225
  case "createBranch":
93
- return requiredLix().createBranch(operation.options);
94
- case "createCheckpoint":
95
- return requiredLix().createCheckpoint();
226
+ return requiredLix(sessionId).createBranch(operation.options);
96
227
  case "undo":
97
- return requiredLix().undo();
228
+ return requiredLix(sessionId).undo();
98
229
  case "redo":
99
- return requiredLix().redo();
230
+ return requiredLix(sessionId).redo();
100
231
  case "switchBranch":
101
- return requiredLix().switchBranch(operation.options);
232
+ return requiredLix(sessionId).switchBranch(operation.options);
102
233
  case "mergeBranchPreview":
103
- return requiredLix().mergeBranchPreview(operation.options);
234
+ return requiredLix(sessionId).mergeBranchPreview(operation.options);
104
235
  case "mergeBranch":
105
- return requiredLix().mergeBranch(operation.options);
236
+ return requiredLix(sessionId).mergeBranch(operation.options);
106
237
  case "importFilesystemPaths":
107
- return requiredLix().importFilesystemPaths(operation.paths);
238
+ return requiredLix(sessionId).importFilesystemPaths(operation.paths);
108
239
  case "syncDiskToLix":
109
- return requiredLix().syncDiskToLix();
110
- case "observe": {
111
- const events = await requiredLix().observe(operation.sql, operation.params);
112
- const observeId = nextObserveId++;
113
- observations.set(observeId, events);
114
- return observeId;
115
- }
240
+ return requiredLix(sessionId).syncDiskToLix();
241
+ case "exportSnapshot":
242
+ {
243
+ const binding = requiredLix(sessionId);
244
+ const exportSnapshot = binding.exportSnapshot;
245
+ if (!exportSnapshot) {
246
+ throw workerStateError("this Lix binding cannot export snapshots");
247
+ }
248
+ const snapshot = exportSnapshot.call(binding);
249
+ const exportId = nextSnapshotExportId++;
250
+ snapshotExports.set(exportId, snapshot);
251
+ return exportId;
252
+ }
253
+ case "exportSnapshot.next":
254
+ throw workerStateError("snapshot pulls bypass the finite operation queue");
255
+ case "exportSnapshot.cancel":
256
+ throw workerStateError("snapshot cancellation bypasses the finite operation queue");
257
+ case "observe":
258
+ throw workerStateError("observe must use the observation lane");
116
259
  case "close": {
117
- const openLix = requiredLix();
260
+ const openLix = requiredLix(sessionId);
118
261
  await openLix.close();
119
- for (const events of observations.values())
120
- events.close();
121
- observations.clear();
122
- transactions.clear();
123
- lix = undefined;
262
+ sessions.delete(sessionId);
124
263
  return undefined;
125
264
  }
126
265
  case "observe.next":
127
266
  throw workerStateError("observe.next must use the observation lane");
128
267
  }
129
268
  }
130
- async function handleObserveNext(observeId) {
269
+ async function handleSnapshotNext(exportId) {
270
+ const snapshot = snapshotExports.get(exportId);
271
+ if (!snapshot)
272
+ return undefined;
273
+ try {
274
+ const chunk = await snapshot.next();
275
+ if (chunk == null)
276
+ snapshotExports.delete(exportId);
277
+ return chunk ?? undefined;
278
+ }
279
+ catch (error) {
280
+ snapshotExports.delete(exportId);
281
+ await Promise.resolve(snapshot.cancel()).catch(() => undefined);
282
+ throw error;
283
+ }
284
+ }
285
+ async function handleSnapshotCancel(exportId) {
286
+ const snapshot = snapshotExports.get(exportId);
287
+ snapshotExports.delete(exportId);
288
+ if (snapshot)
289
+ await snapshot.cancel();
290
+ }
291
+ function ensureSnapshotInput(snapshotId) {
292
+ if (snapshotInputs.has(snapshotId))
293
+ return;
294
+ const stream = new TransformStream(undefined, { highWaterMark: 0 }, { highWaterMark: 0 });
295
+ snapshotInputs.set(snapshotId, {
296
+ readable: stream.readable,
297
+ writer: stream.writable.getWriter(),
298
+ });
299
+ }
300
+ function requiredSnapshotInput(snapshotId) {
301
+ const input = snapshotInputs.get(snapshotId);
302
+ if (!input)
303
+ throw workerStateError("snapshot restore input is closed");
304
+ return input;
305
+ }
306
+ async function handleSnapshotInput(operation) {
307
+ const input = requiredSnapshotInput(operation.snapshotId);
308
+ if (operation.kind === "openSnapshot.write") {
309
+ await input.writer.write(operation.chunk);
310
+ return;
311
+ }
312
+ await input.writer.close();
313
+ }
314
+ function createSyncServerBridge(server) {
315
+ if (!server)
316
+ return undefined;
317
+ return {
318
+ url: server.url,
319
+ headers: server.headers ?? [],
320
+ headerProvider: server.dynamicHeaders
321
+ ? () => {
322
+ const requestId = nextSyncRequestId++;
323
+ return new Promise((resolve, reject) => {
324
+ pendingSyncHeaders.set(requestId, { resolve, reject });
325
+ endpoint.postMessage({ kind: "sync.headers", requestId });
326
+ });
327
+ }
328
+ : undefined,
329
+ fetch: server.customFetch ? bridgeFetch : undefined,
330
+ };
331
+ }
332
+ async function bridgeFetch(input, init) {
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)) {
340
+ throw new TypeError("Browser sync fetch has no valid response limit");
341
+ }
342
+ const requestId = nextSyncRequestId++;
343
+ const requestBase = {
344
+ url: typeof input === "string"
345
+ ? input
346
+ : input instanceof URL
347
+ ? input.toString()
348
+ : input.url,
349
+ method: init?.method ?? "GET",
350
+ headers: headerEntries(init?.headers),
351
+ body: serializableBody(init?.body),
352
+ credentials: init?.credentials,
353
+ };
354
+ const request = streaming
355
+ ? { ...requestBase, responseMode: "stream" }
356
+ : {
357
+ ...requestBase,
358
+ responseMode: "buffered",
359
+ responseLimit: responseLimit,
360
+ };
361
+ const response = new Promise((resolve, reject) => {
362
+ pendingSyncFetch.set(requestId, { resolve, reject });
363
+ endpoint.postMessage({ kind: "sync.fetch", requestId, request });
364
+ });
365
+ const abort = () => {
366
+ const pending = pendingSyncFetch.get(requestId);
367
+ pendingSyncFetch.delete(requestId);
368
+ if (pending) {
369
+ pending.reject(new DOMException("The operation was aborted", "AbortError"));
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);
380
+ };
381
+ if (init?.signal?.aborted)
382
+ abort();
383
+ else
384
+ init?.signal?.addEventListener("abort", abort, { once: true });
385
+ let streamEstablished = false;
386
+ try {
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
+ }
427
+ return responseFromSyncFetch(resolved);
428
+ }
429
+ catch (error) {
430
+ if (streaming && !streamEstablished) {
431
+ endpoint.postMessage({ kind: "sync.fetch.cancel", requestId });
432
+ finishSyncStream(requestId);
433
+ }
434
+ throw error;
435
+ }
436
+ finally {
437
+ pendingSyncFetch.delete(requestId);
438
+ if (!streamEstablished) {
439
+ init?.signal?.removeEventListener("abort", abort);
440
+ }
441
+ }
442
+ }
443
+ async function handleObserveNext(observeId, telemetryParent) {
131
444
  const events = observations.get(observeId);
132
445
  if (!events)
133
446
  return undefined;
447
+ events.setTelemetryParent(telemetryParent);
134
448
  return events.next();
135
449
  }
136
- function requiredLix() {
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
+ }
459
+ function requiredLix(sessionId) {
460
+ const lix = sessions.get(sessionId);
137
461
  if (!lix)
138
- throw workerStateError("Lix worker is closed");
462
+ throw workerStateError("Lix session is closed");
139
463
  return lix;
140
464
  }
141
465
  function requiredTransaction(transactionId) {
@@ -148,9 +472,43 @@ export function startWorkerHost(endpoint) {
148
472
  return transaction;
149
473
  }
150
474
  }
475
+ export function responseFromSyncFetch(resolved) {
476
+ if (resolved.streaming) {
477
+ throw new TypeError("Streaming sync responses require the worker stream bridge");
478
+ }
479
+ const body = resolved.status === 204 ||
480
+ resolved.status === 205 ||
481
+ resolved.status === 304
482
+ ? null
483
+ : resolved.body.slice().buffer;
484
+ return new Response(body, {
485
+ status: resolved.status,
486
+ statusText: resolved.statusText,
487
+ headers: resolved.headers,
488
+ });
489
+ }
151
490
  function workerStateError(message) {
152
491
  const error = new Error(message);
153
492
  error.name = "LixError";
154
493
  error.code = "LIX_ERROR_CLOSED";
155
494
  return error;
156
495
  }
496
+ function headerEntries(headers) {
497
+ const entries = [];
498
+ new Headers(headers).forEach((value, name) => entries.push([name, value]));
499
+ return entries;
500
+ }
501
+ function serializableBody(body) {
502
+ if (body === undefined || body === null)
503
+ return undefined;
504
+ if (typeof body === "string")
505
+ return body;
506
+ if (body instanceof Uint8Array)
507
+ return body;
508
+ if (body instanceof ArrayBuffer)
509
+ return new Uint8Array(body);
510
+ if (ArrayBuffer.isView(body)) {
511
+ return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
512
+ }
513
+ throw new TypeError("Browser sync fetch body is not structured-cloneable");
514
+ }