@zvndev/powdb-client 0.9.0 → 0.11.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/cjs/errors.d.ts +70 -0
- package/dist/cjs/errors.js +68 -0
- package/dist/cjs/escape.d.ts +54 -0
- package/dist/cjs/escape.js +131 -0
- package/dist/cjs/index.d.ts +393 -0
- package/dist/cjs/index.js +1046 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pool.d.ts +140 -0
- package/dist/cjs/pool.js +335 -0
- package/dist/cjs/protocol.d.ts +185 -0
- package/dist/cjs/protocol.js +645 -0
- package/dist/cjs/script.d.ts +25 -0
- package/dist/cjs/script.js +69 -0
- package/dist/cjs/typed.d.ts +49 -0
- package/dist/cjs/typed.js +105 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +15 -6
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Statement-aware PowQL script splitting.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the server-side splitter (`powdb_query::lexer::split_statements`)
|
|
6
|
+
* exactly, so a script file behaves the same whether it is fed to the CLI's
|
|
7
|
+
* `--exec` / `.powql` path or to {@link Client.execScript} over the wire:
|
|
8
|
+
*
|
|
9
|
+
* - `;` splits statements only at the top level.
|
|
10
|
+
* - `;` inside a `"..."` string literal never splits. A backslash inside a
|
|
11
|
+
* string consumes the next character unconditionally (mirroring the
|
|
12
|
+
* PowQL lexer), so `\"` and `\;` stay inside the string.
|
|
13
|
+
* - `#` starts a comment that runs to end-of-line; a `;` inside a comment
|
|
14
|
+
* never splits.
|
|
15
|
+
* - Segments are trimmed; empty segments (leading/doubled/trailing `;`,
|
|
16
|
+
* blank lines) are dropped.
|
|
17
|
+
* - Never errors: an unterminated string simply makes the rest of the
|
|
18
|
+
* input the final segment.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.splitStatements = splitStatements;
|
|
22
|
+
/**
|
|
23
|
+
* Split a PowQL script into individual statements.
|
|
24
|
+
*
|
|
25
|
+
* String-literal- and `#`-comment-aware `;` splitting with the exact
|
|
26
|
+
* semantics of the CLI/server splitter (see module docs above).
|
|
27
|
+
*/
|
|
28
|
+
function splitStatements(input) {
|
|
29
|
+
const out = [];
|
|
30
|
+
let start = 0;
|
|
31
|
+
let state = "normal";
|
|
32
|
+
for (let i = 0; i < input.length; i++) {
|
|
33
|
+
const c = input[i];
|
|
34
|
+
switch (state) {
|
|
35
|
+
case "normal":
|
|
36
|
+
if (c === ";") {
|
|
37
|
+
const seg = input.slice(start, i).trim();
|
|
38
|
+
if (seg.length > 0)
|
|
39
|
+
out.push(seg);
|
|
40
|
+
start = i + 1;
|
|
41
|
+
}
|
|
42
|
+
else if (c === '"') {
|
|
43
|
+
state = "in-string";
|
|
44
|
+
}
|
|
45
|
+
else if (c === "#") {
|
|
46
|
+
state = "in-comment";
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "in-string":
|
|
50
|
+
// Mirror the lexer: a backslash consumes the next char
|
|
51
|
+
// unconditionally, so `\"` and `\;` stay inside the string.
|
|
52
|
+
if (c === "\\") {
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
else if (c === '"') {
|
|
56
|
+
state = "normal";
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
case "in-comment":
|
|
60
|
+
if (c === "\n")
|
|
61
|
+
state = "normal";
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const seg = input.slice(start).trim();
|
|
66
|
+
if (seg.length > 0)
|
|
67
|
+
out.push(seg);
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional row-level type coercion for `queryTyped`.
|
|
3
|
+
*
|
|
4
|
+
* The server's wire format serialises every value to a string (see
|
|
5
|
+
* `crates/server/src/handler.rs::value_to_display`):
|
|
6
|
+
* Int → decimal digits, e.g. "42" or "-7"
|
|
7
|
+
* Float → Rust `{}` format, e.g. "3.14"
|
|
8
|
+
* Bool → "true" / "false"
|
|
9
|
+
* Str → raw UTF-8 (may contain anything)
|
|
10
|
+
* DateTime → microseconds since Unix epoch as decimal, e.g. "1718928000000000"
|
|
11
|
+
* Uuid → canonical "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
12
|
+
* Bytes → "<N bytes>" (LOSSY — see note below)
|
|
13
|
+
*
|
|
14
|
+
* `queryTyped` takes a user-supplied schema and coerces each column string
|
|
15
|
+
* into the corresponding JS value. Rationale for requiring the caller to
|
|
16
|
+
* supply the schema (rather than introspecting): PowDB has no `describe`
|
|
17
|
+
* statement yet, and going via `select` off a system catalog is one extra
|
|
18
|
+
* round trip per typed query. When schema introspection lands, a helper
|
|
19
|
+
* that builds the schema object can be added without changing this API.
|
|
20
|
+
*
|
|
21
|
+
* Bytes caveat: the current wire format is lossy for byte columns (renders
|
|
22
|
+
* `<N bytes>`). Until the wire protocol grows a binary column type, this
|
|
23
|
+
* module throws on `bytes` columns rather than guessing — use `str` in the
|
|
24
|
+
* schema if you just want the `<N bytes>` placeholder.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Supported column types. Mirrors the server's `DataType` enum minus the
|
|
28
|
+
* `Bytes` variant, which is intentionally unsupported here.
|
|
29
|
+
*/
|
|
30
|
+
export type ColumnType = "int" | "float" | "bool" | "str" | "datetime" | "uuid";
|
|
31
|
+
/** Map of column name → declared type. Columns not in the map pass through as strings. */
|
|
32
|
+
export type TypedSchema = Record<string, ColumnType>;
|
|
33
|
+
/** Coerced value type. `int` may return `bigint` if the value exceeds Number.MAX_SAFE_INTEGER. */
|
|
34
|
+
export type Coerced = number | bigint | boolean | string | Date | null;
|
|
35
|
+
/** A row of coerced values keyed by column name. */
|
|
36
|
+
export type TypedRow = Record<string, Coerced>;
|
|
37
|
+
/**
|
|
38
|
+
* Coerce a single string value to the JS type declared in `schema`. `null`
|
|
39
|
+
* is returned for the conventional null sentinel — see note at call site.
|
|
40
|
+
* Unknown column types fall through as strings.
|
|
41
|
+
*/
|
|
42
|
+
export declare function coerceValue(column: string, raw: string, type: ColumnType | undefined): Coerced;
|
|
43
|
+
/**
|
|
44
|
+
* Coerce a single result row (a string tuple) into a typed object keyed by
|
|
45
|
+
* column name.
|
|
46
|
+
*/
|
|
47
|
+
export declare function coerceRow(columns: string[], values: string[], schema: TypedSchema): TypedRow;
|
|
48
|
+
/** Coerce every row in a rows-result into typed objects. */
|
|
49
|
+
export declare function coerceRows(columns: string[], rows: string[][], schema: TypedSchema): TypedRow[];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Optional row-level type coercion for `queryTyped`.
|
|
4
|
+
*
|
|
5
|
+
* The server's wire format serialises every value to a string (see
|
|
6
|
+
* `crates/server/src/handler.rs::value_to_display`):
|
|
7
|
+
* Int → decimal digits, e.g. "42" or "-7"
|
|
8
|
+
* Float → Rust `{}` format, e.g. "3.14"
|
|
9
|
+
* Bool → "true" / "false"
|
|
10
|
+
* Str → raw UTF-8 (may contain anything)
|
|
11
|
+
* DateTime → microseconds since Unix epoch as decimal, e.g. "1718928000000000"
|
|
12
|
+
* Uuid → canonical "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
13
|
+
* Bytes → "<N bytes>" (LOSSY — see note below)
|
|
14
|
+
*
|
|
15
|
+
* `queryTyped` takes a user-supplied schema and coerces each column string
|
|
16
|
+
* into the corresponding JS value. Rationale for requiring the caller to
|
|
17
|
+
* supply the schema (rather than introspecting): PowDB has no `describe`
|
|
18
|
+
* statement yet, and going via `select` off a system catalog is one extra
|
|
19
|
+
* round trip per typed query. When schema introspection lands, a helper
|
|
20
|
+
* that builds the schema object can be added without changing this API.
|
|
21
|
+
*
|
|
22
|
+
* Bytes caveat: the current wire format is lossy for byte columns (renders
|
|
23
|
+
* `<N bytes>`). Until the wire protocol grows a binary column type, this
|
|
24
|
+
* module throws on `bytes` columns rather than guessing — use `str` in the
|
|
25
|
+
* schema if you just want the `<N bytes>` placeholder.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.coerceValue = coerceValue;
|
|
29
|
+
exports.coerceRow = coerceRow;
|
|
30
|
+
exports.coerceRows = coerceRows;
|
|
31
|
+
const errors_js_1 = require("./errors.js");
|
|
32
|
+
/**
|
|
33
|
+
* Coerce a single string value to the JS type declared in `schema`. `null`
|
|
34
|
+
* is returned for the conventional null sentinel — see note at call site.
|
|
35
|
+
* Unknown column types fall through as strings.
|
|
36
|
+
*/
|
|
37
|
+
function coerceValue(column, raw, type) {
|
|
38
|
+
// The server's wire format distinguishes NULL from empty string by the
|
|
39
|
+
// literal "null" bareword in scalar displays. For typed columns we treat
|
|
40
|
+
// the single token "null" as NULL; empty string remains empty string for
|
|
41
|
+
// `str` columns (can be an intentional empty value).
|
|
42
|
+
if (type !== "str" && raw === "null")
|
|
43
|
+
return null;
|
|
44
|
+
switch (type) {
|
|
45
|
+
case undefined:
|
|
46
|
+
case "str":
|
|
47
|
+
return raw;
|
|
48
|
+
case "int": {
|
|
49
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
50
|
+
throw new errors_js_1.PowDBError(`queryTyped: column "${column}" declared int but got ${JSON.stringify(raw)}`, "type_coercion_failed");
|
|
51
|
+
}
|
|
52
|
+
// Promote to BigInt when the value doesn't round-trip through Number.
|
|
53
|
+
const n = Number(raw);
|
|
54
|
+
if (Number.isSafeInteger(n))
|
|
55
|
+
return n;
|
|
56
|
+
return BigInt(raw);
|
|
57
|
+
}
|
|
58
|
+
case "float": {
|
|
59
|
+
const n = Number(raw);
|
|
60
|
+
if (!Number.isFinite(n)) {
|
|
61
|
+
throw new errors_js_1.PowDBError(`queryTyped: column "${column}" declared float but got ${JSON.stringify(raw)}`, "type_coercion_failed");
|
|
62
|
+
}
|
|
63
|
+
return n;
|
|
64
|
+
}
|
|
65
|
+
case "bool":
|
|
66
|
+
if (raw === "true")
|
|
67
|
+
return true;
|
|
68
|
+
if (raw === "false")
|
|
69
|
+
return false;
|
|
70
|
+
throw new errors_js_1.PowDBError(`queryTyped: column "${column}" declared bool but got ${JSON.stringify(raw)}`, "type_coercion_failed");
|
|
71
|
+
case "datetime": {
|
|
72
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
73
|
+
throw new errors_js_1.PowDBError(`queryTyped: column "${column}" declared datetime but got ${JSON.stringify(raw)} (expected microseconds since epoch)`, "type_coercion_failed");
|
|
74
|
+
}
|
|
75
|
+
// Server sends microseconds since epoch. JS Date uses milliseconds.
|
|
76
|
+
// BigInt division avoids float precision loss for far-future dates.
|
|
77
|
+
const micros = BigInt(raw);
|
|
78
|
+
const millis = Number(micros / 1000n);
|
|
79
|
+
return new Date(millis);
|
|
80
|
+
}
|
|
81
|
+
case "uuid": {
|
|
82
|
+
// Canonical 8-4-4-4-12 hex, lowercase (per server format).
|
|
83
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(raw)) {
|
|
84
|
+
throw new errors_js_1.PowDBError(`queryTyped: column "${column}" declared uuid but got ${JSON.stringify(raw)}`, "type_coercion_failed");
|
|
85
|
+
}
|
|
86
|
+
return raw;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Coerce a single result row (a string tuple) into a typed object keyed by
|
|
92
|
+
* column name.
|
|
93
|
+
*/
|
|
94
|
+
function coerceRow(columns, values, schema) {
|
|
95
|
+
const out = {};
|
|
96
|
+
for (let i = 0; i < columns.length; i++) {
|
|
97
|
+
const col = columns[i];
|
|
98
|
+
out[col] = coerceValue(col, values[i] ?? "null", schema[col]);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
/** Coerce every row in a rows-result into typed objects. */
|
|
103
|
+
function coerceRows(columns, rows, schema) {
|
|
104
|
+
return rows.map((r) => coerceRow(columns, r, schema));
|
|
105
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { EventEmitter } from "node:events";
|
|
|
19
19
|
import { type SyncRepairAction, type WireRetainedUnit, type WireSyncStatus } 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.11.0";
|
|
23
23
|
export type QueryResult = {
|
|
24
24
|
kind: "rows";
|
|
25
25
|
columns: string[];
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ 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.
|
|
25
|
+
export const CLIENT_VERSION = "0.11.0";
|
|
26
26
|
/** Transaction-control statements a `transactional` script may not contain. */
|
|
27
27
|
const TX_CONTROL_RE = /^(begin|commit|rollback)\b/i;
|
|
28
28
|
/**
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zvndev/powdb-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "TypeScript client for PowDB (PowQL wire protocol)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.29.3",
|
|
7
|
-
"main": "dist/index.js",
|
|
8
|
-
"
|
|
7
|
+
"main": "./dist/cjs/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/cjs/index.d.ts",
|
|
18
|
+
"default": "./dist/cjs/index.js"
|
|
19
|
+
}
|
|
13
20
|
}
|
|
14
21
|
},
|
|
15
22
|
"files": ["dist", "README.md", "LICENSE", "CHANGELOG.md"],
|
|
@@ -47,7 +54,9 @@
|
|
|
47
54
|
"@types/node": { "optional": true }
|
|
48
55
|
},
|
|
49
56
|
"scripts": {
|
|
50
|
-
"build": "
|
|
57
|
+
"build": "npm run build:esm && npm run build:cjs",
|
|
58
|
+
"build:esm": "tsc -p tsconfig.json",
|
|
59
|
+
"build:cjs": "tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }) + '\\n')\"",
|
|
51
60
|
"test": "tsx test/run-with-server.ts test/client.test.ts",
|
|
52
61
|
"test:escape": "tsx test/escape.test.ts",
|
|
53
62
|
"test:protocol": "tsx test/protocol.test.ts",
|