@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.
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Structured error taxonomy for the PowDB client.
3
+ *
4
+ * Every error thrown by the client is a `PowDBError` with a stable `.code`
5
+ * so callers can branch without string-matching the message. The taxonomy
6
+ * is intentionally small — new codes are added only when a caller has a
7
+ * legitimate reason to handle them differently.
8
+ */
9
+ import type { QueryResult } from "./index.js";
10
+ export type PowDBErrorCode =
11
+ /** TCP/TLS connect, DNS, connect timeout. Transient — safe to retry. */
12
+ "connect_failed"
13
+ /** Server rejected the Connect handshake (bad password, unknown db). Not transient. */
14
+ | "auth_failed"
15
+ /** Server returned an `Error` frame in response to a query. Not transient. */
16
+ | "query_failed"
17
+ /** Caller's AbortSignal fired. Never retry — the caller asked to stop. */
18
+ | "aborted"
19
+ /** Peer sent a frame that exceeds one of the configured caps. Likely a bug/attack — do not retry. */
20
+ | "size_exceeded"
21
+ /** Wire protocol violation (bad framing, unknown message type, truncated payload). */
22
+ | "protocol_error"
23
+ /** `close()` has been called on the client or pool. */
24
+ | "closed"
25
+ /** An operation exceeded its configured time budget. */
26
+ | "timeout"
27
+ /** Type coercion on a row failed (queryTyped). */
28
+ | "type_coercion_failed";
29
+ /**
30
+ * All errors thrown by `@zvndev/powdb-client` are instances of this class.
31
+ * Use `err.code` to branch; `err.cause` optionally carries the underlying
32
+ * cause (e.g. the Node socket error).
33
+ */
34
+ export declare class PowDBError extends Error {
35
+ readonly code: PowDBErrorCode;
36
+ constructor(message: string, code: PowDBErrorCode, options?: {
37
+ cause?: unknown;
38
+ });
39
+ }
40
+ /**
41
+ * Narrow `unknown` to a PowDBError. Useful in catch blocks where you want
42
+ * to branch on `.code` but TypeScript sees `unknown`.
43
+ */
44
+ export declare function isPowDBError(err: unknown): err is PowDBError;
45
+ /**
46
+ * Failure of one statement inside `client.execScript(...)` (fail-fast mode).
47
+ *
48
+ * `code` mirrors the failing statement's error code (`"query_failed"` for a
49
+ * server Error frame, `"aborted"` for a fired AbortSignal, ...), so the usual
50
+ * `.code` branching keeps working; `cause` carries the underlying error.
51
+ * `statementIndex`/`statement` identify the failing statement within the
52
+ * split script, and `results` holds the successful results of every
53
+ * statement before it, in order.
54
+ */
55
+ export declare class PowDBScriptError extends PowDBError {
56
+ /** Zero-based index of the failing statement within the split script. */
57
+ readonly statementIndex: number;
58
+ /** Text of the failing statement (as split, trimmed). */
59
+ readonly statement: string;
60
+ /** Results of the statements before the failing one, in order. */
61
+ readonly results: QueryResult[];
62
+ constructor(message: string, code: PowDBErrorCode, details: {
63
+ statementIndex: number;
64
+ statement: string;
65
+ results: QueryResult[];
66
+ cause?: unknown;
67
+ });
68
+ }
69
+ /** Narrow `unknown` to a PowDBScriptError. */
70
+ export declare function isPowDBScriptError(err: unknown): err is PowDBScriptError;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ /**
3
+ * Structured error taxonomy for the PowDB client.
4
+ *
5
+ * Every error thrown by the client is a `PowDBError` with a stable `.code`
6
+ * so callers can branch without string-matching the message. The taxonomy
7
+ * is intentionally small — new codes are added only when a caller has a
8
+ * legitimate reason to handle them differently.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.PowDBScriptError = exports.PowDBError = void 0;
12
+ exports.isPowDBError = isPowDBError;
13
+ exports.isPowDBScriptError = isPowDBScriptError;
14
+ /**
15
+ * All errors thrown by `@zvndev/powdb-client` are instances of this class.
16
+ * Use `err.code` to branch; `err.cause` optionally carries the underlying
17
+ * cause (e.g. the Node socket error).
18
+ */
19
+ class PowDBError extends Error {
20
+ code;
21
+ constructor(message, code, options) {
22
+ // ErrorOptions.cause is supported in Node 16.9+; we require Node 18+.
23
+ super(message, options);
24
+ this.name = "PowDBError";
25
+ this.code = code;
26
+ // Preserve the prototype chain across `target: es2020` and friends.
27
+ Object.setPrototypeOf(this, PowDBError.prototype);
28
+ }
29
+ }
30
+ exports.PowDBError = PowDBError;
31
+ /**
32
+ * Narrow `unknown` to a PowDBError. Useful in catch blocks where you want
33
+ * to branch on `.code` but TypeScript sees `unknown`.
34
+ */
35
+ function isPowDBError(err) {
36
+ return err instanceof PowDBError;
37
+ }
38
+ /**
39
+ * Failure of one statement inside `client.execScript(...)` (fail-fast mode).
40
+ *
41
+ * `code` mirrors the failing statement's error code (`"query_failed"` for a
42
+ * server Error frame, `"aborted"` for a fired AbortSignal, ...), so the usual
43
+ * `.code` branching keeps working; `cause` carries the underlying error.
44
+ * `statementIndex`/`statement` identify the failing statement within the
45
+ * split script, and `results` holds the successful results of every
46
+ * statement before it, in order.
47
+ */
48
+ class PowDBScriptError extends PowDBError {
49
+ /** Zero-based index of the failing statement within the split script. */
50
+ statementIndex;
51
+ /** Text of the failing statement (as split, trimmed). */
52
+ statement;
53
+ /** Results of the statements before the failing one, in order. */
54
+ results;
55
+ constructor(message, code, details) {
56
+ super(message, code, { cause: details.cause });
57
+ this.name = "PowDBScriptError";
58
+ this.statementIndex = details.statementIndex;
59
+ this.statement = details.statement;
60
+ this.results = details.results;
61
+ Object.setPrototypeOf(this, PowDBScriptError.prototype);
62
+ }
63
+ }
64
+ exports.PowDBScriptError = PowDBScriptError;
65
+ /** Narrow `unknown` to a PowDBScriptError. */
66
+ function isPowDBScriptError(err) {
67
+ return err instanceof PowDBScriptError;
68
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Safe PowQL composition helpers: literal and identifier escaping, plus a
3
+ * tagged-template convenience for building queries without string splicing.
4
+ *
5
+ * const q = powql`insert ${ident("User")} { name := ${userName}, age := ${age} }`;
6
+ * await client.query(q);
7
+ *
8
+ * Security model:
9
+ * - Literals are rendered with quoting/escaping so interpolated values
10
+ * cannot break out of the surrounding literal.
11
+ * - Identifiers are validated against `^[A-Za-z_][A-Za-z0-9_]*$` and passed
12
+ * through unchanged. Anything else throws a `TypeError`.
13
+ *
14
+ * PowQL string-escape rules (verified against `crates/query/src/lexer.rs`):
15
+ * - Strings are delimited by `"`.
16
+ * - `\\` → `\`, `\"` → `"`, `\n` → newline, `\t` → tab.
17
+ * - For any other `\X`, the backslash is dropped and `X` is kept literally.
18
+ * - A bare `"` terminates the string, so `"` inside must be `\"` and any
19
+ * literal backslash must be `\\` (otherwise it would swallow the next char).
20
+ */
21
+ /** Wrapper marking a string as an identifier (vs a literal) for `powql` tagged templates. */
22
+ export declare class PowqlIdent {
23
+ readonly name: string;
24
+ constructor(name: string);
25
+ }
26
+ /** Factory for {@link PowqlIdent}. Prefer this over `new PowqlIdent(...)` at call sites. */
27
+ export declare function ident(name: string): PowqlIdent;
28
+ /**
29
+ * Validate a PowQL identifier (table name, field name, alias). Returns the
30
+ * identifier unchanged on success; throws `TypeError` on any invalid input
31
+ * (non-string, empty, or containing characters outside `[A-Za-z_][A-Za-z0-9_]*`).
32
+ */
33
+ export declare function escapeIdent(name: string): string;
34
+ /**
35
+ * Render a JS value as a PowQL literal. Supports `string`, `number`, `bigint`,
36
+ * `boolean`, and `null`. Rejects `NaN`/`±Infinity`, `undefined`, symbols,
37
+ * objects, and arrays with `TypeError`.
38
+ *
39
+ * - string → `"..."` with `\` and `"` backslash-escaped (C-style, per the
40
+ * PowQL lexer). Backslash must be escaped first to avoid double-processing.
41
+ * - number → decimal; rejects non-finite
42
+ * - bigint → decimal digits
43
+ * - boolean → `true` / `false`
44
+ * - null → `null`
45
+ */
46
+ export declare function escapeLiteral(value: string | number | bigint | boolean | null): string;
47
+ /**
48
+ * Tagged template for safe PowQL composition. Each interpolated value is
49
+ * escaped as a literal by default; wrap it in `ident(...)` to interpolate as
50
+ * an identifier.
51
+ *
52
+ * const q = powql`insert ${ident(table)} { name := ${userName} }`;
53
+ */
54
+ export declare function powql(strings: TemplateStringsArray, ...values: unknown[]): string;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * Safe PowQL composition helpers: literal and identifier escaping, plus a
4
+ * tagged-template convenience for building queries without string splicing.
5
+ *
6
+ * const q = powql`insert ${ident("User")} { name := ${userName}, age := ${age} }`;
7
+ * await client.query(q);
8
+ *
9
+ * Security model:
10
+ * - Literals are rendered with quoting/escaping so interpolated values
11
+ * cannot break out of the surrounding literal.
12
+ * - Identifiers are validated against `^[A-Za-z_][A-Za-z0-9_]*$` and passed
13
+ * through unchanged. Anything else throws a `TypeError`.
14
+ *
15
+ * PowQL string-escape rules (verified against `crates/query/src/lexer.rs`):
16
+ * - Strings are delimited by `"`.
17
+ * - `\\` → `\`, `\"` → `"`, `\n` → newline, `\t` → tab.
18
+ * - For any other `\X`, the backslash is dropped and `X` is kept literally.
19
+ * - A bare `"` terminates the string, so `"` inside must be `\"` and any
20
+ * literal backslash must be `\\` (otherwise it would swallow the next char).
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.PowqlIdent = void 0;
24
+ exports.ident = ident;
25
+ exports.escapeIdent = escapeIdent;
26
+ exports.escapeLiteral = escapeLiteral;
27
+ exports.powql = powql;
28
+ const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
29
+ /** Wrapper marking a string as an identifier (vs a literal) for `powql` tagged templates. */
30
+ class PowqlIdent {
31
+ name;
32
+ constructor(name) {
33
+ this.name = name;
34
+ }
35
+ }
36
+ exports.PowqlIdent = PowqlIdent;
37
+ /** Factory for {@link PowqlIdent}. Prefer this over `new PowqlIdent(...)` at call sites. */
38
+ function ident(name) {
39
+ return new PowqlIdent(name);
40
+ }
41
+ /**
42
+ * Validate a PowQL identifier (table name, field name, alias). Returns the
43
+ * identifier unchanged on success; throws `TypeError` on any invalid input
44
+ * (non-string, empty, or containing characters outside `[A-Za-z_][A-Za-z0-9_]*`).
45
+ */
46
+ function escapeIdent(name) {
47
+ if (typeof name !== "string") {
48
+ throw new TypeError(`escapeIdent: expected string, got ${typeof name}`);
49
+ }
50
+ if (name.length === 0) {
51
+ throw new TypeError("escapeIdent: identifier must not be empty");
52
+ }
53
+ if (!IDENT_RE.test(name)) {
54
+ throw new TypeError(`escapeIdent: invalid identifier ${JSON.stringify(name)} (must match /^[A-Za-z_][A-Za-z0-9_]*$/)`);
55
+ }
56
+ return name;
57
+ }
58
+ /**
59
+ * Render a JS value as a PowQL literal. Supports `string`, `number`, `bigint`,
60
+ * `boolean`, and `null`. Rejects `NaN`/`±Infinity`, `undefined`, symbols,
61
+ * objects, and arrays with `TypeError`.
62
+ *
63
+ * - string → `"..."` with `\` and `"` backslash-escaped (C-style, per the
64
+ * PowQL lexer). Backslash must be escaped first to avoid double-processing.
65
+ * - number → decimal; rejects non-finite
66
+ * - bigint → decimal digits
67
+ * - boolean → `true` / `false`
68
+ * - null → `null`
69
+ */
70
+ function escapeLiteral(value) {
71
+ if (value === null)
72
+ return "null";
73
+ const t = typeof value;
74
+ if (t === "string") {
75
+ const escaped = value
76
+ .replace(/\\/g, "\\\\")
77
+ .replace(/"/g, '\\"');
78
+ return `"${escaped}"`;
79
+ }
80
+ if (t === "number") {
81
+ const n = value;
82
+ if (!Number.isFinite(n)) {
83
+ throw new TypeError(`escapeLiteral: non-finite number ${String(n)} cannot be represented as a PowQL literal`);
84
+ }
85
+ return String(n);
86
+ }
87
+ if (t === "bigint") {
88
+ return value.toString(10);
89
+ }
90
+ if (t === "boolean") {
91
+ return value ? "true" : "false";
92
+ }
93
+ throw new TypeError(`escapeLiteral: unsupported type ${describe(value)}`);
94
+ }
95
+ /**
96
+ * Tagged template for safe PowQL composition. Each interpolated value is
97
+ * escaped as a literal by default; wrap it in `ident(...)` to interpolate as
98
+ * an identifier.
99
+ *
100
+ * const q = powql`insert ${ident(table)} { name := ${userName} }`;
101
+ */
102
+ function powql(strings, ...values) {
103
+ let out = strings[0] ?? "";
104
+ for (let i = 0; i < values.length; i++) {
105
+ out += renderInterpolation(values[i]);
106
+ out += strings[i + 1] ?? "";
107
+ }
108
+ return out;
109
+ }
110
+ // ──────────────────────────────────────────────────────────
111
+ // internals
112
+ // ──────────────────────────────────────────────────────────
113
+ function renderInterpolation(value) {
114
+ if (value instanceof PowqlIdent) {
115
+ return escapeIdent(value.name);
116
+ }
117
+ // `escapeLiteral` enforces the allowed set — for anything else it throws.
118
+ return escapeLiteral(value);
119
+ }
120
+ function describe(value) {
121
+ if (value === undefined)
122
+ return "undefined";
123
+ if (value === null)
124
+ return "null";
125
+ if (Array.isArray(value))
126
+ return "array";
127
+ const t = typeof value;
128
+ if (t === "object")
129
+ return "object";
130
+ return t;
131
+ }