@lix-js/sdk 0.8.4 → 0.10.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 +105 -22
- package/dist/binding-types.d.ts +21 -2
- package/dist/binding.browser.d.ts +2 -2
- package/dist/binding.browser.js +3 -3
- package/dist/binding.node.d.ts +2 -2
- package/dist/binding.node.js +18 -4
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
- package/dist/bundled-plugins.js +2 -2
- package/dist/client-state.d.ts +53 -0
- package/dist/client-state.js +318 -0
- package/dist/index.d.ts +2 -1
- package/dist/lix.d.ts +47 -0
- package/dist/lix.js +378 -0
- package/dist/local-storage-adapter.d.ts +26 -0
- package/dist/local-storage-adapter.js +117 -0
- package/dist/open-lix.d.ts +3 -34
- package/dist/open-lix.js +99 -170
- package/dist/remote/client.d.ts +9 -0
- package/dist/remote/client.js +1150 -0
- package/dist/remote/protocol.d.ts +178 -0
- package/dist/remote/protocol.js +367 -0
- package/dist/remote/sse.d.ts +12 -0
- package/dist/remote/sse.js +87 -0
- package/dist/snapshot-persistence.d.ts +7 -0
- package/dist/snapshot-persistence.js +26 -0
- package/dist/types.d.ts +68 -1
- package/dist/wasm/lix_js_sdk.d.ts +22 -4
- package/dist/wasm/lix_js_sdk.js +96 -17
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +11 -2
- package/dist/worker/client.d.ts +23 -4
- package/dist/worker/client.js +329 -11
- package/dist/worker/factory.browser.d.ts +2 -0
- package/dist/worker/factory.browser.js +2 -0
- package/dist/worker/factory.node.d.ts +2 -0
- package/dist/worker/factory.node.js +5 -0
- package/dist/worker/host.js +36 -2
- package/dist/worker/protocol.d.ts +32 -2
- package/dist/workerd.js +1 -3
- package/package.json +19 -16
- package/dist/bundled-plugins/plugin_md_v2.lixplugin +0 -0
- package/dist/jco/js-component-bindgen-component.core.wasm +0 -0
- package/dist/jco/js-component-bindgen-component.core2.wasm +0 -0
- package/dist/jco/js-component-bindgen-component.js +0 -13662
- package/dist/jco-transpile.browser.d.ts +0 -14
- package/dist/jco-transpile.browser.js +0 -22
- package/dist/plugin-runtime.d.ts +0 -45
- package/dist/plugin-runtime.js +0 -124
package/dist/worker/client.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { createWorkerConnection } from "#worker-factory";
|
|
1
|
+
import { createWorkerConnection, openDirectLixBinding } from "#worker-factory";
|
|
2
|
+
import { snapshotPersistenceAfterCommitError } from "../snapshot-persistence.js";
|
|
2
3
|
import { deserializeWorkerError, } from "./protocol.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
const MAX_IDLE_WORKERS = 1;
|
|
5
|
+
// The common serial reopen path retains one worker so its prepared plugin cache
|
|
6
|
+
// survives close(). Concurrent opens still receive isolated workers.
|
|
7
|
+
const idleWorkers = [];
|
|
8
|
+
export async function openLixWorker(storage, onDisposed, telemetry) {
|
|
9
|
+
let client = idleWorkers.pop();
|
|
10
|
+
while (client?.isDisposed)
|
|
11
|
+
client = idleWorkers.pop();
|
|
12
|
+
client ??= new LixWorkerClient();
|
|
13
|
+
client.beginLease(onDisposed, telemetry);
|
|
5
14
|
try {
|
|
6
|
-
await client.request({
|
|
15
|
+
await client.request({
|
|
16
|
+
kind: "open",
|
|
17
|
+
storage,
|
|
18
|
+
telemetryEnabled: telemetry !== undefined,
|
|
19
|
+
});
|
|
7
20
|
return client;
|
|
8
21
|
}
|
|
9
22
|
catch (error) {
|
|
@@ -11,21 +24,317 @@ export async function openLixWorker(storage, onDisposed) {
|
|
|
11
24
|
throw error;
|
|
12
25
|
}
|
|
13
26
|
}
|
|
27
|
+
/** Opens the local worker transport behind the semantic Lix binding. */
|
|
28
|
+
export async function openLixWorkerBinding(storage, onDisposed, telemetry) {
|
|
29
|
+
if (openDirectLixBinding) {
|
|
30
|
+
const telemetryDispatch = telemetry
|
|
31
|
+
? (span) => {
|
|
32
|
+
try {
|
|
33
|
+
telemetry.onSpan(span);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Telemetry is observational and must not fail engine commands.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
: undefined;
|
|
40
|
+
const binding = await openDirectLixBinding(storage, telemetryDispatch);
|
|
41
|
+
if (!onDisposed)
|
|
42
|
+
return binding;
|
|
43
|
+
let disposed = false;
|
|
44
|
+
return new Proxy(binding, {
|
|
45
|
+
get(target, property, receiver) {
|
|
46
|
+
if (property === "close") {
|
|
47
|
+
return async () => {
|
|
48
|
+
try {
|
|
49
|
+
await target.close();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (!disposed) {
|
|
53
|
+
disposed = true;
|
|
54
|
+
onDisposed();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const value = Reflect.get(target, property, receiver);
|
|
60
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const client = await openLixWorker(storage, onDisposed, telemetry);
|
|
65
|
+
return workerBinding(client);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Opens a browser memory binding from an opaque snapshot and persists a fresh
|
|
69
|
+
* snapshot after every successful mutation. This is an internal composition
|
|
70
|
+
* seam for public storage adapters; it does not route workspace operations.
|
|
71
|
+
*/
|
|
72
|
+
export async function openPersistentLixWorkerBinding(options) {
|
|
73
|
+
if (!options || typeof options !== "object") {
|
|
74
|
+
throw new TypeError("openPersistentLixWorkerBinding() options must be an object");
|
|
75
|
+
}
|
|
76
|
+
if (!options.storage ||
|
|
77
|
+
typeof options.storage.load !== "function" ||
|
|
78
|
+
typeof options.storage.save !== "function") {
|
|
79
|
+
throw new TypeError("openPersistentLixWorkerBinding() storage must implement load() and save()");
|
|
80
|
+
}
|
|
81
|
+
if (typeof options.namespace !== "string" || options.namespace.length === 0) {
|
|
82
|
+
throw new TypeError("openPersistentLixWorkerBinding() namespace must be a non-empty string");
|
|
83
|
+
}
|
|
84
|
+
const snapshot = await options.storage.load(options.namespace);
|
|
85
|
+
if (snapshot !== undefined && !(snapshot instanceof Uint8Array)) {
|
|
86
|
+
throw new TypeError("Snapshot storage load() must return a Uint8Array");
|
|
87
|
+
}
|
|
88
|
+
const binding = await openLixWorkerBinding({
|
|
89
|
+
kind: "memory",
|
|
90
|
+
...(snapshot === undefined ? {} : { snapshot }),
|
|
91
|
+
}, undefined, options.telemetry);
|
|
92
|
+
const persistent = persistentSnapshotBinding(binding, options.storage, options.namespace);
|
|
93
|
+
if (snapshot === undefined) {
|
|
94
|
+
try {
|
|
95
|
+
await persistent.persist();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
await binding.close().catch(() => undefined);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return persistent.binding;
|
|
103
|
+
}
|
|
104
|
+
function workerBinding(client) {
|
|
105
|
+
let closed = false;
|
|
106
|
+
const request = (operation) => {
|
|
107
|
+
if (closed)
|
|
108
|
+
return Promise.reject(workerClosedError());
|
|
109
|
+
return client.request(operation);
|
|
110
|
+
};
|
|
111
|
+
const notify = (notification) => {
|
|
112
|
+
if (!closed)
|
|
113
|
+
client.notify(notification);
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
execute: (sql, params, options) => request({ kind: "execute", sql, params, options }),
|
|
117
|
+
executeBatch: (statements, options) => request({ kind: "executeBatch", statements, options }),
|
|
118
|
+
observe: async (sql, params) => {
|
|
119
|
+
const observeId = await request({
|
|
120
|
+
kind: "observe",
|
|
121
|
+
sql,
|
|
122
|
+
params,
|
|
123
|
+
});
|
|
124
|
+
return workerObserveBinding(request, notify, observeId);
|
|
125
|
+
},
|
|
126
|
+
beginTransaction: async () => {
|
|
127
|
+
const transactionId = await request({
|
|
128
|
+
kind: "beginTransaction",
|
|
129
|
+
});
|
|
130
|
+
return workerTransactionBinding(request, transactionId);
|
|
131
|
+
},
|
|
132
|
+
activeBranchId: () => request({ kind: "activeBranchId" }),
|
|
133
|
+
activeAccountId: () => request({ kind: "activeAccountId" }),
|
|
134
|
+
clientStateEntries: () => request({ kind: "clientState.entries" }),
|
|
135
|
+
clientStateGet: (key) => request({ kind: "clientState.get", key }),
|
|
136
|
+
clientStateSet: (key, value) => request({ kind: "clientState.set", key, value }),
|
|
137
|
+
clientStateDelete: (key) => request({ kind: "clientState.delete", key }),
|
|
138
|
+
createBranch: (options) => request({ kind: "createBranch", options }),
|
|
139
|
+
createCheckpoint: () => request({ kind: "createCheckpoint" }),
|
|
140
|
+
undo: () => request({ kind: "undo" }),
|
|
141
|
+
redo: () => request({ kind: "redo" }),
|
|
142
|
+
switchBranch: (options) => request({ kind: "switchBranch", options }),
|
|
143
|
+
importFilesystemPaths: (paths) => request({ kind: "importFilesystemPaths", paths }),
|
|
144
|
+
mergeBranchPreview: (options) => request({ kind: "mergeBranchPreview", options }),
|
|
145
|
+
mergeBranch: (options) => request({ kind: "mergeBranch", options }),
|
|
146
|
+
syncDiskToLix: () => request({ kind: "syncDiskToLix" }),
|
|
147
|
+
exportSnapshot: () => request({ kind: "exportSnapshot" }),
|
|
148
|
+
close: async () => {
|
|
149
|
+
if (closed)
|
|
150
|
+
return;
|
|
151
|
+
await request({ kind: "close" });
|
|
152
|
+
closed = true;
|
|
153
|
+
await releaseWorker(client);
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function persistentSnapshotBinding(binding, storage, namespace) {
|
|
158
|
+
let persistenceTail = Promise.resolve();
|
|
159
|
+
let closePromise;
|
|
160
|
+
let bindingClosed = false;
|
|
161
|
+
const persist = () => {
|
|
162
|
+
const operation = persistenceTail.then(async () => {
|
|
163
|
+
const exportSnapshot = binding.exportSnapshot;
|
|
164
|
+
if (!exportSnapshot) {
|
|
165
|
+
throw new Error("The open Lix binding does not support snapshot export");
|
|
166
|
+
}
|
|
167
|
+
const snapshot = await exportSnapshot.call(binding);
|
|
168
|
+
await storage.save(namespace, snapshot);
|
|
169
|
+
});
|
|
170
|
+
persistenceTail = operation.catch(() => undefined);
|
|
171
|
+
return operation;
|
|
172
|
+
};
|
|
173
|
+
const afterMutation = async (operation) => {
|
|
174
|
+
const result = await operation;
|
|
175
|
+
try {
|
|
176
|
+
await persist();
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// The Rust transaction is already committed. Preserve that fact so
|
|
180
|
+
// synchronous facades can reflect the live session value while still
|
|
181
|
+
// reporting that durability failed.
|
|
182
|
+
throw snapshotPersistenceAfterCommitError(error);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
};
|
|
186
|
+
const persistentBinding = {
|
|
187
|
+
execute: (sql, params, executeOptions) => afterMutation(binding.execute(sql, params, executeOptions)),
|
|
188
|
+
executeBatch: (statements, batchOptions) => afterMutation(binding.executeBatch(statements, batchOptions)),
|
|
189
|
+
observe: (sql, params) => binding.observe(sql, params),
|
|
190
|
+
beginTransaction: async () => {
|
|
191
|
+
const transaction = await binding.beginTransaction();
|
|
192
|
+
return {
|
|
193
|
+
execute: (sql, params, executeOptions) => transaction.execute(sql, params, executeOptions),
|
|
194
|
+
commit: () => afterMutation(transaction.commit()),
|
|
195
|
+
rollback: () => transaction.rollback(),
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
activeBranchId: () => binding.activeBranchId(),
|
|
199
|
+
activeAccountId: () => binding.activeAccountId(),
|
|
200
|
+
clientStateEntries: () => {
|
|
201
|
+
const method = binding.clientStateEntries;
|
|
202
|
+
if (!method)
|
|
203
|
+
return Promise.reject(clientStateUnsupportedError());
|
|
204
|
+
return method.call(binding);
|
|
205
|
+
},
|
|
206
|
+
clientStateGet: (key) => {
|
|
207
|
+
const method = binding.clientStateGet;
|
|
208
|
+
if (!method)
|
|
209
|
+
return Promise.reject(clientStateUnsupportedError());
|
|
210
|
+
return method.call(binding, key);
|
|
211
|
+
},
|
|
212
|
+
clientStateSet: (key, value) => {
|
|
213
|
+
const method = binding.clientStateSet;
|
|
214
|
+
if (!method)
|
|
215
|
+
return Promise.reject(clientStateUnsupportedError());
|
|
216
|
+
return afterMutation(method.call(binding, key, value));
|
|
217
|
+
},
|
|
218
|
+
clientStateDelete: (key) => {
|
|
219
|
+
const method = binding.clientStateDelete;
|
|
220
|
+
if (!method)
|
|
221
|
+
return Promise.reject(clientStateUnsupportedError());
|
|
222
|
+
return afterMutation(method.call(binding, key));
|
|
223
|
+
},
|
|
224
|
+
createBranch: (branchOptions) => afterMutation(binding.createBranch(branchOptions)),
|
|
225
|
+
createCheckpoint: () => afterMutation(binding.createCheckpoint()),
|
|
226
|
+
undo: () => afterMutation(binding.undo()),
|
|
227
|
+
redo: () => afterMutation(binding.redo()),
|
|
228
|
+
switchBranch: (branchOptions) => afterMutation(binding.switchBranch(branchOptions)),
|
|
229
|
+
importFilesystemPaths: (paths) => afterMutation(binding.importFilesystemPaths(paths)),
|
|
230
|
+
mergeBranchPreview: (branchOptions) => binding.mergeBranchPreview(branchOptions),
|
|
231
|
+
mergeBranch: (branchOptions) => afterMutation(binding.mergeBranch(branchOptions)),
|
|
232
|
+
syncDiskToLix: () => afterMutation(binding.syncDiskToLix()),
|
|
233
|
+
exportSnapshot: () => {
|
|
234
|
+
const exportSnapshot = binding.exportSnapshot;
|
|
235
|
+
if (!exportSnapshot) {
|
|
236
|
+
return Promise.reject(new Error("The open Lix binding does not support snapshot export"));
|
|
237
|
+
}
|
|
238
|
+
return exportSnapshot.call(binding);
|
|
239
|
+
},
|
|
240
|
+
close: () => {
|
|
241
|
+
if (closePromise)
|
|
242
|
+
return closePromise;
|
|
243
|
+
closePromise = (async () => {
|
|
244
|
+
let persistenceError;
|
|
245
|
+
try {
|
|
246
|
+
await persist();
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
persistenceError = error;
|
|
250
|
+
}
|
|
251
|
+
await binding.close();
|
|
252
|
+
bindingClosed = true;
|
|
253
|
+
if (persistenceError !== undefined)
|
|
254
|
+
throw persistenceError;
|
|
255
|
+
})();
|
|
256
|
+
void closePromise.catch((error) => {
|
|
257
|
+
if (!bindingClosed && isActiveTransactionCloseError(error)) {
|
|
258
|
+
closePromise = undefined;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
return closePromise;
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
return { binding: persistentBinding, persist };
|
|
265
|
+
}
|
|
266
|
+
function isActiveTransactionCloseError(error) {
|
|
267
|
+
return (typeof error === "object" &&
|
|
268
|
+
error !== null &&
|
|
269
|
+
"code" in error &&
|
|
270
|
+
error.code === "LIX_INVALID_TRANSACTION_STATE");
|
|
271
|
+
}
|
|
272
|
+
function clientStateUnsupportedError() {
|
|
273
|
+
return new Error("The open Lix binding does not support typed client state");
|
|
274
|
+
}
|
|
275
|
+
function workerTransactionBinding(request, transactionId) {
|
|
276
|
+
return {
|
|
277
|
+
execute: (sql, params, options) => request({
|
|
278
|
+
kind: "transaction.execute",
|
|
279
|
+
transactionId,
|
|
280
|
+
sql,
|
|
281
|
+
params,
|
|
282
|
+
options,
|
|
283
|
+
}),
|
|
284
|
+
commit: () => request({ kind: "transaction.commit", transactionId }),
|
|
285
|
+
rollback: () => request({ kind: "transaction.rollback", transactionId }),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function workerObserveBinding(request, notify, observeId) {
|
|
289
|
+
return {
|
|
290
|
+
next: () => request({ kind: "observe.next", observeId }),
|
|
291
|
+
close: () => notify({ kind: "observe.close", observeId }),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function releaseWorker(client) {
|
|
295
|
+
client.endLease();
|
|
296
|
+
if (!client.isDisposed && idleWorkers.length < MAX_IDLE_WORKERS) {
|
|
297
|
+
idleWorkers.push(client);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
await client.terminate();
|
|
301
|
+
}
|
|
14
302
|
export class LixWorkerClient {
|
|
15
|
-
onDisposed;
|
|
16
303
|
connection;
|
|
17
304
|
nextRequestId = 1;
|
|
18
305
|
pending = new Map();
|
|
19
306
|
disposed = false;
|
|
20
|
-
|
|
21
|
-
|
|
307
|
+
leased = false;
|
|
308
|
+
onDisposed;
|
|
309
|
+
telemetry;
|
|
310
|
+
constructor(connection = createWorkerConnection()) {
|
|
22
311
|
this.connection = connection;
|
|
23
312
|
connection.onMessage((message) => this.handleMessage(message));
|
|
24
313
|
connection.onFatal((error) => this.handleFatal(error));
|
|
25
314
|
}
|
|
315
|
+
get isDisposed() {
|
|
316
|
+
return this.disposed;
|
|
317
|
+
}
|
|
318
|
+
beginLease(onDisposed, telemetry) {
|
|
319
|
+
if (this.disposed || this.leased)
|
|
320
|
+
throw workerClosedError();
|
|
321
|
+
this.leased = true;
|
|
322
|
+
this.onDisposed = onDisposed;
|
|
323
|
+
this.telemetry = telemetry;
|
|
324
|
+
}
|
|
325
|
+
endLease() {
|
|
326
|
+
if (!this.leased)
|
|
327
|
+
return;
|
|
328
|
+
this.leased = false;
|
|
329
|
+
const onDisposed = this.onDisposed;
|
|
330
|
+
this.onDisposed = undefined;
|
|
331
|
+
this.telemetry = undefined;
|
|
332
|
+
onDisposed?.();
|
|
333
|
+
}
|
|
26
334
|
request(operation) {
|
|
27
|
-
if (this.disposed)
|
|
335
|
+
if (this.disposed || !this.leased) {
|
|
28
336
|
return Promise.reject(workerClosedError());
|
|
337
|
+
}
|
|
29
338
|
const id = this.nextRequestId++;
|
|
30
339
|
if (this.pending.size === 0)
|
|
31
340
|
this.connection.ref();
|
|
@@ -46,7 +355,7 @@ export class LixWorkerClient {
|
|
|
46
355
|
});
|
|
47
356
|
}
|
|
48
357
|
notify(notification) {
|
|
49
|
-
if (this.disposed)
|
|
358
|
+
if (this.disposed || !this.leased)
|
|
50
359
|
return;
|
|
51
360
|
try {
|
|
52
361
|
this.connection.postMessage(notification);
|
|
@@ -64,10 +373,19 @@ export class LixWorkerClient {
|
|
|
64
373
|
await this.connection.terminate();
|
|
65
374
|
}
|
|
66
375
|
finally {
|
|
67
|
-
this.
|
|
376
|
+
this.endLease();
|
|
68
377
|
}
|
|
69
378
|
}
|
|
70
379
|
handleMessage(message) {
|
|
380
|
+
if ("kind" in message) {
|
|
381
|
+
try {
|
|
382
|
+
this.telemetry?.onSpan(message.span);
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
// Telemetry callbacks are isolated from Lix operation results.
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
71
389
|
const pending = this.pending.get(message.id);
|
|
72
390
|
if (!pending)
|
|
73
391
|
return;
|
|
@@ -87,7 +405,7 @@ export class LixWorkerClient {
|
|
|
87
405
|
fatal.name = "LixError";
|
|
88
406
|
fatal.code ??= "LIX_WORKER_TERMINATED";
|
|
89
407
|
this.rejectPending(fatal);
|
|
90
|
-
this.
|
|
408
|
+
this.endLease();
|
|
91
409
|
}
|
|
92
410
|
rejectPending(error) {
|
|
93
411
|
for (const pending of this.pending.values())
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { LixBinding, LixStorageConfig, TelemetryDispatch } from "../binding-types.js";
|
|
1
2
|
import type { WorkerConnection } from "./protocol.js";
|
|
3
|
+
export declare const openDirectLixBinding: undefined | ((storage: LixStorageConfig, telemetry?: TelemetryDispatch) => Promise<LixBinding>);
|
|
2
4
|
export declare function createWorkerConnection(): WorkerConnection;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference lib="webworker" />
|
|
2
|
+
// Browser/Wasm execution stays off the main thread.
|
|
3
|
+
export const openDirectLixBinding = undefined;
|
|
2
4
|
export function createWorkerConnection() {
|
|
3
5
|
const worker = new Worker(new URL("./entry.browser.js", import.meta.url), {
|
|
4
6
|
type: "module",
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { LixBinding, LixStorageConfig, TelemetryDispatch } from "../binding-types.js";
|
|
1
2
|
import type { WorkerConnection } from "./protocol.js";
|
|
2
3
|
export declare function createWorkerConnection(): WorkerConnection;
|
|
4
|
+
export declare const openDirectLixBinding: (storage: LixStorageConfig, telemetry?: TelemetryDispatch) => Promise<LixBinding>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { openLixBinding } from "../binding.node.js";
|
|
2
3
|
export function createWorkerConnection() {
|
|
3
4
|
const worker = new Worker(new URL("./entry.node.js", import.meta.url), {
|
|
4
5
|
name: "lix",
|
|
@@ -33,3 +34,7 @@ export function createWorkerConnection() {
|
|
|
33
34
|
},
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
/// Native Lix already owns a dedicated serialized engine actor. Routing it
|
|
38
|
+
/// through a second JavaScript worker adds two message-port hops per query
|
|
39
|
+
/// without adding isolation or concurrency.
|
|
40
|
+
export const openDirectLixBinding = (storage, telemetry) => openLixBinding(storage, telemetry);
|
package/dist/worker/host.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { openLixBinding } from "#binding";
|
|
2
|
-
import { createPluginRuntimeDispatch } from "../plugin-runtime.js";
|
|
3
2
|
import { serializeWorkerError, } from "./protocol.js";
|
|
4
3
|
export function startWorkerHost(endpoint) {
|
|
5
4
|
let lix;
|
|
@@ -58,10 +57,14 @@ export function startWorkerHost(endpoint) {
|
|
|
58
57
|
case "open":
|
|
59
58
|
if (lix)
|
|
60
59
|
throw workerStateError("Lix worker is already open");
|
|
61
|
-
lix = await openLixBinding(operation.storage,
|
|
60
|
+
lix = await openLixBinding(operation.storage, operation.telemetryEnabled
|
|
61
|
+
? (span) => endpoint.postMessage({ kind: "telemetry", span })
|
|
62
|
+
: undefined);
|
|
62
63
|
return undefined;
|
|
63
64
|
case "execute":
|
|
64
65
|
return requiredLix().execute(operation.sql, operation.params, operation.options);
|
|
66
|
+
case "executeBatch":
|
|
67
|
+
return requiredLix().executeBatch(operation.statements, operation.options);
|
|
65
68
|
case "beginTransaction": {
|
|
66
69
|
const transaction = await requiredLix().beginTransaction();
|
|
67
70
|
const transactionId = nextTransactionId++;
|
|
@@ -84,8 +87,24 @@ export function startWorkerHost(endpoint) {
|
|
|
84
87
|
}
|
|
85
88
|
case "activeBranchId":
|
|
86
89
|
return requiredLix().activeBranchId();
|
|
90
|
+
case "activeAccountId":
|
|
91
|
+
return requiredLix().activeAccountId();
|
|
92
|
+
case "clientState.entries":
|
|
93
|
+
return requiredClientStateMethod("clientStateEntries")();
|
|
94
|
+
case "clientState.get":
|
|
95
|
+
return requiredClientStateMethod("clientStateGet")(operation.key);
|
|
96
|
+
case "clientState.set":
|
|
97
|
+
return requiredClientStateMethod("clientStateSet")(operation.key, operation.value);
|
|
98
|
+
case "clientState.delete":
|
|
99
|
+
return requiredClientStateMethod("clientStateDelete")(operation.key);
|
|
87
100
|
case "createBranch":
|
|
88
101
|
return requiredLix().createBranch(operation.options);
|
|
102
|
+
case "createCheckpoint":
|
|
103
|
+
return requiredLix().createCheckpoint();
|
|
104
|
+
case "undo":
|
|
105
|
+
return requiredLix().undo();
|
|
106
|
+
case "redo":
|
|
107
|
+
return requiredLix().redo();
|
|
89
108
|
case "switchBranch":
|
|
90
109
|
return requiredLix().switchBranch(operation.options);
|
|
91
110
|
case "mergeBranchPreview":
|
|
@@ -96,6 +115,14 @@ export function startWorkerHost(endpoint) {
|
|
|
96
115
|
return requiredLix().importFilesystemPaths(operation.paths);
|
|
97
116
|
case "syncDiskToLix":
|
|
98
117
|
return requiredLix().syncDiskToLix();
|
|
118
|
+
case "exportSnapshot": {
|
|
119
|
+
const lix = requiredLix();
|
|
120
|
+
const exportSnapshot = lix.exportSnapshot;
|
|
121
|
+
if (!exportSnapshot) {
|
|
122
|
+
throw workerStateError("The open Lix storage does not support snapshot export");
|
|
123
|
+
}
|
|
124
|
+
return exportSnapshot.call(lix);
|
|
125
|
+
}
|
|
99
126
|
case "observe": {
|
|
100
127
|
const events = await requiredLix().observe(operation.sql, operation.params);
|
|
101
128
|
const observeId = nextObserveId++;
|
|
@@ -127,6 +154,13 @@ export function startWorkerHost(endpoint) {
|
|
|
127
154
|
throw workerStateError("Lix worker is closed");
|
|
128
155
|
return lix;
|
|
129
156
|
}
|
|
157
|
+
function requiredClientStateMethod(key) {
|
|
158
|
+
const method = requiredLix()[key];
|
|
159
|
+
if (!method) {
|
|
160
|
+
throw workerStateError("The open Lix binding does not support typed client state");
|
|
161
|
+
}
|
|
162
|
+
return method.bind(requiredLix());
|
|
163
|
+
}
|
|
130
164
|
function requiredTransaction(transactionId) {
|
|
131
165
|
const transaction = transactions.get(transactionId);
|
|
132
166
|
if (!transaction) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BindingParam, LixStorageConfig } from "../binding-types.js";
|
|
2
|
-
import type { CreateBranchOptions, ExecuteOptions, MergeBranchOptions, SwitchBranchOptions } from "../types.js";
|
|
1
|
+
import type { BindingBatchStatement, BindingParam, LixStorageConfig } from "../binding-types.js";
|
|
2
|
+
import type { CreateBranchOptions, ExecuteOptions, JsonValue, LixBatchOptions, MergeBranchOptions, SwitchBranchOptions, LixTelemetrySpan } from "../types.js";
|
|
3
3
|
export type WorkerRequest = {
|
|
4
4
|
id: number;
|
|
5
5
|
operation: WorkerOperation;
|
|
@@ -7,11 +7,16 @@ export type WorkerRequest = {
|
|
|
7
7
|
export type WorkerOperation = {
|
|
8
8
|
kind: "open";
|
|
9
9
|
storage: LixStorageConfig;
|
|
10
|
+
telemetryEnabled: boolean;
|
|
10
11
|
} | {
|
|
11
12
|
kind: "execute";
|
|
12
13
|
sql: string;
|
|
13
14
|
params: BindingParam[];
|
|
14
15
|
options?: ExecuteOptions;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "executeBatch";
|
|
18
|
+
statements: BindingBatchStatement[];
|
|
19
|
+
options?: LixBatchOptions;
|
|
15
20
|
} | {
|
|
16
21
|
kind: "beginTransaction";
|
|
17
22
|
} | {
|
|
@@ -28,9 +33,29 @@ export type WorkerOperation = {
|
|
|
28
33
|
transactionId: number;
|
|
29
34
|
} | {
|
|
30
35
|
kind: "activeBranchId";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "activeAccountId";
|
|
38
|
+
} | {
|
|
39
|
+
kind: "clientState.entries";
|
|
40
|
+
} | {
|
|
41
|
+
kind: "clientState.get";
|
|
42
|
+
key: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "clientState.set";
|
|
45
|
+
key: string;
|
|
46
|
+
value: JsonValue;
|
|
47
|
+
} | {
|
|
48
|
+
kind: "clientState.delete";
|
|
49
|
+
key: string;
|
|
31
50
|
} | {
|
|
32
51
|
kind: "createBranch";
|
|
33
52
|
options: CreateBranchOptions;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "createCheckpoint";
|
|
55
|
+
} | {
|
|
56
|
+
kind: "undo";
|
|
57
|
+
} | {
|
|
58
|
+
kind: "redo";
|
|
34
59
|
} | {
|
|
35
60
|
kind: "switchBranch";
|
|
36
61
|
options: SwitchBranchOptions;
|
|
@@ -45,6 +70,8 @@ export type WorkerOperation = {
|
|
|
45
70
|
paths: string[];
|
|
46
71
|
} | {
|
|
47
72
|
kind: "syncDiskToLix";
|
|
73
|
+
} | {
|
|
74
|
+
kind: "exportSnapshot";
|
|
48
75
|
} | {
|
|
49
76
|
kind: "observe";
|
|
50
77
|
sql: string;
|
|
@@ -91,6 +118,9 @@ export type WorkerResponse = {
|
|
|
91
118
|
id: number;
|
|
92
119
|
ok: false;
|
|
93
120
|
error: SerializedWorkerError;
|
|
121
|
+
} | {
|
|
122
|
+
kind: "telemetry";
|
|
123
|
+
span: LixTelemetrySpan;
|
|
94
124
|
};
|
|
95
125
|
export declare function serializeWorkerError(error: unknown): SerializedWorkerError;
|
|
96
126
|
export declare function deserializeWorkerError(error: SerializedWorkerError): Error;
|
package/dist/workerd.js
CHANGED
|
@@ -31,7 +31,5 @@ export async function openMemoryLix(options = {}) {
|
|
|
31
31
|
throw new TypeError("openMemoryLix() snapshot must be a Uint8Array");
|
|
32
32
|
}
|
|
33
33
|
initializeWasm();
|
|
34
|
-
return openMemoryFromSnapshot(
|
|
35
|
-
throw new Error("Lix plugin execution is unavailable in Workerd");
|
|
36
|
-
}, options.snapshot);
|
|
34
|
+
return openMemoryFromSnapshot(undefined, options.snapshot);
|
|
37
35
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lix-js/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
"types": "./dist/workerd.d.ts",
|
|
17
17
|
"workerd": "./dist/workerd.js",
|
|
18
18
|
"default": "./dist/workerd.js"
|
|
19
|
+
},
|
|
20
|
+
"./remote-protocol": {
|
|
21
|
+
"types": "./dist/remote/protocol.d.ts",
|
|
22
|
+
"default": "./dist/remote/protocol.js"
|
|
23
|
+
},
|
|
24
|
+
"./local-storage-adapter": {
|
|
25
|
+
"types": "./dist/local-storage-adapter.d.ts",
|
|
26
|
+
"default": "./dist/local-storage-adapter.js"
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
29
|
"imports": {
|
|
@@ -23,10 +31,6 @@
|
|
|
23
31
|
"node": "./dist/binding.node.js",
|
|
24
32
|
"default": "./dist/binding.browser.js"
|
|
25
33
|
},
|
|
26
|
-
"#jco-transpile": {
|
|
27
|
-
"node": "@bytecodealliance/jco-transpile",
|
|
28
|
-
"default": "./dist/jco-transpile.browser.js"
|
|
29
|
-
},
|
|
30
34
|
"#worker-factory": {
|
|
31
35
|
"node": "./dist/worker/factory.node.js",
|
|
32
36
|
"default": "./dist/worker/factory.browser.js"
|
|
@@ -36,13 +40,13 @@
|
|
|
36
40
|
"dist"
|
|
37
41
|
],
|
|
38
42
|
"scripts": {
|
|
39
|
-
"build": "npm run clean && npm run build:native && npm run build:wasm && npm run build:ts && npm run build:
|
|
40
|
-
"build:browser": "npm run clean && npm run build:wasm && npm run build:ts && npm run build:
|
|
41
|
-
"build:jco-browser": "node ./scripts/build-jco-browser.js",
|
|
43
|
+
"build": "npm run clean && npm run build:native && npm run build:wasm && npm run build:ts && npm run build:plugins",
|
|
44
|
+
"build:browser": "npm run clean && npm run build:wasm && npm run build:ts && npm run build:plugins",
|
|
42
45
|
"build:native": "node ./scripts/build-native.js",
|
|
43
46
|
"build:wasm": "node ./scripts/build-wasm.js",
|
|
44
47
|
"build:wasm:dev": "LIX_WASM_PROFILE=dev node ./scripts/build-wasm.js",
|
|
45
48
|
"build:plugins": "node ./scripts/build-bundled-plugins.js",
|
|
49
|
+
"benchmark:plugin-reopen": "node ./scripts/benchmark-plugin-reopen.mjs",
|
|
46
50
|
"clean": "node ./scripts/clean.js",
|
|
47
51
|
"prepare:native-package": "node ./scripts/prepare-native-package.js",
|
|
48
52
|
"build:ts": "tsc -p tsconfig.json",
|
|
@@ -53,21 +57,20 @@
|
|
|
53
57
|
"typecheck": "tsc -p tsconfig.test.json --noEmit"
|
|
54
58
|
},
|
|
55
59
|
"optionalDependencies": {
|
|
56
|
-
"@lix-js/sdk-darwin-arm64": "0.
|
|
57
|
-
"@lix-js/sdk-linux-arm64": "0.
|
|
58
|
-
"@lix-js/sdk-linux-x64": "0.
|
|
59
|
-
"@lix-js/sdk-win32-x64": "0.
|
|
60
|
+
"@lix-js/sdk-darwin-arm64": "0.10.0",
|
|
61
|
+
"@lix-js/sdk-linux-arm64": "0.10.0",
|
|
62
|
+
"@lix-js/sdk-linux-x64": "0.10.0",
|
|
63
|
+
"@lix-js/sdk-win32-x64": "0.10.0"
|
|
60
64
|
},
|
|
61
65
|
"devDependencies": {
|
|
62
|
-
"@vitest/browser-playwright": "4.
|
|
66
|
+
"@vitest/browser-playwright": "4.1.10",
|
|
63
67
|
"@types/node": "^24.10.2",
|
|
64
68
|
"playwright": "1.57.0",
|
|
65
69
|
"typescript": "^5.5.4",
|
|
66
|
-
"
|
|
70
|
+
"vite": "8.1.4",
|
|
71
|
+
"vitest": "4.1.10"
|
|
67
72
|
},
|
|
68
73
|
"dependencies": {
|
|
69
|
-
"@bytecodealliance/jco-transpile": "0.4.2",
|
|
70
|
-
"@bytecodealliance/preview2-shim": "0.19.0",
|
|
71
74
|
"fflate": "^0.8.3"
|
|
72
75
|
}
|
|
73
76
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|