@clickhouse/client 1.22.0 → 1.23.0-head.70ad405.1

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.
Files changed (191) hide show
  1. package/CHANGELOG.md +1342 -0
  2. package/README.md +20 -7
  3. package/dist/client.d.ts +2 -2
  4. package/dist/client.js +11 -4
  5. package/dist/client.js.map +1 -1
  6. package/dist/common/clickhouse_types.d.ts +98 -0
  7. package/dist/common/clickhouse_types.js +30 -0
  8. package/dist/common/clickhouse_types.js.map +1 -0
  9. package/dist/common/client.d.ts +233 -0
  10. package/dist/common/client.js +414 -0
  11. package/dist/common/client.js.map +1 -0
  12. package/dist/common/config.d.ts +234 -0
  13. package/dist/common/config.js +364 -0
  14. package/dist/common/config.js.map +1 -0
  15. package/dist/common/connection.d.ts +124 -0
  16. package/dist/common/connection.js +3 -0
  17. package/dist/common/connection.js.map +1 -0
  18. package/dist/common/data_formatter/format_query_params.d.ts +11 -0
  19. package/dist/common/data_formatter/format_query_params.js +128 -0
  20. package/dist/common/data_formatter/format_query_params.js.map +1 -0
  21. package/dist/common/data_formatter/format_query_settings.d.ts +2 -0
  22. package/dist/common/data_formatter/format_query_settings.js +20 -0
  23. package/dist/common/data_formatter/format_query_settings.js.map +1 -0
  24. package/dist/common/data_formatter/formatter.d.ts +41 -0
  25. package/dist/common/data_formatter/formatter.js +78 -0
  26. package/dist/common/data_formatter/formatter.js.map +1 -0
  27. package/dist/common/data_formatter/index.d.ts +3 -0
  28. package/dist/common/data_formatter/index.js +24 -0
  29. package/dist/common/data_formatter/index.js.map +1 -0
  30. package/dist/common/error/error.d.ts +20 -0
  31. package/dist/common/error/error.js +73 -0
  32. package/dist/common/error/error.js.map +1 -0
  33. package/dist/common/error/index.d.ts +1 -0
  34. package/dist/common/error/index.js +18 -0
  35. package/dist/common/error/index.js.map +1 -0
  36. package/dist/common/index.d.ts +67 -0
  37. package/dist/common/index.js +97 -0
  38. package/dist/common/index.js.map +1 -0
  39. package/dist/common/logger.d.ts +80 -0
  40. package/dist/common/logger.js +154 -0
  41. package/dist/common/logger.js.map +1 -0
  42. package/dist/common/parse/column_types.d.ts +155 -0
  43. package/dist/common/parse/column_types.js +594 -0
  44. package/dist/common/parse/column_types.js.map +1 -0
  45. package/dist/common/parse/index.d.ts +2 -0
  46. package/dist/common/parse/index.js +19 -0
  47. package/dist/common/parse/index.js.map +1 -0
  48. package/dist/common/parse/json_handling.d.ts +19 -0
  49. package/dist/common/parse/json_handling.js +8 -0
  50. package/dist/common/parse/json_handling.js.map +1 -0
  51. package/dist/common/result.d.ts +90 -0
  52. package/dist/common/result.js +3 -0
  53. package/dist/common/result.js.map +1 -0
  54. package/dist/common/settings.d.ts +2007 -0
  55. package/dist/common/settings.js +19 -0
  56. package/dist/common/settings.js.map +1 -0
  57. package/dist/common/tracing.d.ts +146 -0
  58. package/dist/common/tracing.js +76 -0
  59. package/dist/common/tracing.js.map +1 -0
  60. package/dist/common/ts_utils.d.ts +4 -0
  61. package/dist/common/ts_utils.js +3 -0
  62. package/dist/common/ts_utils.js.map +1 -0
  63. package/dist/common/utils/connection.d.ts +21 -0
  64. package/dist/common/utils/connection.js +43 -0
  65. package/dist/common/utils/connection.js.map +1 -0
  66. package/dist/common/utils/index.d.ts +5 -0
  67. package/dist/common/utils/index.js +22 -0
  68. package/dist/common/utils/index.js.map +1 -0
  69. package/dist/common/utils/multipart.d.ts +34 -0
  70. package/dist/common/utils/multipart.js +81 -0
  71. package/dist/common/utils/multipart.js.map +1 -0
  72. package/dist/common/utils/sleep.d.ts +4 -0
  73. package/dist/common/utils/sleep.js +12 -0
  74. package/dist/common/utils/sleep.js.map +1 -0
  75. package/dist/common/utils/stream.d.ts +15 -0
  76. package/dist/common/utils/stream.js +50 -0
  77. package/dist/common/utils/stream.js.map +1 -0
  78. package/dist/common/utils/url.d.ts +20 -0
  79. package/dist/common/utils/url.js +67 -0
  80. package/dist/common/utils/url.js.map +1 -0
  81. package/dist/common/version.d.ts +2 -0
  82. package/dist/common/version.js +4 -0
  83. package/dist/common/version.js.map +1 -0
  84. package/dist/config.d.ts +22 -2
  85. package/dist/config.js +2 -2
  86. package/dist/config.js.map +1 -1
  87. package/dist/connection/compression.d.ts +2 -2
  88. package/dist/connection/compression.js +4 -4
  89. package/dist/connection/compression.js.map +1 -1
  90. package/dist/connection/create_connection.d.ts +1 -1
  91. package/dist/connection/node_base_connection.d.ts +3 -3
  92. package/dist/connection/node_base_connection.js +22 -22
  93. package/dist/connection/node_base_connection.js.map +1 -1
  94. package/dist/connection/node_custom_agent_connection.js +2 -2
  95. package/dist/connection/node_custom_agent_connection.js.map +1 -1
  96. package/dist/connection/node_http_connection.js +2 -2
  97. package/dist/connection/node_http_connection.js.map +1 -1
  98. package/dist/connection/node_https_connection.d.ts +1 -1
  99. package/dist/connection/node_https_connection.js +3 -3
  100. package/dist/connection/node_https_connection.js.map +1 -1
  101. package/dist/connection/socket_pool.d.ts +1 -1
  102. package/dist/connection/socket_pool.js +30 -30
  103. package/dist/connection/socket_pool.js.map +1 -1
  104. package/dist/connection/stream.d.ts +1 -1
  105. package/dist/connection/stream.js +9 -9
  106. package/dist/connection/stream.js.map +1 -1
  107. package/dist/index.d.ts +9 -7
  108. package/dist/index.js +26 -24
  109. package/dist/index.js.map +1 -1
  110. package/dist/result_set.d.ts +1 -1
  111. package/dist/result_set.js +10 -10
  112. package/dist/result_set.js.map +1 -1
  113. package/dist/utils/encoder.d.ts +1 -1
  114. package/dist/utils/encoder.js +5 -5
  115. package/dist/utils/encoder.js.map +1 -1
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.js +1 -1
  118. package/dist/version.js.map +1 -1
  119. package/package.json +10 -7
  120. package/skills/AGENTS.md +8 -0
  121. package/skills/clickhouse-js-node-rowbinary/AGENTS.md +44 -0
  122. package/skills/clickhouse-js-node-rowbinary/CHANGELOG.md +49 -0
  123. package/skills/clickhouse-js-node-rowbinary/EXAMPLES.md +48 -0
  124. package/skills/clickhouse-js-node-rowbinary/README.md +319 -0
  125. package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
  126. package/skills/clickhouse-js-node-rowbinary/case-studies/iot-rowbinary-vs-json.md +83 -0
  127. package/skills/clickhouse-js-node-rowbinary/case-studies/ledger-rowbinary-vs-json.md +103 -0
  128. package/skills/clickhouse-js-node-rowbinary/case-studies/logs-json-wins.md +86 -0
  129. package/skills/clickhouse-js-node-rowbinary/case-studies/wasm-vs-js.md +172 -0
  130. package/skills/clickhouse-js-node-rowbinary/reader.md +126 -0
  131. package/skills/clickhouse-js-node-rowbinary/src/examples/carts.ts +75 -0
  132. package/skills/clickhouse-js-node-rowbinary/src/examples/events.ts +51 -0
  133. package/skills/clickhouse-js-node-rowbinary/src/examples/iot.ts +158 -0
  134. package/skills/clickhouse-js-node-rowbinary/src/examples/ledger.ts +98 -0
  135. package/skills/clickhouse-js-node-rowbinary/src/examples/logs.ts +73 -0
  136. package/skills/clickhouse-js-node-rowbinary/src/examples/observability.ts +141 -0
  137. package/skills/clickhouse-js-node-rowbinary/src/examples/orders.ts +66 -0
  138. package/skills/clickhouse-js-node-rowbinary/src/examples/profiles.ts +60 -0
  139. package/skills/clickhouse-js-node-rowbinary/src/examples/telemetry.ts +102 -0
  140. package/skills/clickhouse-js-node-rowbinary/src/readers/aggregateFunction.ts +34 -0
  141. package/skills/clickhouse-js-node-rowbinary/src/readers/bool.ts +10 -0
  142. package/skills/clickhouse-js-node-rowbinary/src/readers/columnar.ts +125 -0
  143. package/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts +328 -0
  144. package/skills/clickhouse-js-node-rowbinary/src/readers/composite.ts +181 -0
  145. package/skills/clickhouse-js-node-rowbinary/src/readers/core.ts +77 -0
  146. package/skills/clickhouse-js-node-rowbinary/src/readers/datetime.ts +113 -0
  147. package/skills/clickhouse-js-node-rowbinary/src/readers/decimals.ts +57 -0
  148. package/skills/clickhouse-js-node-rowbinary/src/readers/dynamic.ts +332 -0
  149. package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -0
  150. package/skills/clickhouse-js-node-rowbinary/src/readers/floats.ts +32 -0
  151. package/skills/clickhouse-js-node-rowbinary/src/readers/geo.ts +109 -0
  152. package/skills/clickhouse-js-node-rowbinary/src/readers/header.ts +29 -0
  153. package/skills/clickhouse-js-node-rowbinary/src/readers/integers.ts +95 -0
  154. package/skills/clickhouse-js-node-rowbinary/src/readers/interval.ts +54 -0
  155. package/skills/clickhouse-js-node-rowbinary/src/readers/ip.ts +93 -0
  156. package/skills/clickhouse-js-node-rowbinary/src/readers/json.ts +33 -0
  157. package/skills/clickhouse-js-node-rowbinary/src/readers/lowCardinality.ts +18 -0
  158. package/skills/clickhouse-js-node-rowbinary/src/readers/nested.ts +23 -0
  159. package/skills/clickhouse-js-node-rowbinary/src/readers/nothing.ts +29 -0
  160. package/skills/clickhouse-js-node-rowbinary/src/readers/reader.ts +68 -0
  161. package/skills/clickhouse-js-node-rowbinary/src/readers/rowBinaryWithNamesAndTypes.ts +155 -0
  162. package/skills/clickhouse-js-node-rowbinary/src/readers/rows.ts +58 -0
  163. package/skills/clickhouse-js-node-rowbinary/src/readers/simpleAggregateFunction.ts +20 -0
  164. package/skills/clickhouse-js-node-rowbinary/src/readers/stream.ts +276 -0
  165. package/skills/clickhouse-js-node-rowbinary/src/readers/strings.ts +55 -0
  166. package/skills/clickhouse-js-node-rowbinary/src/readers/time.ts +61 -0
  167. package/skills/clickhouse-js-node-rowbinary/src/readers/uuid.ts +153 -0
  168. package/skills/clickhouse-js-node-rowbinary/src/readers/varint.ts +70 -0
  169. package/skills/clickhouse-js-node-rowbinary/src/writers/aggregateFunction.ts +18 -0
  170. package/skills/clickhouse-js-node-rowbinary/src/writers/bool.ts +10 -0
  171. package/skills/clickhouse-js-node-rowbinary/src/writers/composite.ts +140 -0
  172. package/skills/clickhouse-js-node-rowbinary/src/writers/core.ts +92 -0
  173. package/skills/clickhouse-js-node-rowbinary/src/writers/datetime.ts +123 -0
  174. package/skills/clickhouse-js-node-rowbinary/src/writers/decimals.ts +51 -0
  175. package/skills/clickhouse-js-node-rowbinary/src/writers/enums.ts +18 -0
  176. package/skills/clickhouse-js-node-rowbinary/src/writers/floats.ts +40 -0
  177. package/skills/clickhouse-js-node-rowbinary/src/writers/geo.ts +125 -0
  178. package/skills/clickhouse-js-node-rowbinary/src/writers/integers.ts +90 -0
  179. package/skills/clickhouse-js-node-rowbinary/src/writers/interval.ts +11 -0
  180. package/skills/clickhouse-js-node-rowbinary/src/writers/ip.ts +121 -0
  181. package/skills/clickhouse-js-node-rowbinary/src/writers/lowCardinality.ts +12 -0
  182. package/skills/clickhouse-js-node-rowbinary/src/writers/nested.ts +17 -0
  183. package/skills/clickhouse-js-node-rowbinary/src/writers/nothing.ts +21 -0
  184. package/skills/clickhouse-js-node-rowbinary/src/writers/rows.ts +144 -0
  185. package/skills/clickhouse-js-node-rowbinary/src/writers/simpleAggregateFunction.ts +12 -0
  186. package/skills/clickhouse-js-node-rowbinary/src/writers/strings.ts +77 -0
  187. package/skills/clickhouse-js-node-rowbinary/src/writers/time.ts +54 -0
  188. package/skills/clickhouse-js-node-rowbinary/src/writers/uuid.ts +60 -0
  189. package/skills/clickhouse-js-node-rowbinary/src/writers/varint.ts +64 -0
  190. package/skills/clickhouse-js-node-rowbinary/src/writers/writer.ts +101 -0
  191. package/skills/clickhouse-js-node-rowbinary/writer.md +96 -0
@@ -0,0 +1,181 @@
1
+ import { type Reader } from "./core.js";
2
+ import { readUInt8 } from "./integers.js";
3
+ import { readUVarint } from "./varint.js";
4
+
5
+ /**
6
+ * Read a `Nullable(T)`: a 1-byte null flag (0 = present, non-zero = NULL).
7
+ * Curried: pass the inner reader, get a `Reader<T | null>`.
8
+ *
9
+ * GOTCHA: the inner value bytes follow ONLY when the flag is 0. A NULL is the
10
+ * single `0x01` flag byte with nothing after it — so do NOT read the inner value
11
+ * when the flag is set, or the cursor desyncs.
12
+ *
13
+ * `readValue` decodes the inner `T`. This generic combinator is the reference
14
+ * shape; when generating code, MONOMORPHIZE — emit a dedicated `readNullableX`
15
+ * that inlines the inner read:
16
+ *
17
+ * const readNullableUInt32 = (s) => readUInt8(s) !== 0 ? null : readUInt32(s);
18
+ */
19
+ export function readNullable<T>(readValue: Reader<T>): Reader<T | null> {
20
+ return (state) => (readUInt8(state) !== 0 ? null : readValue(state));
21
+ }
22
+
23
+ /**
24
+ * Read an `Array(T)`: a LEB128 element count, then that many `T` values
25
+ * back-to-back. An empty array is just the count byte `0x00`. Curried: pass the
26
+ * element reader, get a `Reader<T[]>`.
27
+ *
28
+ * ARRAY LAYOUT: the count is known up front (the LEB128 prefix), so a generated
29
+ * reader can pre-size. Pick by how the result is used:
30
+ * - small / consumed-as-is (the common case) → DEFAULT to `new Array(n)` +
31
+ * index assignment; it skips `push`'s repeated capacity growth. A clean-room
32
+ * benchmark found this edged out `push` on the small composite arrays here
33
+ * (`baseline/README.md`).
34
+ * - large + iterated/computed-over downstream → `[]` + `push` keeps it a PACKED
35
+ * elements kind (faster to traverse; a pre-sized array is HOLEY), or use a
36
+ * typed array (`Float64Array`…) for numeric elements.
37
+ * This generic combinator uses `push` for simplicity; the monomorphized
38
+ * `readArrayX` below should choose per the rule above.
39
+ *
40
+ * `readElement` decodes one element. This generic combinator is the reference
41
+ * shape; when generating code, MONOMORPHIZE — emit a dedicated `readArrayX` that
42
+ * inlines the element read in the loop (and pre-sizes for the common small case):
43
+ *
44
+ * function readArrayUInt32(s) {
45
+ * const n = readUVarint(s);
46
+ * const out = new Array(n);
47
+ * for (let i = 0; i < n; i++) out[i] = readUInt32(s);
48
+ * return out;
49
+ * }
50
+ */
51
+ export function readArray<T>(readElement: Reader<T>): Reader<T[]> {
52
+ return (state) => {
53
+ const n = readUVarint(state);
54
+ const out: T[] = [];
55
+ for (let i = 0; i < n; i++) out.push(readElement(state));
56
+ return out;
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Read a `QBit(element_type, dimension)` vector. `QBit` is a vector-search type
62
+ * whose ON-DISK layout is quantized and bit-transposed — but that is a STORAGE /
63
+ * Native-format concern. In RowBinary a `QBit` is fully TRANSPARENT: it is the
64
+ * plain vector, encoded byte-for-byte like `Array(element_type)` (a LEB128
65
+ * length, then `dimension` element values). So this is just {@link readArray}.
66
+ *
67
+ * `element_type` is one of `BFloat16` / `Float32` / `Float64`, so `readElement`
68
+ * is the matching float reader. When generating code, MONOMORPHIZE — inline the
69
+ * element read in the loop.
70
+ */
71
+ export function readQBit<T>(readElement: Reader<T>): Reader<T[]> {
72
+ return readArray(readElement);
73
+ }
74
+
75
+ /**
76
+ * Read a `Tuple(...)` into a positional array: each element's value back-to-back,
77
+ * with NO count and NO delimiter. Curried: pass one reader per element (in
78
+ * order), get a `Reader` of the tuple. For a named tuple as an object, use
79
+ * {@link readTupleNamed} (identical wire).
80
+ *
81
+ * Reference shape; when generating code, MONOMORPHIZE — emit the inline sequence
82
+ * with no array-of-readers and no loop:
83
+ *
84
+ * [readUInt32(s), readString(s)]
85
+ */
86
+ export function readTuple<T extends readonly unknown[]>(readers: {
87
+ [K in keyof T]: Reader<T[K]>;
88
+ }): Reader<T> {
89
+ return (state) => {
90
+ const out: unknown[] = [];
91
+ for (const read of readers as ReadonlyArray<Reader<unknown>>) {
92
+ out.push(read(state));
93
+ }
94
+ return out as unknown as T;
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Read a named `Tuple(name1 T1, ...)` into an object. The wire is identical to
100
+ * an unnamed tuple — values back-to-back, no count or delimiter — so the
101
+ * `readers` object's keys MUST be listed in the tuple's declared field order
102
+ * (JS iterates string keys in insertion order), and each reader runs in that
103
+ * order. Curried: pass the readers object, get a `Reader` of the result object.
104
+ *
105
+ * Reference shape; when generating code, MONOMORPHIZE — emit the inline object
106
+ * literal instead of looping over entries:
107
+ *
108
+ * { id: readUInt32(s), name: readString(s) }
109
+ */
110
+ export function readTupleNamed<T extends Record<string, unknown>>(readers: {
111
+ [K in keyof T]: Reader<T[K]>;
112
+ }): Reader<T> {
113
+ const fns = readers as Record<string, Reader<unknown>>;
114
+ const keys = Object.keys(fns);
115
+ return (state) => {
116
+ const out: Record<string, unknown> = {};
117
+ for (const key of keys) out[key] = fns[key]!(state);
118
+ return out as T;
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Read a `Map(K, V)`: a LEB128 pair count, then that many key/value pairs with
124
+ * key and value interleaved (k, v, k, v, ...) — a flattened `Array(Tuple(K, V))`.
125
+ * An empty map is just the count byte `0x00`. Curried: pass the key and value
126
+ * readers, get a `Reader<Map<K, V>>`.
127
+ *
128
+ * The key is read BEFORE the value in each pair. Returns a JS `Map`, which keeps
129
+ * insertion order and accepts any key type.
130
+ *
131
+ * Reference shape; when generating code, MONOMORPHIZE — inline both reads in the
132
+ * loop.
133
+ */
134
+ export function readMap<K, V>(
135
+ readKey: Reader<K>,
136
+ readValue: Reader<V>,
137
+ ): Reader<Map<K, V>> {
138
+ return (state) => {
139
+ const n = readUVarint(state);
140
+ const out = new Map<K, V>();
141
+ for (let i = 0; i < n; i++) {
142
+ const key = readKey(state);
143
+ out.set(key, readValue(state));
144
+ }
145
+ return out;
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Read a `Variant(T1, ..., Tn)`: a 1-byte discriminant selecting the active
151
+ * alternative, then that alternative's value. Discriminant `0xFF` means NULL.
152
+ * Curried: pass the alternative readers (in sorted-type-name order), get a
153
+ * `Reader`.
154
+ *
155
+ * GOTCHA: the discriminant indexes the alternatives sorted by type NAME
156
+ * (ClickHouse globally sorts them), NOT their declaration order. So `readers`
157
+ * MUST be ordered by sorted type name. E.g. `Variant(UInt8, String)` sorts to
158
+ * ["String", "UInt8"], so discriminant 0 = String and 1 = UInt8.
159
+ *
160
+ * Reference shape; when generating code, MONOMORPHIZE — emit a `switch` over the
161
+ * discriminant with each branch inlined, alternatives in sorted order, `0xFF`
162
+ * -> null.
163
+ */
164
+ export function readVariant<T extends readonly unknown[]>(readers: {
165
+ [K in keyof T]: Reader<T[K]>;
166
+ }): Reader<T[number] | null> {
167
+ const fns = readers as ReadonlyArray<Reader<T[number]>>;
168
+ return (state) => {
169
+ const discriminant = readUInt8(state);
170
+ if (discriminant === 0xff) return null;
171
+ const fn = fns[discriminant];
172
+ if (fn === undefined) {
173
+ // Out-of-range discriminant (corrupted/truncated input): fail loudly
174
+ // instead of throwing a cryptic "fns[discriminant] is not a function".
175
+ throw new RangeError(
176
+ `RowBinary Variant: discriminant ${discriminant} out of range (${fns.length} alternatives)`,
177
+ );
178
+ }
179
+ return fn(state);
180
+ };
181
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Thrown by {@link advance} when the buffer lacks the bytes a read needs — the
3
+ * "need more bytes" signal for incremental decoding over a still-filling buffer.
4
+ * A driver catches it (`err === NeedMoreData`), waits for more input, and retries
5
+ * the row from its last committed position.
6
+ *
7
+ * A bare sentinel, NOT an `Error` subclass, on purpose: constructing an Error
8
+ * captures a stack trace — the expensive part of throwing — and on a path that
9
+ * starves once per chunk that cost is pure waste. Throwing a constant skips it,
10
+ * which is why throw + restart beats a generator's yield for realistic chunks
11
+ * (see `streamingRow.bench.ts`).
12
+ */
13
+ export const NeedMoreData = Symbol("RowBinary.NeedMoreData");
14
+
15
+ /**
16
+ * The cursor state every reader threads through: the input `Buffer`, the current
17
+ * position, and a `DataView` over the same bytes.
18
+ *
19
+ * Deliberately STATE only — no read methods. Decoding lives in the free
20
+ * `readX(state, ...)` functions in the sibling modules, so a generated parser
21
+ * pulls in only the per-type readers a result needs. `view`/`buf` are public so
22
+ * those free functions can reach them.
23
+ */
24
+ export class Cursor {
25
+ pos = 0;
26
+
27
+ /**
28
+ * Node-only skill, so the input is a `Buffer`: number reads go through
29
+ * {@link Cursor.view} (DataView), while `String`/`FixedString` use the
30
+ * fast `buf.toString("utf8", ...)`.
31
+ */
32
+ readonly buf: Buffer;
33
+
34
+ /**
35
+ * `DataView` over the same bytes, for fixed-width integer/float reads. Built
36
+ * with the buffer's own `byteOffset`/`byteLength`: a `Buffer` is often a window
37
+ * into a larger pooled `ArrayBuffer`, so `new DataView(buf.buffer)` alone would
38
+ * point at the wrong bytes.
39
+ */
40
+ readonly view: DataView;
41
+
42
+ constructor(buf: Buffer) {
43
+ this.buf = buf;
44
+ this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * A `Reader<T>` decodes one value of type `T` from the cursor, advancing it. Leaf
50
+ * readers (e.g. `readUInt32`) are `Reader`s directly; combinators (e.g.
51
+ * `readArray`) take sub-`Reader`s and return a `Reader`, so types compose with no
52
+ * per-element closures.
53
+ */
54
+ export type Reader<T> = (state: Cursor) => T;
55
+
56
+ /**
57
+ * Reserve `n` bytes for the next read: bounds-check them, advance the cursor past
58
+ * them, and return the offset the read starts at (the value BEFORE advancing).
59
+ * Every fixed-width read goes through this, so the length check and cursor
60
+ * bookkeeping live in one place:
61
+ *
62
+ * function readInt32(s) { return s.view.getInt32(advance(s, 4), true); }
63
+ *
64
+ * Throws {@link NeedMoreData} when fewer than `n` bytes remain, WITHOUT moving the
65
+ * cursor, so a driver can rewind to its last committed row and retry.
66
+ *
67
+ * SAFE TO TOGGLE: for a complete in-memory buffer the check never fires — a parser
68
+ * for that case can drop `advance` and read against `state.pos` directly, trading
69
+ * streaming tolerance for one fewer compare per read.
70
+ */
71
+ export function advance(state: Cursor, n: number): number {
72
+ const start = state.pos;
73
+ const next = start + n;
74
+ if (next > state.buf.length) throw NeedMoreData;
75
+ state.pos = next;
76
+ return start;
77
+ }
@@ -0,0 +1,113 @@
1
+ import { type Reader, Cursor } from "./core.js";
2
+ import { readInt32, readInt64, readUInt16, readUInt32 } from "./integers.js";
3
+
4
+ /**
5
+ * Semantic aliases for `number` that mark the unit of a temporal value in a
6
+ * return type. They are plain `number`s (no runtime brand) — purely to make
7
+ * `[Date, Nanoseconds]` etc. self-documenting at the call site.
8
+ */
9
+ export type Milliseconds = number;
10
+ export type Microseconds = number;
11
+ export type Nanoseconds = number;
12
+
13
+ /**
14
+ * Read a `Date`: 2-byte `UInt16` count of days since 1970-01-01 (UTC), returned
15
+ * as a JS `Date` at UTC midnight. A ClickHouse `Date` has no time or timezone;
16
+ * `.toISOString().slice(0, 10)` gives "YYYY-MM-DD".
17
+ *
18
+ * SAFE TO TOGGLE: a `Date` is an object allocation per value. On a hot path that
19
+ * only needs the calendar number, read the raw `UInt16` (days) instead.
20
+ */
21
+ export function readDate(state: Cursor): Date {
22
+ return new Date(readUInt16(state) * 86_400_000);
23
+ }
24
+
25
+ /**
26
+ * Read a `Date32`: 4-byte signed `Int32` count of days since 1970-01-01 (UTC),
27
+ * returned as a JS `Date` at UTC midnight (pre-1970 dates are negative day
28
+ * counts, which `Date` handles).
29
+ */
30
+ export function readDate32(state: Cursor): Date {
31
+ return new Date(readInt32(state) * 86_400_000);
32
+ }
33
+
34
+ /**
35
+ * Read a `DateTime` (and `DateTime(tz)`): 4-byte `UInt32` Unix seconds, returned
36
+ * as a JS `Date` (exact at second resolution). The instant is UTC-based; a
37
+ * column's timezone is display metadata, not in the bytes.
38
+ */
39
+ export function readDateTime(state: Cursor): Date {
40
+ return new Date(readUInt32(state) * 1000);
41
+ }
42
+
43
+ /**
44
+ * Read a `DateTime64(P)` (and `DateTime64(P, tz)`): 8-byte signed `Int64` count
45
+ * of `10^-P`-second ticks since the epoch. Curried: `readDateTime64(P)` returns
46
+ * the reader.
47
+ *
48
+ * Returns a pair `[date, nanoseconds]`: `date` is a JS `Date` truncated to whole
49
+ * seconds, and `nanoseconds` is the sub-second remainder in nanoseconds
50
+ * (0..999_999_999). The split keeps full precision a `Date` alone (millisecond
51
+ * resolution) can't hold. `nanoseconds` is always in ns regardless of P. Timezone
52
+ * is metadata.
53
+ *
54
+ * For the typical precisions, prefer the specialized variants
55
+ * {@link readDateTime64P3} (ms — returns a plain `Date`),
56
+ * {@link readDateTime64P6} (µs), and {@link readDateTime64P9} (ns).
57
+ */
58
+ export function readDateTime64(precision: number): Reader<[Date, Nanoseconds]> {
59
+ return (state) => {
60
+ const ticks = readInt64(state);
61
+ const scale = 10n ** BigInt(precision);
62
+ let sec = ticks / scale;
63
+ let frac = ticks % scale;
64
+ if (frac < 0n) {
65
+ // Floor toward -inf so the fractional remainder stays in [0, scale).
66
+ frac += scale;
67
+ sec -= 1n;
68
+ }
69
+ return [new Date(Number(sec) * 1000), Number(frac) * 10 ** (9 - precision)];
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Read a `DateTime64(3)` ({@link Milliseconds}) — the most common precision — as
75
+ * a plain JS `Date`. P=3 is exactly a `Date`'s own millisecond resolution, so the
76
+ * instant is represented losslessly with no separate fraction. Specialized
77
+ * variant of {@link readDateTime64} with the scale baked in.
78
+ */
79
+ export function readDateTime64P3(state: Cursor): Date {
80
+ return new Date(Number(readInt64(state)));
81
+ }
82
+
83
+ /**
84
+ * Read a `DateTime64(6)` (microseconds) as `[date, microseconds]`: a JS `Date`
85
+ * truncated to whole seconds plus the sub-second remainder in microseconds.
86
+ * Specialized variant of {@link readDateTime64}.
87
+ */
88
+ export function readDateTime64P6(state: Cursor): [Date, Microseconds] {
89
+ const ticks = readInt64(state);
90
+ let sec = ticks / 1_000_000n;
91
+ let frac = ticks % 1_000_000n; // microseconds within the second
92
+ if (frac < 0n) {
93
+ frac += 1_000_000n;
94
+ sec -= 1n;
95
+ }
96
+ return [new Date(Number(sec) * 1000), Number(frac)];
97
+ }
98
+
99
+ /**
100
+ * Read a `DateTime64(9)` (nanoseconds) as `[date, nanoseconds]`: a JS `Date`
101
+ * truncated to whole seconds plus the sub-second remainder in nanoseconds.
102
+ * Specialized variant of {@link readDateTime64} with the scale baked in.
103
+ */
104
+ export function readDateTime64P9(state: Cursor): [Date, Nanoseconds] {
105
+ const ticks = readInt64(state);
106
+ let sec = ticks / 1_000_000_000n;
107
+ let frac = ticks % 1_000_000_000n; // nanoseconds within the second
108
+ if (frac < 0n) {
109
+ frac += 1_000_000_000n;
110
+ sec -= 1n;
111
+ }
112
+ return [new Date(Number(sec) * 1000), Number(frac)];
113
+ }
@@ -0,0 +1,57 @@
1
+ import { type Reader } from "./core.js";
2
+ import { readInt32, readInt64, readInt128, readInt256 } from "./integers.js";
3
+
4
+ /**
5
+ * A decimal kept lossless as its raw parts: `value = unscaled / 10 ** scale`.
6
+ * The `readDecimal*` readers return this so no precision or scale information is
7
+ * thrown away at decode time.
8
+ */
9
+ export type DecimalValue = readonly [unscaled: bigint, scale: number];
10
+
11
+ /**
12
+ * Format a {@link DecimalValue} as a fixed-point decimal string with `scale`
13
+ * fractional digits (e.g. `[15000n, 4]` -> `"1.5000"`). Plug in only when you
14
+ * need a string.
15
+ *
16
+ * Trailing zeros are preserved to reflect the declared scale, deliberately unlike
17
+ * ClickHouse's text output, which trims them (`"1.5"`) and drops the point for
18
+ * integers (`"10"`).
19
+ */
20
+ export function formatDecimal([unscaled, scale]: DecimalValue): string {
21
+ if (scale === 0) return unscaled.toString();
22
+ if (unscaled < 0n) {
23
+ const digits = (-unscaled).toString().padStart(scale + 1, "0");
24
+ const point = digits.length - scale;
25
+ return `-${digits.slice(0, point)}.${digits.slice(point)}`;
26
+ }
27
+ const digits = unscaled.toString().padStart(scale + 1, "0");
28
+ const point = digits.length - scale;
29
+ return `${digits.slice(0, point)}.${digits.slice(point)}`;
30
+ }
31
+
32
+ /**
33
+ * Read a `Decimal32(P, S)`: a 4-byte little-endian signed integer (same wire
34
+ * shape as `Int32`) scaled by 10^S. Pass the column's scale `S`; returns a
35
+ * `Reader` of the raw `[unscaled, scale]` pair (see {@link formatDecimal}).
36
+ *
37
+ * `Decimal(P, S)` is an alias: pick the width reader by precision P — P<=9 ->
38
+ * Decimal32, <=18 -> Decimal64, <=38 -> Decimal128, <=76 -> Decimal256.
39
+ */
40
+ export function readDecimal32(scale: number): Reader<DecimalValue> {
41
+ return (state) => [BigInt(readInt32(state)), scale];
42
+ }
43
+
44
+ /** Read a `Decimal64(P, S)`: 8-byte LE signed integer scaled by 10^S. Returns `[unscaled, scale]`; see {@link formatDecimal}. */
45
+ export function readDecimal64(scale: number): Reader<DecimalValue> {
46
+ return (state) => [readInt64(state), scale];
47
+ }
48
+
49
+ /** Read a `Decimal128(P, S)`: 16-byte LE signed integer scaled by 10^S. Returns `[unscaled, scale]`; see {@link formatDecimal}. */
50
+ export function readDecimal128(scale: number): Reader<DecimalValue> {
51
+ return (state) => [readInt128(state), scale];
52
+ }
53
+
54
+ /** Read a `Decimal256(P, S)`: 32-byte LE signed integer scaled by 10^S. Returns `[unscaled, scale]`; see {@link formatDecimal}. */
55
+ export function readDecimal256(scale: number): Reader<DecimalValue> {
56
+ return (state) => [readInt256(state), scale];
57
+ }