@clickhouse/client 1.23.0-head.dbc2960.1 → 1.23.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +1342 -0
  2. package/README.md +18 -6
  3. package/dist/version.d.ts +1 -1
  4. package/dist/version.js +1 -1
  5. package/dist/version.js.map +1 -1
  6. package/package.json +7 -6
  7. package/skills/AGENTS.md +8 -0
  8. package/skills/clickhouse-js-node-rowbinary/AGENTS.md +44 -0
  9. package/skills/clickhouse-js-node-rowbinary/CHANGELOG.md +49 -0
  10. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/README.md +78 -14
  11. package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
  12. package/skills/{clickhouse-js-node-rowbinary-parser/SKILL.md → clickhouse-js-node-rowbinary/reader.md} +51 -131
  13. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/carts.ts +9 -5
  14. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/events.ts +5 -5
  15. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/iot.ts +4 -4
  16. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/ledger.ts +3 -3
  17. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/logs.ts +4 -4
  18. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/observability.ts +9 -10
  19. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/orders.ts +10 -9
  20. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/profiles.ts +5 -5
  21. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/telemetry.ts +6 -6
  22. package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/compile.ts +18 -8
  23. package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/dynamic.ts +12 -8
  24. package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -0
  25. package/skills/clickhouse-js-node-rowbinary/src/writers/aggregateFunction.ts +18 -0
  26. package/skills/clickhouse-js-node-rowbinary/src/writers/bool.ts +10 -0
  27. package/skills/clickhouse-js-node-rowbinary/src/writers/composite.ts +140 -0
  28. package/skills/clickhouse-js-node-rowbinary/src/writers/core.ts +92 -0
  29. package/skills/clickhouse-js-node-rowbinary/src/writers/datetime.ts +123 -0
  30. package/skills/clickhouse-js-node-rowbinary/src/writers/decimals.ts +51 -0
  31. package/skills/clickhouse-js-node-rowbinary/src/writers/enums.ts +18 -0
  32. package/skills/clickhouse-js-node-rowbinary/src/writers/floats.ts +40 -0
  33. package/skills/clickhouse-js-node-rowbinary/src/writers/geo.ts +125 -0
  34. package/skills/clickhouse-js-node-rowbinary/src/writers/integers.ts +90 -0
  35. package/skills/clickhouse-js-node-rowbinary/src/writers/interval.ts +11 -0
  36. package/skills/clickhouse-js-node-rowbinary/src/writers/ip.ts +121 -0
  37. package/skills/clickhouse-js-node-rowbinary/src/writers/lowCardinality.ts +12 -0
  38. package/skills/clickhouse-js-node-rowbinary/src/writers/nested.ts +17 -0
  39. package/skills/clickhouse-js-node-rowbinary/src/writers/nothing.ts +21 -0
  40. package/skills/clickhouse-js-node-rowbinary/src/writers/rows.ts +144 -0
  41. package/skills/clickhouse-js-node-rowbinary/src/writers/simpleAggregateFunction.ts +12 -0
  42. package/skills/clickhouse-js-node-rowbinary/src/writers/strings.ts +77 -0
  43. package/skills/clickhouse-js-node-rowbinary/src/writers/time.ts +54 -0
  44. package/skills/clickhouse-js-node-rowbinary/src/writers/uuid.ts +60 -0
  45. package/skills/clickhouse-js-node-rowbinary/src/writers/varint.ts +64 -0
  46. package/skills/clickhouse-js-node-rowbinary/src/writers/writer.ts +101 -0
  47. package/skills/clickhouse-js-node-rowbinary/writer.md +96 -0
  48. package/skills/clickhouse-js-node-rowbinary-parser/src/enums.ts +0 -28
  49. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/EXAMPLES.md +0 -0
  50. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/iot-rowbinary-vs-json.md +0 -0
  51. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/ledger-rowbinary-vs-json.md +0 -0
  52. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/logs-json-wins.md +0 -0
  53. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/wasm-vs-js.md +0 -0
  54. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/aggregateFunction.ts +0 -0
  55. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/bool.ts +0 -0
  56. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/columnar.ts +0 -0
  57. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/composite.ts +0 -0
  58. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/core.ts +0 -0
  59. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/datetime.ts +0 -0
  60. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/decimals.ts +0 -0
  61. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/floats.ts +0 -0
  62. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/geo.ts +0 -0
  63. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/header.ts +0 -0
  64. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/integers.ts +0 -0
  65. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/interval.ts +0 -0
  66. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/ip.ts +0 -0
  67. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/json.ts +0 -0
  68. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/lowCardinality.ts +0 -0
  69. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nested.ts +0 -0
  70. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nothing.ts +0 -0
  71. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/reader.ts +0 -0
  72. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/rowBinaryWithNamesAndTypes.ts +0 -0
  73. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/rows.ts +0 -0
  74. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/simpleAggregateFunction.ts +0 -0
  75. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/stream.ts +0 -0
  76. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/strings.ts +0 -0
  77. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/time.ts +0 -0
  78. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/uuid.ts +0 -0
  79. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/varint.ts +0 -0
@@ -1,46 +1,12 @@
1
- ---
2
- name: clickhouse-js-node-rowbinary-parser
3
- description: >
4
- Generate TypeScript/JavaScript code that reads and decodes ClickHouse
5
- RowBinary streams from the ClickHouse HTTP server.
6
- Use this skill whenever a user wants to parse `RowBinary`,
7
- `RowBinaryWithNames`, or `RowBinaryWithNamesAndTypes`.
8
- Node.js only, doesn't cover browsers.
9
- ---
10
-
11
- # ClickHouse JS RowBinary Parser Generator for Node.js
12
-
13
- ## First: is RowBinary even the right format?
14
-
15
- RowBinary exists for throughput, but it is **not automatically the fastest
16
- path** — match the format to the shape of the data before committing to a
17
- bespoke parser.
18
-
19
- **Prefer a `JSON*` format (e.g. `JSONEachRow`) when** the result is mostly
20
- strings / JSON-like values that you consume wholesale — randomly accessing
21
- essentially every field, running string/regexp methods on them, treating values
22
- as text. V8's native `JSON.parse` is heavily optimized C++ and builds JS strings
23
- and objects faster than a JS-level RowBinary decoder can; pair it with HTTP
24
- response compression (`gzip` / `zstd`, which crushes JSON's repetitive keys) and
25
- the wire cost shrinks too.
26
-
27
- **RowBinary clearly wins when** the result is dominated by:
28
-
29
- - **Wide numerics** — `Int128`/`Int256`/`UInt128`/`UInt256`,
30
- `Decimal128`/`Decimal256`.
31
- - **Binary / fixed-width blobs** — `IPv4`, `IPv6`, `UUID`, `FixedString`.
32
- - **High-volume fixed-width numeric columns** generally, where each value is a
33
- single `DataView` read.
34
-
35
- **Prefer the `Native` format when** columnar load and client-side analytics are
36
- the main goal (fold/scan/filter columns, feed typed arrays to a Worker or WASM).
37
- `Native` is column-major, so it loads straight into one typed array per column
38
- with no transpose.
39
-
40
- For help choosing and consuming a `JSON*` format (or CSV / TSV) instead, use the
41
- **`clickhouse-js-node-coding`** skill.
42
-
43
- ## Second: complete buffer, or incremental stream?
1
+ # RowBinary reader (decode) for Node.js
2
+
3
+ Decoding a `RowBinary` / `RowBinaryWithNames` / `RowBinaryWithNamesAndTypes`
4
+ response from ClickHouse into JS values. Read [SKILL.md](SKILL.md) first for the
5
+ format gate ("is RowBinary even the right format?") and the principles that
6
+ apply to **both** directions; this file covers the decisions and the per-type
7
+ reference specific to **reading**. Writing? See [writer.md](writer.md).
8
+
9
+ ## First: complete buffer, or incremental stream?
44
10
 
45
11
  Decide this before writing the reader — it changes the shape of the code and is
46
12
  a real performance fork.
@@ -56,7 +22,7 @@ a real performance fork.
56
22
 
57
23
  The exposed API is streaming by default and requires an optimisation pass.
58
24
 
59
- ## Third: row objects, or columnar (typed arrays)?
25
+ ## Second: row objects, or columnar (typed arrays)?
60
26
 
61
27
  The default output is one object per row (array-of-structs). For a **numeric,
62
28
  fixed-width result that the consumer reads column-wise**, decode instead into one
@@ -77,56 +43,39 @@ bandwidth). Measured in `tests/iot.columnar.bench.ts`; rationale in
77
43
  complete-row count is `(chunk.length / stride) | 0`, and the leftover bytes
78
44
  carry to the next chunk. Yield one typed-array batch per chunk, each owning a
79
45
  fresh transferable `ArrayBuffer` (see `streamSensorColumns` in
80
- `src/columnar.ts`).
46
+ `src/readers/columnar.ts`).
81
47
  - **Stay row-oriented when** downstream code is row-shaped, the row is
82
48
  string-dominated (columnar's win is numeric — a JS string allocates either
83
49
  way), or the schema is nested/heterogeneous (`Array`/`Map`/`Tuple`).
84
50
  - **Hybrid:** store columnar, expose a lazy `rowAt(i)` accessor that builds an
85
51
  object only for rows actually touched (see `iotRowAt` in `src/examples/iot.ts`).
86
52
 
87
- ## Fourth: are the column types known ahead of time?
53
+ ## Third: are the column types known ahead of time?
88
54
 
89
55
  - **Known (the default).** Generate a straight-line reader specialized to those
90
56
  types — everything below.
91
57
  - **Only at runtime** (the schema varies, or you just want to decode an arbitrary
92
58
  `RowBinaryWithNamesAndTypes` stream). Call
93
- `compileRowBinaryWithNamesAndTypes(cursor)` (`src/rowBinaryWithNamesAndTypes.ts`):
59
+ `compileRowBinaryWithNamesAndTypes(cursor)` (`src/readers/rowBinaryWithNamesAndTypes.ts`):
94
60
  it reads the header, folds each column type's AST into a `Reader`
95
- (`astToReader`, `src/compile.ts`; type strings parsed by
61
+ (`astToReader`, `src/readers/compile.ts`; type strings parsed by
96
62
  `@clickhouse/datatype-parser`), and returns a `readRows` driver for the rest of
97
63
  the stream. Generic and unoptimized (no codegen), so prefer the specialized
98
64
  path whenever the types are fixed.
99
65
 
100
- ## Core guidance
101
-
102
- When generating a parser, follow these:
103
-
104
- - **Little-endian only.** RowBinary is little-endian; target x86/ARM. Read every
105
- multi-byte number with `DataView` accessors passing a **literal** `true` for
106
- the `littleEndian` flag.
66
+ ## Reader guidance
107
67
 
108
- - **Correct first, then optimize.** First emit a correct reader built from the
109
- plain per-type API. Only after it's correct (and tested) specialize it. Don't
110
- bake performance assumptions in before correctness.
111
-
112
- - **Monomorphize generic/composite types.** Emit specialized, inlined code per
113
- type combination instead of passing functions as arguments where the type
114
- is known ahead of time.
68
+ On top of the shared principles in [SKILL.md](SKILL.md), the read path has its own:
115
69
 
116
70
  - **Streaming: throw + restart, not generators.** To signal "need more bytes",
117
71
  a synchronous reader that throws a sentinel (`NeedMoreData`) and restarts the
118
- row beats generators for realistic chunk sizes;
72
+ row beats generators for realistic chunk sizes.
119
73
 
120
74
  - **Keep an eye on chunk sizes.** Partial trailing rows, small chunks are a silent
121
75
  throughput killer: `streamRowBatches` warns once when
122
76
  rows-per-chunk falls too low, and `coalesceChunks(source, { minSize, timeoutMs })`
123
77
  merges small chunks in front of it when the source size isn't yours to raise.
124
78
 
125
- - **Shared scratch is not reentrant.** Some hot methods reuse a module-level
126
- scratch buffer as a write-then-read pair — correct only because reads are fully
127
- synchronous. An `async`/`yield` boundary between populating and reading it
128
- corrupts the value.
129
-
130
79
  - **Hoist the cursor into locals.** Prefer the working buffer and view declared
131
80
  once at the top of the generated reader, and keep the read offset in a **local variable**,
132
81
  operating on it directly instead of re-reading from an object.
@@ -135,72 +84,43 @@ When generating a parser, follow these:
135
84
  neighbouring fixed-width columns has a known combined size, so bounds-check it
136
85
  ONCE.
137
86
 
138
- - **Inline the leaf reads.** The per-type `readX` functions are the correct,
139
- composable reference; the generated parser should INLINE their bodies, not call
140
- them, so the row reader is straight-line with no per-field indirection (and so
141
- the two points above can fold the offset arithmetic together).
142
-
143
- - **Annotate the decoded type per column.** Inlining erases the type structure,
144
- so put a short comment above each column's decode block naming the ClickHouse
145
- type it reads.
146
-
147
87
  - **Pre-allocate small result arrays.** RowBinary gives every array/map its
148
88
  element count up front (the LEB128 prefix), so DEFAULT is to `new Array(n)`.
149
89
  NOTE: for **large** arrays the application will iterate or compute over repeatedly,
150
90
  prefer `[]` + `push` (faster to traverse in V8) — or a typed array (`Float64Array`…)
151
91
  for numeric elements.
152
92
 
153
- - **TypeScript by default.** Generate TypeScript parsers and helpers unless the
154
- user explicitly asks for plain JavaScript.
155
-
156
- ## Type family references
157
-
158
- The readers live as real code under `src/`, split by type family.
159
-
160
- | Result contains (trigger) | Open |
161
- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
162
- | **Always** — cursor state, `advance()`, `NeedMoreData`, `Reader<T>` | `src/core.ts` |
163
- | LEB128 length/count prefixes for `String`/`Array`/`Map` (`readUVarint`) | `src/varint.ts` |
164
- | `Int8`–`Int256`, `UInt8`–`UInt256` | `src/integers.ts` |
165
- | `Bool` | `src/bool.ts` |
166
- | `Enum8`, `Enum16` | `src/enums.ts` |
167
- | `Float32`, `Float64`, `BFloat16` | `src/floats.ts` |
168
- | `Decimal32/64/128/256`, `Decimal(P, S)` | `src/decimals.ts` |
169
- | `String`, `FixedString(N)` | `src/strings.ts` |
170
- | `UUID` | `src/uuid.ts` |
171
- | `IPv4`, `IPv6` | `src/ip.ts` |
172
- | `Date`, `Date32`, `DateTime`, `DateTime(tz)`, `DateTime64(P[, tz])` | `src/datetime.ts` |
173
- | `Time`, `Time64(P)` | `src/time.ts` |
174
- | `IntervalNanosecond` `IntervalYear` | `src/interval.ts` |
175
- | `Array(T)`, `Map(K, V)`, `Tuple(...)`, `Nullable(T)`, `Variant(...)`, `QBit(...)` | `src/composite.ts` |
176
- | `Point`, `Ring`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `Geometry` | `src/geo.ts` |
177
- | `Dynamic` (and `Variant`/`Interval`/`Nested`/`Dynamic` nested inside it) | `src/dynamic.ts` |
178
- | `JSON` | `src/json.ts` |
179
- | The whole result loop rows to EOF (`readRows`) | `src/rows.ts` |
180
- | A chunked HTTP response — `streamRowBatches`, `coalesceChunks` | `src/stream.ts` |
181
- | The `RowBinaryWithNamesAndTypes` headercolumn names + type strings (`readHeader`) | `src/header.ts` |
182
- | Fold one parsed type AST into a `Reader` (`astToReader`)AST in, reader out | `src/compile.ts` |
183
- | **Types known only at runtime** compile a whole header into a row reader (`compileRowBinaryWithNamesAndTypes`, `typeStringToReader`) | `src/rowBinaryWithNamesAndTypes.ts` |
184
- | **Numeric/fixed-width result read column-wise** (aggregate/scan/plot, hand to a Worker/WASM) → decode into typed arrays, not row objects (~4x) | `src/columnar.ts` (`streamSensorColumns` streaming, yields transferable typed-array batches); `decodeIotColumnar` in `src/examples/iot.ts` is the whole-buffer form |
185
- | `LowCardinality(T)` — transparent, decode as `T` | `src/lowCardinality.ts` |
186
- | `SimpleAggregateFunction(f, T)` — transparent, decode as `T` | `src/simpleAggregateFunction.ts` |
187
- | `Nested(...)` — no wire of its own; `Array(Tuple(...))` | `src/nested.ts` |
188
- | `Nothing` — zero-width, never decoded (only wrapped) | `src/nothing.ts` |
189
- | `AggregateFunction(...)` — opaque state; finalize server-side | `src/aggregateFunction.ts` |
190
-
191
- ## Worked examples
192
-
193
- Six end-to-end examples with real speedup are catalogued in [EXAMPLES.md](EXAMPLES.md).
194
-
195
- ## Out of scope
196
-
197
- - **JSON / CSV / TSV / Parquet parsing** → use `clickhouse-js-node-coding`.
198
- - **Connection errors, hangs, type mismatches** → use
199
- `clickhouse-js-node-troubleshooting`.
200
- - **Browser / Web Worker / Edge** → `@clickhouse/client-web`.
201
-
202
- ## Still Stuck?
203
-
204
- - [ClickHouse RowBinary format](https://clickhouse.com/docs/interfaces/formats#rowbinary)
205
- - [ClickHouse data types](https://clickhouse.com/docs/sql-reference/data-types)
206
- - [ClickHouse JS client docs](https://clickhouse.com/docs/integrations/javascript)
93
+ ## Reader type family references
94
+
95
+ The readers live as real code under `src/readers/`, split by type family.
96
+
97
+ | Result contains (trigger) | Open |
98
+ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | **Always** — cursor state, `advance()`, `NeedMoreData`, `Reader<T>` | `src/readers/core.ts` |
100
+ | LEB128 length/count prefixes for `String`/`Array`/`Map` (`readUVarint`) | `src/readers/varint.ts` |
101
+ | `Int8`–`Int256`, `UInt8`–`UInt256` | `src/readers/integers.ts` |
102
+ | `Bool` | `src/readers/bool.ts` |
103
+ | `Enum8`, `Enum16` (resolve to the value's name; `readInt8`/`readInt16` for the raw int) | `src/readers/enums.ts` |
104
+ | `Float32`, `Float64`, `BFloat16` | `src/readers/floats.ts` |
105
+ | `Decimal32/64/128/256`, `Decimal(P, S)` | `src/readers/decimals.ts` |
106
+ | `String`, `FixedString(N)` | `src/readers/strings.ts` |
107
+ | `UUID` | `src/readers/uuid.ts` |
108
+ | `IPv4`, `IPv6` | `src/readers/ip.ts` |
109
+ | `Date`, `Date32`, `DateTime`, `DateTime(tz)`, `DateTime64(P[, tz])` | `src/readers/datetime.ts` |
110
+ | `Time`, `Time64(P)` | `src/readers/time.ts` |
111
+ | `IntervalNanosecond` `IntervalYear` | `src/readers/interval.ts` |
112
+ | `Array(T)`, `Map(K, V)`, `Tuple(...)`, `Nullable(T)`, `Variant(...)`, `QBit(...)` | `src/readers/composite.ts` |
113
+ | `Point`, `Ring`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `Geometry` | `src/readers/geo.ts` |
114
+ | `Dynamic` (and `Variant`/`Interval`/`Nested`/`Dynamic` nested inside it) | `src/readers/dynamic.ts` |
115
+ | `JSON` | `src/readers/json.ts` |
116
+ | The whole result loop rows to EOF (`readRows`) | `src/readers/rows.ts` |
117
+ | A chunked HTTP response `streamRowBatches`, `coalesceChunks` | `src/readers/stream.ts` |
118
+ | The `RowBinaryWithNamesAndTypes` header — column names + type strings (`readHeader`) | `src/readers/header.ts` |
119
+ | Fold one parsed type AST into a `Reader` (`astToReader`) — AST in, reader out | `src/readers/compile.ts` |
120
+ | **Types known only at runtime** compile a whole header into a row reader (`compileRowBinaryWithNamesAndTypes`, `typeStringToReader`) | `src/readers/rowBinaryWithNamesAndTypes.ts` |
121
+ | **Numeric/fixed-width result read column-wise** (aggregate/scan/plot, hand to a Worker/WASM) → decode into typed arrays, not row objects (~4x) | `src/readers/columnar.ts` (`streamSensorColumns`streaming, yields transferable typed-array batches); `decodeIotColumnar` in `src/examples/iot.ts` is the whole-buffer form |
122
+ | `LowCardinality(T)` — transparent, decode as `T` | `src/readers/lowCardinality.ts` |
123
+ | `SimpleAggregateFunction(f, T)`transparent, decode as `T` | `src/readers/simpleAggregateFunction.ts` |
124
+ | `Nested(...)` — no wire of its own; `Array(Tuple(...))` | `src/readers/nested.ts` |
125
+ | `Nothing` — zero-width, never decoded (only wrapped) | `src/readers/nothing.ts` |
126
+ | `AggregateFunction(...)` — opaque state; finalize server-side | `src/readers/aggregateFunction.ts` |
@@ -1,8 +1,12 @@
1
- import { readArray, readNullable, readTupleNamed } from "../composite.js";
2
- import { type Reader, advance } from "../core.js";
3
- import { readInt32, readUInt16, readUInt32 } from "../integers.js";
4
- import { readString } from "../strings.js";
5
- import { readUVarint } from "../varint.js";
1
+ import {
2
+ readArray,
3
+ readNullable,
4
+ readTupleNamed,
5
+ } from "../readers/composite.js";
6
+ import { type Reader, advance } from "../readers/core.js";
7
+ import { readInt32, readUInt16, readUInt32 } from "../readers/integers.js";
8
+ import { readString } from "../readers/strings.js";
9
+ import { readUVarint } from "../readers/varint.js";
6
10
 
7
11
  /**
8
12
  * Example: a carts table — nested generics.
@@ -1,8 +1,8 @@
1
- import { type Reader, advance } from "../core.js";
2
- import { readDateTime } from "../datetime.js";
3
- import { readUInt64 } from "../integers.js";
4
- import { readString } from "../strings.js";
5
- import { readUVarint } from "../varint.js";
1
+ import { type Reader, advance } from "../readers/core.js";
2
+ import { readDateTime } from "../readers/datetime.js";
3
+ import { readUInt64 } from "../readers/integers.js";
4
+ import { readString } from "../readers/strings.js";
5
+ import { readUVarint } from "../readers/varint.js";
6
6
 
7
7
  /**
8
8
  * Example: a plain events table — the scalar baseline.
@@ -1,7 +1,7 @@
1
- import { type Reader, advance } from "../core.js";
2
- import { readDateTime64P3 } from "../datetime.js";
3
- import { readFloat32, readFloat64 } from "../floats.js";
4
- import { readUInt8, readUInt32 } from "../integers.js";
1
+ import { type Reader, advance } from "../readers/core.js";
2
+ import { readDateTime64P3 } from "../readers/datetime.js";
3
+ import { readFloat32, readFloat64 } from "../readers/floats.js";
4
+ import { readUInt8, readUInt32 } from "../readers/integers.js";
5
5
 
6
6
  /**
7
7
  * Example: a table of IoT sensor readings — the dense, fixed-width NUMERIC case
@@ -1,10 +1,10 @@
1
- import { type Reader, advance } from "../core.js";
1
+ import { type Reader, advance } from "../readers/core.js";
2
2
  import {
3
3
  type DecimalValue,
4
4
  readDecimal64,
5
5
  readDecimal128,
6
- } from "../decimals.js";
7
- import { readInt64, readUInt128, readUInt256 } from "../integers.js";
6
+ } from "../readers/decimals.js";
7
+ import { readInt64, readUInt128, readUInt256 } from "../readers/integers.js";
8
8
 
9
9
  /**
10
10
  * Example: a financial ledger — the WIDE-NUMERIC case where RowBinary wins on
@@ -1,7 +1,7 @@
1
- import { type Reader, advance } from "../core.js";
2
- import { readDateTime } from "../datetime.js";
3
- import { readString } from "../strings.js";
4
- import { readUVarint } from "../varint.js";
1
+ import { type Reader, advance } from "../readers/core.js";
2
+ import { readDateTime } from "../readers/datetime.js";
3
+ import { readString } from "../readers/strings.js";
4
+ import { readUVarint } from "../readers/varint.js";
5
5
 
6
6
  /**
7
7
  * Example: an application log table — the STRING-HEAVY case where the skill
@@ -4,15 +4,14 @@ import {
4
4
  readNullable,
5
5
  readTupleNamed,
6
6
  readVariant,
7
- } from "../composite.js";
8
- import { type Reader, advance } from "../core.js";
9
- import { readDateTime64P3 } from "../datetime.js";
10
- import { readEnum8 } from "../enums.js";
11
- import { readFloat64 } from "../floats.js";
12
- import { readInt64, readUInt64 } from "../integers.js";
13
- import { readString } from "../strings.js";
14
- import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
15
- import { readUVarint } from "../varint.js";
7
+ } from "../readers/composite.js";
8
+ import { type Reader, advance } from "../readers/core.js";
9
+ import { readDateTime64P3 } from "../readers/datetime.js";
10
+ import { readFloat64 } from "../readers/floats.js";
11
+ import { readInt8, readInt64, readUInt64 } from "../readers/integers.js";
12
+ import { readString } from "../readers/strings.js";
13
+ import { formatUUID, formatUUIDTable, readUUID } from "../readers/uuid.js";
14
+ import { readUVarint } from "../readers/varint.js";
16
15
 
17
16
  /**
18
17
  * Example: an observability/events table — the gotcha-heavy one. It packs the
@@ -60,7 +59,7 @@ export type ObsRow = {
60
59
  export const readObsRow: Reader<ObsRow> = (s) => ({
61
60
  id: readUInt64(s),
62
61
  ts: readDateTime64P3(s).toISOString(),
63
- level: readEnum8(s),
62
+ level: readInt8(s),
64
63
  traceId: formatUUID(readUUID(s)),
65
64
  payload: readVariant([readFloat64, readInt64, readString])(s),
66
65
  tags: readMap(readString, readString)(s),
@@ -1,8 +1,7 @@
1
- import { type Reader, advance } from "../core.js";
2
- import { type DecimalValue, readDecimal64 } from "../decimals.js";
3
- import { readEnum8 } from "../enums.js";
4
- import { readUInt8 } from "../integers.js";
5
- import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
1
+ import { type Reader, advance } from "../readers/core.js";
2
+ import { type DecimalValue, readDecimal64 } from "../readers/decimals.js";
3
+ import { readInt8, readUInt8 } from "../readers/integers.js";
4
+ import { formatUUID, formatUUIDTable, readUUID } from "../readers/uuid.js";
6
5
 
7
6
  /**
8
7
  * Example: an orders table — UUID, Decimal, and Enum (awkward-as-JSON types).
@@ -15,9 +14,11 @@ import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
15
14
  *
16
15
  * Shows the parse/format split and faithful values: `uid` is read as raw bytes
17
16
  * then formatted with `formatUUID`; `price` stays the exact `[unscaled, scale]`
18
- * pair (`[1234n, 2]` == 12.34), not a lossy float; `status` decodes to the
19
- * underlying `Int8` value (1/2/3), the name<->value map being type metadata, not
20
- * on the wire. The declared scale `2` is baked into `readDecimal64(2)`.
17
+ * pair (`[1234n, 2]` == 12.34), not a lossy float; `status` is read as the raw
18
+ * underlying `Int8` (1/2/3) with `readInt8` a hand-written reader that bakes
19
+ * in the schema can skip name resolution, whereas the generic `readEnum8(map)`
20
+ * (used by the dynamic/header path) resolves the value to its name. The declared
21
+ * scale `2` is baked into `readDecimal64(2)`.
21
22
  */
22
23
  export type OrderRow = {
23
24
  id: number;
@@ -30,7 +31,7 @@ export const readOrderRow: Reader<OrderRow> = (s) => ({
30
31
  id: readUInt8(s),
31
32
  uid: formatUUID(readUUID(s)),
32
33
  price: readDecimal64(2)(s),
33
- status: readEnum8(s),
34
+ status: readInt8(s),
34
35
  });
35
36
 
36
37
  /**
@@ -1,8 +1,8 @@
1
- import { readArray, readNullable } from "../composite.js";
2
- import { type Reader, advance } from "../core.js";
3
- import { readInt32, readUInt32 } from "../integers.js";
4
- import { readString } from "../strings.js";
5
- import { readUVarint } from "../varint.js";
1
+ import { readArray, readNullable } from "../readers/composite.js";
2
+ import { type Reader, advance } from "../readers/core.js";
3
+ import { readInt32, readUInt32 } from "../readers/integers.js";
4
+ import { readString } from "../readers/strings.js";
5
+ import { readUVarint } from "../readers/varint.js";
6
6
 
7
7
  /**
8
8
  * Example: a profiles table — Array and Nullable wrappers.
@@ -3,12 +3,12 @@ import {
3
3
  readMap,
4
4
  readNullable,
5
5
  readTupleNamed,
6
- } from "../composite.js";
7
- import { type Reader, advance } from "../core.js";
8
- import { readFloat64 } from "../floats.js";
9
- import { readUInt16, readUInt32 } from "../integers.js";
10
- import { readString } from "../strings.js";
11
- import { readUVarint } from "../varint.js";
6
+ } from "../readers/composite.js";
7
+ import { type Reader, advance } from "../readers/core.js";
8
+ import { readFloat64 } from "../readers/floats.js";
9
+ import { readUInt16, readUInt32 } from "../readers/integers.js";
10
+ import { readString } from "../readers/strings.js";
11
+ import { readUVarint } from "../readers/varint.js";
12
12
 
13
13
  /**
14
14
  * Example: a telemetry table — composite readers that nest.
@@ -115,10 +115,12 @@ export class RowBinaryTypeError extends Error {
115
115
  */
116
116
  export function astToReader(node: Node): Reader<unknown> {
117
117
  switch (node.kind) {
118
- case NodeKind.EnumDataType:
119
- // Explicit-value enum: the wire value is the underlying int; the
120
- // name<->value map is metadata we don't need to decode.
121
- return node.name === "Enum16" ? readEnum16 : readEnum8;
118
+ case NodeKind.EnumDataType: {
119
+ // Explicit-value enum: the wire value is the underlying int, which we
120
+ // resolve to its NAME via the `'name' = value` pairs the type carries.
121
+ const map = enumNameMap(node);
122
+ return node.name === "Enum16" ? readEnum16(map) : readEnum8(map);
123
+ }
122
124
  case NodeKind.TupleDataType:
123
125
  return tupleReader(node);
124
126
  case NodeKind.DataType:
@@ -185,12 +187,13 @@ function dataTypeReader(node: Node): Reader<unknown> {
185
187
  return readDecimal128(literalInt(requireArg(node, 0)));
186
188
  case "Decimal256":
187
189
  return readDecimal256(literalInt(requireArg(node, 0)));
188
- // Auto-assigned enums arrive as a plain DataType (no explicit values); the
189
- // wire value is still the underlying int.
190
+ // A bare `Enum8`/`Enum16` (no explicit values — only reachable from a
191
+ // hand-written type string) has no names to resolve, so the reader falls
192
+ // back to the stringified underlying int.
190
193
  case "Enum8":
191
- return readEnum8;
194
+ return readEnum8(new Map());
192
195
  case "Enum16":
193
- return readEnum16;
196
+ return readEnum16(new Map());
194
197
 
195
198
  default: {
196
199
  // Interval<Unit> (IntervalSecond, IntervalDay, …): all decode to the
@@ -247,6 +250,13 @@ function nestedReader(node: Node): Reader<unknown> {
247
250
  return readNested(fields);
248
251
  }
249
252
 
253
+ /** Builds an enum's underlying-int -> name lookup from its `'name' = value` pairs. */
254
+ function enumNameMap(node: Node): ReadonlyMap<number, string> {
255
+ const map = new Map<number, string>();
256
+ for (const ev of node.values) map.set(Number(ev.value), ev.name);
257
+ return map;
258
+ }
259
+
250
260
  /** Folds `Decimal(P, S)` to the right width by precision P; scale S drives decoding. */
251
261
  function decimalReader(node: Node): Reader<unknown> {
252
262
  const precision = literalInt(requireArg(node, 0));
@@ -15,6 +15,7 @@ import {
15
15
  readUInt256,
16
16
  } from "./integers.js";
17
17
  import { readBool } from "./bool.js";
18
+ import { readEnum8, readEnum16 } from "./enums.js";
18
19
  import { readFloat32, readFloat64 } from "./floats.js";
19
20
  import { readString, readFixedString } from "./strings.js";
20
21
  import { readUUID } from "./uuid.js";
@@ -143,23 +144,26 @@ export function readDynamicType(state: Cursor): Reader<unknown> {
143
144
  return readString;
144
145
  case 0x16:
145
146
  return readFixedString(readUVarint(state));
146
- // Enum8 / Enum16: a count then (name String, value Int8/Int16) pairs. The
147
- // name<->value map is metadata; the stored value is the underlying int.
147
+ // Enum8 / Enum16: a count then (name String, value Int8/Int16) pairs. We
148
+ // collect them into the name<->value map so the value resolves to its name,
149
+ // matching the textual-type path in compile.ts.
148
150
  case 0x17: {
149
151
  const n = readUVarint(state);
152
+ const map = new Map<number, string>();
150
153
  for (let i = 0; i < n; i++) {
151
- readString(state);
152
- readInt8(state);
154
+ const name = readString(state);
155
+ map.set(readInt8(state), name);
153
156
  }
154
- return readInt8;
157
+ return readEnum8(map);
155
158
  }
156
159
  case 0x18: {
157
160
  const n = readUVarint(state);
161
+ const map = new Map<number, string>();
158
162
  for (let i = 0; i < n; i++) {
159
- readString(state);
160
- readInt16(state);
163
+ const name = readString(state);
164
+ map.set(readInt16(state), name);
161
165
  }
162
- return readInt16;
166
+ return readEnum16(map);
163
167
  }
164
168
  // Decimals: header carries precision P then scale S (both varint). Only S
165
169
  // matters for decoding; P is consumed and dropped. Returns [unscaled, S].
@@ -0,0 +1,40 @@
1
+ import { type Reader, advance } from "./core.js";
2
+
3
+ /**
4
+ * Maps an enum's underlying integer to its name. Built from the type's
5
+ * `'name' = value` pairs (which live in the column type, not the wire) by the
6
+ * compile step / dynamic decoder and handed to the readers below.
7
+ */
8
+ export type EnumNameMap = ReadonlyMap<number, string>;
9
+
10
+ /**
11
+ * Read an `Enum8` and resolve it to its NAME — the ergonomic default, since the
12
+ * point of an enum is usually the label, not the raw `Int8`.
13
+ *
14
+ * This is a factory: the name<->value map is metadata carried by the type, not
15
+ * the bytes, so it is supplied once (per column) and closed over. A value with
16
+ * no matching name falls back to its stringified integer rather than throwing,
17
+ * so a decode never blows up on an unexpected wire value.
18
+ *
19
+ * Need the raw underlying integer instead (e.g. a monomorphized hot path that
20
+ * has the schema baked in)? Read it directly with {@link readInt8} — that is the
21
+ * fast, allocation-free path the enum's name resolution sits on top of.
22
+ */
23
+ export function readEnum8(valueToName: EnumNameMap): Reader<string> {
24
+ return (state) =>
25
+ resolveName(valueToName, state.view.getInt8(advance(state, 1)));
26
+ }
27
+
28
+ /**
29
+ * Read an `Enum16` (2-byte underlying `Int16`) and resolve it to its NAME. See
30
+ * {@link readEnum8}; use {@link readInt16} for the raw underlying integer.
31
+ */
32
+ export function readEnum16(valueToName: EnumNameMap): Reader<string> {
33
+ return (state) =>
34
+ resolveName(valueToName, state.view.getInt16(advance(state, 2), true));
35
+ }
36
+
37
+ function resolveName(valueToName: EnumNameMap, value: number): string {
38
+ const name = valueToName.get(value);
39
+ return name !== undefined ? name : String(value);
40
+ }
@@ -0,0 +1,18 @@
1
+ import { type Writer } from "./core.js";
2
+
3
+ /**
4
+ * Inverse of `readAggregateFunction`: an `AggregateFunction(func, T…)` column is
5
+ * OPAQUE, unframed aggregation state with a layout specific to `func` and the
6
+ * server version, so it cannot be produced generically from a value. Build the
7
+ * state server-side (the `-State` combinators) rather than encoding it on the
8
+ * client.
9
+ *
10
+ * This writer throws to stop a generic encoder from emitting a misaligned row.
11
+ */
12
+ export const writeAggregateFunction: Writer<never> = () => {
13
+ throw new Error(
14
+ "RowBinary: AggregateFunction is opaque, unframed aggregation state with no " +
15
+ "length prefix — not generically encodable. Produce the state server-side " +
16
+ "(the -State combinators) instead of encoding it on the client.",
17
+ );
18
+ };
@@ -0,0 +1,10 @@
1
+ import { Sink } from "./core.js";
2
+ import { writeUInt8 } from "./integers.js";
3
+
4
+ /**
5
+ * Write a `Bool`: 1 byte, stored as `UInt8` (`false` -> 0, `true` -> 1). Mirror
6
+ * of `readBool`.
7
+ */
8
+ export function writeBool(sink: Sink, value: boolean): void {
9
+ writeUInt8(sink, value ? 1 : 0);
10
+ }