@clickhouse/client 1.22.0 → 1.23.0-head.b25cda1.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 (160) hide show
  1. package/README.md +2 -1
  2. package/dist/client.d.ts +2 -2
  3. package/dist/client.js +11 -4
  4. package/dist/client.js.map +1 -1
  5. package/dist/common/clickhouse_types.d.ts +98 -0
  6. package/dist/common/clickhouse_types.js +30 -0
  7. package/dist/common/clickhouse_types.js.map +1 -0
  8. package/dist/common/client.d.ts +233 -0
  9. package/dist/common/client.js +414 -0
  10. package/dist/common/client.js.map +1 -0
  11. package/dist/common/config.d.ts +234 -0
  12. package/dist/common/config.js +364 -0
  13. package/dist/common/config.js.map +1 -0
  14. package/dist/common/connection.d.ts +124 -0
  15. package/dist/common/connection.js +3 -0
  16. package/dist/common/connection.js.map +1 -0
  17. package/dist/common/data_formatter/format_query_params.d.ts +11 -0
  18. package/dist/common/data_formatter/format_query_params.js +128 -0
  19. package/dist/common/data_formatter/format_query_params.js.map +1 -0
  20. package/dist/common/data_formatter/format_query_settings.d.ts +2 -0
  21. package/dist/common/data_formatter/format_query_settings.js +20 -0
  22. package/dist/common/data_formatter/format_query_settings.js.map +1 -0
  23. package/dist/common/data_formatter/formatter.d.ts +41 -0
  24. package/dist/common/data_formatter/formatter.js +78 -0
  25. package/dist/common/data_formatter/formatter.js.map +1 -0
  26. package/dist/common/data_formatter/index.d.ts +3 -0
  27. package/dist/common/data_formatter/index.js +24 -0
  28. package/dist/common/data_formatter/index.js.map +1 -0
  29. package/dist/common/error/error.d.ts +20 -0
  30. package/dist/common/error/error.js +73 -0
  31. package/dist/common/error/error.js.map +1 -0
  32. package/dist/common/error/index.d.ts +1 -0
  33. package/dist/common/error/index.js +18 -0
  34. package/dist/common/error/index.js.map +1 -0
  35. package/dist/common/index.d.ts +67 -0
  36. package/dist/common/index.js +97 -0
  37. package/dist/common/index.js.map +1 -0
  38. package/dist/common/logger.d.ts +80 -0
  39. package/dist/common/logger.js +154 -0
  40. package/dist/common/logger.js.map +1 -0
  41. package/dist/common/parse/column_types.d.ts +127 -0
  42. package/dist/common/parse/column_types.js +586 -0
  43. package/dist/common/parse/column_types.js.map +1 -0
  44. package/dist/common/parse/index.d.ts +2 -0
  45. package/dist/common/parse/index.js +19 -0
  46. package/dist/common/parse/index.js.map +1 -0
  47. package/dist/common/parse/json_handling.d.ts +19 -0
  48. package/dist/common/parse/json_handling.js +8 -0
  49. package/dist/common/parse/json_handling.js.map +1 -0
  50. package/dist/common/result.d.ts +90 -0
  51. package/dist/common/result.js +3 -0
  52. package/dist/common/result.js.map +1 -0
  53. package/dist/common/settings.d.ts +1990 -0
  54. package/dist/common/settings.js +19 -0
  55. package/dist/common/settings.js.map +1 -0
  56. package/dist/common/tracing.d.ts +146 -0
  57. package/dist/common/tracing.js +76 -0
  58. package/dist/common/tracing.js.map +1 -0
  59. package/dist/common/ts_utils.d.ts +4 -0
  60. package/dist/common/ts_utils.js +3 -0
  61. package/dist/common/ts_utils.js.map +1 -0
  62. package/dist/common/utils/connection.d.ts +21 -0
  63. package/dist/common/utils/connection.js +43 -0
  64. package/dist/common/utils/connection.js.map +1 -0
  65. package/dist/common/utils/index.d.ts +5 -0
  66. package/dist/common/utils/index.js +22 -0
  67. package/dist/common/utils/index.js.map +1 -0
  68. package/dist/common/utils/multipart.d.ts +34 -0
  69. package/dist/common/utils/multipart.js +81 -0
  70. package/dist/common/utils/multipart.js.map +1 -0
  71. package/dist/common/utils/sleep.d.ts +4 -0
  72. package/dist/common/utils/sleep.js +12 -0
  73. package/dist/common/utils/sleep.js.map +1 -0
  74. package/dist/common/utils/stream.d.ts +15 -0
  75. package/dist/common/utils/stream.js +50 -0
  76. package/dist/common/utils/stream.js.map +1 -0
  77. package/dist/common/utils/url.d.ts +20 -0
  78. package/dist/common/utils/url.js +67 -0
  79. package/dist/common/utils/url.js.map +1 -0
  80. package/dist/common/version.d.ts +2 -0
  81. package/dist/common/version.js +4 -0
  82. package/dist/common/version.js.map +1 -0
  83. package/dist/config.d.ts +22 -2
  84. package/dist/config.js +2 -2
  85. package/dist/config.js.map +1 -1
  86. package/dist/connection/compression.d.ts +2 -2
  87. package/dist/connection/compression.js +4 -4
  88. package/dist/connection/compression.js.map +1 -1
  89. package/dist/connection/create_connection.d.ts +1 -1
  90. package/dist/connection/node_base_connection.d.ts +3 -3
  91. package/dist/connection/node_base_connection.js +22 -22
  92. package/dist/connection/node_base_connection.js.map +1 -1
  93. package/dist/connection/node_custom_agent_connection.js +2 -2
  94. package/dist/connection/node_custom_agent_connection.js.map +1 -1
  95. package/dist/connection/node_http_connection.js +2 -2
  96. package/dist/connection/node_http_connection.js.map +1 -1
  97. package/dist/connection/node_https_connection.d.ts +1 -1
  98. package/dist/connection/node_https_connection.js +3 -3
  99. package/dist/connection/node_https_connection.js.map +1 -1
  100. package/dist/connection/socket_pool.d.ts +1 -1
  101. package/dist/connection/socket_pool.js +30 -30
  102. package/dist/connection/socket_pool.js.map +1 -1
  103. package/dist/connection/stream.d.ts +1 -1
  104. package/dist/connection/stream.js +9 -9
  105. package/dist/connection/stream.js.map +1 -1
  106. package/dist/index.d.ts +7 -7
  107. package/dist/index.js +24 -24
  108. package/dist/index.js.map +1 -1
  109. package/dist/result_set.d.ts +1 -1
  110. package/dist/result_set.js +10 -10
  111. package/dist/result_set.js.map +1 -1
  112. package/dist/utils/encoder.d.ts +1 -1
  113. package/dist/utils/encoder.js +5 -5
  114. package/dist/utils/encoder.js.map +1 -1
  115. package/dist/version.d.ts +1 -1
  116. package/dist/version.js +1 -1
  117. package/dist/version.js.map +1 -1
  118. package/package.json +7 -5
  119. package/skills/clickhouse-js-node-rowbinary-parser/EXAMPLES.md +48 -0
  120. package/skills/clickhouse-js-node-rowbinary-parser/README.md +248 -0
  121. package/skills/clickhouse-js-node-rowbinary-parser/SKILL.md +190 -0
  122. package/skills/clickhouse-js-node-rowbinary-parser/case-studies/iot-rowbinary-vs-json.md +83 -0
  123. package/skills/clickhouse-js-node-rowbinary-parser/case-studies/ledger-rowbinary-vs-json.md +103 -0
  124. package/skills/clickhouse-js-node-rowbinary-parser/case-studies/logs-json-wins.md +86 -0
  125. package/skills/clickhouse-js-node-rowbinary-parser/case-studies/wasm-vs-js.md +172 -0
  126. package/skills/clickhouse-js-node-rowbinary-parser/src/aggregateFunction.ts +34 -0
  127. package/skills/clickhouse-js-node-rowbinary-parser/src/bool.ts +10 -0
  128. package/skills/clickhouse-js-node-rowbinary-parser/src/columnar.ts +125 -0
  129. package/skills/clickhouse-js-node-rowbinary-parser/src/composite.ts +181 -0
  130. package/skills/clickhouse-js-node-rowbinary-parser/src/core.ts +77 -0
  131. package/skills/clickhouse-js-node-rowbinary-parser/src/datetime.ts +113 -0
  132. package/skills/clickhouse-js-node-rowbinary-parser/src/decimals.ts +57 -0
  133. package/skills/clickhouse-js-node-rowbinary-parser/src/dynamic.ts +328 -0
  134. package/skills/clickhouse-js-node-rowbinary-parser/src/enums.ts +28 -0
  135. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/carts.ts +71 -0
  136. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/events.ts +51 -0
  137. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/iot.ts +158 -0
  138. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/ledger.ts +98 -0
  139. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/logs.ts +73 -0
  140. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/observability.ts +142 -0
  141. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/orders.ts +65 -0
  142. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/profiles.ts +60 -0
  143. package/skills/clickhouse-js-node-rowbinary-parser/src/examples/telemetry.ts +102 -0
  144. package/skills/clickhouse-js-node-rowbinary-parser/src/floats.ts +32 -0
  145. package/skills/clickhouse-js-node-rowbinary-parser/src/geo.ts +109 -0
  146. package/skills/clickhouse-js-node-rowbinary-parser/src/integers.ts +95 -0
  147. package/skills/clickhouse-js-node-rowbinary-parser/src/interval.ts +54 -0
  148. package/skills/clickhouse-js-node-rowbinary-parser/src/ip.ts +93 -0
  149. package/skills/clickhouse-js-node-rowbinary-parser/src/json.ts +33 -0
  150. package/skills/clickhouse-js-node-rowbinary-parser/src/lowCardinality.ts +18 -0
  151. package/skills/clickhouse-js-node-rowbinary-parser/src/nested.ts +23 -0
  152. package/skills/clickhouse-js-node-rowbinary-parser/src/nothing.ts +29 -0
  153. package/skills/clickhouse-js-node-rowbinary-parser/src/reader.ts +51 -0
  154. package/skills/clickhouse-js-node-rowbinary-parser/src/rows.ts +58 -0
  155. package/skills/clickhouse-js-node-rowbinary-parser/src/simpleAggregateFunction.ts +20 -0
  156. package/skills/clickhouse-js-node-rowbinary-parser/src/stream.ts +276 -0
  157. package/skills/clickhouse-js-node-rowbinary-parser/src/strings.ts +55 -0
  158. package/skills/clickhouse-js-node-rowbinary-parser/src/time.ts +61 -0
  159. package/skills/clickhouse-js-node-rowbinary-parser/src/uuid.ts +153 -0
  160. package/skills/clickhouse-js-node-rowbinary-parser/src/varint.ts +70 -0
@@ -0,0 +1,98 @@
1
+ import { type Reader, advance } from "../core.js";
2
+ import {
3
+ type DecimalValue,
4
+ readDecimal64,
5
+ readDecimal128,
6
+ } from "../decimals.js";
7
+ import { readInt64, readUInt128, readUInt256 } from "../integers.js";
8
+
9
+ /**
10
+ * Example: a financial ledger — the WIDE-NUMERIC case where RowBinary wins on
11
+ * *correctness*, not merely speed, and the headline of the wide-int/decimal
12
+ * comparison in `ledger.bench.ts`.
13
+ *
14
+ * Columns (the trigger — generate this reader when a result has these types):
15
+ * txn_id UInt128
16
+ * account Int64
17
+ * amount Decimal128(18)
18
+ * balance Decimal128(18)
19
+ * fee Decimal64(4)
20
+ * volume UInt256
21
+ *
22
+ * Every value here exceeds what a JS `number` (IEEE-754 double, 53-bit mantissa)
23
+ * can hold exactly, and ClickHouse emits them as **bare JSON numbers**. So
24
+ * `JSON.parse` silently rounds every field — the only correct JSON path is to
25
+ * quote the values server-side and re-parse each string into a `bigint` /
26
+ * decimal pair by hand. RowBinary reads each as an exact `bigint` straight off
27
+ * the wire. The whole row is fixed-width (16+8+16+16+8+32 = 96 bytes).
28
+ */
29
+ export type LedgerRow = {
30
+ txn_id: bigint;
31
+ account: bigint;
32
+ amount: DecimalValue;
33
+ balance: DecimalValue;
34
+ fee: DecimalValue;
35
+ volume: bigint;
36
+ };
37
+
38
+ /**
39
+ * API-combinator reader: correct and clear, one leaf reader per column. A fine
40
+ * default; `readLedgerRowFast` is the monomorphized form `ledger.bench.ts`
41
+ * measures. Both return identical values.
42
+ */
43
+ export const readLedgerRow: Reader<LedgerRow> = (s) => ({
44
+ txn_id: readUInt128(s),
45
+ account: readInt64(s),
46
+ amount: readDecimal128(18)(s),
47
+ balance: readDecimal128(18)(s),
48
+ fee: readDecimal64(4)(s),
49
+ volume: readUInt256(s),
50
+ });
51
+
52
+ /**
53
+ * Optimized, monomorphized reader: the six column bounds checks coalesce into
54
+ * one `advance(s, 96)`; each wide value is composed from 64-bit words read at
55
+ * constant offsets off that base, with the high word read **signed** for the
56
+ * signed types (`Int64`, the `Decimal128` unscaled value, no high word needed
57
+ * for the unsigned `UInt128`/`UInt256`). Stays streaming-safe (one `advance`).
58
+ *
59
+ * txn_id UInt128 @ o+0 lo + (hi<<64) unsigned
60
+ * account Int64 @ o+16 getBigInt64
61
+ * amount Decimal128(18) @ o+24 lo + (hiSigned<<64) -> [v, 18]
62
+ * balance Decimal128(18) @ o+40 lo + (hiSigned<<64) -> [v, 18]
63
+ * fee Decimal64(4) @ o+56 getBigInt64 -> [v, 4]
64
+ * volume UInt256 @ o+64 w0 + w1<<64 + w2<<128 + w3<<192 unsigned
65
+ */
66
+ export const readLedgerRowFast: Reader<LedgerRow> = (s) => {
67
+ const { view } = s;
68
+ const o = advance(s, 96); // one bounds check for the whole 96-byte row
69
+
70
+ // UInt128 — unsigned, both words unsigned.
71
+ const txn_id =
72
+ view.getBigUint64(o, true) + (view.getBigUint64(o + 8, true) << 64n);
73
+
74
+ // Int64 — signed.
75
+ const account = view.getBigInt64(o + 16, true);
76
+
77
+ // Decimal128(18) — Int128 unscaled (low word unsigned, high word signed), scale 18.
78
+ const amount: DecimalValue = [
79
+ view.getBigUint64(o + 24, true) + (view.getBigInt64(o + 32, true) << 64n),
80
+ 18,
81
+ ];
82
+ const balance: DecimalValue = [
83
+ view.getBigUint64(o + 40, true) + (view.getBigInt64(o + 48, true) << 64n),
84
+ 18,
85
+ ];
86
+
87
+ // Decimal64(4) — Int64 unscaled, scale 4.
88
+ const fee: DecimalValue = [view.getBigInt64(o + 56, true), 4];
89
+
90
+ // UInt256 — unsigned, four unsigned words.
91
+ const volume =
92
+ view.getBigUint64(o + 64, true) +
93
+ (view.getBigUint64(o + 72, true) << 64n) +
94
+ (view.getBigUint64(o + 80, true) << 128n) +
95
+ (view.getBigUint64(o + 88, true) << 192n);
96
+
97
+ return { txn_id, account, amount, balance, fee, volume };
98
+ };
@@ -0,0 +1,73 @@
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";
5
+
6
+ /**
7
+ * Example: an application log table — the STRING-HEAVY case where the skill
8
+ * steers you AWAY from RowBinary and toward a `JSON*` format. The honest
9
+ * counter-case to the IoT and ledger studies; see `logs.bench.ts`.
10
+ *
11
+ * Columns (the trigger — generate this reader when a result has these types):
12
+ * ts DateTime
13
+ * level LowCardinality(String)
14
+ * service LowCardinality(String)
15
+ * message String
16
+ * trace_id String
17
+ *
18
+ * Four of the five columns are text consumed wholesale, and `LowCardinality(T)`
19
+ * is transparent in RowBinary (decodes as the inner `String`, no dictionary on
20
+ * the wire). A RowBinary string read is a varint length + `buf.toString("utf8",
21
+ * …)` per field in JS; V8's native `JSON.parse` builds the same JS strings in
22
+ * optimized C++ and tends to WIN here. This reader exists so the comparison is
23
+ * apples-to-apples — not because RowBinary is the right call for this shape.
24
+ */
25
+ export type LogRow = {
26
+ ts: Date;
27
+ level: string;
28
+ service: string;
29
+ message: string;
30
+ trace_id: string;
31
+ };
32
+
33
+ /**
34
+ * API-combinator reader: one leaf reader per column, the clear default. There is
35
+ * little to monomorphize on a mostly-string row — the only fixed-width field is
36
+ * `ts` — so `readLogRowFast` below is barely different and barely faster; the
37
+ * real lesson (see `logs.bench.ts`) is that JSON beats both.
38
+ */
39
+ export const readLogRow: Reader<LogRow> = (s) => ({
40
+ ts: readDateTime(s),
41
+ level: readString(s), // LowCardinality(String) — transparent, decode as String
42
+ service: readString(s), // LowCardinality(String) — transparent, decode as String
43
+ message: readString(s),
44
+ trace_id: readString(s),
45
+ });
46
+
47
+ /**
48
+ * Optimized {@link readLogRow}: the four string reads inlined (varint length +
49
+ * `buf.toString` in place) and the `DateTime` read written out. Note how little
50
+ * monomorphization can do when the row is dominated by variable-length strings —
51
+ * there are no adjacent fixed-width columns to coalesce, so this stays close to
52
+ * the API version. Included to make `logs.bench.ts` a fair fight; the takeaway
53
+ * is to pick `JSONEachRow` for this shape, not to tune this reader.
54
+ */
55
+ export const readLogRowFast: Reader<LogRow> = (s) => {
56
+ const { buf } = s;
57
+ // DateTime: 4-byte LE Unix seconds.
58
+ const ts = new Date(s.view.getUint32(advance(s, 4), true) * 1000);
59
+ // Four UTF-8 Strings (the two LowCardinality columns are plain String on the wire).
60
+ let len = readUVarint(s);
61
+ let o = advance(s, len);
62
+ const level = buf.toString("utf8", o, o + len);
63
+ len = readUVarint(s);
64
+ o = advance(s, len);
65
+ const service = buf.toString("utf8", o, o + len);
66
+ len = readUVarint(s);
67
+ o = advance(s, len);
68
+ const message = buf.toString("utf8", o, o + len);
69
+ len = readUVarint(s);
70
+ o = advance(s, len);
71
+ const trace_id = buf.toString("utf8", o, o + len);
72
+ return { ts, level, service, message, trace_id };
73
+ };
@@ -0,0 +1,142 @@
1
+ import {
2
+ readArray,
3
+ readMap,
4
+ readNullable,
5
+ readTupleNamed,
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";
16
+
17
+ /**
18
+ * Example: an observability/events table — the gotcha-heavy one. It packs the
19
+ * traps that trip a from-scratch decoder; the skill's job is getting them right.
20
+ *
21
+ * Columns (the trigger):
22
+ * id UInt64
23
+ * ts DateTime64(3, 'UTC')
24
+ * level Enum8('debug'=1, 'info'=2, 'warn'=3, 'error'=4)
25
+ * trace_id UUID
26
+ * payload Variant(String, Int64, Float64)
27
+ * tags Map(LowCardinality(String), String)
28
+ * metrics Array(Tuple(name LowCardinality(String), value Float64))
29
+ * attrs Array(Nullable(Int64))
30
+ *
31
+ * Gotchas exercised, all in one row:
32
+ * - `Variant(String, Int64, Float64)`: the discriminant indexes the alternatives
33
+ * SORTED BY TYPE NAME — ["Float64", "Int64", "String"] → 0=Float64, 1=Int64,
34
+ * 2=String (NOT declaration order); `0xFF` = NULL. `readVariant` takes the
35
+ * readers in that sorted order.
36
+ * - `DateTime64(3)`: 8-byte Int64 of millisecond ticks; P=3 is exactly a `Date`'s
37
+ * resolution, so `readDateTime64P3` returns a plain `Date` (here ISO-stringed).
38
+ * - `LowCardinality(String)` (in the Map key and the Tuple field) is TRANSPARENT
39
+ * in RowBinary — decode as plain `String`, no dictionary layer.
40
+ * - `UUID` is two little-endian `UInt64` halves, byte-reversed vs the text form.
41
+ * - `Array(Nullable(Int64))`: per element a null flag then (if present) an Int64,
42
+ * kept as `bigint`.
43
+ */
44
+ export type ObsRow = {
45
+ id: bigint;
46
+ ts: string;
47
+ level: number;
48
+ traceId: string;
49
+ payload: number | bigint | string | null;
50
+ tags: Map<string, string>;
51
+ metrics: { name: string; value: number }[];
52
+ attrs: (bigint | null)[];
53
+ };
54
+
55
+ /**
56
+ * API-combinator reader. Note the `Variant` readers are in sorted-type-name
57
+ * order (Float64, Int64, String), and `LowCardinality` columns just use the
58
+ * inner `String` reader.
59
+ */
60
+ export const readObsRow: Reader<ObsRow> = (s) => ({
61
+ id: readUInt64(s),
62
+ ts: readDateTime64P3(s).toISOString(),
63
+ level: readEnum8(s),
64
+ traceId: formatUUID(readUUID(s)),
65
+ payload: readVariant([readFloat64, readInt64, readString])(s),
66
+ tags: readMap(readString, readString)(s),
67
+ metrics: readArray(readTupleNamed({ name: readString, value: readFloat64 }))(
68
+ s,
69
+ ),
70
+ attrs: readArray(readNullable(readInt64))(s),
71
+ });
72
+
73
+ /**
74
+ * Optimized {@link readObsRow}, flattened per the SKILL.md guidance:
75
+ * - `buf`/`view` hoisted to locals.
76
+ * - the leading run of FIXED-WIDTH columns — `id` UInt64 (8) + `ts` DateTime64 (8)
77
+ * + `level` Enum8 (1) + `trace_id` UUID (16) = 33 bytes — is bounds-checked ONCE
78
+ * (`advance(s, 33)`) and read at constant offsets, instead of four `advance`s.
79
+ * - the `Variant` is an inlined `switch` over the discriminant (sorted order).
80
+ * - leaf reads inlined, `formatUUIDTable` for the UUID, pre-sized arrays.
81
+ * The variable-width columns (`payload`/`tags`/`metrics`/`attrs`) each start a new
82
+ * `advance` run because their size isn't known until decoded.
83
+ */
84
+ export const readObsRowFast: Reader<ObsRow> = (s) => {
85
+ const { buf, view } = s;
86
+
87
+ // One bounds check for the 33-byte fixed-width head.
88
+ const o = advance(s, 33);
89
+ const id = view.getBigUint64(o, true);
90
+ const ts = new Date(Number(view.getBigInt64(o + 8, true))).toISOString();
91
+ const level = view.getInt8(o + 16);
92
+ const traceId = formatUUIDTable(buf.subarray(o + 17, o + 33));
93
+
94
+ // payload Variant(String, Int64, Float64): 1-byte discriminant (sorted names:
95
+ // 0=Float64, 1=Int64, 2=String), 0xFF = NULL.
96
+ let payload: number | bigint | string | null;
97
+ const disc = buf[advance(s, 1)]!;
98
+ if (disc === 0xff) {
99
+ payload = null;
100
+ } else if (disc === 0) {
101
+ payload = view.getFloat64(advance(s, 8), true);
102
+ } else if (disc === 1) {
103
+ payload = view.getBigInt64(advance(s, 8), true);
104
+ } else {
105
+ const len = readUVarint(s);
106
+ const st = advance(s, len);
107
+ payload = buf.toString("utf8", st, st + len);
108
+ }
109
+
110
+ // tags Map(LowCardinality(String) -> String): count, then key/value strings.
111
+ const tagN = readUVarint(s);
112
+ const tags = new Map<string, string>();
113
+ for (let i = 0; i < tagN; i++) {
114
+ let len = readUVarint(s);
115
+ let st = advance(s, len);
116
+ const k = buf.toString("utf8", st, st + len);
117
+ len = readUVarint(s);
118
+ st = advance(s, len);
119
+ tags.set(k, buf.toString("utf8", st, st + len));
120
+ }
121
+
122
+ // metrics Array(Tuple(name LowCardinality(String), value Float64)).
123
+ const mN = readUVarint(s);
124
+ const metrics = new Array<{ name: string; value: number }>(mN);
125
+ for (let i = 0; i < mN; i++) {
126
+ const len = readUVarint(s);
127
+ const st = advance(s, len);
128
+ const name = buf.toString("utf8", st, st + len);
129
+ const value = view.getFloat64(advance(s, 8), true);
130
+ metrics[i] = { name, value };
131
+ }
132
+
133
+ // attrs Array(Nullable(Int64)).
134
+ const aN = readUVarint(s);
135
+ const attrs = new Array<bigint | null>(aN);
136
+ for (let i = 0; i < aN; i++) {
137
+ attrs[i] =
138
+ buf[advance(s, 1)]! !== 0 ? null : view.getBigInt64(advance(s, 8), true);
139
+ }
140
+
141
+ return { id, ts, level, traceId, payload, tags, metrics, attrs };
142
+ };
@@ -0,0 +1,65 @@
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";
6
+
7
+ /**
8
+ * Example: an orders table — UUID, Decimal, and Enum (awkward-as-JSON types).
9
+ *
10
+ * Columns (the trigger):
11
+ * id UInt8
12
+ * uid UUID
13
+ * price Decimal64(2)
14
+ * status Enum8('new' = 1, 'shipped' = 2, 'done' = 3)
15
+ *
16
+ * Shows the parse/format split and faithful values: `uid` is read as raw bytes
17
+ * 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)`.
21
+ */
22
+ export type OrderRow = {
23
+ id: number;
24
+ uid: string;
25
+ price: DecimalValue;
26
+ status: number;
27
+ };
28
+
29
+ export const readOrderRow: Reader<OrderRow> = (s) => ({
30
+ id: readUInt8(s),
31
+ uid: formatUUID(readUUID(s)),
32
+ price: readDecimal64(2)(s),
33
+ status: readEnum8(s),
34
+ });
35
+
36
+ /**
37
+ * Optimized {@link readOrderRow}, flattened per the SKILL.md guidance:
38
+ * - `buf`/`view` hoisted to locals (one property load, not one per field).
39
+ * - every column here is FIXED-WIDTH, so the whole row — `id` UInt8 (1) + `uid`
40
+ * UUID (16) + `price` Decimal64 (8) + `status` Enum8 (1) = 26 bytes — is
41
+ * bounds-checked ONCE (`advance(s, 26)`) and read at constant offsets, instead
42
+ * of four separate `advance`s. (This is the exact worked example in SKILL.md.)
43
+ * - the four leaf reads are inlined, and the BigInt `formatUUID` is swapped for
44
+ * the lookup-table `formatUUIDTable` (~1.6x on its own; see `readUUID.bench.ts`).
45
+ * Since this example formats every UUID to a string, that swap is the dominant
46
+ * win — the `readDecimal64(2)` closure (rebuilt per row above) is inlined too.
47
+ *
48
+ * MEASURED (Node 24 / V8, `orders.bench.ts`): ~2.6x faster — the largest win of
49
+ * the examples, dominated by the `formatUUIDTable` swap (every row stringifies a
50
+ * UUID; the table formatter is ~1.7x on its own and the row is otherwise cheap).
51
+ *
52
+ * `formatUUIDTable` uses a shared scratch buffer, so it is non-reentrant — fine
53
+ * here because the bytes are copied into the returned string synchronously before
54
+ * the next call.
55
+ */
56
+ export const readOrderRowFast: Reader<OrderRow> = (s) => {
57
+ const { buf, view } = s;
58
+ // One bounds check for the whole 26-byte fixed-width row.
59
+ const o = advance(s, 26);
60
+ const id = buf[o]!;
61
+ const uid = formatUUIDTable(buf.subarray(o + 1, o + 17));
62
+ const price: DecimalValue = [view.getBigInt64(o + 17, true), 2];
63
+ const status = view.getInt8(o + 25);
64
+ return { id, uid, price, status };
65
+ };
@@ -0,0 +1,60 @@
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";
6
+
7
+ /**
8
+ * Example: a profiles table — Array and Nullable wrappers.
9
+ *
10
+ * Columns (the trigger):
11
+ * id UInt32
12
+ * tags Array(String)
13
+ * score Nullable(Int32)
14
+ *
15
+ * `readArray(elem)` reads a LEB128 length then that many elements; `readNullable`
16
+ * reads a 1-byte present/NULL flag then the value. Both are combinators: pass the
17
+ * inner reader and they return a `Reader`. Empty array and NULL are the sharp
18
+ * cases (a single byte each).
19
+ */
20
+ export type ProfileRow = { id: number; tags: string[]; score: number | null };
21
+
22
+ export const readProfileRow: Reader<ProfileRow> = (s) => ({
23
+ id: readUInt32(s),
24
+ tags: readArray(readString)(s),
25
+ score: readNullable(readInt32)(s),
26
+ });
27
+
28
+ /**
29
+ * Optimized {@link readProfileRow}, monomorphized: `readArray(readString)` and
30
+ * `readNullable(readInt32)` each allocate a fresh combinator closure on EVERY
31
+ * row in the version above; here the array loop and the null-flag branch are
32
+ * inlined, so no per-row closures are created and the element/inner reads are
33
+ * straight-line. This is the kind of win the SKILL's "monomorphize" step targets;
34
+ * see `profiles.bench.ts`.
35
+ *
36
+ * MEASURED (Node 24 / V8, `profiles.bench.ts`): ~1.3x faster — removing the two
37
+ * per-row combinator closures (`readArray(readString)`, `readNullable(readInt32)`)
38
+ * is the win.
39
+ */
40
+ export const readProfileRowFast: Reader<ProfileRow> = (s) => {
41
+ const { buf, view } = s;
42
+
43
+ // id UInt32.
44
+ const id = view.getUint32(advance(s, 4), true);
45
+
46
+ // tags Array(String): count, then each a length-prefixed UTF-8 string.
47
+ const n = readUVarint(s);
48
+ const tags = new Array<string>(n);
49
+ for (let i = 0; i < n; i++) {
50
+ const len = readUVarint(s);
51
+ const start = advance(s, len);
52
+ tags[i] = buf.toString("utf8", start, start + len);
53
+ }
54
+
55
+ // score Nullable(Int32): null-flag byte, then if non-null a 4-byte int.
56
+ const score =
57
+ buf[advance(s, 1)]! !== 0 ? null : view.getInt32(advance(s, 4), true);
58
+
59
+ return { id, tags, score };
60
+ };
@@ -0,0 +1,102 @@
1
+ import {
2
+ readArray,
3
+ readMap,
4
+ readNullable,
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";
12
+
13
+ /**
14
+ * Example: a telemetry table — composite readers that nest.
15
+ *
16
+ * Columns (the trigger):
17
+ * host String
18
+ * tags Map(String, String)
19
+ * cpu Array(Float64)
20
+ * region Nullable(String)
21
+ * window Tuple(start UInt32, count UInt16)
22
+ *
23
+ * The combinators compose exactly the way the column type nests:
24
+ * `readMap(k, v)`, `readArray(elem)`, `readNullable(inner)`, and
25
+ * `readTupleNamed({...})` each take sub-readers and return a `Reader`. This is the
26
+ * generic (closure-per-element) API; a generated parser would monomorphize these
27
+ * into inlined per-type loops, but the result shape is exactly this.
28
+ */
29
+ export type TelemetryRow = {
30
+ host: string;
31
+ tags: Map<string, string>;
32
+ cpu: number[];
33
+ region: string | null;
34
+ window: { start: number; count: number };
35
+ };
36
+
37
+ export const readTelemetryRow: Reader<TelemetryRow> = (s) => ({
38
+ host: readString(s),
39
+ tags: readMap(readString, readString)(s),
40
+ cpu: readArray(readFloat64)(s),
41
+ region: readNullable(readString)(s),
42
+ window: readTupleNamed({ start: readUInt32, count: readUInt16 })(s),
43
+ });
44
+
45
+ /**
46
+ * Optimized {@link readTelemetryRow}, fully monomorphized: the API version above
47
+ * builds FOUR combinator closures per row (`readMap(...)`, `readArray(...)`,
48
+ * `readNullable(...)`, `readTupleNamed(...)`) and, for the named tuple, iterates
49
+ * a keys array building an object field by field. Here every loop and branch is
50
+ * inlined and the `window` object is a flat literal — no per-row closures, no
51
+ * key iteration. The most composite-heavy example.
52
+ *
53
+ * MEASURED (Node 24 / V8, `telemetry.bench.ts`): ~1.4x faster — four per-row
54
+ * combinator closures and the named-tuple key iteration removed.
55
+ */
56
+ export const readTelemetryRowFast: Reader<TelemetryRow> = (s) => {
57
+ const { buf, view } = s;
58
+
59
+ // host String: length prefix, then the bytes.
60
+ let len = readUVarint(s);
61
+ let start = advance(s, len);
62
+ const host = buf.toString("utf8", start, start + len);
63
+
64
+ // tags Map(String, String): count, then key/value strings.
65
+ const mapN = readUVarint(s);
66
+ const tags = new Map<string, string>();
67
+ for (let i = 0; i < mapN; i++) {
68
+ len = readUVarint(s);
69
+ start = advance(s, len);
70
+ const k = buf.toString("utf8", start, start + len);
71
+ len = readUVarint(s);
72
+ start = advance(s, len);
73
+ tags.set(k, buf.toString("utf8", start, start + len));
74
+ }
75
+
76
+ // cpu Array(Float64): count, then 8 bytes each.
77
+ const cpuN = readUVarint(s);
78
+ const cpu = new Array<number>(cpuN);
79
+ for (let i = 0; i < cpuN; i++) {
80
+ cpu[i] = view.getFloat64(advance(s, 8), true);
81
+ }
82
+
83
+ // region Nullable(String): null-flag byte, then if non-null a string.
84
+ let region: string | null;
85
+ if (buf[advance(s, 1)]! !== 0) {
86
+ region = null;
87
+ } else {
88
+ len = readUVarint(s);
89
+ start = advance(s, len);
90
+ region = buf.toString("utf8", start, start + len);
91
+ }
92
+
93
+ // window Tuple(start UInt32, count UInt16): two adjacent fixed-width fields,
94
+ // bounds-checked once (6 bytes), then read at literal offsets.
95
+ const w = advance(s, 6);
96
+ const window = {
97
+ start: view.getUint32(w, true),
98
+ count: view.getUint16(w + 4, true),
99
+ };
100
+
101
+ return { host, tags, cpu, region, window };
102
+ };
@@ -0,0 +1,32 @@
1
+ import { Cursor, advance } from "./core.js";
2
+
3
+ /**
4
+ * Scratch view for widening a `BFloat16`: its 16 bits are the top half of an
5
+ * IEEE 754 float32, so we stage them into a 4-byte buffer and read a float32.
6
+ */
7
+ const bf16Scratch = new DataView(new ArrayBuffer(4));
8
+
9
+ /** Read a `Float32`: 4 bytes, little-endian IEEE 754 single precision. */
10
+ export function readFloat32(state: Cursor): number {
11
+ return state.view.getFloat32(advance(state, 4), true);
12
+ }
13
+
14
+ /** Read a `Float64`: 8 bytes, little-endian IEEE 754 double precision. */
15
+ export function readFloat64(state: Cursor): number {
16
+ return state.view.getFloat64(advance(state, 8), true);
17
+ }
18
+
19
+ /**
20
+ * Read a `BFloat16`: 2 bytes, little-endian. BFloat16 is the high 16 bits of a
21
+ * float32 (same 8-bit exponent, 7-bit mantissa), so placing the bits in the top
22
+ * half of a 32-bit float and reading it back is exact.
23
+ *
24
+ * NOTE: `bf16Scratch` is module-level shared state written-then-read in this
25
+ * function. That is safe because the read is synchronous; do NOT introduce an
26
+ * `await`/`yield` between the `setUint32` and the `getFloat32`.
27
+ */
28
+ export function readBFloat16(state: Cursor): number {
29
+ const bits = state.view.getUint16(advance(state, 2), true);
30
+ bf16Scratch.setUint32(0, bits * 0x10000, true);
31
+ return bf16Scratch.getFloat32(0, true);
32
+ }
@@ -0,0 +1,109 @@
1
+ import { Cursor } from "./core.js";
2
+ import { readFloat64 } from "./floats.js";
3
+ import { readUInt8 } from "./integers.js";
4
+ import { readUVarint } from "./varint.js";
5
+
6
+ /** A geo `Point`: `[x, y]`, the base of every ClickHouse geo type. */
7
+ export type Point = [x: number, y: number];
8
+
9
+ // Geo types are concrete compositions of Point = Tuple(Float64, Float64). They
10
+ // are monomorphic (no sub-readers) — the generator can emit them as-is.
11
+
12
+ /** Read a `Point`: `Tuple(Float64, Float64)` -> `[x, y]`. */
13
+ export function readPoint(state: Cursor): Point {
14
+ const x = readFloat64(state);
15
+ const y = readFloat64(state);
16
+ return [x, y];
17
+ }
18
+
19
+ /**
20
+ * Read a `Ring`: `Array(Point)` — a LEB128 point count, then that many points.
21
+ * `LineString` has the identical wire (see {@link readLineString}). `readPoint`
22
+ * is inlined here (two `readFloat64`s) to drop a call per point on this hot path.
23
+ */
24
+ export function readRing(state: Cursor): Point[] {
25
+ const n = readUVarint(state);
26
+ const out: Point[] = [];
27
+ for (let i = 0; i < n; i++) {
28
+ const x = readFloat64(state);
29
+ const y = readFloat64(state);
30
+ out.push([x, y]);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ /**
36
+ * Read a `LineString`: `Array(Point)` (identical wire to a `Ring`). Points are
37
+ * inlined (two `readFloat64`s) to drop a call per point on this hot path.
38
+ */
39
+ export function readLineString(state: Cursor): Point[] {
40
+ const n = readUVarint(state);
41
+ const out: Point[] = [];
42
+ for (let i = 0; i < n; i++) {
43
+ const x = readFloat64(state);
44
+ const y = readFloat64(state);
45
+ out.push([x, y]);
46
+ }
47
+ return out;
48
+ }
49
+
50
+ /** Read a `Polygon`: `Array(Ring)` — the outer ring first, then any holes. */
51
+ export function readPolygon(state: Cursor): Point[][] {
52
+ const n = readUVarint(state);
53
+ const out: Point[][] = [];
54
+ for (let i = 0; i < n; i++) out.push(readRing(state));
55
+ return out;
56
+ }
57
+
58
+ /** Read a `MultiLineString`: `Array(LineString)` (identical wire to a `Polygon`). */
59
+ export function readMultiLineString(state: Cursor): Point[][] {
60
+ const n = readUVarint(state);
61
+ const out: Point[][] = [];
62
+ for (let i = 0; i < n; i++) out.push(readLineString(state));
63
+ return out;
64
+ }
65
+
66
+ /** Read a `MultiPolygon`: `Array(Polygon)`. */
67
+ export function readMultiPolygon(state: Cursor): Point[][][] {
68
+ const n = readUVarint(state);
69
+ const out: Point[][][] = [];
70
+ for (let i = 0; i < n; i++) out.push(readPolygon(state));
71
+ return out;
72
+ }
73
+
74
+ /**
75
+ * Read a `Geometry`: a named `Variant` over the six geo types. This is the
76
+ * MONOMORPHIZED form of `readVariant` for a concrete variant — a switch over the
77
+ * discriminant with each branch inlined, no reader array. The alternatives,
78
+ * sorted by type name (so in discriminant order), are LineString(0),
79
+ * MultiLineString(1), MultiPolygon(2), Point(3), Polygon(4), Ring(5); 0xFF is NULL.
80
+ *
81
+ * NOTE: the value shapes overlap — LineString and Ring are both `Point[]`,
82
+ * MultiLineString and Polygon both `Point[][]` — so the value alone does not say
83
+ * which geo type it was. If you need the kind, branch on the discriminant.
84
+ */
85
+ export function readGeometry(
86
+ state: Cursor,
87
+ ): Point | Point[] | Point[][] | Point[][][] | null {
88
+ const discriminant = readUInt8(state);
89
+ switch (discriminant) {
90
+ case 0:
91
+ return readLineString(state);
92
+ case 1:
93
+ return readMultiLineString(state);
94
+ case 2:
95
+ return readMultiPolygon(state);
96
+ case 3:
97
+ return readPoint(state);
98
+ case 4:
99
+ return readPolygon(state);
100
+ case 5:
101
+ return readRing(state);
102
+ case 0xff:
103
+ return null;
104
+ default:
105
+ throw new RangeError(
106
+ `RowBinary: unknown Geometry discriminant ${discriminant}`,
107
+ );
108
+ }
109
+ }