@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 - 2026-07-16
|
|
4
|
+
|
|
5
|
+
- Version-alignment release in lockstep with workspace v0.14.0 (conjunction
|
|
6
|
+
index selection, embedded typed results, read-only snapshot serving). No
|
|
7
|
+
TypeScript API changes; servers in read-only mode return a terminal
|
|
8
|
+
"readonly mode: statement requires a writer" error for mutations, which
|
|
9
|
+
surfaces through the existing error path.
|
|
10
|
+
|
|
11
|
+
## 0.13.0 - 2026-07-15
|
|
12
|
+
|
|
13
|
+
- Added the **lossless native typed result API**: `queryNative(powql)`,
|
|
14
|
+
`querySqlNative(sql)`, and low-level `queryNativeRaw(...)` returning tagged
|
|
15
|
+
`WireValue` cells. Native results preserve exact cell types: 64-bit ints as
|
|
16
|
+
`bigint`, raw `bytes`, and JSON documents as parsed values plus the exact
|
|
17
|
+
PJ1 bytes (`pj1`). Empty (SQL NULL / missing) is distinct from JSON `null`
|
|
18
|
+
and from the string `"null"` in raw results.
|
|
19
|
+
- Added native parameterized queries over the new typed request frames.
|
|
20
|
+
- Added `SUPPORTED_CATALOG_VERSION` and `assertServerCatalogVersionSupported`
|
|
21
|
+
so clients fail with a clear error against servers newer than they support.
|
|
22
|
+
- Legacy `query()` / `querySql()` string results are unchanged and remain
|
|
23
|
+
byte-compatible; see README for when to prefer the native API.
|
|
24
|
+
|
|
25
|
+
## 0.12.0 - 2026-07-14
|
|
26
|
+
|
|
27
|
+
- Typed API: `"json"` column kind for the server's native JSON (PJ1) type.
|
|
28
|
+
JSON cells arrive as canonical JSON text on the legacy protocol.
|
|
29
|
+
- No breaking changes; released in lockstep with workspace v0.12.0.
|
|
30
|
+
|
|
31
|
+
## 0.11.0 - 2026-07-13
|
|
32
|
+
|
|
33
|
+
- Version-alignment release in lockstep with workspace v0.11.0 (overflow
|
|
34
|
+
pages, grouped aggregates). No TypeScript API changes.
|
|
35
|
+
|
|
36
|
+
## 0.10.0 - 2026-07-13
|
|
37
|
+
|
|
38
|
+
- Version-alignment release in lockstep with workspace v0.10.0. No
|
|
39
|
+
TypeScript API changes.
|
|
40
|
+
|
|
41
|
+
## 0.9.0 - 2026-07-12
|
|
42
|
+
|
|
43
|
+
- Added `execScript(...)`: transactional, statement-aware script execution
|
|
44
|
+
with all-or-nothing semantics.
|
|
45
|
+
- Connect is now pipelined and eager, reducing first-query latency.
|
|
46
|
+
|
|
3
47
|
## 0.8.0 - 2026-07-02
|
|
4
48
|
|
|
5
49
|
- Released in lockstep with PowDB workspace v0.8.0 (Embedded Sync Milestone 0).
|
package/README.md
CHANGED
|
@@ -287,10 +287,96 @@ const client = await Client.connect({
|
|
|
287
287
|
});
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
##
|
|
290
|
+
## Native typed results
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
Use `queryNative` when values must cross the wire without legacy string
|
|
293
|
+
rendering:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const result = await client.queryNative(
|
|
297
|
+
"Post filter .id = $1 { .id, .data, raw: .attachment }",
|
|
298
|
+
[42],
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (result.kind === "rows") {
|
|
302
|
+
const [id, data, raw] = result.rows[0];
|
|
303
|
+
// id: number, or bigint outside JavaScript's safe integer range
|
|
304
|
+
// data: recursively decoded JSON
|
|
305
|
+
// raw: Uint8Array
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const sqlResult = await client.querySqlNative(
|
|
309
|
+
"SELECT data->'author' FROM Post WHERE id = 42",
|
|
310
|
+
);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Native results preserve each cell's actual type. Integers become `number` when
|
|
314
|
+
safe and `bigint` otherwise; floats and booleans remain primitives; `bytes`
|
|
315
|
+
become `Uint8Array`; UUIDs become canonical strings; datetimes remain epoch
|
|
316
|
+
microseconds as `bigint`; and PJ1 JSON is decoded recursively without a JSON
|
|
317
|
+
text round trip. Empty cells become `null`.
|
|
318
|
+
|
|
319
|
+
`queryNative` accepts the same `$N` parameter array and `AbortSignal` options
|
|
320
|
+
as `query`. `querySqlNative` is the corresponding SQL method. Neither method
|
|
321
|
+
silently replays a request through the legacy string protocol, because replay
|
|
322
|
+
would be unsafe for a mutation after an ambiguous response.
|
|
323
|
+
|
|
324
|
+
Legacy `query` and `querySql` keep their existing string result shapes for
|
|
325
|
+
wire compatibility.
|
|
326
|
+
|
|
327
|
+
### Fully lossless access with `queryNativeRaw`
|
|
328
|
+
|
|
329
|
+
`queryNative` is friendly, but its convenience conversion erases a few
|
|
330
|
+
storage-level distinctions: it maps every empty cell, JSON `null`, and even a
|
|
331
|
+
successful decode to a plain JavaScript `null`, and it drops the raw PJ1 bytes
|
|
332
|
+
that back a JSON value. When you need that fidelity, use `queryNativeRaw`, which
|
|
333
|
+
returns every cell as the raw `WireValue` tagged union straight off the wire:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const raw = await client.queryNativeRaw(
|
|
337
|
+
"Doc filter .id = $1 { .note, .missing, .data }",
|
|
338
|
+
[1],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
if (raw.kind === "rows") {
|
|
342
|
+
const [note, missing, data] = raw.rows[0];
|
|
343
|
+
// note: { type: "str", value: "null" } : the string "null"
|
|
344
|
+
// missing: { type: "empty" } : an absent value
|
|
345
|
+
// data: { type: "json", value: null, pj1: Uint8Array } : a JSON null
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Reach for `queryNativeRaw` when you need:
|
|
350
|
+
|
|
351
|
+
- **Storage-level identity**: telling an absent value (`{ type: "empty" }`)
|
|
352
|
+
apart from the string `"null"` and a JSON `null`, all of which `queryNative`
|
|
353
|
+
collapses to `null`.
|
|
354
|
+
- **Raw PJ1 bytes**: the `pj1: Uint8Array` on a `json` cell is the exact
|
|
355
|
+
on-wire binary JSON, for byte-identical storage, hashing, or re-encoding.
|
|
356
|
+
|
|
357
|
+
It accepts the same `$N` parameter array and `AbortSignal` options as
|
|
358
|
+
`queryNative`. For ordinary reads, prefer `queryNative`.
|
|
359
|
+
|
|
360
|
+
### Null vs missing: which API sees what
|
|
361
|
+
|
|
362
|
+
The legacy string protocol renders an absent value, a JSON `null`, and the
|
|
363
|
+
string `"null"` as the identical string `null`. This is frozen wire behavior:
|
|
364
|
+
existing decoders depend on it, so it will not change. If your application
|
|
365
|
+
(or a driver you maintain) needs to tell these apart, migrate reads to
|
|
366
|
+
`queryNativeRaw`, which is the sanctioned lossless surface.
|
|
367
|
+
|
|
368
|
+
One engine-level caveat applies to every API, native included: extracting a
|
|
369
|
+
scalar with `->` (for example `.data->maybe->leaf`) collapses "path missing"
|
|
370
|
+
and "explicit JSON null at the leaf" into the same empty value before results
|
|
371
|
+
are produced. When that distinction matters, either project the enclosing
|
|
372
|
+
JSON value (a `json` cell preserves `null` exactly) or ask the engine with
|
|
373
|
+
`json_type(.data->maybe->leaf)`, which returns the string `"null"` for an
|
|
374
|
+
explicit JSON null and an empty value for a missing path.
|
|
375
|
+
|
|
376
|
+
## Schema-coerced rows
|
|
377
|
+
|
|
378
|
+
The legacy wire protocol serialises every value as a string. If you want JS
|
|
379
|
+
types back using a caller-supplied schema, call `queryTyped`:
|
|
294
380
|
|
|
295
381
|
```typescript
|
|
296
382
|
import { Client } from "@zvndev/powdb-client";
|
|
@@ -323,9 +409,8 @@ does not throw: it returns the raw string, so a malformed or non-canonical
|
|
|
323
409
|
cell can never turn a readable result into an exception (canonical server
|
|
324
410
|
output always parses, so this fallback is inert in normal use).
|
|
325
411
|
|
|
326
|
-
Bytes columns are intentionally unsupported
|
|
327
|
-
|
|
328
|
-
want the placeholder.
|
|
412
|
+
Bytes columns are intentionally unsupported by `queryTyped` because the
|
|
413
|
+
legacy wire format renders `<N bytes>`. Use `queryNative` for exact bytes.
|
|
329
414
|
|
|
330
415
|
## Structured errors
|
|
331
416
|
|
|
@@ -486,6 +571,27 @@ AbortSignal` cancels the query (see Cancellation above).
|
|
|
486
571
|
const result = await client.querySql("SELECT name, age FROM User WHERE age > 27");
|
|
487
572
|
```
|
|
488
573
|
|
|
574
|
+
### `client.queryNative(query, params?, opts?)`
|
|
575
|
+
|
|
576
|
+
Runs PowQL through the native typed wire surface and returns a
|
|
577
|
+
`Promise<NativeQueryResult>`. Row and scalar cells retain their actual types,
|
|
578
|
+
including exact bytes and recursively decoded JSON. Parameters and abort
|
|
579
|
+
options match `query()`. The method does not replay through the legacy string
|
|
580
|
+
surface.
|
|
581
|
+
|
|
582
|
+
### `client.queryNativeRaw(query, params?, opts?)`
|
|
583
|
+
|
|
584
|
+
Like `queryNative`, but returns every cell as the raw `WireValue` tagged union
|
|
585
|
+
(`Promise<RawNativeQueryResult>`) with no conversion to `NativeValue`. Use it
|
|
586
|
+
for storage-level identity (Empty vs `"null"` vs a JSON null) or the raw PJ1
|
|
587
|
+
bytes of a JSON cell (`pj1`). Parameters and abort options match `queryNative`.
|
|
588
|
+
|
|
589
|
+
### `client.querySqlNative(query, opts?)`
|
|
590
|
+
|
|
591
|
+
Runs SQL through the native typed wire surface and returns a
|
|
592
|
+
`Promise<NativeQueryResult>`. It has the same no-replay guarantee as
|
|
593
|
+
`queryNative()`.
|
|
594
|
+
|
|
489
595
|
### `client.queryTyped(query, schema, opts?)`
|
|
490
596
|
|
|
491
597
|
Like `query()`, but coerces each row's string values to JS types using the
|
package/dist/cjs/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";
|
package/dist/cjs/index.js
CHANGED
|
@@ -49,7 +49,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
49
49
|
};
|
|
50
50
|
})();
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
-
exports.coerceRows = exports.coerceRow = exports.coerceValue = exports.isPowDBScriptError = exports.PowDBScriptError = exports.isPowDBError = exports.PowDBError = exports.splitStatements = exports.Pool = exports.PowqlIdent = exports.powql = exports.ident = exports.escapeIdent = exports.escapeLiteral = 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.tryDecode = exports.encode = exports.Client = exports.CLIENT_VERSION = void 0;
|
|
52
|
+
exports.coerceRows = exports.coerceRow = exports.coerceValue = exports.isPowDBScriptError = exports.PowDBScriptError = exports.isPowDBError = exports.PowDBError = exports.splitStatements = exports.Pool = exports.PowqlIdent = exports.powql = exports.ident = exports.escapeIdent = exports.escapeLiteral = 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.tryDecode = exports.encode = exports.Client = exports.SUPPORTED_CATALOG_VERSION = exports.CLIENT_VERSION = void 0;
|
|
53
|
+
exports.assertServerCatalogVersionSupported = assertServerCatalogVersionSupported;
|
|
53
54
|
const net = __importStar(require("node:net"));
|
|
54
55
|
const tls = __importStar(require("node:tls"));
|
|
55
56
|
const node_events_1 = require("node:events");
|
|
@@ -58,7 +59,28 @@ const errors_js_1 = require("./errors.js");
|
|
|
58
59
|
const script_js_1 = require("./script.js");
|
|
59
60
|
const typed_js_1 = require("./typed.js");
|
|
60
61
|
/** Client library version. Compared to the server's reported version. */
|
|
61
|
-
exports.CLIENT_VERSION = "0.
|
|
62
|
+
exports.CLIENT_VERSION = "0.14.0";
|
|
63
|
+
/**
|
|
64
|
+
* The maximum catalog format version this client can read. State this as the
|
|
65
|
+
* `catalogVersion` in sync pull requests: the server accepts any replica whose
|
|
66
|
+
* maximum is at least its active catalog format and rejects an older replica.
|
|
67
|
+
* When validating a server-reported catalog version, accept anything at or
|
|
68
|
+
* below this and reject anything newer (the client cannot read it).
|
|
69
|
+
*/
|
|
70
|
+
exports.SUPPORTED_CATALOG_VERSION = 6;
|
|
71
|
+
/**
|
|
72
|
+
* Throw when a server-reported catalog format is newer than this client can
|
|
73
|
+
* read. Accepts `serverCatalogVersion <= SUPPORTED_CATALOG_VERSION`; rejects a
|
|
74
|
+
* newer server, which requires upgrading the client.
|
|
75
|
+
*/
|
|
76
|
+
function assertServerCatalogVersionSupported(serverCatalogVersion, clientMax = exports.SUPPORTED_CATALOG_VERSION) {
|
|
77
|
+
if (!Number.isInteger(serverCatalogVersion) || serverCatalogVersion < 1) {
|
|
78
|
+
throw new Error(`invalid server catalog version ${serverCatalogVersion}`);
|
|
79
|
+
}
|
|
80
|
+
if (serverCatalogVersion > clientMax) {
|
|
81
|
+
throw new Error(`server catalog format v${serverCatalogVersion} is newer than this client supports (max v${clientMax}); upgrade the client`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
62
84
|
/** Transaction-control statements a `transactional` script may not contain. */
|
|
63
85
|
const TX_CONTROL_RE = /^(begin|commit|rollback)\b/i;
|
|
64
86
|
/**
|
|
@@ -90,6 +112,67 @@ function toWireParam(p) {
|
|
|
90
112
|
throw new errors_js_1.PowDBError(`unsupported query parameter type: ${typeof p}`, "protocol_error");
|
|
91
113
|
}
|
|
92
114
|
}
|
|
115
|
+
function fromWireValue(value) {
|
|
116
|
+
switch (value.type) {
|
|
117
|
+
case "empty":
|
|
118
|
+
return null;
|
|
119
|
+
case "int":
|
|
120
|
+
return value.value >= BigInt(Number.MIN_SAFE_INTEGER) &&
|
|
121
|
+
value.value <= BigInt(Number.MAX_SAFE_INTEGER)
|
|
122
|
+
? Number(value.value)
|
|
123
|
+
: value.value;
|
|
124
|
+
case "float":
|
|
125
|
+
case "bool":
|
|
126
|
+
case "str":
|
|
127
|
+
return value.value;
|
|
128
|
+
case "datetime":
|
|
129
|
+
return value.value;
|
|
130
|
+
case "uuid": {
|
|
131
|
+
const hex = Array.from(value.value, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
132
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
133
|
+
}
|
|
134
|
+
case "bytes":
|
|
135
|
+
return new Uint8Array(value.value);
|
|
136
|
+
case "json":
|
|
137
|
+
return value.value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function nativeQueryResult(reply) {
|
|
141
|
+
switch (reply.type) {
|
|
142
|
+
case "ResultRowsNative":
|
|
143
|
+
return {
|
|
144
|
+
kind: "rows",
|
|
145
|
+
columns: reply.columns,
|
|
146
|
+
rows: reply.rows.map((row) => row.map(fromWireValue)),
|
|
147
|
+
};
|
|
148
|
+
case "ResultScalarNative":
|
|
149
|
+
return { kind: "scalar", value: fromWireValue(reply.value) };
|
|
150
|
+
case "ResultOk":
|
|
151
|
+
return { kind: "ok", affected: reply.affected };
|
|
152
|
+
case "ResultMessage":
|
|
153
|
+
return { kind: "message", message: reply.message };
|
|
154
|
+
case "Error":
|
|
155
|
+
throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, "query_failed");
|
|
156
|
+
default:
|
|
157
|
+
throw new errors_js_1.PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function rawNativeQueryResult(reply) {
|
|
161
|
+
switch (reply.type) {
|
|
162
|
+
case "ResultRowsNative":
|
|
163
|
+
return { kind: "rows", columns: reply.columns, rows: reply.rows };
|
|
164
|
+
case "ResultScalarNative":
|
|
165
|
+
return { kind: "scalar", value: reply.value };
|
|
166
|
+
case "ResultOk":
|
|
167
|
+
return { kind: "ok", affected: reply.affected };
|
|
168
|
+
case "ResultMessage":
|
|
169
|
+
return { kind: "message", message: reply.message };
|
|
170
|
+
case "Error":
|
|
171
|
+
throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, "query_failed");
|
|
172
|
+
default:
|
|
173
|
+
throw new errors_js_1.PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
93
176
|
function toU64(value, label) {
|
|
94
177
|
if (typeof value === "bigint") {
|
|
95
178
|
if (value < 0n || value > 0xffffffffffffffffn) {
|
|
@@ -325,6 +408,90 @@ class Client extends node_events_1.EventEmitter {
|
|
|
325
408
|
throw err;
|
|
326
409
|
}
|
|
327
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Run PowQL over the lossless typed wire surface. Unlike {@link query},
|
|
413
|
+
* cells are not stringified: bytes remain bytes, JSON is recursive data,
|
|
414
|
+
* and unsafe integers remain bigint. This method never retries as a legacy
|
|
415
|
+
* query, because replaying a mutation after an ambiguous response is unsafe.
|
|
416
|
+
*/
|
|
417
|
+
async queryNative(query, paramsOrOpts, maybeOpts) {
|
|
418
|
+
const hasParams = Array.isArray(paramsOrOpts);
|
|
419
|
+
const params = hasParams ? paramsOrOpts : undefined;
|
|
420
|
+
const opts = hasParams
|
|
421
|
+
? maybeOpts
|
|
422
|
+
: paramsOrOpts;
|
|
423
|
+
const start = Date.now();
|
|
424
|
+
try {
|
|
425
|
+
const request = params === undefined
|
|
426
|
+
? { type: "QueryNative", query }
|
|
427
|
+
: {
|
|
428
|
+
type: "QueryWithParamsNative",
|
|
429
|
+
query,
|
|
430
|
+
params: params.map(toWireParam),
|
|
431
|
+
};
|
|
432
|
+
const result = nativeQueryResult(await this.send(request, opts));
|
|
433
|
+
this.emit("query", {
|
|
434
|
+
query,
|
|
435
|
+
durationMs: Date.now() - start,
|
|
436
|
+
ok: true,
|
|
437
|
+
kind: result.kind,
|
|
438
|
+
});
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
this.emit("query", {
|
|
443
|
+
query,
|
|
444
|
+
durationMs: Date.now() - start,
|
|
445
|
+
ok: false,
|
|
446
|
+
error: err,
|
|
447
|
+
});
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Like {@link queryNative}, but returns every cell as the raw
|
|
453
|
+
* {@link WireValue} tagged union with no conversion to {@link NativeValue}.
|
|
454
|
+
*
|
|
455
|
+
* Reach for this only when the convenience conversion would erase something
|
|
456
|
+
* you need: the raw PJ1 bytes of a JSON cell (`pj1`), or the distinction
|
|
457
|
+
* between an absent value (`{ type: "empty" }`), the string `"null"`, and a
|
|
458
|
+
* JSON null (all three of which {@link queryNative} maps to `null`). For
|
|
459
|
+
* ordinary reads, {@link queryNative} is friendlier.
|
|
460
|
+
*/
|
|
461
|
+
async queryNativeRaw(query, paramsOrOpts, maybeOpts) {
|
|
462
|
+
const hasParams = Array.isArray(paramsOrOpts);
|
|
463
|
+
const params = hasParams ? paramsOrOpts : undefined;
|
|
464
|
+
const opts = hasParams
|
|
465
|
+
? maybeOpts
|
|
466
|
+
: paramsOrOpts;
|
|
467
|
+
const start = Date.now();
|
|
468
|
+
try {
|
|
469
|
+
const request = params === undefined
|
|
470
|
+
? { type: "QueryNative", query }
|
|
471
|
+
: {
|
|
472
|
+
type: "QueryWithParamsNative",
|
|
473
|
+
query,
|
|
474
|
+
params: params.map(toWireParam),
|
|
475
|
+
};
|
|
476
|
+
const result = rawNativeQueryResult(await this.send(request, opts));
|
|
477
|
+
this.emit("query", {
|
|
478
|
+
query,
|
|
479
|
+
durationMs: Date.now() - start,
|
|
480
|
+
ok: true,
|
|
481
|
+
kind: result.kind,
|
|
482
|
+
});
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
this.emit("query", {
|
|
487
|
+
query,
|
|
488
|
+
durationMs: Date.now() - start,
|
|
489
|
+
ok: false,
|
|
490
|
+
error: err,
|
|
491
|
+
});
|
|
492
|
+
throw err;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
328
495
|
/**
|
|
329
496
|
* Run a SQL statement through the server-side SQL frontend. The plain
|
|
330
497
|
* {@link query} method remains PowQL for wire compatibility.
|
|
@@ -370,6 +537,29 @@ class Client extends node_events_1.EventEmitter {
|
|
|
370
537
|
throw err;
|
|
371
538
|
}
|
|
372
539
|
}
|
|
540
|
+
/** Run SQL through the lossless typed wire surface without legacy replay. */
|
|
541
|
+
async querySqlNative(query, opts) {
|
|
542
|
+
const start = Date.now();
|
|
543
|
+
try {
|
|
544
|
+
const result = nativeQueryResult(await this.send({ type: "QuerySqlNative", query }, opts));
|
|
545
|
+
this.emit("query", {
|
|
546
|
+
query,
|
|
547
|
+
durationMs: Date.now() - start,
|
|
548
|
+
ok: true,
|
|
549
|
+
kind: result.kind,
|
|
550
|
+
});
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
catch (err) {
|
|
554
|
+
this.emit("query", {
|
|
555
|
+
query,
|
|
556
|
+
durationMs: Date.now() - start,
|
|
557
|
+
ok: false,
|
|
558
|
+
error: err,
|
|
559
|
+
});
|
|
560
|
+
throw err;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
373
563
|
/**
|
|
374
564
|
* Fetch primary-side sync status for one embedded replica cursor.
|
|
375
565
|
*
|
package/dist/cjs/protocol.d.ts
CHANGED
|
@@ -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;
|