@query-farm/vgi 0.6.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 (206) hide show
  1. package/LICENSE +134 -0
  2. package/README.md +494 -0
  3. package/dist/__dbg.test-d.d.ts +1 -0
  4. package/dist/arguments/argument-spec.d.ts +51 -0
  5. package/dist/arguments/arguments.d.ts +13 -0
  6. package/dist/arrow/codec/branded.d.ts +83 -0
  7. package/dist/arrow/codec/registry.d.ts +29 -0
  8. package/dist/arrow/codec/repr.d.ts +23 -0
  9. package/dist/arrow/codec/type-descriptors.d.ts +109 -0
  10. package/dist/arrow/impl-arrowjs/build.d.ts +19 -0
  11. package/dist/arrow/impl-arrowjs/canonical.d.ts +15 -0
  12. package/dist/arrow/impl-arrowjs/empty.d.ts +7 -0
  13. package/dist/arrow/impl-arrowjs/filter.d.ts +11 -0
  14. package/dist/arrow/impl-arrowjs/index.d.ts +11 -0
  15. package/dist/arrow/impl-arrowjs/ipc.d.ts +24 -0
  16. package/dist/arrow/impl-arrowjs/iterate.d.ts +39 -0
  17. package/dist/arrow/impl-arrowjs/project.d.ts +10 -0
  18. package/dist/arrow/impl-arrowjs/schema.d.ts +62 -0
  19. package/dist/arrow/impl-arrowjs/statistics.d.ts +13 -0
  20. package/dist/arrow/impl-flechette/build.d.ts +14 -0
  21. package/dist/arrow/impl-flechette/canonical.d.ts +18 -0
  22. package/dist/arrow/impl-flechette/empty.d.ts +9 -0
  23. package/dist/arrow/impl-flechette/filter.d.ts +10 -0
  24. package/dist/arrow/impl-flechette/index.d.ts +11 -0
  25. package/dist/arrow/impl-flechette/ipc.d.ts +33 -0
  26. package/dist/arrow/impl-flechette/iterate.d.ts +29 -0
  27. package/dist/arrow/impl-flechette/message-meta.d.ts +10 -0
  28. package/dist/arrow/impl-flechette/normalize-type.d.ts +11 -0
  29. package/dist/arrow/impl-flechette/project.d.ts +3 -0
  30. package/dist/arrow/impl-flechette/schema.d.ts +47 -0
  31. package/dist/arrow/impl-flechette/statistics.d.ts +13 -0
  32. package/dist/arrow/index.d.ts +11 -0
  33. package/dist/arrow/predicates.d.ts +47 -0
  34. package/dist/arrow/repr.test-d.d.ts +52 -0
  35. package/dist/arrow/schema-types.d.ts +71 -0
  36. package/dist/arrow/types.d.ts +57 -0
  37. package/dist/catalog/attach-option.d.ts +33 -0
  38. package/dist/catalog/attach-options.d.ts +30 -0
  39. package/dist/catalog/composite.d.ts +33 -0
  40. package/dist/catalog/descriptors.d.ts +154 -0
  41. package/dist/catalog/interface.d.ts +239 -0
  42. package/dist/catalog/read-only.d.ts +33 -0
  43. package/dist/client/client.d.ts +189 -0
  44. package/dist/client/errors.d.ts +26 -0
  45. package/dist/client/helpers.d.ts +12 -0
  46. package/dist/client/protocol.d.ts +16 -0
  47. package/dist/client/types.d.ts +125 -0
  48. package/dist/client-entry.d.ts +11 -0
  49. package/dist/client-entry.js +48 -0
  50. package/dist/client-entry.js.map +9 -0
  51. package/dist/codec/asd.d.ts +25 -0
  52. package/dist/crypto.d.ts +36 -0
  53. package/dist/errors.d.ts +24 -0
  54. package/dist/filter-pushdown/collector.d.ts +32 -0
  55. package/dist/filter-pushdown/deserialize.d.ts +16 -0
  56. package/dist/filter-pushdown/evaluate.d.ts +51 -0
  57. package/dist/filter-pushdown/index.d.ts +4 -0
  58. package/dist/filter-pushdown/types.d.ts +84 -0
  59. package/dist/functions/aggregate.d.ts +129 -0
  60. package/dist/functions/copy-from.d.ts +81 -0
  61. package/dist/functions/copy-to.d.ts +121 -0
  62. package/dist/functions/registry.d.ts +16 -0
  63. package/dist/functions/scalar.d.ts +98 -0
  64. package/dist/functions/storage-cf-do.d.ts +47 -0
  65. package/dist/functions/storage.d.ts +86 -0
  66. package/dist/functions/table-buffering.d.ts +99 -0
  67. package/dist/functions/table-in-out.d.ts +64 -0
  68. package/dist/functions/table.d.ts +118 -0
  69. package/dist/functions/types.d.ts +157 -0
  70. package/dist/generated/vgi-client.d.ts +466 -0
  71. package/dist/generated/vgi-protocol-schemas.d.ts +115 -0
  72. package/dist/index.core.d.ts +42 -0
  73. package/dist/index.d.ts +2 -0
  74. package/dist/index.js +10888 -0
  75. package/dist/index.js.map +85 -0
  76. package/dist/metadata/resolve.d.ts +3 -0
  77. package/dist/metadata/serialize.d.ts +12 -0
  78. package/dist/metadata/types.d.ts +47 -0
  79. package/dist/protocol/dispatch.d.ts +23 -0
  80. package/dist/protocol/handlers/aggregate.d.ts +3 -0
  81. package/dist/protocol/handlers/catalog/admin.d.ts +3 -0
  82. package/dist/protocol/handlers/catalog/index.d.ts +3 -0
  83. package/dist/protocol/handlers/catalog/index_methods.d.ts +3 -0
  84. package/dist/protocol/handlers/catalog/macro.d.ts +3 -0
  85. package/dist/protocol/handlers/catalog/shared.d.ts +70 -0
  86. package/dist/protocol/handlers/catalog/table.d.ts +3 -0
  87. package/dist/protocol/handlers/catalog/view.d.ts +3 -0
  88. package/dist/protocol/handlers/function.d.ts +8 -0
  89. package/dist/protocol/handlers/shared.d.ts +31 -0
  90. package/dist/protocol/handlers/table-buffering.d.ts +3 -0
  91. package/dist/protocol/serialize.d.ts +4 -0
  92. package/dist/protocol/serializers/arguments.d.ts +3 -0
  93. package/dist/protocol/serializers/bind.d.ts +9 -0
  94. package/dist/protocol/serializers/cardinality.d.ts +7 -0
  95. package/dist/protocol/serializers/init.d.ts +9 -0
  96. package/dist/protocol/serializers/shared.d.ts +12 -0
  97. package/dist/protocol/state-serializer.d.ts +23 -0
  98. package/dist/protocol/types.d.ts +129 -0
  99. package/dist/secrets/helpers.d.ts +15 -0
  100. package/dist/types.d.ts +52 -0
  101. package/dist/util/arrow/index.d.ts +1 -0
  102. package/dist/util/bytes.d.ts +2 -0
  103. package/dist/util/statistics.d.ts +7 -0
  104. package/dist/worker-cf-entry.d.ts +30 -0
  105. package/dist/worker-cf-entry.js +8767 -0
  106. package/dist/worker-cf-entry.js.map +74 -0
  107. package/dist/worker.d.ts +36 -0
  108. package/package.json +96 -0
  109. package/src/arguments/argument-spec.ts +200 -0
  110. package/src/arguments/arguments.ts +81 -0
  111. package/src/arrow/codec/branded.ts +206 -0
  112. package/src/arrow/codec/registry.ts +674 -0
  113. package/src/arrow/codec/repr.ts +181 -0
  114. package/src/arrow/codec/type-descriptors.ts +137 -0
  115. package/src/arrow/impl-arrowjs/build.ts +88 -0
  116. package/src/arrow/impl-arrowjs/canonical.ts +509 -0
  117. package/src/arrow/impl-arrowjs/empty.ts +72 -0
  118. package/src/arrow/impl-arrowjs/filter.ts +53 -0
  119. package/src/arrow/impl-arrowjs/index.ts +54 -0
  120. package/src/arrow/impl-arrowjs/ipc.ts +67 -0
  121. package/src/arrow/impl-arrowjs/iterate.ts +159 -0
  122. package/src/arrow/impl-arrowjs/project.ts +73 -0
  123. package/src/arrow/impl-arrowjs/schema.ts +256 -0
  124. package/src/arrow/impl-arrowjs/statistics.ts +185 -0
  125. package/src/arrow/impl-flechette/build.ts +65 -0
  126. package/src/arrow/impl-flechette/canonical.ts +289 -0
  127. package/src/arrow/impl-flechette/empty.ts +20 -0
  128. package/src/arrow/impl-flechette/filter.ts +42 -0
  129. package/src/arrow/impl-flechette/index.ts +54 -0
  130. package/src/arrow/impl-flechette/ipc.ts +110 -0
  131. package/src/arrow/impl-flechette/iterate.ts +121 -0
  132. package/src/arrow/impl-flechette/message-meta.ts +175 -0
  133. package/src/arrow/impl-flechette/normalize-type.ts +99 -0
  134. package/src/arrow/impl-flechette/project.ts +64 -0
  135. package/src/arrow/impl-flechette/schema.ts +202 -0
  136. package/src/arrow/impl-flechette/statistics.ts +198 -0
  137. package/src/arrow/index.ts +129 -0
  138. package/src/arrow/predicates.ts +66 -0
  139. package/src/arrow/repr.test-d.ts +189 -0
  140. package/src/arrow/schema-types.ts +213 -0
  141. package/src/arrow/types.ts +74 -0
  142. package/src/catalog/attach-option.ts +107 -0
  143. package/src/catalog/attach-options.ts +79 -0
  144. package/src/catalog/composite.ts +180 -0
  145. package/src/catalog/descriptors.ts +164 -0
  146. package/src/catalog/interface.ts +721 -0
  147. package/src/catalog/read-only.ts +909 -0
  148. package/src/client/client.ts +1348 -0
  149. package/src/client/errors.ts +69 -0
  150. package/src/client/helpers.ts +56 -0
  151. package/src/client/protocol.ts +33 -0
  152. package/src/client/types.ts +140 -0
  153. package/src/client-entry.ts +112 -0
  154. package/src/codec/asd.ts +147 -0
  155. package/src/crypto.ts +143 -0
  156. package/src/errors.ts +61 -0
  157. package/src/filter-pushdown/collector.ts +147 -0
  158. package/src/filter-pushdown/deserialize.ts +253 -0
  159. package/src/filter-pushdown/evaluate.ts +591 -0
  160. package/src/filter-pushdown/index.ts +28 -0
  161. package/src/filter-pushdown/types.ts +91 -0
  162. package/src/functions/aggregate.ts +384 -0
  163. package/src/functions/copy-from.ts +282 -0
  164. package/src/functions/copy-to.ts +319 -0
  165. package/src/functions/registry.ts +258 -0
  166. package/src/functions/scalar.ts +356 -0
  167. package/src/functions/storage-cf-do.ts +303 -0
  168. package/src/functions/storage.ts +521 -0
  169. package/src/functions/table-buffering.ts +562 -0
  170. package/src/functions/table-in-out.ts +373 -0
  171. package/src/functions/table.ts +521 -0
  172. package/src/functions/types.ts +192 -0
  173. package/src/generated/vgi-client.ts +560 -0
  174. package/src/generated/vgi-protocol-schemas.ts +864 -0
  175. package/src/index.core.ts +267 -0
  176. package/src/index.ts +11 -0
  177. package/src/metadata/resolve.ts +109 -0
  178. package/src/metadata/serialize.ts +242 -0
  179. package/src/metadata/types.ts +60 -0
  180. package/src/protocol/dispatch.ts +52 -0
  181. package/src/protocol/handlers/aggregate.ts +355 -0
  182. package/src/protocol/handlers/catalog/admin.ts +368 -0
  183. package/src/protocol/handlers/catalog/index.ts +25 -0
  184. package/src/protocol/handlers/catalog/index_methods.ts +65 -0
  185. package/src/protocol/handlers/catalog/macro.ts +114 -0
  186. package/src/protocol/handlers/catalog/shared.ts +291 -0
  187. package/src/protocol/handlers/catalog/table.ts +401 -0
  188. package/src/protocol/handlers/catalog/view.ts +119 -0
  189. package/src/protocol/handlers/function.ts +302 -0
  190. package/src/protocol/handlers/shared.ts +80 -0
  191. package/src/protocol/handlers/table-buffering.ts +207 -0
  192. package/src/protocol/serialize.ts +23 -0
  193. package/src/protocol/serializers/arguments.ts +160 -0
  194. package/src/protocol/serializers/bind.ts +212 -0
  195. package/src/protocol/serializers/cardinality.ts +45 -0
  196. package/src/protocol/serializers/init.ts +247 -0
  197. package/src/protocol/serializers/shared.ts +46 -0
  198. package/src/protocol/state-serializer.ts +181 -0
  199. package/src/protocol/types.ts +153 -0
  200. package/src/secrets/helpers.ts +70 -0
  201. package/src/types.ts +71 -0
  202. package/src/util/arrow/index.ts +23 -0
  203. package/src/util/bytes.ts +14 -0
  204. package/src/util/statistics.ts +40 -0
  205. package/src/worker-cf-entry.ts +172 -0
  206. package/src/worker.ts +212 -0
@@ -0,0 +1,674 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ //
3
+ // Codec registry — the single source of truth for how each Arrow type is
4
+ // represented as a JS value in this SDK.
5
+ //
6
+ // Three layers of representation are in scope for the codec design (phase 1
7
+ // implements the first two; the third is reserved for a later phase):
8
+ //
9
+ // - canonical: the internal pivot. One lossless JS value per Arrow type that
10
+ // mirrors the raw Arrow wire unit. This is what the per-backend
11
+ // `writeCanonicalColumn` / `readCanonicalValue` produce and consume, so it
12
+ // is identical across the arrow-js and flechette backends.
13
+ // - rich: the default author-facing value. Identical to canonical for
14
+ // every type EXCEPT date32 / date64, which surface as a JS `Date` (lossless
15
+ // in both directions). All temporal sub-second precision types stay
16
+ // numeric/bigint because `Date` can't hold us/ns losslessly.
17
+ // - raw: (reserved) a branded-primitive view layered on canonical. Not
18
+ // built in phase 1; the `Codec` interface leaves room for it so a later
19
+ // phase can add `rawToCanonical` / `canonicalToRaw` without reshaping this
20
+ // module.
21
+ //
22
+ // Canonical representation per Arrow type:
23
+ // bool -> boolean
24
+ // int8/16/32, uint8/16/32, float* -> number
25
+ // int64/uint64 -> bigint
26
+ // utf8 / largeUtf8 -> string
27
+ // binary / largeBinary / fsb -> Uint8Array
28
+ // date32 -> number (days since epoch)
29
+ // date64 -> bigint (ms since epoch)
30
+ // time32 -> number (raw unit: s or ms)
31
+ // time64 -> bigint (raw unit: us or ns)
32
+ // timestamp[s/ms/us/ns] -> bigint (raw unit)
33
+ // duration[*] -> bigint (raw unit)
34
+ // decimal128/256 -> bigint (UNSCALED integer)
35
+ // struct -> { field: canonical }
36
+ // list / largeList / fixedSizeList -> canonical[]
37
+ // map -> Array<[keyCanonical, valueCanonical]>
38
+ // dictionary -> the decoded value's canonical
39
+ //
40
+ // rich differs from canonical ONLY for date32 / date64 (both -> Date).
41
+ //
42
+ // Validation: every codec method validates its input and throws a clear error
43
+ // on invalid or lossy data (non-integer where an int is required, a bigint that
44
+ // overflows Number when narrowing, an out-of-range Date, etc.). null/undefined
45
+ // pass straight through as null.
46
+
47
+ import type { VgiDataType } from "../types.js";
48
+ import { TypeId } from "../predicates.js";
49
+
50
+ const MS_PER_DAY = 86_400_000;
51
+
52
+ /** Codec for a single Arrow type. Converts between the author-facing rich
53
+ * representation and the internal canonical pivot. Both directions validate
54
+ * and throw on invalid/lossy input. null/undefined pass through as null.
55
+ *
56
+ * (Raw<->canonical conversions are reserved for a later phase. When added they
57
+ * belong here as `rawToCanonical` / `canonicalToRaw`.) */
58
+ export interface Codec {
59
+ /** Human-readable type label, used in error messages. */
60
+ readonly label: string;
61
+ /** rich author value -> canonical pivot. */
62
+ richToCanonical(value: unknown): unknown;
63
+ /** canonical pivot -> rich author value. */
64
+ canonicalToRich(value: unknown): unknown;
65
+ /** branded raw author value -> canonical pivot. Largely identity on the
66
+ * underlying number/bigint (raw === canonical for every type), but validates
67
+ * and unbrands. Date32/Date64 raw values are plain day-number / ms-bigint,
68
+ * so this differs from richToCanonical only in NOT accepting a JS Date. */
69
+ rawToCanonical(value: unknown): unknown;
70
+ /** canonical pivot -> branded raw author value. Validates and brands
71
+ * (identity at runtime). */
72
+ canonicalToRaw(value: unknown): unknown;
73
+ }
74
+
75
+ function isNullish(v: unknown): boolean {
76
+ return v === null || v === undefined;
77
+ }
78
+
79
+ function err(label: string, msg: string, value: unknown): never {
80
+ let shown: string;
81
+ try {
82
+ shown =
83
+ typeof value === "bigint"
84
+ ? `${value}n`
85
+ : typeof value === "object"
86
+ ? Object.prototype.toString.call(value)
87
+ : String(value);
88
+ } catch {
89
+ shown = "<unprintable>";
90
+ }
91
+ throw new TypeError(`codec[${label}]: ${msg} (got ${typeof value}: ${shown})`);
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Scalar codec builders
96
+ // ---------------------------------------------------------------------------
97
+
98
+ /** Identity codec: rich === canonical === raw. Validates with `check`. */
99
+ function identity(label: string, check: (v: unknown) => void): Codec {
100
+ const conv = (value: unknown): unknown => {
101
+ if (isNullish(value)) return null;
102
+ check(value);
103
+ return value;
104
+ };
105
+ return {
106
+ label,
107
+ richToCanonical: conv,
108
+ canonicalToRich: conv,
109
+ rawToCanonical: conv,
110
+ canonicalToRaw: conv,
111
+ };
112
+ }
113
+
114
+ function checkBoolean(label: string) {
115
+ return (v: unknown) => {
116
+ if (typeof v !== "boolean") err(label, "expected boolean", v);
117
+ };
118
+ }
119
+
120
+ /** Integer-valued `number` within [min,max]. Accepts a bigint that fits. */
121
+ function checkIntNumber(label: string, min: number, max: number) {
122
+ return (v: unknown) => {
123
+ let n: number;
124
+ if (typeof v === "number") n = v;
125
+ else if (typeof v === "bigint") {
126
+ if (v < BigInt(min) || v > BigInt(max)) err(label, `bigint out of ${label} range`, v);
127
+ return;
128
+ } else return err(label, "expected number", v);
129
+ if (!Number.isInteger(n)) err(label, "expected an integer", v);
130
+ if (n < min || n > max) err(label, `value out of ${label} range [${min}, ${max}]`, v);
131
+ };
132
+ }
133
+
134
+ function checkFloat(label: string) {
135
+ return (v: unknown) => {
136
+ if (typeof v === "number") return;
137
+ if (typeof v === "bigint") return; // a bigint is acceptable for a float slot
138
+ err(label, "expected number", v);
139
+ };
140
+ }
141
+
142
+ function checkBigInt(label: string, bits: number, signed: boolean) {
143
+ const max = signed ? (1n << BigInt(bits - 1)) - 1n : (1n << BigInt(bits)) - 1n;
144
+ const min = signed ? -(1n << BigInt(bits - 1)) : 0n;
145
+ return (v: unknown) => {
146
+ let b: bigint;
147
+ if (typeof v === "bigint") b = v;
148
+ else if (typeof v === "number") {
149
+ if (!Number.isInteger(v)) err(label, "expected an integer", v);
150
+ b = BigInt(v);
151
+ } else return err(label, "expected bigint", v);
152
+ if (b < min || b > max) err(label, `value out of ${label} range`, v);
153
+ };
154
+ }
155
+
156
+ function checkString(label: string) {
157
+ return (v: unknown) => {
158
+ if (typeof v !== "string") err(label, "expected string", v);
159
+ };
160
+ }
161
+
162
+ /** Coerce a number/bigint to a bigint, requiring integral input. */
163
+ function toBigIntStrict(label: string, v: unknown): bigint {
164
+ if (typeof v === "bigint") return v;
165
+ if (typeof v === "number") {
166
+ if (!Number.isInteger(v)) err(label, "expected an integer value", v);
167
+ return BigInt(v);
168
+ }
169
+ return err(label, "expected number or bigint", v);
170
+ }
171
+
172
+ /** Narrow a bigint to a number, throwing if it can't be held losslessly. */
173
+ function bigIntToSafeNumber(label: string, b: bigint): number {
174
+ if (b < -9007199254740991n || b > 9007199254740991n) {
175
+ err(label, "bigint overflows the safe-integer range when narrowing to number", b);
176
+ }
177
+ return Number(b);
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Date codecs (the only types where rich != canonical)
182
+ // ---------------------------------------------------------------------------
183
+
184
+ /** date32: canonical = number (days since epoch); rich = Date. */
185
+ function dateDayCodec(): Codec {
186
+ const label = "date32";
187
+ return {
188
+ label,
189
+ richToCanonical(value) {
190
+ if (isNullish(value)) return null;
191
+ if (value instanceof Date) {
192
+ const ms = value.getTime();
193
+ if (Number.isNaN(ms)) err(label, "invalid Date", value);
194
+ if (ms % MS_PER_DAY !== 0) {
195
+ // Tolerate any Date by flooring to whole days — DuckDB DATE has no
196
+ // sub-day component, so a Date with a time-of-day maps to its day.
197
+ return Math.floor(ms / MS_PER_DAY);
198
+ }
199
+ return ms / MS_PER_DAY;
200
+ }
201
+ if (typeof value === "number") {
202
+ if (!Number.isInteger(value)) err(label, "day-number must be an integer", value);
203
+ return value;
204
+ }
205
+ if (typeof value === "bigint") return bigIntToSafeNumber(label, value);
206
+ return err(label, "expected Date, number (days), or bigint", value);
207
+ },
208
+ canonicalToRich(value) {
209
+ if (isNullish(value)) return null;
210
+ let days: number;
211
+ if (typeof value === "number") {
212
+ if (!Number.isInteger(value)) err(label, "canonical day-number must be an integer", value);
213
+ days = value;
214
+ } else if (typeof value === "bigint") {
215
+ days = bigIntToSafeNumber(label, value);
216
+ } else if (value instanceof Date) {
217
+ return value;
218
+ } else return err(label, "expected number (days) canonical", value);
219
+ const ms = days * MS_PER_DAY;
220
+ const d = new Date(ms);
221
+ if (Number.isNaN(d.getTime())) err(label, "day-number produces an invalid Date", value);
222
+ return d;
223
+ },
224
+ // raw date32 IS the day-number (no Date involved).
225
+ rawToCanonical(value) {
226
+ if (isNullish(value)) return null;
227
+ if (typeof value === "number") {
228
+ if (!Number.isInteger(value)) err(label, "raw day-number must be an integer", value);
229
+ return value;
230
+ }
231
+ if (typeof value === "bigint") return bigIntToSafeNumber(label, value);
232
+ return err(label, "expected branded Date32 (day-number)", value);
233
+ },
234
+ canonicalToRaw(value) {
235
+ if (isNullish(value)) return null;
236
+ if (typeof value === "number") {
237
+ if (!Number.isInteger(value)) err(label, "canonical day-number must be an integer", value);
238
+ return value;
239
+ }
240
+ if (typeof value === "bigint") return bigIntToSafeNumber(label, value);
241
+ if (value instanceof Date) {
242
+ const ms = value.getTime();
243
+ if (Number.isNaN(ms)) err(label, "invalid Date", value);
244
+ return Math.floor(ms / MS_PER_DAY);
245
+ }
246
+ return err(label, "expected number (days) canonical", value);
247
+ },
248
+ };
249
+ }
250
+
251
+ /** date64: canonical = bigint (ms since epoch); rich = Date. */
252
+ function dateMillisCodec(): Codec {
253
+ const label = "date64";
254
+ return {
255
+ label,
256
+ richToCanonical(value) {
257
+ if (isNullish(value)) return null;
258
+ if (value instanceof Date) {
259
+ const ms = value.getTime();
260
+ if (Number.isNaN(ms)) err(label, "invalid Date", value);
261
+ return BigInt(ms);
262
+ }
263
+ if (typeof value === "bigint") return value;
264
+ if (typeof value === "number") {
265
+ if (!Number.isInteger(value)) err(label, "ms value must be an integer", value);
266
+ return BigInt(value);
267
+ }
268
+ return err(label, "expected Date, bigint (ms), or number", value);
269
+ },
270
+ canonicalToRich(value) {
271
+ if (isNullish(value)) return null;
272
+ if (value instanceof Date) return value;
273
+ const ms = toBigIntStrict(label, value);
274
+ const n = bigIntToSafeNumber(label, ms);
275
+ const d = new Date(n);
276
+ if (Number.isNaN(d.getTime())) err(label, "ms value produces an invalid Date", value);
277
+ return d;
278
+ },
279
+ // raw date64 IS the ms-bigint (no Date involved).
280
+ rawToCanonical(value) {
281
+ if (isNullish(value)) return null;
282
+ if (value instanceof Date) return err(label, "raw Date64Ms must be a bigint (ms), not a Date", value);
283
+ return toBigIntStrict(label, value);
284
+ },
285
+ canonicalToRaw(value) {
286
+ if (isNullish(value)) return null;
287
+ if (value instanceof Date) return BigInt(value.getTime());
288
+ return toBigIntStrict(label, value);
289
+ },
290
+ };
291
+ }
292
+
293
+ // ---------------------------------------------------------------------------
294
+ // Temporal codecs where rich == canonical (no Date — would be lossy)
295
+ // ---------------------------------------------------------------------------
296
+
297
+ /** time32: canonical/rich/raw = number (raw unit). */
298
+ function time32Codec(): Codec {
299
+ const label = "time32";
300
+ const toCanon = (value: unknown): unknown => {
301
+ if (isNullish(value)) return null;
302
+ if (typeof value === "number") {
303
+ if (!Number.isInteger(value)) err(label, "time32 value must be an integer", value);
304
+ return value;
305
+ }
306
+ if (typeof value === "bigint") return bigIntToSafeNumber(label, value);
307
+ return err(label, "expected number (raw time unit)", value);
308
+ };
309
+ const fromCanon = (value: unknown): unknown => {
310
+ if (isNullish(value)) return null;
311
+ if (typeof value === "number") return value;
312
+ if (typeof value === "bigint") return bigIntToSafeNumber(label, value);
313
+ return err(label, "expected number canonical", value);
314
+ };
315
+ return {
316
+ label,
317
+ richToCanonical: toCanon,
318
+ canonicalToRich: fromCanon,
319
+ rawToCanonical: toCanon,
320
+ canonicalToRaw: fromCanon,
321
+ };
322
+ }
323
+
324
+ /** time64/timestamp/duration: canonical/rich/raw = bigint (raw unit). */
325
+ function bigIntUnitCodec(label: string): Codec {
326
+ const conv = (value: unknown): unknown => {
327
+ if (isNullish(value)) return null;
328
+ return toBigIntStrict(label, value);
329
+ };
330
+ return {
331
+ label,
332
+ richToCanonical: conv,
333
+ canonicalToRich: conv,
334
+ rawToCanonical: conv,
335
+ canonicalToRaw: conv,
336
+ };
337
+ }
338
+
339
+ /** Decode a little-endian two's-complement byte view to a signed bigint. */
340
+ function decimalViewToBigInt(view: ArrayBufferView): bigint {
341
+ const u8 = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
342
+ let bi = 0n;
343
+ for (let i = u8.length - 1; i >= 0; i--) bi = (bi << 8n) | BigInt(u8[i]);
344
+ if (u8.length > 0 && (u8[u8.length - 1] & 0x80)) bi -= 1n << BigInt(u8.length * 8);
345
+ return bi;
346
+ }
347
+
348
+ /** decimal128/256: canonical/rich = bigint (UNSCALED integer). Producers may
349
+ * also pass the raw little-endian byte view that an Arrow decimal scalar
350
+ * exposes (Uint32Array / Uint8Array); decode it to the unscaled bigint. */
351
+ function decimalCodec(): Codec {
352
+ const label = "decimal";
353
+ const toCanon = (value: unknown): unknown => {
354
+ if (isNullish(value)) return null;
355
+ if (typeof value === "bigint") return value;
356
+ if (typeof value === "number") return toBigIntStrict(label, value);
357
+ if (ArrayBuffer.isView(value)) return decimalViewToBigInt(value as ArrayBufferView);
358
+ return err(label, "expected bigint, number, or decimal byte view", value);
359
+ };
360
+ const fromCanon = (value: unknown): unknown => {
361
+ if (isNullish(value)) return null;
362
+ return toCanon(value);
363
+ };
364
+ return {
365
+ label,
366
+ richToCanonical: toCanon,
367
+ canonicalToRich: fromCanon,
368
+ rawToCanonical: toCanon,
369
+ canonicalToRaw: fromCanon,
370
+ };
371
+ }
372
+
373
+ // ---------------------------------------------------------------------------
374
+ // Composite codecs
375
+ // ---------------------------------------------------------------------------
376
+
377
+ function structCodec(type: VgiDataType): Codec {
378
+ const children = (type as any).children as Array<{ name: string; type: VgiDataType }>;
379
+ const childCodecs = children.map((c) => ({ name: c.name, codec: codecFor(c.type) }));
380
+ const label = "struct";
381
+ return {
382
+ label,
383
+ richToCanonical(value) {
384
+ if (isNullish(value)) return null;
385
+ if (typeof value !== "object") err(label, "expected an object", value);
386
+ const obj = value as any;
387
+ const out: Record<string, unknown> = {};
388
+ for (const { name, codec } of childCodecs) {
389
+ out[name] = codec.richToCanonical(obj[name] ?? null);
390
+ }
391
+ return out;
392
+ },
393
+ canonicalToRich(value) {
394
+ if (isNullish(value)) return null;
395
+ if (typeof value !== "object") err(label, "expected an object", value);
396
+ const obj = value as any;
397
+ const out: Record<string, unknown> = {};
398
+ for (const { name, codec } of childCodecs) {
399
+ out[name] = codec.canonicalToRich(obj[name] ?? null);
400
+ }
401
+ return out;
402
+ },
403
+ rawToCanonical(value) {
404
+ if (isNullish(value)) return null;
405
+ if (typeof value !== "object") err(label, "expected an object", value);
406
+ const obj = value as any;
407
+ const out: Record<string, unknown> = {};
408
+ for (const { name, codec } of childCodecs) {
409
+ out[name] = codec.rawToCanonical(obj[name] ?? null);
410
+ }
411
+ return out;
412
+ },
413
+ canonicalToRaw(value) {
414
+ if (isNullish(value)) return null;
415
+ if (typeof value !== "object") err(label, "expected an object", value);
416
+ const obj = value as any;
417
+ const out: Record<string, unknown> = {};
418
+ for (const { name, codec } of childCodecs) {
419
+ out[name] = codec.canonicalToRaw(obj[name] ?? null);
420
+ }
421
+ return out;
422
+ },
423
+ };
424
+ }
425
+
426
+ function listCodec(type: VgiDataType): Codec {
427
+ const childField = (type as any).children?.[0];
428
+ if (!childField) throw new Error("codec[list]: list type has no child field");
429
+ const child = codecFor(childField.type as VgiDataType);
430
+ const label = "list";
431
+ const toArray = (value: unknown): unknown[] => {
432
+ if (Array.isArray(value)) return value;
433
+ if (value != null && typeof (value as any)[Symbol.iterator] === "function") {
434
+ return Array.from(value as Iterable<unknown>);
435
+ }
436
+ return err(label, "expected an array or iterable", value);
437
+ };
438
+ return {
439
+ label,
440
+ richToCanonical(value) {
441
+ if (isNullish(value)) return null;
442
+ return toArray(value).map((v) => child.richToCanonical(v));
443
+ },
444
+ canonicalToRich(value) {
445
+ if (isNullish(value)) return null;
446
+ return toArray(value).map((v) => child.canonicalToRich(v));
447
+ },
448
+ rawToCanonical(value) {
449
+ if (isNullish(value)) return null;
450
+ return toArray(value).map((v) => child.rawToCanonical(v));
451
+ },
452
+ canonicalToRaw(value) {
453
+ if (isNullish(value)) return null;
454
+ return toArray(value).map((v) => child.canonicalToRaw(v));
455
+ },
456
+ };
457
+ }
458
+
459
+ function mapCodec(type: VgiDataType): Codec {
460
+ // children[0] is the "entries" struct field holding [key, value].
461
+ const entries = (type as any).children?.[0];
462
+ const entryChildren = entries?.type?.children;
463
+ if (!entryChildren || entryChildren.length < 2) {
464
+ throw new Error("codec[map]: map type has no key/value entry fields");
465
+ }
466
+ const keyCodec = codecFor(entryChildren[0].type as VgiDataType);
467
+ const valueCodec = codecFor(entryChildren[1].type as VgiDataType);
468
+ const label = "map";
469
+ /** Normalize a rich map value into an array of [k, v] pairs. */
470
+ const toPairs = (value: unknown): Array<[unknown, unknown]> => {
471
+ if (Array.isArray(value)) return value as Array<[unknown, unknown]>;
472
+ if (value instanceof Map) return Array.from(value.entries());
473
+ if (value != null && typeof (value as any)[Symbol.iterator] === "function") {
474
+ return Array.from(value as Iterable<[unknown, unknown]>);
475
+ }
476
+ if (value != null && typeof value === "object") {
477
+ return Object.entries(value as Record<string, unknown>);
478
+ }
479
+ return err(label, "expected Map, array of pairs, or object", value);
480
+ };
481
+ return {
482
+ label,
483
+ richToCanonical(value) {
484
+ if (isNullish(value)) return null;
485
+ return toPairs(value).map(([k, v]) => [
486
+ keyCodec.richToCanonical(k),
487
+ valueCodec.richToCanonical(v),
488
+ ]);
489
+ },
490
+ canonicalToRich(value) {
491
+ if (isNullish(value)) return null;
492
+ return toPairs(value).map(([k, v]) => [
493
+ keyCodec.canonicalToRich(k),
494
+ valueCodec.canonicalToRich(v),
495
+ ]);
496
+ },
497
+ rawToCanonical(value) {
498
+ if (isNullish(value)) return null;
499
+ return toPairs(value).map(([k, v]) => [
500
+ keyCodec.rawToCanonical(k),
501
+ valueCodec.rawToCanonical(v),
502
+ ]);
503
+ },
504
+ canonicalToRaw(value) {
505
+ if (isNullish(value)) return null;
506
+ return toPairs(value).map(([k, v]) => [
507
+ keyCodec.canonicalToRaw(k),
508
+ valueCodec.canonicalToRaw(v),
509
+ ]);
510
+ },
511
+ };
512
+ }
513
+
514
+ function dictionaryCodec(type: VgiDataType): Codec {
515
+ // The decoded value's type lives on `type.dictionary` (both backends).
516
+ const valueType = (type as any).dictionary as VgiDataType;
517
+ if (!valueType) throw new Error("codec[dictionary]: missing dictionary value type");
518
+ // Delegate entirely to the decoded value's codec — canonical for a
519
+ // dictionary IS the decoded value's canonical.
520
+ const inner = codecFor(valueType);
521
+ return {
522
+ label: `dictionary<${inner.label}>`,
523
+ richToCanonical: (v) => inner.richToCanonical(v),
524
+ canonicalToRich: (v) => inner.canonicalToRich(v),
525
+ rawToCanonical: (v) => inner.rawToCanonical(v),
526
+ canonicalToRaw: (v) => inner.canonicalToRaw(v),
527
+ };
528
+ }
529
+
530
+ // ---------------------------------------------------------------------------
531
+ // Resolver
532
+ // ---------------------------------------------------------------------------
533
+
534
+ const INT8 = identity("int8", checkIntNumber("int8", -128, 127));
535
+ const INT16 = identity("int16", checkIntNumber("int16", -32768, 32767));
536
+ const INT32 = identity("int32", checkIntNumber("int32", -2147483648, 2147483647));
537
+ const UINT8 = identity("uint8", checkIntNumber("uint8", 0, 255));
538
+ const UINT16 = identity("uint16", checkIntNumber("uint16", 0, 65535));
539
+ const UINT32 = identity("uint32", checkIntNumber("uint32", 0, 4294967295));
540
+ const INT64 = identity("int64", checkBigInt("int64", 64, true));
541
+ const UINT64 = identity("uint64", checkBigInt("uint64", 64, false));
542
+ const FLOAT = identity("float", checkFloat("float"));
543
+ const BOOL = identity("bool", checkBoolean("bool"));
544
+ /** utf8: canonical/rich = string. A string passes through; any other non-null
545
+ * value is stringified — this preserves the long-standing behavior where a
546
+ * foreign scalar fed to a utf8 column (e.g. an Int32Array INTERVAL quad that
547
+ * arrow-js surfaced as "m,d,lo,hi") was coerced to its string form. */
548
+ const UTF8: Codec = {
549
+ label: "utf8",
550
+ richToCanonical(value) {
551
+ if (isNullish(value)) return null;
552
+ return typeof value === "string" ? value : String(value);
553
+ },
554
+ canonicalToRich(value) {
555
+ if (isNullish(value)) return null;
556
+ checkString("utf8")(value);
557
+ return value;
558
+ },
559
+ rawToCanonical(value) {
560
+ if (isNullish(value)) return null;
561
+ return typeof value === "string" ? value : String(value);
562
+ },
563
+ canonicalToRaw(value) {
564
+ if (isNullish(value)) return null;
565
+ checkString("utf8")(value);
566
+ return value;
567
+ },
568
+ };
569
+
570
+ /** Normalize any ArrayBuffer view to a Uint8Array over the same bytes. */
571
+ function toCanonBytes(label: string, fixedWidth?: number) {
572
+ return (value: unknown): unknown => {
573
+ if (isNullish(value)) return null;
574
+ let u8: Uint8Array;
575
+ if (value instanceof Uint8Array) u8 = value;
576
+ else if (ArrayBuffer.isView(value)) {
577
+ const v = value as ArrayBufferView;
578
+ u8 = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
579
+ } else return err(label, "expected Uint8Array", value);
580
+ if (fixedWidth != null && u8.length !== fixedWidth) {
581
+ err(label, `expected exactly ${fixedWidth} bytes`, value);
582
+ }
583
+ return u8;
584
+ };
585
+ }
586
+
587
+ function bytesCodec(label: string, fixedWidth?: number): Codec {
588
+ const conv = toCanonBytes(label, fixedWidth);
589
+ return {
590
+ label,
591
+ richToCanonical: conv,
592
+ canonicalToRich: conv,
593
+ rawToCanonical: conv,
594
+ canonicalToRaw: conv,
595
+ };
596
+ }
597
+
598
+ const BINARY = bytesCodec("binary");
599
+ const NULL_CODEC: Codec = {
600
+ label: "null",
601
+ richToCanonical: () => null,
602
+ canonicalToRich: () => null,
603
+ rawToCanonical: () => null,
604
+ canonicalToRaw: () => null,
605
+ };
606
+
607
+ /**
608
+ * Resolve the {@link Codec} for an Arrow type. Dispatches by `typeId` and
609
+ * reads unit / bitWidth / scale off the type. Composite types recurse into
610
+ * their child codecs.
611
+ */
612
+ export function codecFor(type: VgiDataType): Codec {
613
+ switch (type.typeId) {
614
+ case TypeId.Null:
615
+ return NULL_CODEC;
616
+ case TypeId.Bool:
617
+ return BOOL;
618
+ case TypeId.Int: {
619
+ const bw = (type as any).bitWidth ?? 32;
620
+ const signed = (type as any).isSigned ?? (type as any).signed ?? true;
621
+ if (bw === 64) return signed ? INT64 : UINT64;
622
+ if (signed) {
623
+ if (bw === 8) return INT8;
624
+ if (bw === 16) return INT16;
625
+ return INT32;
626
+ }
627
+ if (bw === 8) return UINT8;
628
+ if (bw === 16) return UINT16;
629
+ return UINT32;
630
+ }
631
+ case TypeId.Float:
632
+ return FLOAT;
633
+ case TypeId.Utf8:
634
+ case TypeId.LargeUtf8:
635
+ return UTF8;
636
+ case TypeId.Binary:
637
+ case TypeId.LargeBinary:
638
+ return BINARY;
639
+ case TypeId.FixedSizeBinary:
640
+ return bytesCodec("fixedSizeBinary", (type as any).byteWidth ?? (type as any).stride);
641
+ case TypeId.Decimal:
642
+ return decimalCodec();
643
+ case TypeId.Date:
644
+ // unit DAY=0 -> date32 (days, number); MILLISECOND=1 -> date64 (ms, bigint)
645
+ return (type as any).unit === 0 ? dateDayCodec() : dateMillisCodec();
646
+ case TypeId.Time:
647
+ // bitWidth 32 -> number (raw s/ms); 64 -> bigint (raw us/ns)
648
+ return ((type as any).bitWidth ?? 32) === 64 ? bigIntUnitCodec("time64") : time32Codec();
649
+ case TypeId.Timestamp:
650
+ return bigIntUnitCodec("timestamp");
651
+ case TypeId.Duration:
652
+ return bigIntUnitCodec("duration");
653
+ case TypeId.Struct:
654
+ return structCodec(type);
655
+ case TypeId.Union:
656
+ // The canonical readers decode a union into a TaggedUnion { tag, value }.
657
+ // That shape is already the rich representation, so pass it through.
658
+ return identity("union", () => {});
659
+ case TypeId.List:
660
+ case TypeId.FixedSizeList:
661
+ return listCodec(type);
662
+ case TypeId.Map:
663
+ return mapCodec(type);
664
+ case TypeId.Dictionary:
665
+ return dictionaryCodec(type);
666
+ case TypeId.Interval:
667
+ // Interval is not in the phase-1 canonical spec; pass through unchanged.
668
+ return identity("interval", () => {});
669
+ default:
670
+ // Unknown / not-yet-modeled types pass through unchanged so the suite
671
+ // stays green; a later phase can add explicit codecs.
672
+ return identity(`type#${type.typeId}`, () => {});
673
+ }
674
+ }