@zvndev/powdb-client 0.3.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/README.md +309 -0
- package/dist/errors.d.ts +44 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/escape.d.ts +55 -0
- package/dist/escape.d.ts.map +1 -0
- package/dist/escape.js +124 -0
- package/dist/escape.js.map +1 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +515 -0
- package/dist/index.js.map +1 -0
- package/dist/pool.d.ts +124 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +315 -0
- package/dist/pool.js.map +1 -0
- package/dist/protocol.d.ts +59 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +188 -0
- package/dist/protocol.js.map +1 -0
- package/dist/typed.d.ts +50 -0
- package/dist/typed.d.ts.map +1 -0
- package/dist/typed.js +101 -0
- package/dist/typed.js.map +1 -0
- package/package.json +42 -0
- package/src/errors.ts +54 -0
- package/src/escape.ts +141 -0
- package/src/index.ts +652 -0
- package/src/pool.ts +375 -0
- package/src/protocol.ts +218 -0
- package/src/typed.ts +148 -0
package/src/typed.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
import { PowDBError } from "./errors.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Supported column types. Mirrors the server's `DataType` enum minus the
|
|
31
|
+
* `Bytes` variant, which is intentionally unsupported here.
|
|
32
|
+
*/
|
|
33
|
+
export type ColumnType = "int" | "float" | "bool" | "str" | "datetime" | "uuid";
|
|
34
|
+
|
|
35
|
+
/** Map of column name → declared type. Columns not in the map pass through as strings. */
|
|
36
|
+
export type TypedSchema = Record<string, ColumnType>;
|
|
37
|
+
|
|
38
|
+
/** Coerced value type. `int` may return `bigint` if the value exceeds Number.MAX_SAFE_INTEGER. */
|
|
39
|
+
export type Coerced = number | bigint | boolean | string | Date | null;
|
|
40
|
+
|
|
41
|
+
/** A row of coerced values keyed by column name. */
|
|
42
|
+
export type TypedRow = Record<string, Coerced>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Coerce a single string value to the JS type declared in `schema`. `null`
|
|
46
|
+
* is returned for the conventional null sentinel — see note at call site.
|
|
47
|
+
* Unknown column types fall through as strings.
|
|
48
|
+
*/
|
|
49
|
+
export function coerceValue(
|
|
50
|
+
column: string,
|
|
51
|
+
raw: string,
|
|
52
|
+
type: ColumnType | undefined,
|
|
53
|
+
): Coerced {
|
|
54
|
+
// The server's wire format distinguishes NULL from empty string by the
|
|
55
|
+
// literal "null" bareword in scalar displays. For typed columns we treat
|
|
56
|
+
// the single token "null" as NULL; empty string remains empty string for
|
|
57
|
+
// `str` columns (can be an intentional empty value).
|
|
58
|
+
if (type !== "str" && raw === "null") return null;
|
|
59
|
+
|
|
60
|
+
switch (type) {
|
|
61
|
+
case undefined:
|
|
62
|
+
case "str":
|
|
63
|
+
return raw;
|
|
64
|
+
|
|
65
|
+
case "int": {
|
|
66
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
67
|
+
throw new PowDBError(
|
|
68
|
+
`queryTyped: column "${column}" declared int but got ${JSON.stringify(raw)}`,
|
|
69
|
+
"type_coercion_failed",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
// Promote to BigInt when the value doesn't round-trip through Number.
|
|
73
|
+
const n = Number(raw);
|
|
74
|
+
if (Number.isSafeInteger(n)) return n;
|
|
75
|
+
return BigInt(raw);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "float": {
|
|
79
|
+
const n = Number(raw);
|
|
80
|
+
if (!Number.isFinite(n)) {
|
|
81
|
+
throw new PowDBError(
|
|
82
|
+
`queryTyped: column "${column}" declared float but got ${JSON.stringify(raw)}`,
|
|
83
|
+
"type_coercion_failed",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return n;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case "bool":
|
|
90
|
+
if (raw === "true") return true;
|
|
91
|
+
if (raw === "false") return false;
|
|
92
|
+
throw new PowDBError(
|
|
93
|
+
`queryTyped: column "${column}" declared bool but got ${JSON.stringify(raw)}`,
|
|
94
|
+
"type_coercion_failed",
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
case "datetime": {
|
|
98
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
99
|
+
throw new PowDBError(
|
|
100
|
+
`queryTyped: column "${column}" declared datetime but got ${JSON.stringify(raw)} (expected microseconds since epoch)`,
|
|
101
|
+
"type_coercion_failed",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
// Server sends microseconds since epoch. JS Date uses milliseconds.
|
|
105
|
+
// BigInt division avoids float precision loss for far-future dates.
|
|
106
|
+
const micros = BigInt(raw);
|
|
107
|
+
const millis = Number(micros / 1000n);
|
|
108
|
+
return new Date(millis);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case "uuid": {
|
|
112
|
+
// Canonical 8-4-4-4-12 hex, lowercase (per server format).
|
|
113
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(raw)) {
|
|
114
|
+
throw new PowDBError(
|
|
115
|
+
`queryTyped: column "${column}" declared uuid but got ${JSON.stringify(raw)}`,
|
|
116
|
+
"type_coercion_failed",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return raw;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Coerce a single result row (a string tuple) into a typed object keyed by
|
|
126
|
+
* column name.
|
|
127
|
+
*/
|
|
128
|
+
export function coerceRow(
|
|
129
|
+
columns: string[],
|
|
130
|
+
values: string[],
|
|
131
|
+
schema: TypedSchema,
|
|
132
|
+
): TypedRow {
|
|
133
|
+
const out: TypedRow = {};
|
|
134
|
+
for (let i = 0; i < columns.length; i++) {
|
|
135
|
+
const col = columns[i]!;
|
|
136
|
+
out[col] = coerceValue(col, values[i] ?? "null", schema[col]);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Coerce every row in a rows-result into typed objects. */
|
|
142
|
+
export function coerceRows(
|
|
143
|
+
columns: string[],
|
|
144
|
+
rows: string[][],
|
|
145
|
+
schema: TypedSchema,
|
|
146
|
+
): TypedRow[] {
|
|
147
|
+
return rows.map((r) => coerceRow(columns, r, schema));
|
|
148
|
+
}
|