@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.
- package/README.md +187 -22
- package/dist/binding-types.d.ts +20 -6
- package/dist/binding.browser.d.ts +2 -0
- package/dist/binding.browser.js +14 -4
- package/dist/binding.node-wasm.d.ts +1 -1
- package/dist/binding.node-wasm.js +5 -3
- package/dist/binding.node.d.ts +2 -0
- package/dist/binding.node.js +34 -8
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
- package/dist/compatibility.d.ts +6 -0
- package/dist/compatibility.js +6 -0
- package/dist/component-host/dispatch.d.ts +12 -0
- package/dist/component-host/dispatch.js +364 -0
- package/dist/component-host/index.d.ts +15 -0
- package/dist/component-host/index.js +84 -0
- package/dist/component-host/instrument.d.ts +6 -0
- package/dist/component-host/instrument.js +260 -0
- package/dist/conversion-provider.d.ts +4 -0
- package/dist/conversion-provider.js +20 -0
- package/dist/hosted-lix.js +1 -1
- package/dist/http-transport.d.ts +25 -0
- package/dist/http-transport.js +162 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/lix.d.ts +20 -10
- package/dist/lix.js +68 -23
- package/dist/migration-binding.browser.d.ts +33 -0
- package/dist/migration-binding.browser.js +46 -0
- package/dist/migration-binding.node.d.ts +6 -0
- package/dist/migration-binding.node.js +5 -0
- package/dist/migration-wasm/lix_js_sdk.d.ts +271 -0
- package/dist/migration-wasm/lix_js_sdk.js +1852 -0
- package/dist/migration-wasm/lix_js_sdk_bg.wasm +4 -0
- package/dist/migration-wasm/lix_js_sdk_bg.wasm.d.ts +101 -0
- package/dist/migration.d.ts +23 -0
- package/dist/migration.js +55 -0
- package/dist/open-lix.js +29 -18
- package/dist/open-progress.d.ts +10 -0
- package/dist/open-progress.js +59 -0
- package/dist/remote/client.d.ts +2 -1
- package/dist/remote/client.js +11 -1
- package/dist/result.d.ts +4 -3
- package/dist/result.js +3 -4
- package/dist/storage-adapter.d.ts +7 -5
- package/dist/storage-ownership.d.ts +5 -0
- package/dist/storage-ownership.js +4 -0
- package/dist/types.d.ts +132 -22
- package/dist/wasm/lix_js_sdk.d.ts +57 -16
- package/dist/wasm/lix_js_sdk.js +268 -60
- package/dist/wasm/lix_js_sdk_bg.wasm +2 -2
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +23 -9
- package/dist/worker/client.d.ts +12 -3
- package/dist/worker/client.js +98 -81
- package/dist/worker/durable-local-admission.d.ts +20 -0
- package/dist/worker/durable-local-admission.js +87 -0
- package/dist/worker/entry.shared.browser.d.ts +1 -0
- package/dist/worker/entry.shared.browser.js +211 -0
- package/dist/worker/factory.browser.d.ts +2 -0
- package/dist/worker/factory.browser.js +65 -1
- package/dist/worker/factory.node.d.ts +1 -0
- package/dist/worker/factory.node.js +3 -0
- package/dist/worker/host.d.ts +4 -2
- package/dist/worker/host.js +92 -32
- package/dist/worker/protocol.d.ts +33 -8
- package/dist/worker/protocol.js +25 -7
- package/dist/worker/shared-admission.d.ts +26 -0
- package/dist/worker/shared-admission.js +116 -0
- package/dist/worker/shared-engine.d.ts +34 -0
- package/dist/worker/shared-engine.js +194 -0
- package/package.json +21 -13
- package/dist/remote/server-protocol.d.ts +0 -185
- 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
|
|
15
|
-
export const
|
|
16
|
-
export const
|
|
17
|
-
export const
|
|
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
|
|
76
|
-
export const
|
|
77
|
-
export const
|
|
78
|
-
export const
|
|
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;
|
package/dist/worker/client.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/worker/client.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
530
|
-
if (!
|
|
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
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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.
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
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 {};
|