@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.
- package/README.md +54 -10
- package/dist/binding-types.d.ts +37 -9
- package/dist/binding.browser.d.ts +2 -2
- package/dist/binding.browser.js +20 -13
- package/dist/binding.node-wasm.d.ts +2 -2
- package/dist/binding.node-wasm.js +9 -11
- package/dist/binding.node.d.ts +3 -3
- package/dist/binding.node.js +70 -13
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/lix.d.ts +27 -5
- package/dist/lix.js +125 -6
- package/dist/open-lix.d.ts +4 -5
- package/dist/open-lix.js +174 -33
- package/dist/remote/client.d.ts +1 -2
- package/dist/remote/client.js +40 -1151
- package/dist/remote/server-protocol.d.ts +5 -6
- package/dist/remote/server-protocol.js +40 -6
- package/dist/result.d.ts +6 -13
- package/dist/result.js +9 -35
- package/dist/snapshot-restore.d.ts +6 -0
- package/dist/snapshot-restore.js +101 -0
- package/dist/storage-adapter.d.ts +175 -19
- package/dist/storage-adapter.js +21 -0
- package/dist/types.d.ts +104 -34
- package/dist/value.d.ts +1 -0
- package/dist/value.js +8 -0
- package/dist/wasm/lix_js_sdk.d.ts +121 -24
- package/dist/wasm/lix_js_sdk.js +606 -78
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +48 -6
- package/dist/wasm-init.d.ts +2 -0
- package/dist/wasm-init.js +23 -0
- package/dist/worker/client.d.ts +31 -5
- package/dist/worker/client.js +541 -45
- package/dist/worker/factory.browser.d.ts +2 -2
- package/dist/worker/factory.node.d.ts +2 -2
- package/dist/worker/factory.node.js +3 -10
- package/dist/worker/host.d.ts +4 -2
- package/dist/worker/host.js +401 -43
- package/dist/worker/protocol.d.ts +107 -3
- package/package.json +6 -10
- package/dist/indexeddb-backend.d.ts +0 -19
- package/dist/indexeddb-backend.js +0 -121
- package/dist/remote/sse.d.ts +0 -12
- package/dist/remote/sse.js +0 -87
- package/dist/workerd.d.ts +0 -25
- 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 {
|
|
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
|
-
|
|
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
|
};
|
package/dist/worker/host.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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;
|
package/dist/worker/host.js
CHANGED
|
@@ -1,24 +1,77 @@
|
|
|
1
1
|
import { openLixBinding } from "#binding";
|
|
2
|
-
import { serializeWorkerError, } from "./protocol.js";
|
|
3
|
-
export function startWorkerHost(endpoint) {
|
|
4
|
-
|
|
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 === "
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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 (
|
|
167
|
+
if (sessions.size > 0)
|
|
59
168
|
throw workerStateError("Lix worker is already open");
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 "
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|