@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.
- package/README.md +162 -44
- package/dist/binding-types.d.ts +15 -5
- 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 +14 -9
- package/dist/lix.js +44 -9
- 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 -1
- package/dist/storage-ownership.d.ts +5 -0
- package/dist/storage-ownership.js +4 -0
- package/dist/types.d.ts +65 -31
- package/dist/wasm/lix_js_sdk.d.ts +45 -16
- package/dist/wasm/lix_js_sdk.js +214 -60
- package/dist/wasm/lix_js_sdk_bg.wasm +2 -2
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +17 -9
- package/dist/worker/client.d.ts +12 -3
- package/dist/worker/client.js +95 -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 +86 -32
- package/dist/worker/protocol.d.ts +25 -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 -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,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
|
+
}
|
package/dist/hosted-lix.js
CHANGED
|
@@ -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
|
|
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,
|
|
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<
|
|
22
|
+
}): Promise<ExecuteBatchResult<ResultArrayRow>>;
|
|
23
23
|
executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions & {
|
|
24
24
|
rowMode?: "object";
|
|
25
|
-
}): Promise<
|
|
26
|
-
executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<
|
|
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
|
|
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<
|
|
70
|
+
}): Promise<StatementResult<ResultArrayRow>>;
|
|
66
71
|
execute<TRow extends object = ResultObjectRow>(sql: string, params?: SqlParam[], options?: ExecuteOptions & {
|
|
67
72
|
rowMode?: "object";
|
|
68
|
-
}): Promise<
|
|
69
|
-
execute(sql: string, params: SqlParam[] | undefined, options?: ExecuteOptions): Promise<
|
|
70
|
-
commit(): Promise<
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
|
|
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);
|