@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,247 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ // InitRequest / GlobalInitResponse wire serialization. Schema mirrors
3
+ // vgi-python's InitRequest dataclass — outer wire shape:
4
+ // bind_call: binary (nested IPC of BindRequest)
5
+ // output_schema: binary (nullable serialized Schema)
6
+ // bind_opaque_data: binary (nullable)
7
+ // projection_ids: list<int64> (nullable)
8
+ // pushdown_filters: large_binary (nullable) — nested IPC of filter batch
9
+ // join_keys: list<large_binary> (non-null list of IPC-serialized batches)
10
+ // phase: utf8 (nullable) — dictionary(int16,string) in Python
11
+ // execution_id: binary (nullable)
12
+ // init_opaque_data: binary (nullable)
13
+ // Order pushdown hints + TABLESAMPLE pushdown hints follow.
14
+
15
+ import {
16
+ type VgiBatch,
17
+ schema as makeSchema,
18
+ field,
19
+ utf8,
20
+ binary,
21
+ int64,
22
+ float64,
23
+ list,
24
+ } from "../../arrow/index.js";
25
+ // RecordBatch type alias kept for the joinKeys closure below — it's a runtime
26
+ // value that lands in BindRequest, not a wire shape.
27
+ type RecordBatch = VgiBatch;
28
+ import { TableInOutPhase } from "../../types.js";
29
+ import {
30
+ OrderByDirection,
31
+ OrderByNullOrder,
32
+ type InitRequest,
33
+ type GlobalInitResponse,
34
+ } from "../types.js";
35
+ import {
36
+ serializeSchema,
37
+ deserializeSchema,
38
+ serializeBatch,
39
+ deserializeBatch,
40
+ batchToScalarDict,
41
+ } from "../../util/arrow/index.js";
42
+ import { toUint8Array, buildSingleRowBatch } from "./shared.js";
43
+ import { serializeBindRequest, deserializeBindRequest } from "./bind.js";
44
+ import { decodeDictValue } from "../../util/arrow/index.js";
45
+
46
+ const INIT_REQUEST_SCHEMA = makeSchema([
47
+ field("bind_call", binary(), false),
48
+ field("output_schema", binary(), false),
49
+ field("bind_opaque_data", binary(), true),
50
+ field("projection_ids", list(field("item", int64(), false)), true),
51
+ field("pushdown_filters", binary(), true),
52
+ field("join_keys", list(field("item", binary(), true)), false),
53
+ field("phase", utf8(), true),
54
+ field("execution_id", binary(), true),
55
+ field("init_opaque_data", binary(), true),
56
+ // Order pushdown hints from DuckDB's RowGroupPruner (all null when no hint).
57
+ field("order_by_column_name", utf8(), true),
58
+ field("order_by_direction", utf8(), true),
59
+ field("order_by_null_order", utf8(), true),
60
+ field("order_by_limit", int64(), true),
61
+ // TABLESAMPLE pushdown hints from DuckDB's SamplingPushdown optimizer.
62
+ field("tablesample_percentage", float64(), true),
63
+ field("tablesample_seed", int64(), true),
64
+ // Buffered-table finalize stream identifier — C++ appends this last.
65
+ field("finalize_state_id", binary(), true),
66
+ ]);
67
+
68
+ const GLOBAL_INIT_RESPONSE_SCHEMA = makeSchema([
69
+ field("execution_id", binary(), false),
70
+ field("opaque_data", binary(), true),
71
+ field("max_workers", int64(), false),
72
+ ]);
73
+
74
+ export function serializeInitRequest(req: InitRequest): VgiBatch {
75
+ const bindCallBatch = serializeBindRequest(req.bind_call);
76
+ const bindCallBytes = serializeBatch(bindCallBatch);
77
+
78
+ const row: Record<string, any> = {
79
+ bind_call: bindCallBytes,
80
+ output_schema: serializeSchema(req.output_schema),
81
+ bind_opaque_data: req.bind_opaque_data ?? null,
82
+ projection_ids: req.projection_ids?.map((n) => BigInt(n)) ?? null,
83
+ pushdown_filters: req.pushdown_filters
84
+ ? serializeBatch(req.pushdown_filters)
85
+ : null,
86
+ join_keys: (req.join_keys ?? []).map((b) => serializeBatch(b)),
87
+ phase: req.phase ?? null,
88
+ execution_id: req.execution_id ?? null,
89
+ init_opaque_data: req.init_opaque_data ?? null,
90
+ order_by_column_name: req.order_by_column_name ?? null,
91
+ order_by_direction: req.order_by_direction ?? null,
92
+ order_by_null_order: req.order_by_null_order ?? null,
93
+ order_by_limit: req.order_by_limit ?? null,
94
+ tablesample_percentage: req.tablesample_percentage ?? null,
95
+ tablesample_seed: req.tablesample_seed ?? null,
96
+ finalize_state_id: req.finalize_state_id ?? null,
97
+ };
98
+ return buildSingleRowBatch(INIT_REQUEST_SCHEMA, row);
99
+ }
100
+
101
+ export function deserializeInitRequest(
102
+ params: Record<string, any>
103
+ ): InitRequest {
104
+ const bindCallBytes = toUint8Array(params.bind_call);
105
+ const bindCallBatch = deserializeBatch(bindCallBytes);
106
+ // Extract the single row as params via the codec/canonical path.
107
+ const bindParams = batchToScalarDict(bindCallBatch);
108
+
109
+ const bindCall = deserializeBindRequest(bindParams);
110
+
111
+ // Parse projection_ids - may be a list/array of Int32
112
+ let projectionIds: number[] | null = null;
113
+ if (params.projection_ids != null) {
114
+ const raw = params.projection_ids;
115
+ if (Array.isArray(raw)) {
116
+ projectionIds = raw.map(Number);
117
+ } else if (raw && typeof raw[Symbol.iterator] === "function") {
118
+ projectionIds = [...raw].map(Number);
119
+ }
120
+ }
121
+
122
+ // Parse phase. DuckDB sends this as a Dictionary on the wire; vgi-rpc's
123
+ // row extractor doesn't auto-decode dictionaries, so values come in as raw
124
+ // Arrow Data objects. decodeDictValue resolves them to the underlying string.
125
+ let phase: TableInOutPhase | null = null;
126
+ const phaseRaw = decodeDictValue(params.phase);
127
+ if (phaseRaw != null) {
128
+ const phaseStr = String(phaseRaw);
129
+ if (phaseStr === "INPUT") {
130
+ phase = TableInOutPhase.INPUT;
131
+ } else if (phaseStr === "FINALIZE") {
132
+ phase = TableInOutPhase.FINALIZE;
133
+ } else if (phaseStr === "TABLE_BUFFERING") {
134
+ phase = TableInOutPhase.TABLE_BUFFERING;
135
+ } else if (phaseStr === "TABLE_BUFFERING_FINALIZE") {
136
+ phase = TableInOutPhase.TABLE_BUFFERING_FINALIZE;
137
+ }
138
+ }
139
+
140
+ // Parse join_keys - list of IPC-serialized RecordBatches
141
+ const joinKeys: RecordBatch[] = [];
142
+ if (params.join_keys != null) {
143
+ const raw = params.join_keys;
144
+ const iter: Iterable<any> = Array.isArray(raw)
145
+ ? raw
146
+ : typeof raw[Symbol.iterator] === "function"
147
+ ? raw
148
+ : [];
149
+ for (const entry of iter) {
150
+ if (entry == null) continue;
151
+ try {
152
+ joinKeys.push(deserializeBatch(toUint8Array(entry)));
153
+ } catch {
154
+ // Skip malformed batches rather than failing init.
155
+ }
156
+ }
157
+ }
158
+
159
+ return {
160
+ bind_call: bindCall,
161
+ output_schema: deserializeSchema(toUint8Array(params.output_schema)),
162
+ bind_opaque_data: params.bind_opaque_data
163
+ ? toUint8Array(params.bind_opaque_data)
164
+ : null,
165
+ projection_ids: projectionIds,
166
+ pushdown_filters: params.pushdown_filters
167
+ ? deserializeBatch(toUint8Array(params.pushdown_filters))
168
+ : null,
169
+ join_keys: joinKeys,
170
+ phase,
171
+ finalize_state_id: params.finalize_state_id
172
+ ? toUint8Array(params.finalize_state_id)
173
+ : null,
174
+ execution_id: params.execution_id
175
+ ? toUint8Array(params.execution_id)
176
+ : null,
177
+ init_opaque_data: params.init_opaque_data
178
+ ? toUint8Array(params.init_opaque_data)
179
+ : null,
180
+ order_by_column_name: parseEnum(decodeDictValue(params.order_by_column_name)) ?? null,
181
+ order_by_direction: parseDirection(decodeDictValue(params.order_by_direction)),
182
+ order_by_null_order: parseNullOrder(decodeDictValue(params.order_by_null_order)),
183
+ order_by_limit: parseBigInt(params.order_by_limit),
184
+ tablesample_percentage: parseNumber(params.tablesample_percentage),
185
+ tablesample_seed: parseBigInt(params.tablesample_seed),
186
+ };
187
+ }
188
+
189
+ function parseEnum(v: any): string | null {
190
+ if (v === null || v === undefined) return null;
191
+ return String(v);
192
+ }
193
+ function parseDirection(v: any): OrderByDirection | null {
194
+ if (v === null || v === undefined) return null;
195
+ const s = String(v);
196
+ if (s === "ASC") return OrderByDirection.ASC;
197
+ if (s === "DESC") return OrderByDirection.DESC;
198
+ return null;
199
+ }
200
+ function parseNullOrder(v: any): OrderByNullOrder | null {
201
+ if (v === null || v === undefined) return null;
202
+ const s = String(v);
203
+ if (s === "NULLS_FIRST") return OrderByNullOrder.NULLS_FIRST;
204
+ if (s === "NULLS_LAST") return OrderByNullOrder.NULLS_LAST;
205
+ return null;
206
+ }
207
+ function parseBigInt(v: any): bigint | null {
208
+ if (v === null || v === undefined) return null;
209
+ if (typeof v === "bigint") return v;
210
+ if (typeof v === "number") return BigInt(v);
211
+ try {
212
+ return BigInt(String(v));
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ function parseNumber(v: any): number | null {
218
+ if (v === null || v === undefined) return null;
219
+ if (typeof v === "number") return v;
220
+ if (typeof v === "bigint") return Number(v);
221
+ const n = Number(v);
222
+ return Number.isNaN(n) ? null : n;
223
+ }
224
+
225
+ export function serializeGlobalInitResponse(
226
+ resp: GlobalInitResponse
227
+ ): Record<string, any> {
228
+ return {
229
+ execution_id: resp.execution_id,
230
+ opaque_data: resp.opaque_data ?? null,
231
+ max_workers: resp.max_workers,
232
+ };
233
+ }
234
+
235
+ export function deserializeGlobalInitResponse(
236
+ params: Record<string, any>
237
+ ): GlobalInitResponse {
238
+ return {
239
+ execution_id: toUint8Array(params.execution_id),
240
+ opaque_data: params.opaque_data
241
+ ? toUint8Array(params.opaque_data)
242
+ : null,
243
+ max_workers: Number(params.max_workers),
244
+ };
245
+ }
246
+
247
+ export { INIT_REQUEST_SCHEMA, GLOBAL_INIT_RESPONSE_SCHEMA };
@@ -0,0 +1,46 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ // Shared helpers for protocol wire serialization.
3
+
4
+ import {
5
+ type VgiSchema,
6
+ type VgiBatch,
7
+ isInt,
8
+ batchFromColumns,
9
+ } from "../../arrow/index.js";
10
+ import { toUint8Array as toUint8ArrayBase } from "../../util/bytes.js";
11
+
12
+ /**
13
+ * Coerce a wire value (string / Uint8Array / Buffer / etc.) to a Uint8Array.
14
+ * Throws when the input is non-null but produces zero bytes — that signals a
15
+ * truly unsupported shape, distinct from a legitimate empty Uint8Array.
16
+ */
17
+ export function toUint8Array(val: any): Uint8Array {
18
+ if (typeof val === "string") return new TextEncoder().encode(val);
19
+ const result = toUint8ArrayBase(val);
20
+ if (result.length === 0 && val != null) {
21
+ throw new Error(`Cannot convert ${typeof val} to Uint8Array`);
22
+ }
23
+ return result;
24
+ }
25
+
26
+ /**
27
+ * Build a single-row RecordBatch from a {fieldName: value} dict matching the
28
+ * given schema. Auto-coerces JS numbers to BigInt for Int64 fields.
29
+ */
30
+ export function buildSingleRowBatch(
31
+ schema: VgiSchema,
32
+ values: Record<string, any>,
33
+ ): VgiBatch {
34
+ // Build column dict, coercing Int64 numbers to BigInt where the schema
35
+ // declares one — vectorFromArray/columnFromArray rejects plain Number for
36
+ // 64-bit ints under both backends.
37
+ const cols: Record<string, any[]> = {};
38
+ for (const f of schema.fields) {
39
+ let val = values[f.name];
40
+ if (isInt(f.type) && (f.type as any).bitWidth === 64 && typeof val === "number") {
41
+ val = BigInt(val);
42
+ }
43
+ cols[f.name] = [val];
44
+ }
45
+ return batchFromColumns(cols, schema);
46
+ }
@@ -0,0 +1,181 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ // Arrow IPC state serializer for HTTP transport.
3
+ // Replaces JSON serialization with Arrow IPC for the exchange state token,
4
+ // avoiding base64/hex overhead and staying consistent with the rest of the protocol.
5
+
6
+ import {
7
+ type VgiDataType,
8
+ type VgiField,
9
+ schema as makeSchema,
10
+ field,
11
+ struct as makeStruct,
12
+ binary,
13
+ int64,
14
+ bool,
15
+ float64,
16
+ utf8,
17
+ nullType,
18
+ isStruct,
19
+ isBinary,
20
+ serializeBatch,
21
+ deserializeBatch,
22
+ batchFromColumns,
23
+ readCanonicalValue,
24
+ } from "../arrow/index.js";
25
+ import { codecFor } from "../arrow/codec/registry.js";
26
+ import type { StateSerializer } from "@query-farm/vgi-rpc";
27
+ import { toUint8Array } from "../util/bytes.js";
28
+
29
+ /** Schema for the exchange state carried in HTTP state tokens. */
30
+ export const EXCHANGE_STATE_SCHEMA = makeSchema([
31
+ field("function_name", binary(), false),
32
+ field("init_request", binary(), false),
33
+ field("execution_id", binary(), false),
34
+ field("max_workers", int64(), false),
35
+ field("opaque_data", binary(), true),
36
+ field("is_producer", bool(), false),
37
+ field("user_state", binary(), true),
38
+ ]);
39
+
40
+ const TEXT_ENCODER = new TextEncoder();
41
+ const TEXT_DECODER = new TextDecoder();
42
+
43
+ /** Infer an Arrow DataType from a JS value for user state serialization. */
44
+ export function inferFieldType(value: any): VgiDataType {
45
+ if (value === null || value === undefined) return nullType();
46
+ switch (typeof value) {
47
+ case "number": return float64();
48
+ case "bigint": return int64();
49
+ case "string": return utf8();
50
+ case "boolean": return bool();
51
+ case "object":
52
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer) return binary();
53
+ if (ArrayBuffer.isView(value)) return binary();
54
+ if (Array.isArray(value)) {
55
+ throw new Error(`inferFieldType: arrays are not supported in userState (convert to a serializable form first)`);
56
+ }
57
+ if (value instanceof Map) {
58
+ throw new Error(`inferFieldType: Map is not supported in userState (use a plain object instead)`);
59
+ }
60
+ if (value instanceof Set) {
61
+ throw new Error(`inferFieldType: Set is not supported in userState (use an array or plain object instead)`);
62
+ }
63
+ if (value instanceof Date) {
64
+ throw new Error(`inferFieldType: Date is not supported in userState (use a number (epoch ms) or ISO string instead)`);
65
+ }
66
+ if (value instanceof RegExp) {
67
+ throw new Error(`inferFieldType: RegExp is not supported in userState (use a string pattern instead)`);
68
+ }
69
+ // Plain object → Struct
70
+ const fields = Object.entries(value).map(
71
+ ([k, v]) => field(k, inferFieldType(v), true),
72
+ );
73
+ return makeStruct(fields);
74
+ default:
75
+ throw new Error(`inferFieldType: unsupported type '${typeof value}'`);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Serialize userState to Arrow IPC bytes.
81
+ * Infers schema from the JS object at runtime. Arrow IPC is self-describing,
82
+ * so deserialization doesn't need the schema ahead of time.
83
+ *
84
+ * For an empty object `{}`, we emit a 0-row batch with empty schema —
85
+ * `deserializeUserState` recognizes that shape and returns `{}` rather than
86
+ * `null`. (`null` is reserved for "no userState declared at all".)
87
+ */
88
+ export function serializeUserState(userState: any): Uint8Array | null {
89
+ if (userState == null) return null;
90
+ const entries = Object.entries(userState);
91
+ if (entries.length === 0) {
92
+ // 0-row, 0-field batch — round-trips back to {} via deserializeUserState.
93
+ return serializeBatch(batchFromColumns({}, makeSchema([])));
94
+ }
95
+ const fields = entries.map(
96
+ ([k, v]) => field(k, inferFieldType(v), true),
97
+ );
98
+ const sch = makeSchema(fields);
99
+ const columns: Record<string, any[]> = {};
100
+ for (const [key, val] of entries) {
101
+ columns[key] = [val];
102
+ }
103
+ return serializeBatch(batchFromColumns(columns, sch));
104
+ }
105
+
106
+ /**
107
+ * Extract a typed value from an Arrow column in RICH form via the codec /
108
+ * canonical path. Backend-agnostic and lossless (BigInt for Int64, Uint8Array
109
+ * for Binary, plain objects for Struct). userState forbids Date/temporal types
110
+ * (see {@link inferFieldType}), so rich == canonical for everything stored here.
111
+ */
112
+ function extractTypedValue(col: any, index: number, type: VgiDataType): any {
113
+ return codecFor(type).canonicalToRich(readCanonicalValue(type, col, index));
114
+ }
115
+
116
+ /**
117
+ * Deserialize userState from Arrow IPC bytes.
118
+ * Reconstructs a plain JS object, preserving BigInt for Int64 columns.
119
+ */
120
+ export function deserializeUserState(bytes: Uint8Array | null): any {
121
+ if (bytes == null) return null;
122
+ const batch = deserializeBatch(bytes);
123
+ if (batch.numRows === 0 && batch.schema.fields.length === 0) {
124
+ return {};
125
+ }
126
+ if (batch.numRows === 0) return null;
127
+ const result: Record<string, any> = {};
128
+ for (const f of batch.schema.fields) {
129
+ const col = batch.getChild(f.name);
130
+ if (!col) { result[f.name] = null; continue; }
131
+ result[f.name] = extractTypedValue(col, 0, f.type);
132
+ }
133
+ return result;
134
+ }
135
+
136
+ /** Arrow IPC state serializer — stores all data as native binary columns. */
137
+ export const arrowStateSerializer: StateSerializer = {
138
+ serialize(state: any): Uint8Array {
139
+ const columns: Record<string, any[]> = {
140
+ function_name: [TEXT_ENCODER.encode(state.functionName)],
141
+ init_request: [state.initRequestIpc],
142
+ execution_id: [state.executionId],
143
+ max_workers: [state.maxWorkers],
144
+ opaque_data: [state.opaqueData ?? null],
145
+ is_producer: [state.isProducer],
146
+ user_state: [serializeUserState(state.userState)],
147
+ };
148
+ const batch = batchFromColumns(columns, EXCHANGE_STATE_SCHEMA);
149
+ return serializeBatch(batch);
150
+ },
151
+
152
+ deserialize(bytes: Uint8Array): any {
153
+ const batch = deserializeBatch(bytes);
154
+ const fieldByName = new Map(batch.schema.fields.map((f) => [f.name, f]));
155
+ const get = (name: string) => {
156
+ const col = batch.getChild(name);
157
+ const f = fieldByName.get(name);
158
+ if (!col || !f) return null;
159
+ const type = f.type as unknown as VgiDataType;
160
+ return codecFor(type).canonicalToRich(readCanonicalValue(type, col, 0));
161
+ };
162
+
163
+ const isProducer = get("is_producer");
164
+ const opaqueDataRaw = get("opaque_data");
165
+ const fnNameRaw = get("function_name");
166
+
167
+ return {
168
+ functionName: fnNameRaw != null ? TEXT_DECODER.decode(toUint8Array(fnNameRaw)) : "",
169
+ initRequestIpc: toUint8Array(get("init_request")),
170
+ executionId: toUint8Array(get("execution_id")),
171
+ maxWorkers: Number(get("max_workers")),
172
+ opaqueData: opaqueDataRaw != null ? toUint8Array(opaqueDataRaw) : null,
173
+ isProducer,
174
+ // vgi-rpc dispatch reads __isProducer to choose producer vs exchange mode
175
+ __isProducer: isProducer,
176
+ userState: deserializeUserState(
177
+ get("user_state") != null ? toUint8Array(get("user_state")) : null,
178
+ ),
179
+ };
180
+ },
181
+ };
@@ -0,0 +1,153 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ // Protocol-level types for the VGI worker. Snake_case to match the Python
3
+ // side and the generated vgi-client.ts wire interfaces. These are the
4
+ // *deserialized* shapes handlers see — fields that cross the wire as `binary`
5
+ // here hold the parsed Arrow object (Schema / RecordBatch / Arguments) rather
6
+ // than raw bytes, so the handler doesn't have to deserialize itself.
7
+ //
8
+ // Domain-layer types (FunctionMeta, TableFunctionConfig, TableProcessParams,
9
+ // StreamHandlers, VgiClientOptions, etc.) remain camelCase as JS-idiomatic
10
+ // public API surfaces. Conversions happen at the layer boundary in
11
+ // src/protocol/serialize.ts and the function/catalog dispatch sites.
12
+
13
+ import type { VgiSchema, VgiBatch } from "../arrow/index.js";
14
+ import type { Arguments } from "../arguments/arguments.js";
15
+ import { FunctionType, TableInOutPhase, type TableCardinality } from "../types.js";
16
+
17
+ /**
18
+ * Context for a `COPY ... FROM` read, threaded onto {@link BindRequest}.
19
+ *
20
+ * Present only when the bind/init opens a COPY-FROM scan (`null`/absent
21
+ * otherwise — set by the VGI extension's `copy_from_bind`). `InitRequest`
22
+ * embeds the same `BindRequest` as `bind_call`, so process()/init also reach it
23
+ * via `initCall.bind_call.copy_from`. The handler's options arrive through the
24
+ * normal `BindRequest.arguments` (built from the COPY options), so they are not
25
+ * duplicated here. Mirrors vgi-python's `protocol.CopyFromContext`.
26
+ */
27
+ export interface CopyFromContext {
28
+ /** The `FORMAT` name resolved at COPY bind time. */
29
+ format: string;
30
+ /** The source path from the `COPY ... FROM 'path'` statement. */
31
+ file_path: string;
32
+ /**
33
+ * The COPY target's column schema (name + type, in target order). The worker
34
+ * must bind its output to, and emit columns whose types match, this schema
35
+ * exactly — DuckDB inserts no cast between the scan and the INSERT. Holds the
36
+ * parsed Arrow Schema (the wire field is binary; deserialized here).
37
+ */
38
+ expected_schema: VgiSchema;
39
+ }
40
+
41
+ /**
42
+ * Context for a `COPY ... TO` write, threaded onto {@link BindRequest}.
43
+ *
44
+ * Present only when the bind/init opens a COPY-TO sink (`null`/absent
45
+ * otherwise — set by the VGI extension's `copy_to_bind`). `InitRequest`
46
+ * embeds the same `BindRequest` as `bind_call`, so process()/combine() also
47
+ * reach it via `initCall.bind_call.copy_to`. The handler's options arrive
48
+ * through the normal `BindRequest.arguments`; the **source** columns ride the
49
+ * existing `BindRequest.input_schema` (so they are not duplicated here).
50
+ * Mirrors vgi-python's `protocol.CopyToContext`.
51
+ */
52
+ export interface CopyToContext {
53
+ /** The `FORMAT` name resolved at COPY bind time. */
54
+ format: string;
55
+ /** The destination path from the `COPY ... TO 'path'` statement. */
56
+ file_path: string;
57
+ }
58
+
59
+ export interface BindRequest {
60
+ function_name: string;
61
+ arguments: Arguments;
62
+ function_type: FunctionType;
63
+ input_schema: VgiSchema | null;
64
+ settings: VgiBatch | null;
65
+ secrets: VgiBatch | null;
66
+ attach_opaque_data: Uint8Array | null;
67
+ transaction_opaque_data: Uint8Array | null;
68
+ resolved_secrets_provided: boolean;
69
+ /**
70
+ * COPY ... FROM context — `null`/absent unless this bind/init opens a
71
+ * COPY-FROM scan. Additive + name-keyed, so ordinary scans (which omit it on
72
+ * the wire) deserialize to `null`. Mirrors vgi-python's `BindRequest.copy_from`.
73
+ */
74
+ copy_from?: CopyFromContext | null;
75
+ /**
76
+ * COPY ... TO context — `null`/absent unless this bind/init opens a COPY-TO
77
+ * sink. Additive + name-keyed, same wire-safe rationale as `copy_from`.
78
+ * Mirrors vgi-python's `BindRequest.copy_to`.
79
+ */
80
+ copy_to?: CopyToContext | null;
81
+ /**
82
+ * Time travel: the AT (TIMESTAMP|VERSION ...) clause for this scan, threaded
83
+ * from DuckDB's per-reference bind. Both `null` when the scan has no AT clause.
84
+ * For inline-bound (function-backed) tables the actual on_bind RPC runs once at
85
+ * attach with no AT, so the per-scan AT is carried on the bind request embedded
86
+ * in each InitRequest — read it at init via `init_call.bind_call.at_unit` (or
87
+ * `TableProcessParams.atUnit`). Mirrors vgi-python's `BindRequest.at_unit`.
88
+ */
89
+ at_unit?: string | null;
90
+ at_value?: string | null;
91
+ }
92
+
93
+ export interface BindResponse {
94
+ output_schema: VgiSchema;
95
+ opaque_data: Uint8Array | null;
96
+ lookup_secret_types?: string[];
97
+ lookup_scopes?: string[];
98
+ lookup_names?: string[];
99
+ }
100
+
101
+ export interface InitRequest {
102
+ bind_call: BindRequest;
103
+ output_schema: VgiSchema;
104
+ bind_opaque_data: Uint8Array | null;
105
+ projection_ids: number[] | null;
106
+ pushdown_filters: VgiBatch | null;
107
+ /**
108
+ * Join-key value batches, one per join-keys column. Keyed by the column
109
+ * name inside each batch's schema. Populated when DuckDB promotes
110
+ * IN/OR lists or join predicates to batched join-keys pushdowns.
111
+ */
112
+ join_keys: VgiBatch[];
113
+ phase: TableInOutPhase | null;
114
+ /**
115
+ * Buffered-table finalize stream: which finalize_state_id this stream
116
+ * serves. Set when phase=TABLE_BUFFERING_FINALIZE; null otherwise. Opaque
117
+ * bytes the worker's combine() chose.
118
+ */
119
+ finalize_state_id: Uint8Array | null;
120
+ execution_id: Uint8Array | null;
121
+ init_opaque_data: Uint8Array | null;
122
+ // Order pushdown hints from DuckDB's RowGroupPruner (null when no hint).
123
+ order_by_column_name: string | null;
124
+ order_by_direction: OrderByDirection | null;
125
+ order_by_null_order: OrderByNullOrder | null;
126
+ order_by_limit: bigint | null;
127
+ // TABLESAMPLE pushdown hints.
128
+ tablesample_percentage: number | null;
129
+ tablesample_seed: bigint | null;
130
+ }
131
+
132
+ export enum OrderByDirection {
133
+ ASC = "ASC",
134
+ DESC = "DESC",
135
+ }
136
+
137
+ export enum OrderByNullOrder {
138
+ NULLS_FIRST = "NULLS_FIRST",
139
+ NULLS_LAST = "NULLS_LAST",
140
+ }
141
+
142
+ export interface GlobalInitResponse {
143
+ max_workers: number;
144
+ execution_id: Uint8Array;
145
+ opaque_data: Uint8Array | null;
146
+ }
147
+
148
+ export interface TableFunctionCardinalityRequest {
149
+ bind_call: BindRequest;
150
+ bind_opaque_data: Uint8Array | null;
151
+ }
152
+
153
+ export { TableCardinality };
@@ -0,0 +1,70 @@
1
+ // Type- and scope-aware selection over resolved secrets.
2
+ //
3
+ // Resolved secrets reach a worker as a plain object keyed by each secret's
4
+ // unique DuckDB secret name (so several secrets of the same type — e.g. one per
5
+ // S3 bucket — coexist). Each secret carries its connector-serialized `type` (the
6
+ // DuckDB secret type) and `scope` (newline-joined scope prefixes) fields, plus
7
+ // type-specific fields. These helpers mirror `vgi::Secrets` in the Rust SDK.
8
+
9
+ export type SecretFields = Record<string, any>;
10
+ export type SecretsDict = Record<string, SecretFields>;
11
+
12
+ function scalarStr(v: any): string {
13
+ if (v === null || v === undefined) return "";
14
+ return typeof v === "string" ? v : String(v);
15
+ }
16
+
17
+ /** The DuckDB secret type of the named secret (its serialized `type` field). */
18
+ export function secretType(secrets: SecretsDict, name: string): string | undefined {
19
+ const f = secrets?.[name];
20
+ return f && "type" in f ? scalarStr(f.type) : undefined;
21
+ }
22
+
23
+ /** Every resolved secret whose serialized `type` field matches `type`. */
24
+ export function secretsOfType(secrets: SecretsDict, type: string): SecretFields[] {
25
+ return Object.values(secrets ?? {}).filter((f) => scalarStr(f.type) === type);
26
+ }
27
+
28
+ /**
29
+ * The secret whose `scope` is the longest prefix of `path`. The connector
30
+ * serializes each secret's scope as a newline-joined list of prefixes; a secret
31
+ * with no (or empty) scope matches as a last-resort fallback. Returns undefined
32
+ * only when there are no candidate secrets.
33
+ */
34
+ export function secretForScope(secrets: SecretsDict, path: string): SecretFields | undefined {
35
+ return selectForScope(secrets, path, undefined);
36
+ }
37
+
38
+ /** Like {@link secretForScope} but only over secrets of `type`. */
39
+ export function secretForScopeOfType(
40
+ secrets: SecretsDict,
41
+ path: string,
42
+ type: string,
43
+ ): SecretFields | undefined {
44
+ return selectForScope(secrets, path, type);
45
+ }
46
+
47
+ function selectForScope(
48
+ secrets: SecretsDict,
49
+ path: string,
50
+ type: string | undefined,
51
+ ): SecretFields | undefined {
52
+ let best: SecretFields | undefined;
53
+ let bestLen = -1;
54
+ let fallback: SecretFields | undefined;
55
+ for (const fields of Object.values(secrets ?? {})) {
56
+ if (type !== undefined && scalarStr(fields.type) !== type) continue;
57
+ const scope = scalarStr(fields.scope);
58
+ if (!scope) {
59
+ if (!fallback) fallback = fields;
60
+ continue;
61
+ }
62
+ for (const prefix of scope.split("\n")) {
63
+ if (prefix && path.startsWith(prefix) && prefix.length > bestLen) {
64
+ bestLen = prefix.length;
65
+ best = fields;
66
+ }
67
+ }
68
+ }
69
+ return best ?? fallback;
70
+ }