@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/dist/index.js CHANGED
@@ -22,7 +22,28 @@ import { PowDBError, PowDBScriptError, isPowDBError } from "./errors.js";
22
22
  import { splitStatements } from "./script.js";
23
23
  import { coerceRows, } from "./typed.js";
24
24
  /** Client library version. Compared to the server's reported version. */
25
- export const CLIENT_VERSION = "0.12.0";
25
+ export const CLIENT_VERSION = "0.14.0";
26
+ /**
27
+ * The maximum catalog format version this client can read. State this as the
28
+ * `catalogVersion` in sync pull requests: the server accepts any replica whose
29
+ * maximum is at least its active catalog format and rejects an older replica.
30
+ * When validating a server-reported catalog version, accept anything at or
31
+ * below this and reject anything newer (the client cannot read it).
32
+ */
33
+ export const SUPPORTED_CATALOG_VERSION = 6;
34
+ /**
35
+ * Throw when a server-reported catalog format is newer than this client can
36
+ * read. Accepts `serverCatalogVersion <= SUPPORTED_CATALOG_VERSION`; rejects a
37
+ * newer server, which requires upgrading the client.
38
+ */
39
+ export function assertServerCatalogVersionSupported(serverCatalogVersion, clientMax = SUPPORTED_CATALOG_VERSION) {
40
+ if (!Number.isInteger(serverCatalogVersion) || serverCatalogVersion < 1) {
41
+ throw new Error(`invalid server catalog version ${serverCatalogVersion}`);
42
+ }
43
+ if (serverCatalogVersion > clientMax) {
44
+ throw new Error(`server catalog format v${serverCatalogVersion} is newer than this client supports (max v${clientMax}); upgrade the client`);
45
+ }
46
+ }
26
47
  /** Transaction-control statements a `transactional` script may not contain. */
27
48
  const TX_CONTROL_RE = /^(begin|commit|rollback)\b/i;
28
49
  /**
@@ -54,6 +75,67 @@ function toWireParam(p) {
54
75
  throw new PowDBError(`unsupported query parameter type: ${typeof p}`, "protocol_error");
55
76
  }
56
77
  }
78
+ function fromWireValue(value) {
79
+ switch (value.type) {
80
+ case "empty":
81
+ return null;
82
+ case "int":
83
+ return value.value >= BigInt(Number.MIN_SAFE_INTEGER) &&
84
+ value.value <= BigInt(Number.MAX_SAFE_INTEGER)
85
+ ? Number(value.value)
86
+ : value.value;
87
+ case "float":
88
+ case "bool":
89
+ case "str":
90
+ return value.value;
91
+ case "datetime":
92
+ return value.value;
93
+ case "uuid": {
94
+ const hex = Array.from(value.value, (byte) => byte.toString(16).padStart(2, "0")).join("");
95
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
96
+ }
97
+ case "bytes":
98
+ return new Uint8Array(value.value);
99
+ case "json":
100
+ return value.value;
101
+ }
102
+ }
103
+ function nativeQueryResult(reply) {
104
+ switch (reply.type) {
105
+ case "ResultRowsNative":
106
+ return {
107
+ kind: "rows",
108
+ columns: reply.columns,
109
+ rows: reply.rows.map((row) => row.map(fromWireValue)),
110
+ };
111
+ case "ResultScalarNative":
112
+ return { kind: "scalar", value: fromWireValue(reply.value) };
113
+ case "ResultOk":
114
+ return { kind: "ok", affected: reply.affected };
115
+ case "ResultMessage":
116
+ return { kind: "message", message: reply.message };
117
+ case "Error":
118
+ throw new PowDBError(`query failed: ${reply.message}`, "query_failed");
119
+ default:
120
+ throw new PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
121
+ }
122
+ }
123
+ function rawNativeQueryResult(reply) {
124
+ switch (reply.type) {
125
+ case "ResultRowsNative":
126
+ return { kind: "rows", columns: reply.columns, rows: reply.rows };
127
+ case "ResultScalarNative":
128
+ return { kind: "scalar", value: reply.value };
129
+ case "ResultOk":
130
+ return { kind: "ok", affected: reply.affected };
131
+ case "ResultMessage":
132
+ return { kind: "message", message: reply.message };
133
+ case "Error":
134
+ throw new PowDBError(`query failed: ${reply.message}`, "query_failed");
135
+ default:
136
+ throw new PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
137
+ }
138
+ }
57
139
  function toU64(value, label) {
58
140
  if (typeof value === "bigint") {
59
141
  if (value < 0n || value > 0xffffffffffffffffn) {
@@ -289,6 +371,90 @@ export class Client extends EventEmitter {
289
371
  throw err;
290
372
  }
291
373
  }
374
+ /**
375
+ * Run PowQL over the lossless typed wire surface. Unlike {@link query},
376
+ * cells are not stringified: bytes remain bytes, JSON is recursive data,
377
+ * and unsafe integers remain bigint. This method never retries as a legacy
378
+ * query, because replaying a mutation after an ambiguous response is unsafe.
379
+ */
380
+ async queryNative(query, paramsOrOpts, maybeOpts) {
381
+ const hasParams = Array.isArray(paramsOrOpts);
382
+ const params = hasParams ? paramsOrOpts : undefined;
383
+ const opts = hasParams
384
+ ? maybeOpts
385
+ : paramsOrOpts;
386
+ const start = Date.now();
387
+ try {
388
+ const request = params === undefined
389
+ ? { type: "QueryNative", query }
390
+ : {
391
+ type: "QueryWithParamsNative",
392
+ query,
393
+ params: params.map(toWireParam),
394
+ };
395
+ const result = nativeQueryResult(await this.send(request, opts));
396
+ this.emit("query", {
397
+ query,
398
+ durationMs: Date.now() - start,
399
+ ok: true,
400
+ kind: result.kind,
401
+ });
402
+ return result;
403
+ }
404
+ catch (err) {
405
+ this.emit("query", {
406
+ query,
407
+ durationMs: Date.now() - start,
408
+ ok: false,
409
+ error: err,
410
+ });
411
+ throw err;
412
+ }
413
+ }
414
+ /**
415
+ * Like {@link queryNative}, but returns every cell as the raw
416
+ * {@link WireValue} tagged union with no conversion to {@link NativeValue}.
417
+ *
418
+ * Reach for this only when the convenience conversion would erase something
419
+ * you need: the raw PJ1 bytes of a JSON cell (`pj1`), or the distinction
420
+ * between an absent value (`{ type: "empty" }`), the string `"null"`, and a
421
+ * JSON null (all three of which {@link queryNative} maps to `null`). For
422
+ * ordinary reads, {@link queryNative} is friendlier.
423
+ */
424
+ async queryNativeRaw(query, paramsOrOpts, maybeOpts) {
425
+ const hasParams = Array.isArray(paramsOrOpts);
426
+ const params = hasParams ? paramsOrOpts : undefined;
427
+ const opts = hasParams
428
+ ? maybeOpts
429
+ : paramsOrOpts;
430
+ const start = Date.now();
431
+ try {
432
+ const request = params === undefined
433
+ ? { type: "QueryNative", query }
434
+ : {
435
+ type: "QueryWithParamsNative",
436
+ query,
437
+ params: params.map(toWireParam),
438
+ };
439
+ const result = rawNativeQueryResult(await this.send(request, opts));
440
+ this.emit("query", {
441
+ query,
442
+ durationMs: Date.now() - start,
443
+ ok: true,
444
+ kind: result.kind,
445
+ });
446
+ return result;
447
+ }
448
+ catch (err) {
449
+ this.emit("query", {
450
+ query,
451
+ durationMs: Date.now() - start,
452
+ ok: false,
453
+ error: err,
454
+ });
455
+ throw err;
456
+ }
457
+ }
292
458
  /**
293
459
  * Run a SQL statement through the server-side SQL frontend. The plain
294
460
  * {@link query} method remains PowQL for wire compatibility.
@@ -334,6 +500,29 @@ export class Client extends EventEmitter {
334
500
  throw err;
335
501
  }
336
502
  }
503
+ /** Run SQL through the lossless typed wire surface without legacy replay. */
504
+ async querySqlNative(query, opts) {
505
+ const start = Date.now();
506
+ try {
507
+ const result = nativeQueryResult(await this.send({ type: "QuerySqlNative", query }, opts));
508
+ this.emit("query", {
509
+ query,
510
+ durationMs: Date.now() - start,
511
+ ok: true,
512
+ kind: result.kind,
513
+ });
514
+ return result;
515
+ }
516
+ catch (err) {
517
+ this.emit("query", {
518
+ query,
519
+ durationMs: Date.now() - start,
520
+ ok: false,
521
+ error: err,
522
+ });
523
+ throw err;
524
+ }
525
+ }
337
526
  /**
338
527
  * Fetch primary-side sync status for one embedded replica cursor.
339
528
  *
@@ -32,6 +32,11 @@ export declare const MSG_RESULT_MSG = 11;
32
32
  export declare const MSG_DISCONNECT = 16;
33
33
  export declare const MSG_PING = 17;
34
34
  export declare const MSG_PONG = 18;
35
+ export declare const MSG_QUERY_NATIVE = 19;
36
+ export declare const MSG_QUERY_PARAMS_NATIVE = 20;
37
+ export declare const MSG_QUERY_SQL_NATIVE = 21;
38
+ export declare const MSG_RESULT_ROWS_NATIVE = 22;
39
+ export declare const MSG_RESULT_SCALAR_NATIVE = 23;
35
40
  /** Maximum payload size accepted from the wire (64 MB). */
36
41
  export declare const MAX_PAYLOAD_SIZE: number;
37
42
  /** Maximum number of rows allowed in a single result message. */
@@ -70,6 +75,39 @@ export type WireParam = {
70
75
  tag: "str";
71
76
  value: string;
72
77
  };
78
+ /** Recursively decoded PJ1 JSON. Integers outside JS's safe range are bigint. */
79
+ export type NativeJson = null | boolean | number | bigint | string | NativeJson[] | {
80
+ [key: string]: NativeJson;
81
+ };
82
+ /** Lossless typed cell used internally by the native result protocol. */
83
+ export type WireValue = {
84
+ type: "empty";
85
+ } | {
86
+ type: "int";
87
+ value: bigint;
88
+ } | {
89
+ type: "float";
90
+ value: number;
91
+ } | {
92
+ type: "bool";
93
+ value: boolean;
94
+ } | {
95
+ type: "str";
96
+ value: string;
97
+ } | {
98
+ type: "datetime";
99
+ value: bigint;
100
+ } | {
101
+ type: "uuid";
102
+ value: Uint8Array;
103
+ } | {
104
+ type: "bytes";
105
+ value: Uint8Array;
106
+ } | {
107
+ type: "json";
108
+ value: NativeJson;
109
+ pj1: Uint8Array;
110
+ };
73
111
  export type SyncRepairAction = "none" | "pull" | "awaitArchive" | "rebootstrap";
74
112
  export interface WireSyncStatus {
75
113
  replicaId: string;
@@ -116,6 +154,16 @@ export type Message = {
116
154
  type: "QueryWithParams";
117
155
  query: string;
118
156
  params: WireParam[];
157
+ } | {
158
+ type: "QueryNative";
159
+ query: string;
160
+ } | {
161
+ type: "QueryWithParamsNative";
162
+ query: string;
163
+ params: WireParam[];
164
+ } | {
165
+ type: "QuerySqlNative";
166
+ query: string;
119
167
  } | {
120
168
  type: "SyncStatus";
121
169
  replicaId: string;
@@ -157,6 +205,13 @@ export type Message = {
157
205
  } | {
158
206
  type: "ResultScalar";
159
207
  value: string;
208
+ } | {
209
+ type: "ResultRowsNative";
210
+ columns: string[];
211
+ rows: WireValue[][];
212
+ } | {
213
+ type: "ResultScalarNative";
214
+ value: WireValue;
160
215
  } | {
161
216
  type: "ResultOk";
162
217
  affected: bigint;