@zvndev/powdb-client 0.3.3 → 0.4.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/src/typed.ts DELETED
@@ -1,148 +0,0 @@
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
- }