@lix-js/sdk 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +162 -44
  2. package/dist/binding-types.d.ts +15 -5
  3. package/dist/binding.browser.d.ts +2 -0
  4. package/dist/binding.browser.js +14 -4
  5. package/dist/binding.node-wasm.d.ts +1 -1
  6. package/dist/binding.node-wasm.js +5 -3
  7. package/dist/binding.node.d.ts +2 -0
  8. package/dist/binding.node.js +34 -8
  9. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  10. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  11. package/dist/compatibility.d.ts +6 -0
  12. package/dist/compatibility.js +6 -0
  13. package/dist/component-host/dispatch.d.ts +12 -0
  14. package/dist/component-host/dispatch.js +364 -0
  15. package/dist/component-host/index.d.ts +15 -0
  16. package/dist/component-host/index.js +84 -0
  17. package/dist/component-host/instrument.d.ts +6 -0
  18. package/dist/component-host/instrument.js +260 -0
  19. package/dist/conversion-provider.d.ts +4 -0
  20. package/dist/conversion-provider.js +20 -0
  21. package/dist/hosted-lix.js +1 -1
  22. package/dist/http-transport.d.ts +25 -0
  23. package/dist/http-transport.js +162 -0
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +1 -0
  26. package/dist/lix.d.ts +14 -9
  27. package/dist/lix.js +44 -9
  28. package/dist/migration-binding.browser.d.ts +33 -0
  29. package/dist/migration-binding.browser.js +46 -0
  30. package/dist/migration-binding.node.d.ts +6 -0
  31. package/dist/migration-binding.node.js +5 -0
  32. package/dist/migration-wasm/lix_js_sdk.d.ts +271 -0
  33. package/dist/migration-wasm/lix_js_sdk.js +1852 -0
  34. package/dist/migration-wasm/lix_js_sdk_bg.wasm +4 -0
  35. package/dist/migration-wasm/lix_js_sdk_bg.wasm.d.ts +101 -0
  36. package/dist/migration.d.ts +23 -0
  37. package/dist/migration.js +55 -0
  38. package/dist/open-lix.js +29 -18
  39. package/dist/open-progress.d.ts +10 -0
  40. package/dist/open-progress.js +59 -0
  41. package/dist/remote/client.d.ts +2 -1
  42. package/dist/remote/client.js +11 -1
  43. package/dist/result.d.ts +4 -3
  44. package/dist/result.js +3 -4
  45. package/dist/storage-adapter.d.ts +7 -1
  46. package/dist/storage-ownership.d.ts +5 -0
  47. package/dist/storage-ownership.js +4 -0
  48. package/dist/types.d.ts +65 -31
  49. package/dist/wasm/lix_js_sdk.d.ts +45 -16
  50. package/dist/wasm/lix_js_sdk.js +214 -60
  51. package/dist/wasm/lix_js_sdk_bg.wasm +2 -2
  52. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +17 -9
  53. package/dist/worker/client.d.ts +12 -3
  54. package/dist/worker/client.js +95 -81
  55. package/dist/worker/durable-local-admission.d.ts +20 -0
  56. package/dist/worker/durable-local-admission.js +87 -0
  57. package/dist/worker/entry.shared.browser.d.ts +1 -0
  58. package/dist/worker/entry.shared.browser.js +211 -0
  59. package/dist/worker/factory.browser.d.ts +2 -0
  60. package/dist/worker/factory.browser.js +65 -1
  61. package/dist/worker/factory.node.d.ts +1 -0
  62. package/dist/worker/factory.node.js +3 -0
  63. package/dist/worker/host.d.ts +4 -2
  64. package/dist/worker/host.js +86 -32
  65. package/dist/worker/protocol.d.ts +25 -8
  66. package/dist/worker/protocol.js +25 -7
  67. package/dist/worker/shared-admission.d.ts +26 -0
  68. package/dist/worker/shared-admission.js +116 -0
  69. package/dist/worker/shared-engine.d.ts +34 -0
  70. package/dist/worker/shared-engine.js +194 -0
  71. package/package.json +21 -9
@@ -0,0 +1,260 @@
1
+ import binaryen from "binaryen";
2
+ export const TICK_MODULE = "lix:runtime/deadline";
3
+ /** Fields containing child expressions for the supported, non-GC core ISA. */
4
+ const fields = {
5
+ Block: ["children"],
6
+ If: ["condition", "ifTrue", "ifFalse"],
7
+ Loop: ["body"],
8
+ Break: ["condition", "value"],
9
+ Switch: ["condition", "value"],
10
+ Call: ["operands"],
11
+ CallIndirect: ["target", "operands"],
12
+ LocalGet: [],
13
+ LocalSet: ["value"],
14
+ GlobalGet: [],
15
+ GlobalSet: ["value"],
16
+ TableGet: ["index"],
17
+ TableSet: ["index", "value"],
18
+ TableSize: [],
19
+ TableGrow: ["value", "delta"],
20
+ Load: ["ptr"],
21
+ Store: ["ptr", "value"],
22
+ Const: [],
23
+ Unary: ["value"],
24
+ Binary: ["left", "right"],
25
+ Select: ["ifTrue", "ifFalse", "condition"],
26
+ Drop: ["value"],
27
+ Return: ["value"],
28
+ Nop: [],
29
+ Unreachable: [],
30
+ MemorySize: [],
31
+ MemoryGrow: ["delta"],
32
+ MemoryInit: ["dest", "offset", "size"],
33
+ DataDrop: [],
34
+ MemoryCopy: ["dest", "source", "size"],
35
+ MemoryFill: ["dest", "value", "size"],
36
+ RefNull: [],
37
+ RefFunc: [],
38
+ RefIs: ["value"],
39
+ RefAs: ["value"],
40
+ RefEq: ["left", "right"],
41
+ TupleMake: ["operands"],
42
+ TupleExtract: ["tuple"],
43
+ };
44
+ const childFields = new Map(Object.entries(fields).map(([name, keys]) => [
45
+ binaryen[`${name}Id`],
46
+ keys,
47
+ ]));
48
+ /** Add checks on every cycle (function/loop entry) and cap memory before compilation. */
49
+ export function instrumentCore(bytes, maxMemoryBytes) {
50
+ if (!Number.isSafeInteger(maxMemoryBytes) || maxMemoryBytes < 65536)
51
+ throw new Error("Component memory limit must be at least one Wasm page");
52
+ const module = binaryen.readBinary(capMemory(bytes, Math.floor(maxMemoryBytes / 65536)));
53
+ try {
54
+ module.setFeatures(binaryen.Features.All);
55
+ // Match the native host's table element limit as well as linear memory.
56
+ for (let index = 0; index < module.getNumTables(); index++) {
57
+ const table = module.getTableByIndex(index);
58
+ const info = binaryen.getTableInfo(table);
59
+ if (info.initial > 1_000_000)
60
+ throw new Error("Component table exceeds element limit");
61
+ binaryen._BinaryenTableSetMax(table, Math.min(info.max ?? 1_000_000, 1_000_000));
62
+ }
63
+ if (module.hasMemory() && Boolean(module.getMemoryInfo().module))
64
+ throw new Error("Imported component memories are unsupported");
65
+ if (module.getExport("__lix_runtime_memory"))
66
+ throw new Error("Reserved runtime memory export");
67
+ let serial = 0;
68
+ let tick = "lix_deadline";
69
+ while (module.getFunction(tick))
70
+ tick = `lix_deadline_${++serial}`;
71
+ module.addFunctionImport(tick, TICK_MODULE, "tick", binaryen.none, binaryen.none);
72
+ const check = () => module.call(tick, [], binaryen.none);
73
+ for (let index = 0; index < module.getNumFunctions(); index++) {
74
+ const func = module.getFunctionByIndex(index);
75
+ const body = binaryen.getFunctionInfo(func).body;
76
+ if (!body)
77
+ continue;
78
+ const pending = [body];
79
+ while (pending.length) {
80
+ const expression = pending.pop();
81
+ const expressionId = binaryen.getExpressionId(expression);
82
+ if (!childFields.has(expressionId))
83
+ throw new Error(`Unsupported component instruction ${expressionId}`);
84
+ if (childFields.get(expressionId).length === 0)
85
+ continue;
86
+ const info = binaryen.getExpressionInfo(expression);
87
+ const keys = childFields.get(info.id);
88
+ if (!keys)
89
+ throw new Error(`Unsupported component instruction ${info.id}`);
90
+ for (const key of keys) {
91
+ const value = info[key];
92
+ if (Array.isArray(value))
93
+ pending.push(...value.filter(Boolean));
94
+ else if (value)
95
+ pending.push(value);
96
+ }
97
+ if (info.id === binaryen.LoopId) {
98
+ binaryen._BinaryenLoopSetBody(expression, module.block(null, [check(), info.body], binaryen.getExpressionType(info.body)));
99
+ }
100
+ }
101
+ binaryen._BinaryenFunctionSetBody(func, module.block(null, [check(), body], binaryen.getExpressionType(body)));
102
+ }
103
+ if (!module.validate())
104
+ throw new Error("Invalid instrumented component module");
105
+ return exposeMemory(module.emitBinary(), module.hasMemory());
106
+ }
107
+ finally {
108
+ module.dispose();
109
+ }
110
+ }
111
+ /** Rewrite only the core memory section; all other sections stay byte-identical. */
112
+ function capMemory(bytes, pages) {
113
+ let offset = 8;
114
+ const read = () => {
115
+ let value = 0, shift = 0;
116
+ for (let count = 0; count < 5; count++) {
117
+ const byte = bytes[offset++];
118
+ if (byte === undefined)
119
+ throw new Error("Truncated Wasm integer");
120
+ value += (byte & 127) * 2 ** shift;
121
+ if (!(byte & 128))
122
+ return value;
123
+ shift += 7;
124
+ }
125
+ throw new Error("Invalid Wasm integer");
126
+ };
127
+ const leb = (value) => {
128
+ const result = [];
129
+ do {
130
+ const byte = value % 128;
131
+ value = Math.floor(value / 128);
132
+ result.push(byte | (value ? 128 : 0));
133
+ } while (value);
134
+ return result;
135
+ };
136
+ while (offset < bytes.length) {
137
+ const sectionStart = offset;
138
+ const id = bytes[offset++];
139
+ const length = read();
140
+ const end = offset + length;
141
+ if (end > bytes.length)
142
+ throw new Error("Truncated Wasm section");
143
+ if (id !== 5) {
144
+ offset = end;
145
+ continue;
146
+ }
147
+ const count = read();
148
+ if (count !== 1)
149
+ throw new Error("Multiple component memories are unsupported");
150
+ const flags = read();
151
+ if (flags !== 0 && flags !== 1)
152
+ throw new Error("Shared and 64-bit component memories are unsupported");
153
+ const minimum = read();
154
+ const maximum = flags === 1 ? read() : pages;
155
+ if (offset !== end || minimum > pages)
156
+ throw new Error("Component initial memory exceeds limit");
157
+ const memory = [1, 1, ...leb(minimum), ...leb(Math.min(maximum, pages))];
158
+ const section = new Uint8Array([5, ...leb(memory.length), ...memory]);
159
+ const result = new Uint8Array(sectionStart + section.length + bytes.length - end);
160
+ result.set(bytes.subarray(0, sectionStart));
161
+ result.set(section, sectionStart);
162
+ result.set(bytes.subarray(end), sectionStart + section.length);
163
+ return result;
164
+ }
165
+ return bytes;
166
+ }
167
+ /** Add an inspection-only memory export without changing any guest index. */
168
+ function exposeMemory(bytes, hasMemory) {
169
+ if (!hasMemory)
170
+ return bytes;
171
+ const name = new TextEncoder().encode("__lix_runtime_memory");
172
+ const encode = (value) => {
173
+ const out = [];
174
+ do {
175
+ const byte = value % 128;
176
+ value = Math.floor(value / 128);
177
+ out.push(byte | (value ? 128 : 0));
178
+ } while (value);
179
+ return out;
180
+ };
181
+ let offset = 8;
182
+ const read = () => {
183
+ let result = 0, shift = 0;
184
+ for (;;) {
185
+ const b = bytes[offset++];
186
+ result += (b & 127) * 2 ** shift;
187
+ if (!(b & 128))
188
+ return result;
189
+ shift += 7;
190
+ }
191
+ };
192
+ while (offset < bytes.length) {
193
+ const start = offset;
194
+ const id = bytes[offset++];
195
+ const size = read();
196
+ const end = offset + size;
197
+ if (id < 7 || id === 0) {
198
+ offset = end;
199
+ continue;
200
+ }
201
+ let payload;
202
+ let replaceEnd = start;
203
+ if (id === 7) {
204
+ const count = read();
205
+ payload = new Uint8Array([
206
+ ...encode(count + 1),
207
+ ...bytes.subarray(offset, end),
208
+ ...encode(name.length),
209
+ ...name,
210
+ 2,
211
+ 0,
212
+ ]);
213
+ replaceEnd = end;
214
+ }
215
+ else
216
+ payload = new Uint8Array([1, ...encode(name.length), ...name, 2, 0]);
217
+ const header = new Uint8Array([7, ...encode(payload.length)]);
218
+ const result = new Uint8Array(start + header.length + payload.length + bytes.length - replaceEnd);
219
+ result.set(bytes.subarray(0, start));
220
+ result.set(header, start);
221
+ result.set(payload, start + header.length);
222
+ result.set(bytes.subarray(replaceEnd), start + header.length + payload.length);
223
+ return result;
224
+ }
225
+ const payload = new Uint8Array([1, ...encode(name.length), ...name, 2, 0]);
226
+ const result = new Uint8Array(bytes.length + payload.length + encode(payload.length).length + 1);
227
+ result.set(bytes);
228
+ result.set([7, ...encode(payload.length), ...payload], bytes.length);
229
+ return result;
230
+ }
231
+ /** Count defined memories to apportion the component's aggregate ceiling. */
232
+ export function coreMemoryCount(bytes) {
233
+ return coreSectionCount(bytes, 5);
234
+ }
235
+ export function coreTableCount(bytes) {
236
+ return coreSectionCount(bytes, 4);
237
+ }
238
+ function coreSectionCount(bytes, section) {
239
+ let offset = 8;
240
+ const read = () => {
241
+ let value = 0;
242
+ for (let shift = 0; shift < 35; shift += 7) {
243
+ const byte = bytes[offset++];
244
+ if (byte === undefined)
245
+ throw new Error("Truncated Wasm integer");
246
+ value += (byte & 127) * 2 ** shift;
247
+ if (!(byte & 128))
248
+ return value;
249
+ }
250
+ throw new Error("Invalid Wasm integer");
251
+ };
252
+ while (offset < bytes.length) {
253
+ const id = bytes[offset++];
254
+ const size = read();
255
+ if (id === section)
256
+ return read();
257
+ offset += size;
258
+ }
259
+ return 0;
260
+ }
@@ -0,0 +1,4 @@
1
+ /** The closed-storage binding owns one provider close, including setup failures. */
2
+ export declare function withConversionProvider<T>(provider: {
3
+ close(): Promise<void>;
4
+ }, convert: () => Promise<T>): Promise<T>;
@@ -0,0 +1,20 @@
1
+ /** The closed-storage binding owns one provider close, including setup failures. */
2
+ export async function withConversionProvider(provider, convert) {
3
+ let failed = false;
4
+ try {
5
+ return await convert();
6
+ }
7
+ catch (error) {
8
+ failed = true;
9
+ throw error;
10
+ }
11
+ finally {
12
+ try {
13
+ await provider.close();
14
+ }
15
+ catch (error) {
16
+ if (!failed)
17
+ throw error;
18
+ }
19
+ }
20
+ }
@@ -6,7 +6,7 @@ async function resolveServer(server) {
6
6
  if ("fetch" in server && server.fetch !== undefined)
7
7
  throw new TypeError("hosted lifecycle does not accept a custom fetch");
8
8
  if ("mode" in server)
9
- throw new TypeError("server.mode was removed");
9
+ throw new TypeError("hosted lifecycle does not accept server.mode; mode selects openLix execution");
10
10
  const url = new URL(server.url).toString();
11
11
  const headers = new Headers(typeof server.headers === "function"
12
12
  ? await server.headers()
@@ -0,0 +1,25 @@
1
+ /** The internal HTTP ABI shared by WASM, worker RPC, sync and admission. */
2
+ export type HttpResponsePolicy = {
3
+ mode: "buffered";
4
+ maxBytes: number;
5
+ } | {
6
+ mode: "streaming";
7
+ };
8
+ export type HttpRequest = {
9
+ url: string;
10
+ init: RequestInit;
11
+ response: HttpResponsePolicy;
12
+ };
13
+ export type HttpTransport = (request: HttpRequest) => Promise<Response>;
14
+ export declare class HttpTransportError extends Error {
15
+ readonly code: string;
16
+ constructor(code: string, message: string, options?: ErrorOptions);
17
+ }
18
+ export declare function validateHttpRequest(request: HttpRequest): void;
19
+ /** Invoke browser fetch with failure classification at the actual I/O boundary.
20
+ * Custom telemetry adapters should delegate here and preserve the typed error.
21
+ */
22
+ export declare function networkFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
23
+ /** Public fetch callbacks are network adapters; they must preserve explicit credentials. */
24
+ export declare function fetchTransport(fetcher?: typeof fetch): HttpTransport;
25
+ export declare function boundedResponseBody(response: Response, maxBytes: number): Promise<Uint8Array<ArrayBuffer>>;
@@ -0,0 +1,162 @@
1
+ export class HttpTransportError extends Error {
2
+ code;
3
+ constructor(code, message, options) {
4
+ super(message, options);
5
+ this.code = code;
6
+ this.name = "HttpTransportError";
7
+ }
8
+ }
9
+ export function validateHttpRequest(request) {
10
+ if (!request || typeof request.url !== "string" || !request.init ||
11
+ (request.response?.mode !== "streaming" &&
12
+ !(request.response?.mode === "buffered" && Number.isSafeInteger(request.response.maxBytes) && request.response.maxBytes > 0))) {
13
+ throw new HttpTransportError("LIX_TRANSPORT_CONTRACT", "HTTP request requires an explicit response policy");
14
+ }
15
+ }
16
+ /** Invoke browser fetch with failure classification at the actual I/O boundary.
17
+ * Custom telemetry adapters should delegate here and preserve the typed error.
18
+ */
19
+ export async function networkFetch(input, init) {
20
+ const request = validatedRequest(input, init);
21
+ let response;
22
+ try {
23
+ response = await globalThis.fetch(request);
24
+ }
25
+ catch (error) {
26
+ throw nativeNetworkFailure(error, request.signal);
27
+ }
28
+ return classifiedResponse(response, error => nativeNetworkFailure(error, request.signal));
29
+ }
30
+ // Keep error classification attached to the body producer, including failures
31
+ // after response headers. This wrapper preserves pull-driven backpressure.
32
+ function classifiedResponse(response, classify) {
33
+ // Fetch instrumentation may expose an empty stream for a status whose
34
+ // body is forbidden by the Response constructor.
35
+ if (response.status === 204 || response.status === 205 || response.status === 304) {
36
+ void response.body?.cancel().catch(() => undefined);
37
+ return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
38
+ }
39
+ if (!response.body)
40
+ return response;
41
+ const reader = response.body.getReader();
42
+ let released = false;
43
+ const release = () => { if (!released) {
44
+ released = true;
45
+ reader.releaseLock();
46
+ } };
47
+ const body = new ReadableStream({
48
+ async pull(controller) {
49
+ try {
50
+ const chunk = await reader.read();
51
+ if (chunk.done) {
52
+ release();
53
+ controller.close();
54
+ }
55
+ else
56
+ controller.enqueue(chunk.value);
57
+ }
58
+ catch (error) {
59
+ release();
60
+ controller.error(classify(error));
61
+ }
62
+ },
63
+ async cancel(reason) { try {
64
+ await reader.cancel(reason);
65
+ }
66
+ finally {
67
+ release();
68
+ } },
69
+ });
70
+ return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
71
+ }
72
+ function nativeNetworkFailure(error, signal) {
73
+ if (signal.aborted || (error instanceof Error && error.name === "AbortError")) {
74
+ return new HttpTransportError("LIX_TRANSPORT_ABORTED", "HTTP request was cancelled");
75
+ }
76
+ if (error instanceof TypeError)
77
+ return new HttpTransportError("LIX_TRANSPORT_NETWORK", "HTTP network request failed");
78
+ return new HttpTransportError("LIX_TRANSPORT_CALLBACK", "HTTP adapter failed");
79
+ }
80
+ /** Public fetch callbacks are network adapters; they must preserve explicit credentials. */
81
+ export function fetchTransport(fetcher) {
82
+ return async (request) => {
83
+ validateHttpRequest(request);
84
+ // Validate browser request arguments outside the network-failure boundary.
85
+ const validated = validatedRequest(request.url, request.init);
86
+ let response;
87
+ try {
88
+ response = await (fetcher ?? networkFetch)(validated.url, request.init);
89
+ }
90
+ catch (error) {
91
+ if (request.init.signal?.aborted || (error instanceof Error && error.name === "AbortError")) {
92
+ throw new HttpTransportError("LIX_TRANSPORT_ABORTED", "HTTP request was cancelled");
93
+ }
94
+ if (isTransportFailure(error))
95
+ throw error;
96
+ // Arbitrary user callbacks may throw TypeError for programming errors.
97
+ throw new HttpTransportError("LIX_TRANSPORT_CALLBACK", "HTTP adapter failed");
98
+ }
99
+ if (fetcher) {
100
+ response = classifiedResponse(response, error => {
101
+ if (isTransportFailure(error))
102
+ return error;
103
+ if (request.init.signal?.aborted)
104
+ return new HttpTransportError("LIX_TRANSPORT_ABORTED", "HTTP request was cancelled");
105
+ return new HttpTransportError("LIX_TRANSPORT_CALLBACK", "HTTP response adapter failed");
106
+ });
107
+ }
108
+ if (request.response.mode === "streaming")
109
+ return response;
110
+ const bytes = await boundedResponseBody(response, request.response.maxBytes);
111
+ return new Response(response.status === 204 || response.status === 205 || response.status === 304 ? null : bytes, { status: response.status, statusText: response.statusText, headers: response.headers });
112
+ };
113
+ }
114
+ export async function boundedResponseBody(response, maxBytes) {
115
+ const oversized = () => new HttpTransportError("LIX_TRANSPORT_RESPONSE_LIMIT", "HTTP response exceeds its declared resource budget");
116
+ if (Number(response.headers.get("content-length")) > maxBytes) {
117
+ await response.body?.cancel().catch(() => undefined);
118
+ throw oversized();
119
+ }
120
+ if (!response.body)
121
+ return new Uint8Array();
122
+ const reader = response.body.getReader();
123
+ const chunks = [];
124
+ let total = 0;
125
+ try {
126
+ for (;;) {
127
+ const { value, done } = await reader.read();
128
+ if (done)
129
+ break;
130
+ total += value.byteLength;
131
+ if (total > maxBytes) {
132
+ await reader.cancel().catch(() => undefined);
133
+ throw oversized();
134
+ }
135
+ chunks.push(value);
136
+ }
137
+ }
138
+ finally {
139
+ reader.releaseLock();
140
+ }
141
+ const bytes = new Uint8Array(total);
142
+ let offset = 0;
143
+ for (const chunk of chunks) {
144
+ bytes.set(chunk, offset);
145
+ offset += chunk.byteLength;
146
+ }
147
+ return bytes;
148
+ }
149
+ function validatedRequest(input, init) {
150
+ try {
151
+ return new Request(input, init);
152
+ }
153
+ catch {
154
+ throw new HttpTransportError("LIX_TRANSPORT_CONTRACT", "Invalid HTTP request arguments");
155
+ }
156
+ }
157
+ // Error codes, unlike constructor identity, survive worker RPC and duplicate
158
+ // package realms. Only the explicit transport namespace crosses this boundary.
159
+ function isTransportFailure(error) {
160
+ return error instanceof Error && typeof error.code === "string" &&
161
+ error.code.startsWith("LIX_TRANSPORT_");
162
+ }
package/dist/index.d.ts CHANGED
@@ -3,5 +3,6 @@ export type { LixStorage, LixStorageBound, LixStorageChangeWatch, LixStorageComm
3
3
  export { LixStorageError } from "./storage-adapter.js";
4
4
  export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plugins.js";
5
5
  export { Value } from "./value.js";
6
- export type { CreateBranchOptions, CreateBranchReceipt, RedoReceipt, CommitSpan, ExecuteOptions, ExecuteResult, ExecuteBatchResult, LixBatchOptions, LixBatchStatement, JsonValue, LixValue, ResultArrayRow, ResultColumn, ResultColumnType, ResultObjectRow, ResultRow, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, OpenAnotherSessionOptions, LixTelemetryOptions, LixTelemetryParentContext, LixTelemetrySpan, LixTelemetrySpanLink, LixOpenMigrationReport, LixOpenPhase, LixOpenProgress, LixOpenProgressOptions, LixOpenReport, ReplicaRecoverySource, ReplicaRecoveryRow, ReplicaRecoveryBranch, ReplicaRecoveryBlob, ReplicaRecoveryFile, ReplicaRecoveryExport, ReplicaRecoveryReceipt, RemoteLixFetch, LixServerOptions, HostedLix, CreateLixOptions, DeleteLixOptions, UndoReceipt, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
6
+ export type { CreateBranchOptions, CreateBranchReceipt, RedoReceipt, CommitSpan, ExecuteOptions, ExecuteResult, ExecuteBatchResult, ExecuteBatchStatementResult, CommitReceipt, StatementResult, LixBatchOptions, LixBatchStatement, JsonValue, LixValue, ResultArrayRow, ResultColumn, ResultColumnType, ResultObjectRow, ResultRow, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, ObserveEvent, OpenLixOptions, Durability, OpenAnotherSessionOptions, LixTelemetryOptions, LixTelemetryParentContext, LixTelemetrySpan, LixTelemetrySpanLink, LixOpenMigrationReport, LixOpenPhase, LixOpenProgress, LixOpenProgressOptions, LixOpenReport, SyncHealth, ReplicaRecoverySource, ReplicaRecoveryRow, ReplicaRecoveryBranch, ReplicaRecoveryBlob, ReplicaRecoveryFile, ReplicaRecoveryExport, ReplicaRecoveryReceipt, RemoteLixFetch, LixServerOptions, RemoteLixServerOptions, PartialReplicaLixServerOptions, HostedLix, CreateLixOptions, DeleteLixOptions, UndoReceipt, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
7
7
  export { createLix, deleteLix } from "./hosted-lix.js";
8
+ export { networkFetch, HttpTransportError } from "./http-transport.js";
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { LixStorageError } from "./storage-adapter.js";
3
3
  export { bundledPluginArchives, } from "./bundled-plugins.js";
4
4
  export { Value } from "./value.js";
5
5
  export { createLix, deleteLix } from "./hosted-lix.js";
6
+ export { networkFetch, HttpTransportError } from "./http-transport.js";
package/dist/lix.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { LixBinding, LixTransactionBinding, ObserveEventsBinding } from "./binding-types.js";
2
- import type { CreateBranchOptions, CreateBranchReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, ExecuteBatchResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, OpenAnotherSessionOptions, LixOpenReport, ReplicaRecoverySource, ReplicaRecoveryExport, ReplicaRecoveryReceipt, SqlParam, ResultArrayRow, ResultObjectRow, ResultRow, SwitchBranchOptions, SwitchBranchReceipt, UndoReceipt } from "./types.js";
2
+ import type { CreateBranchOptions, CreateBranchReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, ExecuteBatchResult, CommitReceipt, StatementResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, OpenAnotherSessionOptions, LixOpenReport, ReplicaRecoverySource, ReplicaRecoveryExport, ReplicaRecoveryReceipt, SqlParam, ResultArrayRow, ResultObjectRow, ResultRow, SwitchBranchOptions, SwitchBranchReceipt, UndoReceipt } from "./types.js";
3
3
  /** @internal Used by createLix without adding another method to Lix. */
4
4
  export declare function createHostedFromLix(lix: Lix, server: () => Promise<import("./binding-types.js").HostedServerBindingOptions>): Promise<import("./types.js").HostedLix>;
5
5
  export declare class Lix {
@@ -19,11 +19,11 @@ export declare class Lix {
19
19
  execute(sql: string, params: SqlParam[] | undefined, options?: ExecuteOptions): Promise<ExecuteResult<ResultRow>>;
20
20
  executeBatch(statements: readonly LixBatchStatement[], options: LixBatchOptions & {
21
21
  rowMode: "array";
22
- }): Promise<readonly ExecuteBatchResult<ResultArrayRow>[]>;
22
+ }): Promise<ExecuteBatchResult<ResultArrayRow>>;
23
23
  executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions & {
24
24
  rowMode?: "object";
25
- }): Promise<readonly ExecuteBatchResult<ResultObjectRow>[]>;
26
- executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteBatchResult<ResultRow>[]>;
25
+ }): Promise<ExecuteBatchResult<ResultObjectRow>>;
26
+ executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<ExecuteBatchResult<ResultRow>>;
27
27
  observe(sql: string, params?: SqlParam[]): ObserveEvents;
28
28
  beginTransaction(): Promise<LixTransaction>;
29
29
  /** Lists preserved generations belonging to this local repository. */
@@ -32,12 +32,17 @@ export declare class Lix {
32
32
  exportReplicaRecovery(id: string): Promise<ReplicaRecoveryExport>;
33
33
  /** Restores supported rows onto separate recovery branches; preserves the source. */
34
34
  recoverReplica(id: string): Promise<ReplicaRecoveryReceipt>;
35
+ /** Explicitly hydrates retained-source recovery dependencies; does not start sync. */
36
+ recoverReplicaWithServer(id: string, server: import("./types.js").LixServerOptions): Promise<ReplicaRecoveryReceipt>;
37
+ /** Local worker health; independent of whether a warm SQL read succeeds. */
38
+ syncHealth(): Promise<import("./types.js").SyncHealth>;
35
39
  activeBranchId(): Promise<string>;
36
40
  activeAccountId(): Promise<string>;
37
41
  /** Subscribes to successful branch switches made through this Lix handle. */
38
42
  subscribeActiveBranch(listener: () => void): () => void;
39
43
  createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
40
- /** Streams a deterministic snapshot of the complete Lix. */
44
+ /** Streams this handle's state. Local partial replicas include their cached
45
+ * inputs and pending edits; remote handles export the complete authority. */
41
46
  exportSnapshot(): ReadableStream<Uint8Array>;
42
47
  undo(): Promise<UndoReceipt>;
43
48
  redo(): Promise<RedoReceipt>;
@@ -62,12 +67,12 @@ export declare class LixTransaction {
62
67
  constructor(binding: LixTransactionBinding, onFinish?: () => void);
63
68
  execute(sql: string, params: SqlParam[] | undefined, options: ExecuteOptions & {
64
69
  rowMode: "array";
65
- }): Promise<ExecuteResult<ResultArrayRow>>;
70
+ }): Promise<StatementResult<ResultArrayRow>>;
66
71
  execute<TRow extends object = ResultObjectRow>(sql: string, params?: SqlParam[], options?: ExecuteOptions & {
67
72
  rowMode?: "object";
68
- }): Promise<ExecuteResult<TRow>>;
69
- execute(sql: string, params: SqlParam[] | undefined, options?: ExecuteOptions): Promise<ExecuteResult<ResultRow>>;
70
- commit(): Promise<void>;
73
+ }): Promise<StatementResult<TRow>>;
74
+ execute(sql: string, params: SqlParam[] | undefined, options?: ExecuteOptions): Promise<StatementResult<ResultRow>>;
75
+ commit(): Promise<CommitReceipt>;
71
76
  rollback(): Promise<void>;
72
77
  private finish;
73
78
  }
package/dist/lix.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { fetchTransport } from "./http-transport.js";
1
2
  import { invalidArgument } from "./errors.js";
2
3
  import { normalizeOptionals, wrapExecuteBatchResult, wrapExecuteResult, } from "./result.js";
3
4
  import { normalizeParam, toNativeValue } from "./value.js";
@@ -65,7 +66,10 @@ export class Lix {
65
66
  const { rowMode = "object", ...bindingOptions } = options ?? {};
66
67
  return this.#runOperation(async () => {
67
68
  const results = await this.binding.executeBatch(normalizedStatements, bindingOptions);
68
- return results.map((result) => wrapExecuteBatchResult(result, rowMode));
69
+ return {
70
+ results: results.results.map((result) => wrapExecuteBatchResult(result, rowMode)),
71
+ commit: results.commit ?? null,
72
+ };
69
73
  });
70
74
  }
71
75
  observe(sql, params = []) {
@@ -107,6 +111,24 @@ export class Lix {
107
111
  async recoverReplica(id) {
108
112
  return this.#runOperation(() => this.binding.recoverReplica(id));
109
113
  }
114
+ /** Explicitly hydrates retained-source recovery dependencies; does not start sync. */
115
+ async recoverReplicaWithServer(id, server) {
116
+ const entries = (headers) => {
117
+ const result = [];
118
+ new Headers(headers).forEach((value, key) => result.push([key, value]));
119
+ return result;
120
+ };
121
+ return this.#runOperation(() => this.binding.recoverReplicaWithServer(id, {
122
+ url: new URL(server.url).toString(),
123
+ headers: typeof server.headers === "function" ? [] : entries(server.headers),
124
+ headerProvider: typeof server.headers === "function" ? async () => entries(await server.headers()) : undefined,
125
+ transport: server.fetch ? fetchTransport(server.fetch) : undefined,
126
+ }));
127
+ }
128
+ /** Local worker health; independent of whether a warm SQL read succeeds. */
129
+ async syncHealth() {
130
+ return this.binding.syncHealth();
131
+ }
110
132
  async activeBranchId() {
111
133
  return this.#runOperation(() => this.binding.activeBranchId());
112
134
  }
@@ -125,7 +147,8 @@ export class Lix {
125
147
  async createBranch(options) {
126
148
  return this.#runOperation(() => this.binding.createBranch(options));
127
149
  }
128
- /** Streams a deterministic snapshot of the complete Lix. */
150
+ /** Streams this handle's state. Local partial replicas include their cached
151
+ * inputs and pending edits; remote handles export the complete authority. */
129
152
  exportSnapshot() {
130
153
  let snapshot;
131
154
  const start = () => {
@@ -353,13 +376,14 @@ export class LixTransaction {
353
376
  throw transactionClosedError();
354
377
  assertExecuteArgs("lixTransaction", sql, params, options);
355
378
  const { rowMode = "object", ...bindingOptions } = options ?? {};
356
- return wrapExecuteResult(await this.binding.execute(sql, params.map((param, index) => toNativeValue(normalizeParam(param, index))), bindingOptions), rowMode);
379
+ const { commit: _commit, ...statement } = wrapExecuteResult(await this.binding.execute(sql, params.map((param, index) => toNativeValue(normalizeParam(param, index))), bindingOptions), rowMode);
380
+ return statement;
357
381
  }
358
382
  async commit() {
359
- return this.finish("transaction.commit");
383
+ return (await this.finish("transaction.commit"));
360
384
  }
361
385
  async rollback() {
362
- return this.finish("transaction.rollback");
386
+ await this.finish("transaction.rollback");
363
387
  }
364
388
  async finish(kind) {
365
389
  if (this.finished)
@@ -368,10 +392,11 @@ export class LixTransaction {
368
392
  // a concurrent rollback must never report a pending commit's success.
369
393
  this.finished = true;
370
394
  try {
371
- if (kind === "transaction.commit")
372
- await this.binding.commit();
373
- else
374
- await this.binding.rollback();
395
+ if (kind === "transaction.commit") {
396
+ const receipt = await this.binding.commit();
397
+ return { commit: receipt.commit ?? null };
398
+ }
399
+ await this.binding.rollback();
375
400
  }
376
401
  finally {
377
402
  // Keep the parent transaction lease until the binding settles. A
@@ -395,6 +420,11 @@ function assertExecuteArgs(receiver, sql, params, options) {
395
420
  if (!options || typeof options !== "object" || Array.isArray(options)) {
396
421
  throw invalidArgument("execute", "options", "object", typeof options, receiver);
397
422
  }
423
+ if (options.maxAutoCommitRetries !== undefined &&
424
+ (!Number.isInteger(options.maxAutoCommitRetries) ||
425
+ options.maxAutoCommitRetries < 0 || options.maxAutoCommitRetries > 0xffff_ffff)) {
426
+ throw invalidArgument("execute", "options.maxAutoCommitRetries", "integer between 0 and 4294967295", typeof options.maxAutoCommitRetries, receiver);
427
+ }
398
428
  if (options.originKey !== undefined &&
399
429
  typeof options.originKey !== "string") {
400
430
  throw invalidArgument("execute", "options.originKey", "string", typeof options.originKey, receiver);
@@ -460,6 +490,11 @@ function assertBatchOptions(options) {
460
490
  if (!options || typeof options !== "object" || Array.isArray(options)) {
461
491
  throw invalidArgument("executeBatch", "options", "object", typeof options);
462
492
  }
493
+ if (options.maxAutoCommitRetries !== undefined &&
494
+ (!Number.isInteger(options.maxAutoCommitRetries) ||
495
+ options.maxAutoCommitRetries < 0 || options.maxAutoCommitRetries > 0xffff_ffff)) {
496
+ throw invalidArgument("executeBatch", "options.maxAutoCommitRetries", "integer between 0 and 4294967295", typeof options.maxAutoCommitRetries);
497
+ }
463
498
  if (options.originKey !== undefined &&
464
499
  typeof options.originKey !== "string") {
465
500
  throw invalidArgument("executeBatch", "options.originKey", "string", typeof options.originKey);