@clickhouse/client 1.23.0-head.fae5998.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 (88) hide show
  1. package/CHANGELOG.md +1342 -0
  2. package/README.md +18 -6
  3. package/dist/common/index.d.ts +2 -2
  4. package/dist/common/index.js +2 -2
  5. package/dist/common/index.js.map +1 -1
  6. package/dist/common/parse/column_types.d.ts +30 -2
  7. package/dist/common/parse/column_types.js +8 -0
  8. package/dist/common/parse/column_types.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/version.d.ts +1 -1
  13. package/dist/version.js +1 -1
  14. package/dist/version.js.map +1 -1
  15. package/package.json +7 -6
  16. package/skills/AGENTS.md +8 -0
  17. package/skills/clickhouse-js-node-rowbinary/AGENTS.md +44 -0
  18. package/skills/clickhouse-js-node-rowbinary/CHANGELOG.md +49 -0
  19. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/README.md +85 -14
  20. package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
  21. package/skills/{clickhouse-js-node-rowbinary-parser/SKILL.md → clickhouse-js-node-rowbinary/reader.md} +59 -123
  22. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/carts.ts +9 -5
  23. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/events.ts +5 -5
  24. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/iot.ts +4 -4
  25. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/ledger.ts +3 -3
  26. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/logs.ts +4 -4
  27. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/observability.ts +9 -10
  28. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/orders.ts +10 -9
  29. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/profiles.ts +5 -5
  30. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/telemetry.ts +6 -6
  31. package/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts +328 -0
  32. package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/dynamic.ts +12 -8
  33. package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -0
  34. package/skills/clickhouse-js-node-rowbinary/src/readers/header.ts +29 -0
  35. package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/reader.ts +17 -0
  36. package/skills/clickhouse-js-node-rowbinary/src/readers/rowBinaryWithNamesAndTypes.ts +155 -0
  37. package/skills/clickhouse-js-node-rowbinary/src/writers/aggregateFunction.ts +18 -0
  38. package/skills/clickhouse-js-node-rowbinary/src/writers/bool.ts +10 -0
  39. package/skills/clickhouse-js-node-rowbinary/src/writers/composite.ts +140 -0
  40. package/skills/clickhouse-js-node-rowbinary/src/writers/core.ts +92 -0
  41. package/skills/clickhouse-js-node-rowbinary/src/writers/datetime.ts +123 -0
  42. package/skills/clickhouse-js-node-rowbinary/src/writers/decimals.ts +51 -0
  43. package/skills/clickhouse-js-node-rowbinary/src/writers/enums.ts +18 -0
  44. package/skills/clickhouse-js-node-rowbinary/src/writers/floats.ts +40 -0
  45. package/skills/clickhouse-js-node-rowbinary/src/writers/geo.ts +125 -0
  46. package/skills/clickhouse-js-node-rowbinary/src/writers/integers.ts +90 -0
  47. package/skills/clickhouse-js-node-rowbinary/src/writers/interval.ts +11 -0
  48. package/skills/clickhouse-js-node-rowbinary/src/writers/ip.ts +121 -0
  49. package/skills/clickhouse-js-node-rowbinary/src/writers/lowCardinality.ts +12 -0
  50. package/skills/clickhouse-js-node-rowbinary/src/writers/nested.ts +17 -0
  51. package/skills/clickhouse-js-node-rowbinary/src/writers/nothing.ts +21 -0
  52. package/skills/clickhouse-js-node-rowbinary/src/writers/rows.ts +144 -0
  53. package/skills/clickhouse-js-node-rowbinary/src/writers/simpleAggregateFunction.ts +12 -0
  54. package/skills/clickhouse-js-node-rowbinary/src/writers/strings.ts +77 -0
  55. package/skills/clickhouse-js-node-rowbinary/src/writers/time.ts +54 -0
  56. package/skills/clickhouse-js-node-rowbinary/src/writers/uuid.ts +60 -0
  57. package/skills/clickhouse-js-node-rowbinary/src/writers/varint.ts +64 -0
  58. package/skills/clickhouse-js-node-rowbinary/src/writers/writer.ts +101 -0
  59. package/skills/clickhouse-js-node-rowbinary/writer.md +96 -0
  60. package/skills/clickhouse-js-node-rowbinary-parser/src/enums.ts +0 -28
  61. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/EXAMPLES.md +0 -0
  62. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/iot-rowbinary-vs-json.md +0 -0
  63. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/ledger-rowbinary-vs-json.md +0 -0
  64. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/logs-json-wins.md +0 -0
  65. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/wasm-vs-js.md +0 -0
  66. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/aggregateFunction.ts +0 -0
  67. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/bool.ts +0 -0
  68. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/columnar.ts +0 -0
  69. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/composite.ts +0 -0
  70. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/core.ts +0 -0
  71. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/datetime.ts +0 -0
  72. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/decimals.ts +0 -0
  73. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/floats.ts +0 -0
  74. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/geo.ts +0 -0
  75. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/integers.ts +0 -0
  76. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/interval.ts +0 -0
  77. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/ip.ts +0 -0
  78. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/json.ts +0 -0
  79. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/lowCardinality.ts +0 -0
  80. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nested.ts +0 -0
  81. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nothing.ts +0 -0
  82. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/rows.ts +0 -0
  83. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/simpleAggregateFunction.ts +0 -0
  84. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/stream.ts +0 -0
  85. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/strings.ts +0 -0
  86. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/time.ts +0 -0
  87. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/uuid.ts +0 -0
  88. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/varint.ts +0 -0
@@ -0,0 +1,54 @@
1
+ import { type Writer, Sink } from "./core.js";
2
+ import { type ScaledTicks, type Seconds } from "../readers/time.js";
3
+ import { writeInt32, writeInt64 } from "./integers.js";
4
+
5
+ /**
6
+ * Write a `Time`: 4-byte signed `Int32` seconds-of-day. The inverse of `readTime`;
7
+ * pair with {@link parseTime} to start from an "[-]HH:MM:SS" string.
8
+ */
9
+ export function writeTime(sink: Sink, value: Seconds): void {
10
+ writeInt32(sink, value);
11
+ }
12
+
13
+ /**
14
+ * Write a `Time64(P)`: 8-byte signed `Int64` count of `10^-P`-second ticks, from
15
+ * a {@link ScaledTicks} `[ticks, precision]`. The inverse of `readTime64`; the
16
+ * precision lives in the type, so only `ticks` is written. Pair with
17
+ * {@link parseTime64} to start from a string.
18
+ */
19
+ export const writeTime64: Writer<ScaledTicks> = (sink, [ticks]) =>
20
+ writeInt64(sink, ticks);
21
+
22
+ /**
23
+ * Parse an "[-]HH:MM:SS" string into signed seconds-of-day — the inverse of
24
+ * `formatTime`. The hour field may exceed two digits (range ±999:59:59).
25
+ */
26
+ export function parseTime(text: string): Seconds {
27
+ const neg = text.startsWith("-");
28
+ const body = neg ? text.slice(1) : text;
29
+ const [hh, mm, ss] = body.split(":");
30
+ const seconds = Number(hh) * 3600 + Number(mm) * 60 + Number(ss);
31
+ return neg ? -seconds : seconds;
32
+ }
33
+
34
+ /**
35
+ * Parse an "[-]HH:MM:SS[.fff]" string into a {@link ScaledTicks} at the given
36
+ * `precision` — the inverse of `formatTime64`. A shorter fraction is right-padded
37
+ * with zeros to `precision`; a longer one is truncated.
38
+ */
39
+ export function parseTime64(text: string, precision: number): ScaledTicks {
40
+ const neg = text.startsWith("-");
41
+ const body = neg ? text.slice(1) : text;
42
+ const dot = body.indexOf(".");
43
+ const timePart = dot < 0 ? body : body.slice(0, dot);
44
+ const fracPart = dot < 0 ? "" : body.slice(dot + 1);
45
+ const [hh, mm, ss] = timePart.split(":");
46
+ const scale = 10n ** BigInt(precision);
47
+ const wholeSeconds =
48
+ BigInt(Number(hh) * 3600 + Number(mm) * 60 + Number(ss)) * scale;
49
+ const frac = BigInt(
50
+ (fracPart + "0".repeat(precision)).slice(0, precision) || "0",
51
+ );
52
+ const ticks = wholeSeconds + frac;
53
+ return [neg ? -ticks : ticks, precision];
54
+ }
@@ -0,0 +1,60 @@
1
+ import { Sink, reserve } from "./core.js";
2
+
3
+ /**
4
+ * Parse a canonical `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` UUID string into the
5
+ * raw 16 wire bytes — the inverse of `formatUUID`. ClickHouse stores a UUID as
6
+ * two little-endian `UInt64` halves (high then low), so the 32 hex digits are
7
+ * split at the midpoint and each half written little-endian. Pair with
8
+ * {@link writeUUID}.
9
+ */
10
+ export function parseUUID(text: string): Buffer {
11
+ const hex = text.replace(/-/g, "");
12
+ if (hex.length !== 32) {
13
+ throw new RangeError(
14
+ `RowBinary: invalid UUID string ${JSON.stringify(text)}`,
15
+ );
16
+ }
17
+ const v = BigInt("0x" + hex);
18
+ // SAFE: allocUnsafe — the two writeBigUInt64LE calls below overwrite all 16
19
+ // bytes (offsets 0..7 and 8..15), so no uninitialized pool memory survives.
20
+ const b = Buffer.allocUnsafe(16);
21
+ b.writeBigUInt64LE(v >> 64n, 0); // high half -> first 8 bytes
22
+ b.writeBigUInt64LE(v & 0xffffffffffffffffn, 8); // low half -> last 8 bytes
23
+ return b;
24
+ }
25
+
26
+ /**
27
+ * Write a `UUID` from its raw 16 wire bytes (as produced by `readUUID` or
28
+ * {@link parseUUID}): copied verbatim. The inverse of `readUUID`. Throws unless
29
+ * exactly 16 bytes are given.
30
+ */
31
+ export function writeUUID(sink: Sink, value: Uint8Array): void {
32
+ if (value.length !== 16) {
33
+ throw new RangeError(
34
+ `RowBinary: UUID must be 16 bytes, got ${value.length}`,
35
+ );
36
+ }
37
+ const o = reserve(sink, 16);
38
+ sink.buf.set(value, o);
39
+ }
40
+
41
+ /**
42
+ * Write a `UUID` from a single 128-bit `bigint` (`hi << 64 | lo`) — the inverse
43
+ * of `readUUIDBigInt`. The high 64 bits go to the first little-endian `UInt64`
44
+ * half, the low 64 bits to the second.
45
+ */
46
+ export function writeUUIDBigInt(sink: Sink, value: bigint): void {
47
+ const o = reserve(sink, 16);
48
+ sink.buf.writeBigUInt64LE((value >> 64n) & 0xffffffffffffffffn, o);
49
+ sink.buf.writeBigUInt64LE(value & 0xffffffffffffffffn, o + 8);
50
+ }
51
+
52
+ /**
53
+ * Write a `UUID` from its two raw little-endian `UInt64` halves `[hi, lo]` — the
54
+ * inverse of `readUUIDHiLo`, the faithful wire split with no combining work.
55
+ */
56
+ export function writeUUIDHiLo(sink: Sink, [hi, lo]: [bigint, bigint]): void {
57
+ const o = reserve(sink, 16);
58
+ sink.buf.writeBigUInt64LE(hi, o);
59
+ sink.buf.writeBigUInt64LE(lo, o + 8);
60
+ }
@@ -0,0 +1,64 @@
1
+ import { Sink, reserve } from "./core.js";
2
+
3
+ /**
4
+ * Write a LEB128 unsigned varint — the encode mirror of `readUVarint` (used for
5
+ * string/array/map lengths).
6
+ *
7
+ * Takes a JS `number`, so it is NOT bigint-friendly: the value MUST be a
8
+ * non-negative integer no larger than `Number.MAX_SAFE_INTEGER` (2^53 - 1). That
9
+ * precondition is NOT checked here — at this level the data is expected to be
10
+ * correct (a length the encoder itself produced), and an out-of-range value is a
11
+ * programming error the server will reject. If you genuinely need lengths beyond
12
+ * 2^53, write a bigint version with a bigint accumulator instead of widening this
13
+ * one.
14
+ *
15
+ * UNROLLED, mirroring `readUVarint`: branch on magnitude so the exact byte count
16
+ * is known up front for a single {@link reserve}, with no length-counting loop.
17
+ * Each byte carries 7 payload bits low-first, with the continuation bit (`+ 0x80`)
18
+ * set while more bits remain. `/` and `%` (never `>>>`/`&`): JS bitwise operators
19
+ * are 32-bit and would corrupt values past bit 31. The overwhelmingly common
20
+ * 1–2 byte case costs one or two compares.
21
+ */
22
+ export function writeUVarint(sink: Sink, value: number): void {
23
+ if (value < 0x80) {
24
+ sink.buf[reserve(sink, 1)] = value;
25
+ return;
26
+ }
27
+ if (value < 0x4000) {
28
+ const o = reserve(sink, 2);
29
+ sink.buf[o] = (value % 128) + 0x80;
30
+ sink.buf[o + 1] = Math.floor(value / 128);
31
+ return;
32
+ }
33
+ if (value < 0x200000) {
34
+ const o = reserve(sink, 3);
35
+ sink.buf[o] = (value % 128) + 0x80;
36
+ sink.buf[o + 1] = (Math.floor(value / 128) % 128) + 0x80;
37
+ sink.buf[o + 2] = Math.floor(value / 16384);
38
+ return;
39
+ }
40
+ if (value < 0x10000000) {
41
+ const o = reserve(sink, 4);
42
+ sink.buf[o] = (value % 128) + 0x80;
43
+ sink.buf[o + 1] = (Math.floor(value / 128) % 128) + 0x80;
44
+ sink.buf[o + 2] = (Math.floor(value / 16384) % 128) + 0x80;
45
+ sink.buf[o + 3] = Math.floor(value / 2097152);
46
+ return;
47
+ }
48
+ // >= 2^28 — rare for RowBinary lengths. Fall back to a short loop writing into
49
+ // a single span sized by a leading magnitude count (still one reserve()).
50
+ let size = 5;
51
+ for (
52
+ let v = Math.floor(value / 268435456);
53
+ v >= 0x80;
54
+ v = Math.floor(v / 128)
55
+ )
56
+ size++;
57
+ const o = reserve(sink, size);
58
+ let v = value;
59
+ for (let i = 0; i < size - 1; i++) {
60
+ sink.buf[o + i] = (v % 128) + 0x80;
61
+ v = Math.floor(v / 128);
62
+ }
63
+ sink.buf[o + size - 1] = v;
64
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Barrel re-export of the RowBinary WRITER — the encode mirror of `reader.ts`,
3
+ * split by type family across parallel `*.ts` modules (the readers stay in
4
+ * their own files untouched). Import from here for everything in one place, or
5
+ * from a specific module (e.g. `./integers.js`, `./strings.js`) to
6
+ * pull in only the sub-writers a given result needs — the latter is what a
7
+ * generated encoder should do, copying just the modules its column types require.
8
+ *
9
+ * Each `writeX` is the inverse of the matching `readX`: it appends the value's
10
+ * RowBinary bytes to a {@link Sink} (the write-side mirror of the reader's
11
+ * `Cursor`). Leaf writers are `Writer<T>`s directly; combinators (e.g.
12
+ * `writeArray`) take sub-writers and return a `Writer`, so types compose with no
13
+ * per-element closures — exactly like the reader combinators.
14
+ *
15
+ * const sink = new Sink(Buffer.allocUnsafe(64));
16
+ * writeUInt8(sink, 255);
17
+ * sink.bytes(); // the encoded RowBinary
18
+ *
19
+ * - core — Sink, Writer<T>, reserve (mirror of Cursor, Reader<T>, advance)
20
+ * - varint — writeUVarint
21
+ *
22
+ * The dynamic AST-based encode path (the inverse of `compile.ts` /
23
+ * `rowBinaryWithNamesAndTypes.ts` / `dynamic.ts`) is intentionally NOT part of
24
+ * this barrel yet.
25
+ */
26
+ export { Sink, reserve, BufferFull, type Writer } from "./core.js";
27
+ export { writeUVarint } from "./varint.js";
28
+ export {
29
+ writeUInt8,
30
+ writeInt8,
31
+ writeUInt16,
32
+ writeInt16,
33
+ writeUInt32,
34
+ writeInt32,
35
+ writeUInt64,
36
+ writeInt64,
37
+ writeUInt128,
38
+ writeInt128,
39
+ writeUInt256,
40
+ writeInt256,
41
+ } from "./integers.js";
42
+ export { writeBool } from "./bool.js";
43
+ export { writeEnum8, writeEnum16 } from "./enums.js";
44
+ export { writeFloat32, writeFloat64, writeBFloat16 } from "./floats.js";
45
+ export {
46
+ writeDecimal32,
47
+ writeDecimal64,
48
+ writeDecimal128,
49
+ writeDecimal256,
50
+ parseDecimal,
51
+ } from "./decimals.js";
52
+ export {
53
+ writeString,
54
+ writeStringBytes,
55
+ writeFixedString,
56
+ writeFixedStringBytes,
57
+ } from "./strings.js";
58
+ export {
59
+ writeUUID,
60
+ writeUUIDBigInt,
61
+ writeUUIDHiLo,
62
+ parseUUID,
63
+ } from "./uuid.js";
64
+ export { writeIPv4, writeIPv6, parseIPv4, parseIPv6 } from "./ip.js";
65
+ export {
66
+ writeDate,
67
+ writeDate32,
68
+ writeDateTime,
69
+ writeDateTime64,
70
+ writeDateTime64P3,
71
+ writeDateTime64P6,
72
+ writeDateTime64P9,
73
+ } from "./datetime.js";
74
+ export { writeTime, writeTime64, parseTime, parseTime64 } from "./time.js";
75
+ export { writeInterval } from "./interval.js";
76
+ export {
77
+ writeNullable,
78
+ writeArray,
79
+ writeQBit,
80
+ writeTuple,
81
+ writeTupleNamed,
82
+ writeMap,
83
+ writeVariant,
84
+ type VariantValue,
85
+ } from "./composite.js";
86
+ export { writeRows, FLUSH_CHANNEL_NAME, type WriteRowsFlush } from "./rows.js";
87
+ export {
88
+ writePoint,
89
+ writeRing,
90
+ writeLineString,
91
+ writePolygon,
92
+ writeMultiLineString,
93
+ writeMultiPolygon,
94
+ writeGeometry,
95
+ type GeometryValue,
96
+ } from "./geo.js";
97
+ export { writeLowCardinality } from "./lowCardinality.js";
98
+ export { writeSimpleAggregateFunction } from "./simpleAggregateFunction.js";
99
+ export { writeNested } from "./nested.js";
100
+ export { writeNothing } from "./nothing.js";
101
+ export { writeAggregateFunction } from "./aggregateFunction.js";
@@ -0,0 +1,96 @@
1
+ # RowBinary writer (encode) for Node.js
2
+
3
+ Encoding JS values into a `RowBinary` payload to send to ClickHouse. Read
4
+ [SKILL.md](SKILL.md) first for the format gate ("is RowBinary even the right
5
+ format?") and the principles that apply to **both** directions; this file covers
6
+ what's specific to **writing**. Reading? See [reader.md](reader.md).
7
+
8
+ Each `writeX` encodes one value, appending its RowBinary bytes to a `Sink` (a
9
+ caller-supplied byte buffer plus the current write offset — state only, no write
10
+ methods). Leaf writers (`writeUInt8`, `writeString`, …) encode directly;
11
+ combinators (`writeArray`, `writeTuple`, …) take sub-writers and return a writer,
12
+ so composite types compose with no per-element closures. For the `Sink`/`Writer<T>`
13
+ types and how to drain the encoded bytes, see `src/writers/core.ts`. Import the
14
+ barrel as `@clickhouse/rowbinary/writer`, or a per-type module for just what you
15
+ need. (Structurally this is the mirror of the decode side in
16
+ [reader.md](reader.md), but you don't need the read side to write.)
17
+
18
+ ## Writer guidance
19
+
20
+ On top of the shared principles in [SKILL.md](SKILL.md), the write path has its own:
21
+
22
+ - **Reserve before you write.** Every fixed-width write goes through `reserve()`,
23
+ which bounds-checks against the fixed-length buffer and throws the `BufferFull`
24
+ sentinel when the chunk is full — your cue to flush what's written and continue
25
+ into a fresh buffer. Exact signature, return value, and throw contract are in
26
+ `src/writers/core.ts`.
27
+
28
+ - **Coalesce `reserve()` across a run of adjacent fixed-width columns.** Their
29
+ combined size is statically known, so reserve ONCE for the whole run and write
30
+ each value at a constant offset off the returned base — one bounds-check instead
31
+ of one per column. Only applies where every column in the run is fixed-width (a
32
+ variable-width writer like `writeString` reserves on its own).
33
+
34
+ - **Hoist sink state into locals in the generated writer.** `Sink.buf`/`Sink.view`
35
+ are `readonly`, so bind them to locals once at the top and address them directly
36
+ instead of through `sink.` on every write. Keep the write position (`sink.pos`)
37
+ in a local too — but sync it back to `sink.pos` before any `reserve()` or
38
+ `BufferFull` throw, since those read and mutate it to decide capacity and where a
39
+ flushed buffer resumes.
40
+
41
+ - **Stream the whole result with `writeRows`, not a one-shot writer.** When you
42
+ need to encode a large or unbounded row source, reach for `writeRows` rather than
43
+ a `Writer<readonly T[]>`: it owns a fixed buffer and yields it as a generator,
44
+ streaming the result out chunk by chunk instead of demanding it all fit at once.
45
+ It never leaks a half-written row (it rewinds to the last whole-row boundary
46
+ before flushing) and never fails on a single big row (it grows the buffer to fit).
47
+ It also publishes a per-flush diagnostics-channel event for buffer-utilization
48
+ metrics. Signature, default buffer size, channel name and payload type, the
49
+ growth/rewind details, and a usage example are all in `src/writers/rows.ts`.
50
+
51
+ - **No defensive validation on the hot path.** Don't add `isFinite`/`NaN`/range
52
+ checks to `writeX`; document the precondition in JSDoc instead. Two narrow
53
+ exceptions — framing-keeping checks and zero-cost parse-time helpers — are
54
+ spelled out in [AGENTS.md](AGENTS.md); `src/writers/ip.ts` is the worked
55
+ example (`writeIPv6`, `parseIPv6`).
56
+
57
+ - **Lossy time conversions floor, never round.** Every date/time writer in
58
+ `src/writers/datetime.ts` drops the sub-unit it can't encode by flooring toward
59
+ −∞, so a caller's value is never silently shifted _up_ to the wrong
60
+ day/second/tick and pre-1970 instants stay correct (not rounded toward the
61
+ epoch). See its JSDoc for the per-function specifics.
62
+
63
+ ## Writer type family references
64
+
65
+ The writers live as real code under `src/writers/`, one file per type family
66
+ (same basenames as the readers, under the `writers/` directory).
67
+
68
+ | Value to encode (trigger) | Open |
69
+ | --------------------------------------------------------------------------------------- | ---------------------------------------- |
70
+ | **Always** — sink state, `reserve()`, `BufferFull`, `Writer<T>` | `src/writers/core.ts` |
71
+ | LEB128 length/count prefixes for `String`/`Array`/`Map` (`writeUVarint`) | `src/writers/varint.ts` |
72
+ | `Int8`–`Int256`, `UInt8`–`UInt256` | `src/writers/integers.ts` |
73
+ | `Bool` (`writeBool`) | `src/writers/bool.ts` |
74
+ | `Enum8`, `Enum16` (`writeEnum8`/`writeEnum16`; pass the raw int) | `src/writers/enums.ts` |
75
+ | `Float32`, `Float64`, `BFloat16` | `src/writers/floats.ts` |
76
+ | `Decimal32/64/128/256`, `Decimal(P, S)` (`parseDecimal`) | `src/writers/decimals.ts` |
77
+ | `String`, `FixedString(N)` (`writeString`/`writeStringBytes`/`writeFixedString`) | `src/writers/strings.ts` |
78
+ | `UUID` (`writeUUID`, `parseUUID`) | `src/writers/uuid.ts` |
79
+ | `IPv4`, `IPv6` (`writeIPv4`/`writeIPv6`, `parseIPv4`/`parseIPv6`) | `src/writers/ip.ts` |
80
+ | `Date`, `Date32`, `DateTime`, `DateTime(tz)`, `DateTime64(P[, tz])` | `src/writers/datetime.ts` |
81
+ | `Time`, `Time64(P)` (`parseTime`/`parseTime64`) | `src/writers/time.ts` |
82
+ | `IntervalNanosecond` … `IntervalYear` | `src/writers/interval.ts` |
83
+ | `Array(T)`, `Map(K, V)`, `Tuple(...)`, `Nullable(T)`, `Variant(...)`, `QBit(...)` | `src/writers/composite.ts` |
84
+ | `Point`, `Ring`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `Geometry` | `src/writers/geo.ts` |
85
+ | The whole result — write rows from a value source (`writeRows`) | `src/writers/rows.ts` |
86
+ | `LowCardinality(T)` — transparent, encode as `T` | `src/writers/lowCardinality.ts` |
87
+ | `SimpleAggregateFunction(f, T)` — transparent, encode as `T` | `src/writers/simpleAggregateFunction.ts` |
88
+ | `Nested(...)` — no wire of its own; `Array(Tuple(...))` | `src/writers/nested.ts` |
89
+ | `Nothing` — zero-width, never encoded (only wrapped) | `src/writers/nothing.ts` |
90
+ | `AggregateFunction(...)` — opaque state; produce server-side | `src/writers/aggregateFunction.ts` |
91
+
92
+ **No writer counterpart yet** — these reader paths are decode-only for now:
93
+ `dynamic.ts`, `json.ts`, `stream.ts`, the `RowBinaryWithNamesAndTypes`
94
+ header/compile/runtime path (`header.ts`, `compile.ts`,
95
+ `rowBinaryWithNamesAndTypes.ts`), and the columnar typed-array path
96
+ (`columnar.ts`). The AST-based dynamic encode path is intentionally not built.
@@ -1,28 +0,0 @@
1
- import { Cursor, advance } from "./core.js";
2
-
3
- /**
4
- * Read an `Enum8`: the value's underlying signed `Int8`. The name<->value map
5
- * lives in the column's type, not the bytes. Two strategies, both better than one
6
- * shared name-resolving reader:
7
- *
8
- * - Keep the number: carry the raw Int8 and map to a name only where needed —
9
- * most hot loops never need it.
10
- * - Or generate a per-enum reader with a baked-in constant map, so the JIT can
11
- * monomorphize each enum's decode:
12
- *
13
- * const STATUS = { 1: "active", 2: "closed" } as const;
14
- * const readStatusEnum = (s) => STATUS[readInt8(s) as keyof typeof STATUS];
15
- */
16
- export function readEnum8(state: Cursor): number {
17
- return state.view.getInt8(advance(state, 1));
18
- }
19
-
20
- /**
21
- * Read an `Enum16`: the value's underlying signed `Int16` (2 bytes). The
22
- * name<->value map lives in the column's type definition, not the bytes. Prefer
23
- * keeping the number, or a generated per-enum reader with a baked-in constant
24
- * map so the JIT can optimize each enum's decode independently.
25
- */
26
- export function readEnum16(state: Cursor): number {
27
- return state.view.getInt16(advance(state, 2), true);
28
- }