@lix-js/sdk 0.15.1 → 0.16.1

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.
@@ -1,417 +0,0 @@
1
- export const SERVER_PROTOCOL_VERSION = 6;
2
- export function encodeWireValue(value) {
3
- switch (value.kind) {
4
- case "null":
5
- return { kind: "null", value: null };
6
- case "boolean":
7
- return { kind: "bool", value: value.value };
8
- case "integer":
9
- return { kind: "int", value: value.value };
10
- case "real":
11
- return { kind: "float", value: value.value };
12
- case "text":
13
- return { kind: "text", value: value.value };
14
- case "jsonb":
15
- return { kind: "jsonb", value: value.value };
16
- case "row_ref":
17
- return { kind: "row_ref", value: value.value };
18
- case "timestamptz":
19
- return { kind: "timestamptz", value: value.value };
20
- case "blob":
21
- return { kind: "blob", base64: bytesToBase64(value.blob) };
22
- }
23
- }
24
- export function decodeExecuteResult(value) {
25
- const result = record(value, "execute result");
26
- if (!Array.isArray(result.columns)) {
27
- throw protocolError("execute result columns must be an array");
28
- }
29
- const columns = result.columns.map((column, index) => {
30
- const item = record(column, `execute result column ${index}`);
31
- if (typeof item.name !== "string" || !isResultColumnType(item.type)) {
32
- throw protocolError(`execute result column ${index} requires a string name and valid type`);
33
- }
34
- return { name: item.name, type: item.type };
35
- });
36
- if (!Array.isArray(result.rows)) {
37
- throw protocolError("execute result rows must be an array");
38
- }
39
- const rows = result.rows.map((row, rowIndex) => {
40
- if (!Array.isArray(row)) {
41
- throw protocolError(`execute result row ${rowIndex} must be an array`);
42
- }
43
- if (row.length !== columns.length) {
44
- throw protocolError(`execute result row ${rowIndex} has ${row.length} values for ${columns.length} columns`);
45
- }
46
- return row.map((entry, columnIndex) => {
47
- const decoded = decodeWireValue(entry);
48
- const declaredType = columns[columnIndex]?.type;
49
- if (decoded.kind !== "null" && decoded.kind !== declaredType) {
50
- throw protocolError(`execute result row ${rowIndex} column ${columnIndex} declares ${String(declaredType)} but contains ${decoded.kind}`);
51
- }
52
- return decoded;
53
- });
54
- });
55
- if (typeof result.rowsAffected !== "number" ||
56
- !Number.isSafeInteger(result.rowsAffected) ||
57
- result.rowsAffected < 0) {
58
- throw protocolError("execute result rowsAffected must be a non-negative safe integer");
59
- }
60
- if (!Array.isArray(result.notices)) {
61
- throw protocolError("execute result notices must be an array");
62
- }
63
- const notices = result.notices.map((notice, index) => {
64
- const item = record(notice, `execute result notice ${index}`);
65
- if (typeof item.code !== "string" || typeof item.message !== "string") {
66
- throw protocolError(`execute result notice ${index} requires code and message`);
67
- }
68
- if (item.hint !== undefined && typeof item.hint !== "string") {
69
- throw protocolError(`execute result notice ${index} hint must be a string`);
70
- }
71
- return {
72
- code: item.code,
73
- message: item.message,
74
- ...(item.hint === undefined ? {} : { hint: item.hint }),
75
- };
76
- });
77
- return { columns, rows, rowsAffected: result.rowsAffected, notices };
78
- }
79
- function isResultColumnType(value) {
80
- return (value === "null" ||
81
- value === "boolean" ||
82
- value === "integer" ||
83
- value === "real" ||
84
- value === "text" ||
85
- value === "jsonb" ||
86
- value === "row_ref" ||
87
- value === "timestamptz" ||
88
- value === "blob");
89
- }
90
- export function decodeExecuteBatchResult(value) {
91
- const result = record(value, "execute batch result");
92
- const statementIndex = nonNegativeSafeInteger(result.statementIndex, "execute batch result statementIndex");
93
- if (result.label !== undefined && typeof result.label !== "string") {
94
- throw protocolError("execute batch result label must be a string when present");
95
- }
96
- return {
97
- ...decodeExecuteResult(value),
98
- statementIndex,
99
- ...(result.label === undefined ? {} : { label: result.label }),
100
- };
101
- }
102
- export function decodeHandshake(value) {
103
- const handshake = record(value, "Lix Server Protocol handshake");
104
- if (handshake.protocolVersion !== SERVER_PROTOCOL_VERSION) {
105
- throw protocolError(`unsupported Lix Server Protocol version: ${String(handshake.protocolVersion)}`);
106
- }
107
- if (typeof handshake.activeBranchId !== "string" ||
108
- handshake.activeBranchId.length === 0) {
109
- throw protocolError("Lix Server Protocol handshake requires activeBranchId");
110
- }
111
- if (typeof handshake.activeAccountId !== "string" ||
112
- handshake.activeAccountId.length === 0) {
113
- throw protocolError("Lix Server Protocol handshake requires activeAccountId");
114
- }
115
- if (typeof handshake.sessionId !== "string" ||
116
- !/^[\x21-\x7e]{1,256}$/.test(handshake.sessionId)) {
117
- throw protocolError("Lix Server Protocol handshake requires a valid sessionId");
118
- }
119
- return {
120
- protocolVersion: SERVER_PROTOCOL_VERSION,
121
- activeBranchId: handshake.activeBranchId,
122
- activeAccountId: handshake.activeAccountId,
123
- sessionId: handshake.sessionId,
124
- };
125
- }
126
- export function decodeObserveEvent(value, base) {
127
- const event = record(value, "observe event");
128
- if (typeof event.sequence !== "number" ||
129
- !Number.isSafeInteger(event.sequence) ||
130
- event.sequence < 0) {
131
- throw protocolError("observe event sequence must be a non-negative safe integer");
132
- }
133
- if (typeof event.mutationSequence !== "number" ||
134
- !Number.isSafeInteger(event.mutationSequence) ||
135
- event.mutationSequence < 0) {
136
- throw protocolError("observe event mutationSequence must be a non-negative safe integer");
137
- }
138
- const hasResult = event.result !== undefined;
139
- const hasDelta = event.delta !== undefined;
140
- if (hasResult === hasDelta) {
141
- throw protocolError("observe event requires exactly one of result or delta");
142
- }
143
- const sequence = event.sequence;
144
- return {
145
- sequence,
146
- mutationSequence: event.mutationSequence,
147
- rows: hasResult
148
- ? decodeExecuteResult(event.result)
149
- : applyObserveDelta(event.delta, sequence, base),
150
- };
151
- }
152
- function applyObserveDelta(value, sequence, base) {
153
- const delta = record(value, "observe event delta");
154
- switch (delta.kind) {
155
- case "single-blob-splice":
156
- return applyObserveBlobDelta(delta, sequence, base);
157
- case "row-splice":
158
- return applyObserveRowSplice(delta, sequence, base);
159
- default:
160
- throw protocolError(`unknown observe delta kind: ${String(delta.kind)}`);
161
- }
162
- }
163
- function applyObserveBlobDelta(delta, sequence, base) {
164
- const baseSequence = nonNegativeSafeInteger(delta.baseSequence, "observe delta baseSequence");
165
- const prefixBytes = nonNegativeSafeInteger(delta.prefixBytes, "observe delta prefixBytes");
166
- const suffixBytes = nonNegativeSafeInteger(delta.suffixBytes, "observe delta suffixBytes");
167
- if (typeof delta.insertBase64 !== "string") {
168
- throw protocolError("observe delta insertBase64 must be a string");
169
- }
170
- if (base === undefined ||
171
- base.sequence !== baseSequence ||
172
- sequence !== baseSequence + 1) {
173
- throw protocolError("observe blob delta does not match its transport base");
174
- }
175
- const baseValue = base.rows.rows[0]?.[0];
176
- if (base.rows.columns.length !== 1 ||
177
- base.rows.columns[0]?.name !== "content" ||
178
- base.rows.columns[0]?.type !== "blob" ||
179
- base.rows.rows.length !== 1 ||
180
- base.rows.rows[0]?.length !== 1 ||
181
- base.rows.rowsAffected !== 0 ||
182
- base.rows.notices.length !== 0 ||
183
- baseValue?.kind !== "blob") {
184
- throw protocolError("observe blob delta base is not a point blob result");
185
- }
186
- if (prefixBytes + suffixBytes > baseValue.blob.byteLength) {
187
- throw protocolError("observe blob delta prefix and suffix overlap");
188
- }
189
- const insert = base64ToBytes(delta.insertBase64);
190
- const nextLength = prefixBytes + insert.byteLength + suffixBytes;
191
- if (!Number.isSafeInteger(nextLength)) {
192
- throw protocolError("observe blob delta result is too large");
193
- }
194
- let blob;
195
- try {
196
- blob = new Uint8Array(nextLength);
197
- }
198
- catch {
199
- throw protocolError("observe blob delta result is too large");
200
- }
201
- blob.set(baseValue.blob.subarray(0, prefixBytes), 0);
202
- blob.set(insert, prefixBytes);
203
- blob.set(baseValue.blob.subarray(baseValue.blob.byteLength - suffixBytes), prefixBytes + insert.byteLength);
204
- return {
205
- columns: [{ name: "content", type: "blob" }],
206
- rows: [[{ kind: "blob", value: null, blob }]],
207
- rowsAffected: 0,
208
- notices: [],
209
- };
210
- }
211
- function applyObserveRowSplice(delta, sequence, base) {
212
- const baseSequence = nonNegativeSafeInteger(delta.baseSequence, "observe row delta baseSequence");
213
- const prefixRows = nonNegativeSafeInteger(delta.prefixRows, "observe row delta prefixRows");
214
- const deleteRows = nonNegativeSafeInteger(delta.deleteRows, "observe row delta deleteRows");
215
- if (base === undefined ||
216
- base.sequence !== baseSequence ||
217
- sequence !== baseSequence + 1) {
218
- throw protocolError("observe row delta does not match its transport base");
219
- }
220
- if (!Array.isArray(delta.insertRows)) {
221
- throw protocolError("observe row delta insertRows must be an array");
222
- }
223
- if (prefixRows > base.rows.rows.length ||
224
- deleteRows > base.rows.rows.length - prefixRows) {
225
- throw protocolError("observe row delta splice range is outside its transport base");
226
- }
227
- const suffixStart = prefixRows + deleteRows;
228
- const nextRowCount = prefixRows + delta.insertRows.length + (base.rows.rows.length - suffixStart);
229
- if (!Number.isSafeInteger(nextRowCount) ||
230
- nextRowCount > 0xffff_ffff) {
231
- throw protocolError("observe row delta result is too large");
232
- }
233
- let rows;
234
- try {
235
- rows = new Array(nextRowCount);
236
- }
237
- catch {
238
- throw protocolError("observe row delta result is too large");
239
- }
240
- let destination = 0;
241
- for (let index = 0; index < prefixRows; index += 1) {
242
- rows[destination++] = base.rows.rows[index];
243
- }
244
- for (let rowIndex = 0; rowIndex < delta.insertRows.length; rowIndex += 1) {
245
- const row = delta.insertRows[rowIndex];
246
- if (!Array.isArray(row)) {
247
- throw protocolError(`observe row delta insert row ${rowIndex} must be an array`);
248
- }
249
- if (row.length !== base.rows.columns.length) {
250
- throw protocolError(`observe row delta insert row ${rowIndex} has ${row.length} values for ${base.rows.columns.length} columns`);
251
- }
252
- rows[destination++] = row.map((entry) => decodeWireValue(entry));
253
- }
254
- for (let index = suffixStart; index < base.rows.rows.length; index += 1) {
255
- rows[destination++] = base.rows.rows[index];
256
- }
257
- return {
258
- columns: [...base.rows.columns],
259
- rows,
260
- rowsAffected: base.rows.rowsAffected,
261
- notices: base.rows.notices.map((notice) => ({ ...notice })),
262
- };
263
- }
264
- export function remoteError(code, message, options = {}) {
265
- const error = new Error(message);
266
- error.name = "LixError";
267
- error.code = code;
268
- error.hint = options.hint;
269
- error.details = options.details;
270
- error.status = options.status;
271
- return error;
272
- }
273
- export function protocolError(message) {
274
- return remoteError("LIX_SERVER_PROTOCOL_ERROR", message);
275
- }
276
- export function errorFromResponseBody(value, status) {
277
- const body = record(value, "Lix Server Protocol error response");
278
- const rawError = record(body.error, "Lix Server Protocol error response error");
279
- return remoteError(typeof rawError.code === "string"
280
- ? rawError.code
281
- : "LIX_REMOTE_REQUEST_FAILED", typeof rawError.message === "string"
282
- ? rawError.message
283
- : status === undefined
284
- ? "Remote Lix operation failed"
285
- : `Remote Lix request failed with status ${status}`, {
286
- hint: typeof rawError.hint === "string" ? rawError.hint : undefined,
287
- details: rawError.details,
288
- status,
289
- });
290
- }
291
- export function record(value, description) {
292
- if (!value || typeof value !== "object" || Array.isArray(value)) {
293
- throw protocolError(`${description} must be an object`);
294
- }
295
- return value;
296
- }
297
- function decodeWireValue(value) {
298
- const wire = record(value, "wire value");
299
- switch (wire.kind) {
300
- case "null":
301
- if (wire.value !== null)
302
- throw protocolError("null wire value is invalid");
303
- return { kind: "null", value: null };
304
- case "bool":
305
- if (typeof wire.value !== "boolean") {
306
- throw protocolError("bool wire value is invalid");
307
- }
308
- return { kind: "boolean", value: wire.value };
309
- case "int":
310
- if (typeof wire.value !== "number" || !Number.isSafeInteger(wire.value)) {
311
- throw protocolError("int wire value is invalid");
312
- }
313
- return { kind: "integer", value: wire.value };
314
- case "float":
315
- if (typeof wire.value !== "number" || !Number.isFinite(wire.value)) {
316
- throw protocolError("float wire value is invalid");
317
- }
318
- return { kind: "real", value: wire.value };
319
- case "text":
320
- if (typeof wire.value !== "string") {
321
- throw protocolError("text wire value is invalid");
322
- }
323
- return { kind: "text", value: wire.value };
324
- case "jsonb":
325
- assertJsonValue(wire.value, "jsonb wire value");
326
- return { kind: "jsonb", value: wire.value };
327
- case "row_ref":
328
- if (typeof wire.value !== "string") {
329
- throw protocolError("row_ref wire value is invalid");
330
- }
331
- return { kind: "row_ref", value: wire.value };
332
- case "timestamptz":
333
- if (typeof wire.value !== "string") {
334
- throw protocolError("timestamptz wire value is invalid");
335
- }
336
- return { kind: "timestamptz", value: wire.value };
337
- case "blob":
338
- if (typeof wire.base64 !== "string") {
339
- throw protocolError("blob wire value is invalid");
340
- }
341
- return { kind: "blob", value: null, blob: base64ToBytes(wire.base64) };
342
- default:
343
- throw protocolError(`unknown wire value kind: ${String(wire.kind)}`);
344
- }
345
- }
346
- function stringArray(value, description) {
347
- if (!Array.isArray(value) ||
348
- !value.every((entry) => typeof entry === "string")) {
349
- throw protocolError(`${description} must be an array of strings`);
350
- }
351
- return [...value];
352
- }
353
- function nonNegativeSafeInteger(value, description) {
354
- if (typeof value !== "number" ||
355
- !Number.isSafeInteger(value) ||
356
- value < 0) {
357
- throw protocolError(`${description} must be a non-negative safe integer`);
358
- }
359
- return value;
360
- }
361
- function assertJsonValue(value, description) {
362
- if (value === null ||
363
- typeof value === "boolean" ||
364
- (typeof value === "number" &&
365
- Number.isFinite(value) &&
366
- (!Number.isInteger(value) || Number.isSafeInteger(value))) ||
367
- (typeof value === "string" && value.isWellFormed())) {
368
- return;
369
- }
370
- if (Array.isArray(value)) {
371
- for (const entry of value)
372
- assertJsonValue(entry, description);
373
- return;
374
- }
375
- if (value && typeof value === "object") {
376
- for (const entry of Object.values(value)) {
377
- assertJsonValue(entry, description);
378
- }
379
- return;
380
- }
381
- throw protocolError(`${description} is not valid Lix JSON`);
382
- }
383
- function bytesToBase64(bytes) {
384
- const nativeToBase64 = bytes.toBase64;
385
- if (typeof nativeToBase64 === "function") {
386
- return nativeToBase64.call(bytes);
387
- }
388
- let binary = "";
389
- const chunkSize = 0x8000;
390
- for (let offset = 0; offset < bytes.length; offset += chunkSize) {
391
- binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
392
- }
393
- return btoa(binary);
394
- }
395
- function base64ToBytes(base64) {
396
- const nativeFromBase64 = Uint8Array.fromBase64;
397
- if (typeof nativeFromBase64 === "function") {
398
- try {
399
- return nativeFromBase64(base64);
400
- }
401
- catch {
402
- throw protocolError("blob wire value contains invalid base64");
403
- }
404
- }
405
- let binary;
406
- try {
407
- binary = atob(base64);
408
- }
409
- catch {
410
- throw protocolError("blob wire value contains invalid base64");
411
- }
412
- const bytes = new Uint8Array(binary.length);
413
- for (let index = 0; index < binary.length; index += 1) {
414
- bytes[index] = binary.charCodeAt(index);
415
- }
416
- return bytes;
417
- }