@zvndev/powdb-client 0.12.0 → 0.14.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/CHANGELOG.md +44 -0
- package/README.md +112 -6
- package/dist/cjs/index.d.ts +87 -3
- package/dist/cjs/index.js +192 -2
- package/dist/cjs/protocol.d.ts +55 -0
- package/dist/cjs/protocol.js +405 -33
- package/dist/index.d.ts +87 -3
- package/dist/index.js +190 -1
- package/dist/protocol.d.ts +55 -0
- package/dist/protocol.js +404 -32
- package/package.json +1 -1
package/dist/cjs/protocol.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* This mirrors crates/server/src/protocol.rs — keep them in sync.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.MAX_SYNC_PULL_BYTES = exports.MAX_SYNC_PULL_UNITS = exports.MAX_SYNC_UNITS = exports.MAX_PARAMS = exports.MAX_COLUMNS = exports.MAX_ROWS = exports.MAX_PAYLOAD_SIZE = exports.MSG_PONG = exports.MSG_PING = exports.MSG_DISCONNECT = exports.MSG_RESULT_MSG = exports.MSG_ERROR = exports.MSG_RESULT_OK = exports.MSG_RESULT_SCALAR = exports.MSG_RESULT_ROWS = exports.MSG_SYNC_ACK_RESULT = exports.MSG_SYNC_PULL_RESULT = exports.MSG_SYNC_STATUS_RESULT = exports.MSG_SYNC_ACK = exports.MSG_SYNC_PULL = exports.MSG_SYNC_STATUS = exports.MSG_QUERY_SQL = exports.MSG_QUERY_PARAMS = exports.MSG_QUERY = exports.MSG_CONNECT_OK = exports.MSG_CONNECT = void 0;
|
|
11
|
+
exports.MAX_SYNC_PULL_BYTES = exports.MAX_SYNC_PULL_UNITS = exports.MAX_SYNC_UNITS = exports.MAX_PARAMS = exports.MAX_COLUMNS = exports.MAX_ROWS = exports.MAX_PAYLOAD_SIZE = exports.MSG_RESULT_SCALAR_NATIVE = exports.MSG_RESULT_ROWS_NATIVE = exports.MSG_QUERY_SQL_NATIVE = exports.MSG_QUERY_PARAMS_NATIVE = exports.MSG_QUERY_NATIVE = exports.MSG_PONG = exports.MSG_PING = exports.MSG_DISCONNECT = exports.MSG_RESULT_MSG = exports.MSG_ERROR = exports.MSG_RESULT_OK = exports.MSG_RESULT_SCALAR = exports.MSG_RESULT_ROWS = exports.MSG_SYNC_ACK_RESULT = exports.MSG_SYNC_PULL_RESULT = exports.MSG_SYNC_STATUS_RESULT = exports.MSG_SYNC_ACK = exports.MSG_SYNC_PULL = exports.MSG_SYNC_STATUS = exports.MSG_QUERY_SQL = exports.MSG_QUERY_PARAMS = exports.MSG_QUERY = exports.MSG_CONNECT_OK = exports.MSG_CONNECT = void 0;
|
|
12
12
|
exports.encode = encode;
|
|
13
13
|
exports.tryDecode = tryDecode;
|
|
14
14
|
exports.MSG_CONNECT = 0x01;
|
|
@@ -37,6 +37,11 @@ exports.MSG_RESULT_MSG = 0x0b;
|
|
|
37
37
|
exports.MSG_DISCONNECT = 0x10;
|
|
38
38
|
exports.MSG_PING = 0x11;
|
|
39
39
|
exports.MSG_PONG = 0x12;
|
|
40
|
+
exports.MSG_QUERY_NATIVE = 0x13;
|
|
41
|
+
exports.MSG_QUERY_PARAMS_NATIVE = 0x14;
|
|
42
|
+
exports.MSG_QUERY_SQL_NATIVE = 0x15;
|
|
43
|
+
exports.MSG_RESULT_ROWS_NATIVE = 0x16;
|
|
44
|
+
exports.MSG_RESULT_SCALAR_NATIVE = 0x17;
|
|
40
45
|
// ───── Size limits (mirror crates/server/src/protocol.rs) ──────────────────
|
|
41
46
|
/** Maximum payload size accepted from the wire (64 MB). */
|
|
42
47
|
exports.MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
|
|
@@ -85,41 +90,22 @@ function encode(msg) {
|
|
|
85
90
|
msgType = exports.MSG_QUERY_SQL;
|
|
86
91
|
break;
|
|
87
92
|
case "QueryWithParams": {
|
|
88
|
-
|
|
89
|
-
const count = Buffer.alloc(2);
|
|
90
|
-
count.writeUInt16LE(msg.params.length, 0);
|
|
91
|
-
parts.push(count);
|
|
92
|
-
for (const p of msg.params) {
|
|
93
|
-
switch (p.tag) {
|
|
94
|
-
case "null":
|
|
95
|
-
parts.push(Buffer.from([0]));
|
|
96
|
-
break;
|
|
97
|
-
case "int": {
|
|
98
|
-
const b = Buffer.alloc(9);
|
|
99
|
-
b.writeUInt8(1, 0);
|
|
100
|
-
b.writeBigInt64LE(p.value, 1);
|
|
101
|
-
parts.push(b);
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
case "float": {
|
|
105
|
-
const b = Buffer.alloc(9);
|
|
106
|
-
b.writeUInt8(2, 0);
|
|
107
|
-
b.writeDoubleLE(p.value, 1);
|
|
108
|
-
parts.push(b);
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
case "bool":
|
|
112
|
-
parts.push(Buffer.from([3, p.value ? 1 : 0]));
|
|
113
|
-
break;
|
|
114
|
-
case "str":
|
|
115
|
-
parts.push(Buffer.from([4]), encodeString(p.value));
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
payload = Buffer.concat(parts);
|
|
93
|
+
payload = encodeQueryWithParams(msg.query, msg.params);
|
|
120
94
|
msgType = exports.MSG_QUERY_PARAMS;
|
|
121
95
|
break;
|
|
122
96
|
}
|
|
97
|
+
case "QueryNative":
|
|
98
|
+
payload = encodeString(msg.query);
|
|
99
|
+
msgType = exports.MSG_QUERY_NATIVE;
|
|
100
|
+
break;
|
|
101
|
+
case "QueryWithParamsNative":
|
|
102
|
+
payload = encodeQueryWithParams(msg.query, msg.params);
|
|
103
|
+
msgType = exports.MSG_QUERY_PARAMS_NATIVE;
|
|
104
|
+
break;
|
|
105
|
+
case "QuerySqlNative":
|
|
106
|
+
payload = encodeString(msg.query);
|
|
107
|
+
msgType = exports.MSG_QUERY_SQL_NATIVE;
|
|
108
|
+
break;
|
|
123
109
|
case "SyncStatus":
|
|
124
110
|
payload = encodeString(msg.replicaId);
|
|
125
111
|
msgType = exports.MSG_SYNC_STATUS;
|
|
@@ -197,6 +183,23 @@ function encode(msg) {
|
|
|
197
183
|
payload = encodeString(msg.value);
|
|
198
184
|
msgType = exports.MSG_RESULT_SCALAR;
|
|
199
185
|
break;
|
|
186
|
+
case "ResultRowsNative": {
|
|
187
|
+
const parts = [u16LE(msg.columns.length)];
|
|
188
|
+
for (const column of msg.columns)
|
|
189
|
+
parts.push(encodeString(column));
|
|
190
|
+
parts.push(u32LE(msg.rows.length));
|
|
191
|
+
for (const row of msg.rows) {
|
|
192
|
+
for (const value of row)
|
|
193
|
+
parts.push(encodeWireValue(value));
|
|
194
|
+
}
|
|
195
|
+
payload = Buffer.concat(parts);
|
|
196
|
+
msgType = exports.MSG_RESULT_ROWS_NATIVE;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "ResultScalarNative":
|
|
200
|
+
payload = encodeWireValue(msg.value);
|
|
201
|
+
msgType = exports.MSG_RESULT_SCALAR_NATIVE;
|
|
202
|
+
break;
|
|
200
203
|
case "ResultOk": {
|
|
201
204
|
payload = Buffer.alloc(8);
|
|
202
205
|
payload.writeBigUInt64LE(msg.affected, 0);
|
|
@@ -231,6 +234,83 @@ function encode(msg) {
|
|
|
231
234
|
payload.copy(frame, 6);
|
|
232
235
|
return frame;
|
|
233
236
|
}
|
|
237
|
+
function encodeQueryWithParams(query, params) {
|
|
238
|
+
const parts = [encodeString(query), u16LE(params.length)];
|
|
239
|
+
for (const param of params) {
|
|
240
|
+
switch (param.tag) {
|
|
241
|
+
case "null":
|
|
242
|
+
parts.push(Buffer.from([0]));
|
|
243
|
+
break;
|
|
244
|
+
case "int": {
|
|
245
|
+
const body = Buffer.alloc(9);
|
|
246
|
+
body.writeUInt8(1, 0);
|
|
247
|
+
body.writeBigInt64LE(param.value, 1);
|
|
248
|
+
parts.push(body);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "float": {
|
|
252
|
+
const body = Buffer.alloc(9);
|
|
253
|
+
body.writeUInt8(2, 0);
|
|
254
|
+
body.writeDoubleLE(param.value, 1);
|
|
255
|
+
parts.push(body);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case "bool":
|
|
259
|
+
parts.push(Buffer.from([3, param.value ? 1 : 0]));
|
|
260
|
+
break;
|
|
261
|
+
case "str":
|
|
262
|
+
parts.push(Buffer.from([4]), encodeString(param.value));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Buffer.concat(parts);
|
|
267
|
+
}
|
|
268
|
+
function encodeWireValue(value) {
|
|
269
|
+
let tag;
|
|
270
|
+
let body;
|
|
271
|
+
switch (value.type) {
|
|
272
|
+
case "empty":
|
|
273
|
+
tag = 0;
|
|
274
|
+
body = Buffer.alloc(0);
|
|
275
|
+
break;
|
|
276
|
+
case "int":
|
|
277
|
+
case "datetime":
|
|
278
|
+
tag = value.type === "int" ? 1 : 5;
|
|
279
|
+
body = Buffer.alloc(8);
|
|
280
|
+
body.writeBigInt64LE(value.value, 0);
|
|
281
|
+
break;
|
|
282
|
+
case "float":
|
|
283
|
+
tag = 2;
|
|
284
|
+
body = Buffer.alloc(8);
|
|
285
|
+
body.writeDoubleLE(value.value, 0);
|
|
286
|
+
break;
|
|
287
|
+
case "bool":
|
|
288
|
+
tag = 3;
|
|
289
|
+
body = Buffer.from([value.value ? 1 : 0]);
|
|
290
|
+
break;
|
|
291
|
+
case "str":
|
|
292
|
+
tag = 4;
|
|
293
|
+
body = Buffer.from(value.value, "utf8");
|
|
294
|
+
break;
|
|
295
|
+
case "uuid":
|
|
296
|
+
if (value.value.length !== 16) {
|
|
297
|
+
throw new Error(`native UUID must be exactly 16 bytes, got ${value.value.length}`);
|
|
298
|
+
}
|
|
299
|
+
tag = 6;
|
|
300
|
+
body = Buffer.from(value.value);
|
|
301
|
+
break;
|
|
302
|
+
case "bytes":
|
|
303
|
+
tag = 7;
|
|
304
|
+
body = Buffer.from(value.value);
|
|
305
|
+
break;
|
|
306
|
+
case "json":
|
|
307
|
+
tag = 8;
|
|
308
|
+
body = Buffer.from(value.pj1);
|
|
309
|
+
decodePj1(body);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
return Buffer.concat([Buffer.from([tag]), u32LE(body.length), body]);
|
|
313
|
+
}
|
|
234
314
|
// ───── Decoding ────────────────────────────────────────────────────────────
|
|
235
315
|
/**
|
|
236
316
|
* Attempts to parse a single frame from the start of `buf`. Returns the parsed
|
|
@@ -310,6 +390,21 @@ function decodePayload(msgType, payload) {
|
|
|
310
390
|
}
|
|
311
391
|
return { type: "QueryWithParams", query, params };
|
|
312
392
|
}
|
|
393
|
+
case exports.MSG_QUERY_NATIVE: {
|
|
394
|
+
const query = decodeStringStrict(payload, cursor, "native PowQL query");
|
|
395
|
+
requirePayloadEnd(payload, cursor, "native PowQL query");
|
|
396
|
+
return { type: "QueryNative", query };
|
|
397
|
+
}
|
|
398
|
+
case exports.MSG_QUERY_PARAMS_NATIVE: {
|
|
399
|
+
const { query, params } = decodeNativeQueryWithParams(payload, cursor);
|
|
400
|
+
requirePayloadEnd(payload, cursor, "native parameterized query");
|
|
401
|
+
return { type: "QueryWithParamsNative", query, params };
|
|
402
|
+
}
|
|
403
|
+
case exports.MSG_QUERY_SQL_NATIVE: {
|
|
404
|
+
const query = decodeStringStrict(payload, cursor, "native SQL query");
|
|
405
|
+
requirePayloadEnd(payload, cursor, "native SQL query");
|
|
406
|
+
return { type: "QuerySqlNative", query };
|
|
407
|
+
}
|
|
313
408
|
case exports.MSG_SYNC_STATUS:
|
|
314
409
|
return { type: "SyncStatus", replicaId: decodeString(payload, cursor) };
|
|
315
410
|
case exports.MSG_SYNC_PULL: {
|
|
@@ -407,6 +502,42 @@ function decodePayload(msgType, payload) {
|
|
|
407
502
|
}
|
|
408
503
|
case exports.MSG_RESULT_SCALAR:
|
|
409
504
|
return { type: "ResultScalar", value: decodeString(payload, cursor) };
|
|
505
|
+
case exports.MSG_RESULT_ROWS_NATIVE: {
|
|
506
|
+
const colCount = readU16(payload, cursor, "native column count");
|
|
507
|
+
if (colCount > exports.MAX_COLUMNS) {
|
|
508
|
+
throw new Error(`too many columns: ${colCount} (max ${exports.MAX_COLUMNS})`);
|
|
509
|
+
}
|
|
510
|
+
const columns = [];
|
|
511
|
+
for (let i = 0; i < colCount; i++) {
|
|
512
|
+
columns.push(decodeStringStrict(payload, cursor, "native column name"));
|
|
513
|
+
}
|
|
514
|
+
const rowCount = readU32(payload, cursor, "native row count");
|
|
515
|
+
if (rowCount > exports.MAX_ROWS) {
|
|
516
|
+
throw new Error(`too many rows: ${rowCount} (max ${exports.MAX_ROWS})`);
|
|
517
|
+
}
|
|
518
|
+
if (colCount === 0 && rowCount > 0) {
|
|
519
|
+
throw new Error("nonzero native row count with zero columns");
|
|
520
|
+
}
|
|
521
|
+
const minimumBytes = BigInt(rowCount) * BigInt(colCount) * 5n;
|
|
522
|
+
if (BigInt(payload.length - cursor.pos) < minimumBytes) {
|
|
523
|
+
throw new Error(`native row data too short for declared shape: ${rowCount} rows x ${colCount} columns`);
|
|
524
|
+
}
|
|
525
|
+
const rows = [];
|
|
526
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
527
|
+
const row = [];
|
|
528
|
+
for (let columnIndex = 0; columnIndex < colCount; columnIndex++) {
|
|
529
|
+
row.push(decodeWireValue(payload, cursor));
|
|
530
|
+
}
|
|
531
|
+
rows.push(row);
|
|
532
|
+
}
|
|
533
|
+
requirePayloadEnd(payload, cursor, "native rows");
|
|
534
|
+
return { type: "ResultRowsNative", columns, rows };
|
|
535
|
+
}
|
|
536
|
+
case exports.MSG_RESULT_SCALAR_NATIVE: {
|
|
537
|
+
const value = decodeWireValue(payload, cursor);
|
|
538
|
+
requirePayloadEnd(payload, cursor, "native scalar");
|
|
539
|
+
return { type: "ResultScalarNative", value };
|
|
540
|
+
}
|
|
410
541
|
case exports.MSG_RESULT_OK: {
|
|
411
542
|
const affected = readU64(payload, cursor, "affected count");
|
|
412
543
|
return { type: "ResultOk", affected };
|
|
@@ -425,6 +556,94 @@ function decodePayload(msgType, payload) {
|
|
|
425
556
|
throw new Error(`unknown message type: 0x${msgType.toString(16)}`);
|
|
426
557
|
}
|
|
427
558
|
}
|
|
559
|
+
function decodeNativeQueryWithParams(payload, cursor) {
|
|
560
|
+
const query = decodeStringStrict(payload, cursor, "native query");
|
|
561
|
+
const count = readU16(payload, cursor, "native param count");
|
|
562
|
+
if (count > exports.MAX_PARAMS) {
|
|
563
|
+
throw new Error(`too many parameters: ${count} (max ${exports.MAX_PARAMS})`);
|
|
564
|
+
}
|
|
565
|
+
if (count > payload.length - cursor.pos) {
|
|
566
|
+
throw new Error("parameter count exceeds payload size");
|
|
567
|
+
}
|
|
568
|
+
const params = [];
|
|
569
|
+
for (let index = 0; index < count; index++) {
|
|
570
|
+
const tag = readU8(payload, cursor, "param tag");
|
|
571
|
+
switch (tag) {
|
|
572
|
+
case 0:
|
|
573
|
+
params.push({ tag: "null" });
|
|
574
|
+
break;
|
|
575
|
+
case 1:
|
|
576
|
+
params.push({ tag: "int", value: readI64(payload, cursor, "int param") });
|
|
577
|
+
break;
|
|
578
|
+
case 2:
|
|
579
|
+
params.push({ tag: "float", value: readF64(payload, cursor, "float param") });
|
|
580
|
+
break;
|
|
581
|
+
case 3:
|
|
582
|
+
params.push({ tag: "bool", value: readBool(payload, cursor, "bool param") });
|
|
583
|
+
break;
|
|
584
|
+
case 4:
|
|
585
|
+
params.push({
|
|
586
|
+
tag: "str",
|
|
587
|
+
value: decodeStringStrict(payload, cursor, "string param"),
|
|
588
|
+
});
|
|
589
|
+
break;
|
|
590
|
+
default:
|
|
591
|
+
throw new Error(`unknown param tag: ${tag}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return { query, params };
|
|
595
|
+
}
|
|
596
|
+
function decodeWireValue(buf, cursor) {
|
|
597
|
+
const tag = readU8(buf, cursor, "typed value tag");
|
|
598
|
+
const bodyLength = readU32(buf, cursor, "typed value body length");
|
|
599
|
+
const body = readFixedBytes(buf, cursor, bodyLength, "typed value body");
|
|
600
|
+
const requireLength = (expected, label) => {
|
|
601
|
+
if (body.length !== expected) {
|
|
602
|
+
throw new Error(`invalid ${label} typed value length: expected ${expected}, got ${body.length}`);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
switch (tag) {
|
|
606
|
+
case 0:
|
|
607
|
+
requireLength(0, "Empty");
|
|
608
|
+
return { type: "empty" };
|
|
609
|
+
case 1:
|
|
610
|
+
requireLength(8, "Int");
|
|
611
|
+
return { type: "int", value: body.readBigInt64LE(0) };
|
|
612
|
+
case 2: {
|
|
613
|
+
requireLength(8, "Float");
|
|
614
|
+
return { type: "float", value: body.readDoubleLE(0) };
|
|
615
|
+
}
|
|
616
|
+
case 3:
|
|
617
|
+
requireLength(1, "Bool");
|
|
618
|
+
if (body[0] !== 0 && body[0] !== 1) {
|
|
619
|
+
throw new Error(`invalid typed boolean: ${body[0]}`);
|
|
620
|
+
}
|
|
621
|
+
return { type: "bool", value: body[0] === 1 };
|
|
622
|
+
case 4:
|
|
623
|
+
return { type: "str", value: decodeUtf8Strict(body, "typed string") };
|
|
624
|
+
case 5:
|
|
625
|
+
requireLength(8, "DateTime");
|
|
626
|
+
return { type: "datetime", value: body.readBigInt64LE(0) };
|
|
627
|
+
case 6:
|
|
628
|
+
requireLength(16, "UUID");
|
|
629
|
+
return { type: "uuid", value: new Uint8Array(body) };
|
|
630
|
+
case 7:
|
|
631
|
+
return { type: "bytes", value: new Uint8Array(body) };
|
|
632
|
+
case 8:
|
|
633
|
+
return {
|
|
634
|
+
type: "json",
|
|
635
|
+
value: decodePj1(body),
|
|
636
|
+
pj1: new Uint8Array(body),
|
|
637
|
+
};
|
|
638
|
+
default:
|
|
639
|
+
throw new Error(`unknown typed value tag: ${tag}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function requirePayloadEnd(payload, cursor, label) {
|
|
643
|
+
if (cursor.pos !== payload.length) {
|
|
644
|
+
throw new Error(`trailing bytes in ${label} payload`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
428
647
|
function encodeSyncStatus(status) {
|
|
429
648
|
return Buffer.concat([
|
|
430
649
|
encodeString(status.replicaId),
|
|
@@ -551,6 +770,159 @@ function decodeString(buf, cursor) {
|
|
|
551
770
|
cursor.pos += len;
|
|
552
771
|
return s;
|
|
553
772
|
}
|
|
773
|
+
const strictUtf8 = new TextDecoder("utf-8", { fatal: true });
|
|
774
|
+
function decodeUtf8Strict(bytes, label) {
|
|
775
|
+
try {
|
|
776
|
+
return strictUtf8.decode(bytes);
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
throw new Error(`invalid UTF-8 in ${label}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function decodeStringStrict(buf, cursor, label) {
|
|
783
|
+
const len = readU32(buf, cursor, `${label} length`);
|
|
784
|
+
const bytes = readFixedBytes(buf, cursor, len, label);
|
|
785
|
+
return decodeUtf8Strict(bytes, label);
|
|
786
|
+
}
|
|
787
|
+
function decodePj1(doc) {
|
|
788
|
+
const [value, end] = decodePj1Node(doc, 0, 0);
|
|
789
|
+
if (end !== doc.length)
|
|
790
|
+
throw new Error("invalid typed PJ1 JSON: trailing bytes");
|
|
791
|
+
return value;
|
|
792
|
+
}
|
|
793
|
+
function decodePj1Node(doc, start, depth) {
|
|
794
|
+
if (depth > 128)
|
|
795
|
+
throw new Error("invalid typed PJ1 JSON: nesting too deep");
|
|
796
|
+
if (start >= doc.length)
|
|
797
|
+
throw new Error("invalid typed PJ1 JSON: truncated node");
|
|
798
|
+
const tag = doc[start];
|
|
799
|
+
switch (tag) {
|
|
800
|
+
case 0:
|
|
801
|
+
return [null, start + 1];
|
|
802
|
+
case 1:
|
|
803
|
+
return [false, start + 1];
|
|
804
|
+
case 2:
|
|
805
|
+
return [true, start + 1];
|
|
806
|
+
case 3: {
|
|
807
|
+
ensurePj1Range(doc, start + 1, 8);
|
|
808
|
+
const integer = doc.readBigInt64LE(start + 1);
|
|
809
|
+
const value = integer >= BigInt(Number.MIN_SAFE_INTEGER) &&
|
|
810
|
+
integer <= BigInt(Number.MAX_SAFE_INTEGER)
|
|
811
|
+
? Number(integer)
|
|
812
|
+
: integer;
|
|
813
|
+
return [value, start + 9];
|
|
814
|
+
}
|
|
815
|
+
case 4: {
|
|
816
|
+
ensurePj1Range(doc, start + 1, 8);
|
|
817
|
+
const value = doc.readDoubleLE(start + 1);
|
|
818
|
+
if (!Number.isFinite(value)) {
|
|
819
|
+
throw new Error("invalid typed PJ1 JSON: non-finite float");
|
|
820
|
+
}
|
|
821
|
+
return [value, start + 9];
|
|
822
|
+
}
|
|
823
|
+
case 5: {
|
|
824
|
+
const [value, end] = decodePj1String(doc, start + 1);
|
|
825
|
+
return [value, end];
|
|
826
|
+
}
|
|
827
|
+
case 6:
|
|
828
|
+
return decodePj1Array(doc, start, depth);
|
|
829
|
+
case 7:
|
|
830
|
+
return decodePj1Object(doc, start, depth);
|
|
831
|
+
default:
|
|
832
|
+
throw new Error(`invalid typed PJ1 JSON: reserved/invalid tag ${tag}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function decodePj1Array(doc, start, depth) {
|
|
836
|
+
const count = pj1U32(doc, start + 1);
|
|
837
|
+
const headerSize = 5 + 4 * (count + 1);
|
|
838
|
+
ensurePj1Range(doc, start, headerSize);
|
|
839
|
+
let expectedOffset = headerSize;
|
|
840
|
+
const values = [];
|
|
841
|
+
for (let index = 0; index < count; index++) {
|
|
842
|
+
const offset = pj1U32(doc, start + 5 + index * 4);
|
|
843
|
+
const nextOffset = pj1U32(doc, start + 5 + (index + 1) * 4);
|
|
844
|
+
if (offset !== expectedOffset || nextOffset < offset) {
|
|
845
|
+
throw new Error("invalid typed PJ1 JSON: array offsets not canonical");
|
|
846
|
+
}
|
|
847
|
+
const [value, end] = decodePj1Node(doc, start + offset, depth + 1);
|
|
848
|
+
if (end !== start + nextOffset) {
|
|
849
|
+
throw new Error("invalid typed PJ1 JSON: array element length mismatch");
|
|
850
|
+
}
|
|
851
|
+
values.push(value);
|
|
852
|
+
expectedOffset = nextOffset;
|
|
853
|
+
}
|
|
854
|
+
const total = pj1U32(doc, start + 5 + count * 4);
|
|
855
|
+
if (total !== expectedOffset) {
|
|
856
|
+
throw new Error("invalid typed PJ1 JSON: array end offset mismatch");
|
|
857
|
+
}
|
|
858
|
+
ensurePj1Range(doc, start, total);
|
|
859
|
+
return [values, start + total];
|
|
860
|
+
}
|
|
861
|
+
function decodePj1Object(doc, start, depth) {
|
|
862
|
+
const count = pj1U32(doc, start + 1);
|
|
863
|
+
const headerSize = 5 + count * 8 + 4;
|
|
864
|
+
ensurePj1Range(doc, start, headerSize);
|
|
865
|
+
const keys = [];
|
|
866
|
+
let expectedKeyOffset = headerSize;
|
|
867
|
+
for (let index = 0; index < count; index++) {
|
|
868
|
+
const keyOffset = pj1U32(doc, start + 5 + index * 8);
|
|
869
|
+
if (keyOffset !== expectedKeyOffset) {
|
|
870
|
+
throw new Error("invalid typed PJ1 JSON: object key offset mismatch");
|
|
871
|
+
}
|
|
872
|
+
const [text, end, bytes] = decodePj1String(doc, start + keyOffset);
|
|
873
|
+
const previous = keys[keys.length - 1];
|
|
874
|
+
if (previous && Buffer.compare(previous.bytes, bytes) >= 0) {
|
|
875
|
+
throw new Error("invalid typed PJ1 JSON: object keys not strictly sorted");
|
|
876
|
+
}
|
|
877
|
+
keys.push({ text, bytes });
|
|
878
|
+
expectedKeyOffset = end - start;
|
|
879
|
+
}
|
|
880
|
+
const result = {};
|
|
881
|
+
let expectedValueOffset = expectedKeyOffset;
|
|
882
|
+
for (let index = 0; index < count; index++) {
|
|
883
|
+
const valueOffset = pj1U32(doc, start + 5 + index * 8 + 4);
|
|
884
|
+
if (valueOffset !== expectedValueOffset) {
|
|
885
|
+
throw new Error("invalid typed PJ1 JSON: object value offset mismatch");
|
|
886
|
+
}
|
|
887
|
+
const [value, end] = decodePj1Node(doc, start + valueOffset, depth + 1);
|
|
888
|
+
const key = keys[index];
|
|
889
|
+
if (!key)
|
|
890
|
+
throw new Error("invalid typed PJ1 JSON: missing object key");
|
|
891
|
+
Object.defineProperty(result, key.text, {
|
|
892
|
+
value,
|
|
893
|
+
enumerable: true,
|
|
894
|
+
configurable: true,
|
|
895
|
+
writable: true,
|
|
896
|
+
});
|
|
897
|
+
expectedValueOffset = end - start;
|
|
898
|
+
}
|
|
899
|
+
const total = pj1U32(doc, start + 5 + count * 8);
|
|
900
|
+
if (total !== expectedValueOffset) {
|
|
901
|
+
throw new Error("invalid typed PJ1 JSON: object end offset mismatch");
|
|
902
|
+
}
|
|
903
|
+
ensurePj1Range(doc, start, total);
|
|
904
|
+
return [result, start + total];
|
|
905
|
+
}
|
|
906
|
+
function decodePj1String(doc, lengthOffset) {
|
|
907
|
+
const len = pj1U32(doc, lengthOffset);
|
|
908
|
+
const dataStart = lengthOffset + 4;
|
|
909
|
+
ensurePj1Range(doc, dataStart, len);
|
|
910
|
+
const bytes = doc.subarray(dataStart, dataStart + len);
|
|
911
|
+
return [decodeUtf8Strict(bytes, "PJ1 string"), dataStart + len, bytes];
|
|
912
|
+
}
|
|
913
|
+
function pj1U32(doc, offset) {
|
|
914
|
+
ensurePj1Range(doc, offset, 4);
|
|
915
|
+
return doc.readUInt32LE(offset);
|
|
916
|
+
}
|
|
917
|
+
function ensurePj1Range(doc, offset, len) {
|
|
918
|
+
if (!Number.isSafeInteger(offset) ||
|
|
919
|
+
!Number.isSafeInteger(len) ||
|
|
920
|
+
offset < 0 ||
|
|
921
|
+
len < 0 ||
|
|
922
|
+
offset + len > doc.length) {
|
|
923
|
+
throw new Error("invalid typed PJ1 JSON: truncated or overflowing range");
|
|
924
|
+
}
|
|
925
|
+
}
|
|
554
926
|
function readU8(buf, cursor, label) {
|
|
555
927
|
if (cursor.pos + 1 > buf.length) {
|
|
556
928
|
throw new Error(`truncated ${label}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -16,10 +16,24 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as tls from "node:tls";
|
|
18
18
|
import { EventEmitter } from "node:events";
|
|
19
|
-
import { type SyncRepairAction, type WireRetainedUnit, type WireSyncStatus } from "./protocol.js";
|
|
19
|
+
import { type NativeJson, type SyncRepairAction, type WireRetainedUnit, type WireSyncStatus, type WireValue } from "./protocol.js";
|
|
20
20
|
import { type TypedRow, type TypedSchema } from "./typed.js";
|
|
21
21
|
/** Client library version. Compared to the server's reported version. */
|
|
22
|
-
export declare const CLIENT_VERSION = "0.
|
|
22
|
+
export declare const CLIENT_VERSION = "0.14.0";
|
|
23
|
+
/**
|
|
24
|
+
* The maximum catalog format version this client can read. State this as the
|
|
25
|
+
* `catalogVersion` in sync pull requests: the server accepts any replica whose
|
|
26
|
+
* maximum is at least its active catalog format and rejects an older replica.
|
|
27
|
+
* When validating a server-reported catalog version, accept anything at or
|
|
28
|
+
* below this and reject anything newer (the client cannot read it).
|
|
29
|
+
*/
|
|
30
|
+
export declare const SUPPORTED_CATALOG_VERSION = 6;
|
|
31
|
+
/**
|
|
32
|
+
* Throw when a server-reported catalog format is newer than this client can
|
|
33
|
+
* read. Accepts `serverCatalogVersion <= SUPPORTED_CATALOG_VERSION`; rejects a
|
|
34
|
+
* newer server, which requires upgrading the client.
|
|
35
|
+
*/
|
|
36
|
+
export declare function assertServerCatalogVersionSupported(serverCatalogVersion: number, clientMax?: number): void;
|
|
23
37
|
export type QueryResult = {
|
|
24
38
|
kind: "rows";
|
|
25
39
|
columns: string[];
|
|
@@ -34,6 +48,46 @@ export type QueryResult = {
|
|
|
34
48
|
kind: "message";
|
|
35
49
|
message: string;
|
|
36
50
|
};
|
|
51
|
+
export type { NativeJson } from "./protocol.js";
|
|
52
|
+
/** A value returned by the lossless native wire surface. */
|
|
53
|
+
export type NativeValue = null | number | bigint | boolean | string | Uint8Array | NativeJson;
|
|
54
|
+
export type NativeQueryResult = {
|
|
55
|
+
kind: "rows";
|
|
56
|
+
columns: string[];
|
|
57
|
+
rows: NativeValue[][];
|
|
58
|
+
} | {
|
|
59
|
+
kind: "scalar";
|
|
60
|
+
value: NativeValue;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "ok";
|
|
63
|
+
affected: bigint;
|
|
64
|
+
} | {
|
|
65
|
+
kind: "message";
|
|
66
|
+
message: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The fully lossless result of {@link Client.queryNativeRaw}: every cell is the
|
|
70
|
+
* raw {@link WireValue} tagged union straight off the wire, with no conversion
|
|
71
|
+
* to {@link NativeValue}. Use this when you need storage-level identity that the
|
|
72
|
+
* convenience conversion erases: the raw PJ1 bytes of a JSON cell (`pj1`), or
|
|
73
|
+
* telling an absent value (`{ type: "empty" }`) apart from the string `"null"`
|
|
74
|
+
* (`{ type: "str", value: "null" }`) or a JSON null (`{ type: "json", value:
|
|
75
|
+
* null }`), all of which {@link queryNative} collapses to `null`.
|
|
76
|
+
*/
|
|
77
|
+
export type RawNativeQueryResult = {
|
|
78
|
+
kind: "rows";
|
|
79
|
+
columns: string[];
|
|
80
|
+
rows: WireValue[][];
|
|
81
|
+
} | {
|
|
82
|
+
kind: "scalar";
|
|
83
|
+
value: WireValue;
|
|
84
|
+
} | {
|
|
85
|
+
kind: "ok";
|
|
86
|
+
affected: bigint;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "message";
|
|
89
|
+
message: string;
|
|
90
|
+
};
|
|
37
91
|
/**
|
|
38
92
|
* A value bound to a positional `$N` placeholder in {@link Client.query}.
|
|
39
93
|
*
|
|
@@ -256,6 +310,32 @@ export declare class Client extends EventEmitter<ClientEvents> {
|
|
|
256
310
|
}, maybeOpts?: {
|
|
257
311
|
signal?: AbortSignal;
|
|
258
312
|
}): Promise<QueryResult>;
|
|
313
|
+
/**
|
|
314
|
+
* Run PowQL over the lossless typed wire surface. Unlike {@link query},
|
|
315
|
+
* cells are not stringified: bytes remain bytes, JSON is recursive data,
|
|
316
|
+
* and unsafe integers remain bigint. This method never retries as a legacy
|
|
317
|
+
* query, because replaying a mutation after an ambiguous response is unsafe.
|
|
318
|
+
*/
|
|
319
|
+
queryNative(query: string, paramsOrOpts?: QueryParam[] | {
|
|
320
|
+
signal?: AbortSignal;
|
|
321
|
+
}, maybeOpts?: {
|
|
322
|
+
signal?: AbortSignal;
|
|
323
|
+
}): Promise<NativeQueryResult>;
|
|
324
|
+
/**
|
|
325
|
+
* Like {@link queryNative}, but returns every cell as the raw
|
|
326
|
+
* {@link WireValue} tagged union with no conversion to {@link NativeValue}.
|
|
327
|
+
*
|
|
328
|
+
* Reach for this only when the convenience conversion would erase something
|
|
329
|
+
* you need: the raw PJ1 bytes of a JSON cell (`pj1`), or the distinction
|
|
330
|
+
* between an absent value (`{ type: "empty" }`), the string `"null"`, and a
|
|
331
|
+
* JSON null (all three of which {@link queryNative} maps to `null`). For
|
|
332
|
+
* ordinary reads, {@link queryNative} is friendlier.
|
|
333
|
+
*/
|
|
334
|
+
queryNativeRaw(query: string, paramsOrOpts?: QueryParam[] | {
|
|
335
|
+
signal?: AbortSignal;
|
|
336
|
+
}, maybeOpts?: {
|
|
337
|
+
signal?: AbortSignal;
|
|
338
|
+
}): Promise<RawNativeQueryResult>;
|
|
259
339
|
/**
|
|
260
340
|
* Run a SQL statement through the server-side SQL frontend. The plain
|
|
261
341
|
* {@link query} method remains PowQL for wire compatibility.
|
|
@@ -263,6 +343,10 @@ export declare class Client extends EventEmitter<ClientEvents> {
|
|
|
263
343
|
querySql(query: string, opts?: {
|
|
264
344
|
signal?: AbortSignal;
|
|
265
345
|
}): Promise<QueryResult>;
|
|
346
|
+
/** Run SQL through the lossless typed wire surface without legacy replay. */
|
|
347
|
+
querySqlNative(query: string, opts?: {
|
|
348
|
+
signal?: AbortSignal;
|
|
349
|
+
}): Promise<NativeQueryResult>;
|
|
266
350
|
/**
|
|
267
351
|
* Fetch primary-side sync status for one embedded replica cursor.
|
|
268
352
|
*
|
|
@@ -381,7 +465,7 @@ export declare class Client extends EventEmitter<ClientEvents> {
|
|
|
381
465
|
private onClose;
|
|
382
466
|
}
|
|
383
467
|
export { encode, tryDecode } from "./protocol.js";
|
|
384
|
-
export type { Message, SyncRepairAction, WireParam, WireRetainedUnit, WireSyncStatus, } from "./protocol.js";
|
|
468
|
+
export type { Message, SyncRepairAction, WireValue, WireParam, WireRetainedUnit, WireSyncStatus, } from "./protocol.js";
|
|
385
469
|
export { MAX_PAYLOAD_SIZE, MAX_ROWS, MAX_COLUMNS, MAX_PARAMS, MAX_SYNC_UNITS, MAX_SYNC_PULL_UNITS, MAX_SYNC_PULL_BYTES, } from "./protocol.js";
|
|
386
470
|
export { escapeLiteral, escapeIdent, ident, powql, PowqlIdent, } from "./escape.js";
|
|
387
471
|
export { Pool } from "./pool.js";
|