@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.
Files changed (49) hide show
  1. package/README.md +105 -22
  2. package/dist/binding-types.d.ts +21 -2
  3. package/dist/binding.browser.d.ts +2 -2
  4. package/dist/binding.browser.js +3 -3
  5. package/dist/binding.node.d.ts +2 -2
  6. package/dist/binding.node.js +18 -4
  7. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  8. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  9. package/dist/bundled-plugins.js +2 -2
  10. package/dist/client-state.d.ts +53 -0
  11. package/dist/client-state.js +318 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/lix.d.ts +47 -0
  14. package/dist/lix.js +378 -0
  15. package/dist/local-storage-adapter.d.ts +26 -0
  16. package/dist/local-storage-adapter.js +117 -0
  17. package/dist/open-lix.d.ts +3 -34
  18. package/dist/open-lix.js +99 -170
  19. package/dist/remote/client.d.ts +9 -0
  20. package/dist/remote/client.js +1150 -0
  21. package/dist/remote/protocol.d.ts +178 -0
  22. package/dist/remote/protocol.js +367 -0
  23. package/dist/remote/sse.d.ts +12 -0
  24. package/dist/remote/sse.js +87 -0
  25. package/dist/snapshot-persistence.d.ts +7 -0
  26. package/dist/snapshot-persistence.js +26 -0
  27. package/dist/types.d.ts +68 -1
  28. package/dist/wasm/lix_js_sdk.d.ts +22 -4
  29. package/dist/wasm/lix_js_sdk.js +96 -17
  30. package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
  31. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +11 -2
  32. package/dist/worker/client.d.ts +23 -4
  33. package/dist/worker/client.js +329 -11
  34. package/dist/worker/factory.browser.d.ts +2 -0
  35. package/dist/worker/factory.browser.js +2 -0
  36. package/dist/worker/factory.node.d.ts +2 -0
  37. package/dist/worker/factory.node.js +5 -0
  38. package/dist/worker/host.js +36 -2
  39. package/dist/worker/protocol.d.ts +32 -2
  40. package/dist/workerd.js +1 -3
  41. package/package.json +19 -16
  42. package/dist/bundled-plugins/plugin_md_v2.lixplugin +0 -0
  43. package/dist/jco/js-component-bindgen-component.core.wasm +0 -0
  44. package/dist/jco/js-component-bindgen-component.core2.wasm +0 -0
  45. package/dist/jco/js-component-bindgen-component.js +0 -13662
  46. package/dist/jco-transpile.browser.d.ts +0 -14
  47. package/dist/jco-transpile.browser.js +0 -22
  48. package/dist/plugin-runtime.d.ts +0 -45
  49. package/dist/plugin-runtime.js +0 -124
@@ -0,0 +1,178 @@
1
+ import type { BindingExecuteResult, BindingObserveEvent } from "../binding-types.js";
2
+ import type { NativeLixValue } from "../value.js";
3
+ export declare const REMOTE_PROTOCOL_VERSION = 2;
4
+ export declare const REMOTE_PROTOCOL_PATH = "/lix/v1/";
5
+ export type WireValue = {
6
+ kind: "null";
7
+ value: null;
8
+ } | {
9
+ kind: "bool";
10
+ value: boolean;
11
+ } | {
12
+ kind: "int";
13
+ value: number;
14
+ } | {
15
+ kind: "float";
16
+ value: number;
17
+ } | {
18
+ kind: "text";
19
+ value: string;
20
+ } | {
21
+ kind: "json";
22
+ value: unknown;
23
+ } | {
24
+ kind: "blob";
25
+ base64: string;
26
+ };
27
+ export type WireRequestBlobSplice = {
28
+ kind: "blob-splice";
29
+ baseSha256: string;
30
+ resultSha256: string;
31
+ prefixBytes: number;
32
+ suffixBytes: number;
33
+ insertBase64: string;
34
+ };
35
+ export type WireRequestValue = WireValue | WireRequestBlobSplice;
36
+ export type RemoteHandshake = {
37
+ protocolVersion: number;
38
+ activeBranchId: string;
39
+ activeAccountId: string;
40
+ sessionId: string;
41
+ };
42
+ export type RemoteHandshakeRequest = {
43
+ activeBranchId?: string;
44
+ activeAccountId?: string;
45
+ };
46
+ export type RemoteExecuteRequest = {
47
+ sql: string;
48
+ params: WireRequestValue[];
49
+ options?: {
50
+ originKey?: string;
51
+ };
52
+ cacheBlobs?: true;
53
+ };
54
+ export type RemoteExecuteBatchRequest = {
55
+ statements: Array<{
56
+ sql: string;
57
+ params: WireRequestValue[];
58
+ }>;
59
+ options?: {
60
+ originKey?: string;
61
+ };
62
+ cacheBlobs?: true;
63
+ };
64
+ export type RemoteExecuteResponse = {
65
+ columns: string[];
66
+ rows: WireValue[][];
67
+ rowsAffected: number;
68
+ notices: Array<{
69
+ code: string;
70
+ message: string;
71
+ hint?: string;
72
+ }>;
73
+ };
74
+ export type RemoteObserveRequest = {
75
+ sql: string;
76
+ params: WireValue[];
77
+ };
78
+ export type RemoteObserveSubscription = RemoteObserveRequest & {
79
+ id: string;
80
+ };
81
+ export type RemoteMultiplexObserveRequest = {
82
+ subscriptions: RemoteObserveSubscription[];
83
+ };
84
+ type RemoteObserveEventBase = {
85
+ sequence: number;
86
+ mutationSequence: number;
87
+ };
88
+ export type RemoteObserveBlobDelta = {
89
+ kind: "single-blob-splice";
90
+ baseSequence: number;
91
+ prefixBytes: number;
92
+ suffixBytes: number;
93
+ insertBase64: string;
94
+ };
95
+ type RemoteObserveRowSplice = {
96
+ kind: "row-splice";
97
+ baseSequence: number;
98
+ prefixRows: number;
99
+ deleteRows: number;
100
+ insertRows: WireValue[][];
101
+ };
102
+ type RemoteObserveDelta = RemoteObserveBlobDelta | RemoteObserveRowSplice;
103
+ export type RemoteObserveEvent = RemoteObserveEventBase & ({
104
+ result: RemoteExecuteResponse;
105
+ delta?: never;
106
+ } | {
107
+ result?: never;
108
+ delta: RemoteObserveDelta;
109
+ });
110
+ export type RemoteMultiplexObserveEvent = RemoteObserveEvent & {
111
+ subscriptionId: string;
112
+ };
113
+ export type RemoteCreateBranchRequest = {
114
+ id?: string;
115
+ name: string;
116
+ fromCommitId?: string;
117
+ };
118
+ export type RemoteCreateBranchResponse = {
119
+ id: string;
120
+ name: string;
121
+ hidden: boolean;
122
+ commitId: string;
123
+ };
124
+ export type RemoteCreateCheckpointResponse = {
125
+ commitId: string;
126
+ };
127
+ export type RemoteUndoResponse = {
128
+ branchId: string;
129
+ targetCommitId: string;
130
+ inverseCommitId: string;
131
+ };
132
+ export type RemoteRedoResponse = {
133
+ branchId: string;
134
+ targetCommitId: string;
135
+ replayCommitId: string;
136
+ };
137
+ export type RemoteSwitchBranchRequest = {
138
+ branchId: string;
139
+ };
140
+ export type RemoteSwitchBranchResponse = {
141
+ branchId: string;
142
+ };
143
+ export type RemoteErrorBody = {
144
+ error: {
145
+ code?: string;
146
+ message?: string;
147
+ hint?: string;
148
+ details?: unknown;
149
+ };
150
+ };
151
+ export type RemoteObserveErrorEvent = RemoteErrorBody & {
152
+ retryable?: boolean;
153
+ };
154
+ export type RemoteMultiplexObserveErrorEvent = RemoteObserveErrorEvent & {
155
+ subscriptionId?: string;
156
+ };
157
+ export declare function encodeWireValue(value: NativeLixValue): WireValue;
158
+ export declare function decodeExecuteResult(value: unknown): BindingExecuteResult;
159
+ export declare function decodeHandshake(value: unknown): RemoteHandshake;
160
+ export declare function decodeObserveEvent(value: unknown, base?: BindingObserveEvent): BindingObserveEvent;
161
+ export declare function remoteError(code: string, message: string, options?: {
162
+ hint?: string;
163
+ details?: unknown;
164
+ status?: number;
165
+ }): Error & {
166
+ code: string;
167
+ hint?: string;
168
+ details?: unknown;
169
+ status?: number;
170
+ };
171
+ export declare function protocolError(message: string): Error & {
172
+ code: string;
173
+ };
174
+ export declare function errorFromResponseBody(value: unknown, status?: number): Error & {
175
+ code: string;
176
+ };
177
+ export declare function record(value: unknown, description: string): Record<string, unknown>;
178
+ export {};
@@ -0,0 +1,367 @@
1
+ export const REMOTE_PROTOCOL_VERSION = 2;
2
+ export const REMOTE_PROTOCOL_PATH = "/lix/v1/";
3
+ export function encodeWireValue(value) {
4
+ switch (value.kind) {
5
+ case "null":
6
+ return { kind: "null", value: null };
7
+ case "boolean":
8
+ return { kind: "bool", value: value.value };
9
+ case "integer":
10
+ return { kind: "int", value: value.value };
11
+ case "real":
12
+ return { kind: "float", value: value.value };
13
+ case "text":
14
+ return { kind: "text", value: value.value };
15
+ case "json":
16
+ return { kind: "json", value: value.value };
17
+ case "blob":
18
+ return { kind: "blob", base64: bytesToBase64(value.blob) };
19
+ }
20
+ }
21
+ export function decodeExecuteResult(value) {
22
+ const result = record(value, "execute result");
23
+ const columns = stringArray(result.columns, "execute result columns");
24
+ if (!Array.isArray(result.rows)) {
25
+ throw protocolError("execute result rows must be an array");
26
+ }
27
+ const rows = result.rows.map((row, rowIndex) => {
28
+ if (!Array.isArray(row)) {
29
+ throw protocolError(`execute result row ${rowIndex} must be an array`);
30
+ }
31
+ if (row.length !== columns.length) {
32
+ throw protocolError(`execute result row ${rowIndex} has ${row.length} values for ${columns.length} columns`);
33
+ }
34
+ return row.map((entry) => decodeWireValue(entry));
35
+ });
36
+ if (typeof result.rowsAffected !== "number" ||
37
+ !Number.isSafeInteger(result.rowsAffected) ||
38
+ result.rowsAffected < 0) {
39
+ throw protocolError("execute result rowsAffected must be a non-negative safe integer");
40
+ }
41
+ if (!Array.isArray(result.notices)) {
42
+ throw protocolError("execute result notices must be an array");
43
+ }
44
+ const notices = result.notices.map((notice, index) => {
45
+ const item = record(notice, `execute result notice ${index}`);
46
+ if (typeof item.code !== "string" || typeof item.message !== "string") {
47
+ throw protocolError(`execute result notice ${index} requires code and message`);
48
+ }
49
+ if (item.hint !== undefined && typeof item.hint !== "string") {
50
+ throw protocolError(`execute result notice ${index} hint must be a string`);
51
+ }
52
+ return {
53
+ code: item.code,
54
+ message: item.message,
55
+ ...(item.hint === undefined ? {} : { hint: item.hint }),
56
+ };
57
+ });
58
+ return { columns, rows, rowsAffected: result.rowsAffected, notices };
59
+ }
60
+ export function decodeHandshake(value) {
61
+ const handshake = record(value, "remote handshake");
62
+ if (handshake.protocolVersion !== REMOTE_PROTOCOL_VERSION) {
63
+ throw protocolError(`unsupported remote protocol version: ${String(handshake.protocolVersion)}`);
64
+ }
65
+ if (typeof handshake.activeBranchId !== "string" ||
66
+ handshake.activeBranchId.length === 0) {
67
+ throw protocolError("remote handshake requires activeBranchId");
68
+ }
69
+ if (typeof handshake.activeAccountId !== "string" ||
70
+ handshake.activeAccountId.length === 0) {
71
+ throw protocolError("remote handshake requires activeAccountId");
72
+ }
73
+ if (typeof handshake.sessionId !== "string" ||
74
+ !/^[\x21-\x7e]{1,256}$/.test(handshake.sessionId)) {
75
+ throw protocolError("remote handshake requires a valid sessionId");
76
+ }
77
+ return {
78
+ protocolVersion: REMOTE_PROTOCOL_VERSION,
79
+ activeBranchId: handshake.activeBranchId,
80
+ activeAccountId: handshake.activeAccountId,
81
+ sessionId: handshake.sessionId,
82
+ };
83
+ }
84
+ export function decodeObserveEvent(value, base) {
85
+ const event = record(value, "observe event");
86
+ if (typeof event.sequence !== "number" ||
87
+ !Number.isSafeInteger(event.sequence) ||
88
+ event.sequence < 0) {
89
+ throw protocolError("observe event sequence must be a non-negative safe integer");
90
+ }
91
+ if (typeof event.mutationSequence !== "number" ||
92
+ !Number.isSafeInteger(event.mutationSequence) ||
93
+ event.mutationSequence < 0) {
94
+ throw protocolError("observe event mutationSequence must be a non-negative safe integer");
95
+ }
96
+ const hasResult = event.result !== undefined;
97
+ const hasDelta = event.delta !== undefined;
98
+ if (hasResult === hasDelta) {
99
+ throw protocolError("observe event requires exactly one of result or delta");
100
+ }
101
+ const sequence = event.sequence;
102
+ return {
103
+ sequence,
104
+ mutationSequence: event.mutationSequence,
105
+ rows: hasResult
106
+ ? decodeExecuteResult(event.result)
107
+ : applyObserveDelta(event.delta, sequence, base),
108
+ };
109
+ }
110
+ function applyObserveDelta(value, sequence, base) {
111
+ const delta = record(value, "observe event delta");
112
+ switch (delta.kind) {
113
+ case "single-blob-splice":
114
+ return applyObserveBlobDelta(delta, sequence, base);
115
+ case "row-splice":
116
+ return applyObserveRowSplice(delta, sequence, base);
117
+ default:
118
+ throw protocolError(`unknown observe delta kind: ${String(delta.kind)}`);
119
+ }
120
+ }
121
+ function applyObserveBlobDelta(delta, sequence, base) {
122
+ const baseSequence = nonNegativeSafeInteger(delta.baseSequence, "observe delta baseSequence");
123
+ const prefixBytes = nonNegativeSafeInteger(delta.prefixBytes, "observe delta prefixBytes");
124
+ const suffixBytes = nonNegativeSafeInteger(delta.suffixBytes, "observe delta suffixBytes");
125
+ if (typeof delta.insertBase64 !== "string") {
126
+ throw protocolError("observe delta insertBase64 must be a string");
127
+ }
128
+ if (base === undefined ||
129
+ base.sequence !== baseSequence ||
130
+ sequence !== baseSequence + 1) {
131
+ throw protocolError("observe blob delta does not match its transport base");
132
+ }
133
+ const baseValue = base.rows.rows[0]?.[0];
134
+ if (base.rows.columns.length !== 1 ||
135
+ base.rows.columns[0] !== "content" ||
136
+ base.rows.rows.length !== 1 ||
137
+ base.rows.rows[0]?.length !== 1 ||
138
+ base.rows.rowsAffected !== 0 ||
139
+ base.rows.notices.length !== 0 ||
140
+ baseValue?.kind !== "blob") {
141
+ throw protocolError("observe blob delta base is not a point blob result");
142
+ }
143
+ if (prefixBytes + suffixBytes > baseValue.blob.byteLength) {
144
+ throw protocolError("observe blob delta prefix and suffix overlap");
145
+ }
146
+ const insert = base64ToBytes(delta.insertBase64);
147
+ const nextLength = prefixBytes + insert.byteLength + suffixBytes;
148
+ if (!Number.isSafeInteger(nextLength)) {
149
+ throw protocolError("observe blob delta result is too large");
150
+ }
151
+ let blob;
152
+ try {
153
+ blob = new Uint8Array(nextLength);
154
+ }
155
+ catch {
156
+ throw protocolError("observe blob delta result is too large");
157
+ }
158
+ blob.set(baseValue.blob.subarray(0, prefixBytes), 0);
159
+ blob.set(insert, prefixBytes);
160
+ blob.set(baseValue.blob.subarray(baseValue.blob.byteLength - suffixBytes), prefixBytes + insert.byteLength);
161
+ return {
162
+ columns: ["content"],
163
+ rows: [[{ kind: "blob", value: null, blob }]],
164
+ rowsAffected: 0,
165
+ notices: [],
166
+ };
167
+ }
168
+ function applyObserveRowSplice(delta, sequence, base) {
169
+ const baseSequence = nonNegativeSafeInteger(delta.baseSequence, "observe row delta baseSequence");
170
+ const prefixRows = nonNegativeSafeInteger(delta.prefixRows, "observe row delta prefixRows");
171
+ const deleteRows = nonNegativeSafeInteger(delta.deleteRows, "observe row delta deleteRows");
172
+ if (base === undefined ||
173
+ base.sequence !== baseSequence ||
174
+ sequence !== baseSequence + 1) {
175
+ throw protocolError("observe row delta does not match its transport base");
176
+ }
177
+ if (!Array.isArray(delta.insertRows)) {
178
+ throw protocolError("observe row delta insertRows must be an array");
179
+ }
180
+ if (prefixRows > base.rows.rows.length ||
181
+ deleteRows > base.rows.rows.length - prefixRows) {
182
+ throw protocolError("observe row delta splice range is outside its transport base");
183
+ }
184
+ const suffixStart = prefixRows + deleteRows;
185
+ const nextRowCount = prefixRows + delta.insertRows.length + (base.rows.rows.length - suffixStart);
186
+ if (!Number.isSafeInteger(nextRowCount) ||
187
+ nextRowCount > 0xffff_ffff) {
188
+ throw protocolError("observe row delta result is too large");
189
+ }
190
+ let rows;
191
+ try {
192
+ rows = new Array(nextRowCount);
193
+ }
194
+ catch {
195
+ throw protocolError("observe row delta result is too large");
196
+ }
197
+ let destination = 0;
198
+ for (let index = 0; index < prefixRows; index += 1) {
199
+ rows[destination++] = base.rows.rows[index];
200
+ }
201
+ for (let rowIndex = 0; rowIndex < delta.insertRows.length; rowIndex += 1) {
202
+ const row = delta.insertRows[rowIndex];
203
+ if (!Array.isArray(row)) {
204
+ throw protocolError(`observe row delta insert row ${rowIndex} must be an array`);
205
+ }
206
+ if (row.length !== base.rows.columns.length) {
207
+ throw protocolError(`observe row delta insert row ${rowIndex} has ${row.length} values for ${base.rows.columns.length} columns`);
208
+ }
209
+ rows[destination++] = row.map((entry) => decodeWireValue(entry));
210
+ }
211
+ for (let index = suffixStart; index < base.rows.rows.length; index += 1) {
212
+ rows[destination++] = base.rows.rows[index];
213
+ }
214
+ return {
215
+ columns: [...base.rows.columns],
216
+ rows,
217
+ rowsAffected: base.rows.rowsAffected,
218
+ notices: base.rows.notices.map((notice) => ({ ...notice })),
219
+ };
220
+ }
221
+ export function remoteError(code, message, options = {}) {
222
+ const error = new Error(message);
223
+ error.name = "LixError";
224
+ error.code = code;
225
+ error.hint = options.hint;
226
+ error.details = options.details;
227
+ error.status = options.status;
228
+ return error;
229
+ }
230
+ export function protocolError(message) {
231
+ return remoteError("LIX_REMOTE_PROTOCOL_ERROR", message);
232
+ }
233
+ export function errorFromResponseBody(value, status) {
234
+ const body = record(value, "remote error response");
235
+ const rawError = record(body.error, "remote error response error");
236
+ return remoteError(typeof rawError.code === "string"
237
+ ? rawError.code
238
+ : "LIX_REMOTE_REQUEST_FAILED", typeof rawError.message === "string"
239
+ ? rawError.message
240
+ : status === undefined
241
+ ? "Remote Lix operation failed"
242
+ : `Remote Lix request failed with status ${status}`, {
243
+ hint: typeof rawError.hint === "string" ? rawError.hint : undefined,
244
+ details: rawError.details,
245
+ status,
246
+ });
247
+ }
248
+ export function record(value, description) {
249
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
250
+ throw protocolError(`${description} must be an object`);
251
+ }
252
+ return value;
253
+ }
254
+ function isRecord(value) {
255
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
256
+ }
257
+ function decodeWireValue(value) {
258
+ const wire = record(value, "wire value");
259
+ switch (wire.kind) {
260
+ case "null":
261
+ if (wire.value !== null)
262
+ throw protocolError("null wire value is invalid");
263
+ return { kind: "null", value: null };
264
+ case "bool":
265
+ if (typeof wire.value !== "boolean") {
266
+ throw protocolError("bool wire value is invalid");
267
+ }
268
+ return { kind: "boolean", value: wire.value };
269
+ case "int":
270
+ if (typeof wire.value !== "number" || !Number.isSafeInteger(wire.value)) {
271
+ throw protocolError("int wire value is invalid");
272
+ }
273
+ return { kind: "integer", value: wire.value };
274
+ case "float":
275
+ if (typeof wire.value !== "number" || !Number.isFinite(wire.value)) {
276
+ throw protocolError("float wire value is invalid");
277
+ }
278
+ return { kind: "real", value: wire.value };
279
+ case "text":
280
+ if (typeof wire.value !== "string") {
281
+ throw protocolError("text wire value is invalid");
282
+ }
283
+ return { kind: "text", value: wire.value };
284
+ case "json":
285
+ assertJsonValue(wire.value, "json wire value");
286
+ return { kind: "json", value: wire.value };
287
+ case "blob":
288
+ if (typeof wire.base64 !== "string") {
289
+ throw protocolError("blob wire value is invalid");
290
+ }
291
+ return { kind: "blob", value: null, blob: base64ToBytes(wire.base64) };
292
+ default:
293
+ throw protocolError(`unknown wire value kind: ${String(wire.kind)}`);
294
+ }
295
+ }
296
+ function stringArray(value, description) {
297
+ if (!Array.isArray(value) ||
298
+ !value.every((entry) => typeof entry === "string")) {
299
+ throw protocolError(`${description} must be an array of strings`);
300
+ }
301
+ return [...value];
302
+ }
303
+ function nonNegativeSafeInteger(value, description) {
304
+ if (typeof value !== "number" ||
305
+ !Number.isSafeInteger(value) ||
306
+ value < 0) {
307
+ throw protocolError(`${description} must be a non-negative safe integer`);
308
+ }
309
+ return value;
310
+ }
311
+ function assertJsonValue(value, description) {
312
+ if (value === null ||
313
+ typeof value === "boolean" ||
314
+ (typeof value === "number" &&
315
+ Number.isFinite(value) &&
316
+ (!Number.isInteger(value) || Number.isSafeInteger(value))) ||
317
+ (typeof value === "string" && value.isWellFormed())) {
318
+ return;
319
+ }
320
+ if (Array.isArray(value)) {
321
+ for (const entry of value)
322
+ assertJsonValue(entry, description);
323
+ return;
324
+ }
325
+ if (value && typeof value === "object") {
326
+ for (const entry of Object.values(value)) {
327
+ assertJsonValue(entry, description);
328
+ }
329
+ return;
330
+ }
331
+ throw protocolError(`${description} is not valid Lix JSON`);
332
+ }
333
+ function bytesToBase64(bytes) {
334
+ const nativeToBase64 = bytes.toBase64;
335
+ if (typeof nativeToBase64 === "function") {
336
+ return nativeToBase64.call(bytes);
337
+ }
338
+ let binary = "";
339
+ const chunkSize = 0x8000;
340
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
341
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
342
+ }
343
+ return btoa(binary);
344
+ }
345
+ function base64ToBytes(base64) {
346
+ const nativeFromBase64 = Uint8Array.fromBase64;
347
+ if (typeof nativeFromBase64 === "function") {
348
+ try {
349
+ return nativeFromBase64(base64);
350
+ }
351
+ catch {
352
+ throw protocolError("blob wire value contains invalid base64");
353
+ }
354
+ }
355
+ let binary;
356
+ try {
357
+ binary = atob(base64);
358
+ }
359
+ catch {
360
+ throw protocolError("blob wire value contains invalid base64");
361
+ }
362
+ const bytes = new Uint8Array(binary.length);
363
+ for (let index = 0; index < binary.length; index += 1) {
364
+ bytes[index] = binary.charCodeAt(index);
365
+ }
366
+ return bytes;
367
+ }
@@ -0,0 +1,12 @@
1
+ export type SseEvent = {
2
+ event: string;
3
+ data: string;
4
+ retry?: number;
5
+ };
6
+ /**
7
+ * Parses a fetch response body as a stream of server-sent events.
8
+ *
9
+ * The caller remains responsible for validating the response status and
10
+ * content type before passing its body here.
11
+ */
12
+ export declare function readSseEvents(stream: ReadableStream<Uint8Array>): AsyncGenerator<SseEvent, void, void>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Parses a fetch response body as a stream of server-sent events.
3
+ *
4
+ * The caller remains responsible for validating the response status and
5
+ * content type before passing its body here.
6
+ */
7
+ export async function* readSseEvents(stream) {
8
+ const decoder = new TextDecoder();
9
+ const reader = stream.getReader();
10
+ let bufferedText = "";
11
+ let eventName = "";
12
+ let retry;
13
+ let dataLines = [];
14
+ function processLine(line) {
15
+ if (line.length === 0) {
16
+ return dispatchEvent();
17
+ }
18
+ if (line.startsWith(":")) {
19
+ return undefined;
20
+ }
21
+ const colonIndex = line.indexOf(":");
22
+ const field = colonIndex === -1 ? line : line.slice(0, colonIndex);
23
+ let value = colonIndex === -1 ? "" : line.slice(colonIndex + 1);
24
+ if (value.startsWith(" "))
25
+ value = value.slice(1);
26
+ switch (field) {
27
+ case "event":
28
+ eventName = value;
29
+ break;
30
+ case "data":
31
+ dataLines.push(value);
32
+ break;
33
+ case "retry": {
34
+ if (/^\d+$/.test(value)) {
35
+ const parsed = Number(value);
36
+ if (Number.isSafeInteger(parsed))
37
+ retry = parsed;
38
+ }
39
+ break;
40
+ }
41
+ // Event IDs and extension fields are intentionally ignored. Remote Lix
42
+ // observations use their payload sequence for resumption instead.
43
+ default:
44
+ break;
45
+ }
46
+ return undefined;
47
+ }
48
+ function dispatchEvent() {
49
+ const hasData = dataLines.length !== 0;
50
+ const event = hasData
51
+ ? {
52
+ event: eventName.length === 0 ? "message" : eventName,
53
+ data: dataLines.join("\n"),
54
+ ...(retry === undefined ? {} : { retry }),
55
+ }
56
+ : undefined;
57
+ eventName = "";
58
+ retry = undefined;
59
+ dataLines = [];
60
+ return event;
61
+ }
62
+ try {
63
+ while (true) {
64
+ const { done, value } = await reader.read();
65
+ if (done)
66
+ break;
67
+ bufferedText += decoder.decode(value, { stream: true });
68
+ let newlineIndex = bufferedText.indexOf("\n");
69
+ while (newlineIndex !== -1) {
70
+ let line = bufferedText.slice(0, newlineIndex);
71
+ bufferedText = bufferedText.slice(newlineIndex + 1);
72
+ if (line.endsWith("\r"))
73
+ line = line.slice(0, -1);
74
+ const event = processLine(line);
75
+ if (event !== undefined)
76
+ yield event;
77
+ newlineIndex = bufferedText.indexOf("\n");
78
+ }
79
+ }
80
+ decoder.decode();
81
+ // SSE dispatches only at a blank line. An incomplete final frame indicates
82
+ // a truncated connection and is deliberately left for the caller to retry.
83
+ }
84
+ finally {
85
+ reader.releaseLock();
86
+ }
87
+ }
@@ -0,0 +1,7 @@
1
+ export type SnapshotPersistenceAfterCommitError = Error & {
2
+ readonly code: "LIX_SNAPSHOT_PERSISTENCE_FAILED";
3
+ readonly lixSnapshotOperationCommitted: true;
4
+ };
5
+ /** Marks that the engine operation committed before durable snapshot saving failed. */
6
+ export declare function snapshotPersistenceAfterCommitError(cause: unknown): SnapshotPersistenceAfterCommitError;
7
+ export declare function isSnapshotPersistenceAfterCommitError(error: unknown): error is SnapshotPersistenceAfterCommitError;
@@ -0,0 +1,26 @@
1
+ const OPERATION_COMMITTED = "lixSnapshotOperationCommitted";
2
+ /** Marks that the engine operation committed before durable snapshot saving failed. */
3
+ export function snapshotPersistenceAfterCommitError(cause) {
4
+ const message = cause instanceof Error
5
+ ? cause.message
6
+ : "Saving the committed Lix snapshot failed";
7
+ const error = new Error(message, { cause });
8
+ error.name = "LixSnapshotPersistenceError";
9
+ Object.defineProperties(error, {
10
+ code: {
11
+ value: "LIX_SNAPSHOT_PERSISTENCE_FAILED",
12
+ enumerable: true,
13
+ },
14
+ [OPERATION_COMMITTED]: {
15
+ value: true,
16
+ enumerable: true,
17
+ },
18
+ });
19
+ return error;
20
+ }
21
+ export function isSnapshotPersistenceAfterCommitError(error) {
22
+ return (typeof error === "object" &&
23
+ error !== null &&
24
+ OPERATION_COMMITTED in error &&
25
+ error[OPERATION_COMMITTED] === true);
26
+ }