@query-farm/vgi 0.42.0 → 0.44.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 (59) hide show
  1. package/README.md +5 -2
  2. package/dist/attach-ticket.d.ts +18 -7
  3. package/dist/client-entry.js +215 -91
  4. package/dist/client-entry.js.map +10 -9
  5. package/dist/codec/asd.d.ts +3 -1
  6. package/dist/codec/conform.d.ts +51 -0
  7. package/dist/errors.d.ts +5 -1
  8. package/dist/generated/vgi-service.d.ts +1315 -0
  9. package/dist/index.core.d.ts +1 -1
  10. package/dist/index.js +2143 -932
  11. package/dist/index.js.map +29 -28
  12. package/dist/protocol/dispatch.d.ts +14 -5
  13. package/dist/protocol/handlers/aggregate.d.ts +3 -2
  14. package/dist/protocol/handlers/catalog/admin.d.ts +2 -2
  15. package/dist/protocol/handlers/catalog/index.d.ts +3 -2
  16. package/dist/protocol/handlers/catalog/index_methods.d.ts +2 -2
  17. package/dist/protocol/handlers/catalog/macro.d.ts +2 -2
  18. package/dist/protocol/handlers/catalog/shared.d.ts +7 -14
  19. package/dist/protocol/handlers/catalog/table.d.ts +2 -2
  20. package/dist/protocol/handlers/catalog/view.d.ts +2 -2
  21. package/dist/protocol/handlers/function.d.ts +3 -2
  22. package/dist/protocol/handlers/shared.d.ts +2 -2
  23. package/dist/protocol/handlers/table-buffering.d.ts +3 -2
  24. package/dist/protocol/handlers/unimplemented.d.ts +4 -17
  25. package/dist/protocol/serializers/shared.d.ts +2 -1
  26. package/dist/rpc-server.d.ts +2 -2
  27. package/dist/serve-entry.js +2104 -903
  28. package/dist/serve-entry.js.map +25 -24
  29. package/dist/worker-cf-entry.js +2045 -842
  30. package/dist/worker-cf-entry.js.map +29 -28
  31. package/package.json +3 -3
  32. package/src/attach-ticket.ts +41 -13
  33. package/src/catalog/composite.ts +2 -1
  34. package/src/catalog/interface.ts +5 -4
  35. package/src/catalog/read-only.ts +8 -6
  36. package/src/client/client.ts +13 -5
  37. package/src/codec/asd.ts +5 -2
  38. package/src/codec/conform.ts +193 -0
  39. package/src/errors.ts +27 -7
  40. package/src/functions/registry.ts +3 -2
  41. package/src/generated/vgi-service.ts +2678 -0
  42. package/src/http/fetch.ts +1 -1
  43. package/src/index.core.ts +3 -1
  44. package/src/protocol/dispatch.ts +40 -29
  45. package/src/protocol/handlers/aggregate.ts +205 -232
  46. package/src/protocol/handlers/catalog/admin.ts +228 -299
  47. package/src/protocol/handlers/catalog/index.ts +17 -16
  48. package/src/protocol/handlers/catalog/index_methods.ts +26 -36
  49. package/src/protocol/handlers/catalog/macro.ts +54 -77
  50. package/src/protocol/handlers/catalog/shared.ts +30 -47
  51. package/src/protocol/handlers/catalog/table.ts +205 -270
  52. package/src/protocol/handlers/catalog/view.ts +59 -85
  53. package/src/protocol/handlers/function.ts +127 -156
  54. package/src/protocol/handlers/shared.ts +8 -6
  55. package/src/protocol/handlers/table-buffering.ts +53 -71
  56. package/src/protocol/handlers/unimplemented.ts +18 -62
  57. package/src/protocol/serializers/bind.ts +3 -1
  58. package/src/protocol/serializers/shared.ts +5 -2
  59. package/src/rpc-server.ts +7 -7
@@ -10,24 +10,16 @@
10
10
  // vgi-python's worker._load_table_buffering_params.
11
11
 
12
12
  import { deserializeBatch } from "../../arrow/index.js";
13
- import { Protocol } from "@query-farm/vgi-rpc";
13
+ import type { VgiService } from "../../generated/vgi-service.js";
14
14
  import type { FunctionRegistry } from "../../functions/registry.js";
15
15
  import { toUint8Array } from "../../util/bytes.js";
16
16
  import { batchToScalarDict, batchToSecretDict } from "../../util/arrow/index.js";
17
17
  import {
18
- TableBufferingCombineParamsSchema,
19
18
  TableBufferingCombineResultSchema,
20
- TableBufferingDestructorParamsSchema,
21
19
  TableBufferingDestructorResultSchema,
22
- TableBufferingProcessParamsSchema,
23
20
  TableBufferingProcessResultSchema,
24
21
  } from "../../generated/vgi-protocol-schemas.js";
25
- import {
26
- REQUEST_PARAMS_SCHEMA,
27
- RESULT_BINARY_SCHEMA,
28
- unwrapRequest,
29
- wrapResult,
30
- } from "./shared.js";
22
+ import { unwrapRequest, wrapResult } from "./shared.js";
31
23
  import { openAttach } from "./catalog/shared.js";
32
24
  import {
33
25
  BoundStorage,
@@ -41,11 +33,11 @@ import {
41
33
  } from "../../functions/table-buffering.js";
42
34
  import { Arguments } from "../../arguments/arguments.js";
43
35
 
44
- export function registerTableBufferingMethods(
45
- protocol: Protocol,
36
+ /** The table_buffering lifecycle's `vgi.v2` handlers. */
37
+ export function tableBufferingHandlers(
46
38
  registry: FunctionRegistry,
47
39
  signingKey?: Uint8Array,
48
- ): void {
40
+ ): Partial<VgiService> {
49
41
  // `schemaPath` scopes resolution to the schema the caller named. The
50
42
  // buffering process/combine RPCs carry it (protocol 1.2.0), so a name
51
43
  // declared in two schemas resolves to the one the request names rather than
@@ -123,72 +115,62 @@ export function registerTableBufferingMethods(
123
115
  // ------------------------------------------------------------------------
124
116
  // table_buffering_process — sink one batch, return state_id (unary)
125
117
  // ------------------------------------------------------------------------
126
- protocol.unary("table_buffering_process", {
127
- params: TableBufferingProcessParamsSchema,
128
- result: RESULT_BINARY_SCHEMA,
129
- handler: async (rpcParams, ctx?: any) => {
130
- const inner = unwrapRequest(rpcParams.request);
131
- const clientLog = makeClientLog(ctx);
132
- const { func, params } = await loadParams(inner, clientLog, ctx);
133
- if (inner.batch_index != null) {
134
- params.batchIndex = Number(inner.batch_index);
135
- }
136
- const batch = deserializeBatch(toUint8Array(inner.input_batch));
137
- const stateId = await func.bufferingConfig.process(batch, params);
138
- if (!(stateId instanceof Uint8Array)) {
139
- throw new Error(
140
- `${func.meta.name}.process() must return Uint8Array (the opaque state_id)`,
141
- );
142
- }
143
- return wrapResult({ state_id: stateId }, TableBufferingProcessResultSchema);
144
- },
145
- });
118
+ const tableBufferingProcess: VgiService["tableBufferingProcess"] = async (rpcParams, ctx?: any) => {
119
+ const inner = unwrapRequest(rpcParams.request);
120
+ const clientLog = makeClientLog(ctx);
121
+ const { func, params } = await loadParams(inner, clientLog, ctx);
122
+ if (inner.batch_index != null) {
123
+ params.batchIndex = Number(inner.batch_index);
124
+ }
125
+ const batch = deserializeBatch(toUint8Array(inner.input_batch));
126
+ const stateId = await func.bufferingConfig.process(batch, params);
127
+ if (!(stateId instanceof Uint8Array)) {
128
+ throw new Error(
129
+ `${func.meta.name}.process() must return Uint8Array (the opaque state_id)`,
130
+ );
131
+ }
132
+ return wrapResult({ state_id: stateId }, TableBufferingProcessResultSchema);
133
+ };
146
134
 
147
135
  // ------------------------------------------------------------------------
148
136
  // table_buffering_combine — group/merge state_ids (unary)
149
137
  // ------------------------------------------------------------------------
150
- protocol.unary("table_buffering_combine", {
151
- params: TableBufferingCombineParamsSchema,
152
- result: RESULT_BINARY_SCHEMA,
153
- handler: async (rpcParams, ctx?: any) => {
154
- const inner = unwrapRequest(rpcParams.request);
155
- const clientLog = makeClientLog(ctx);
156
- const { func, params } = await loadParams(inner, clientLog, ctx);
157
- const stateIds = decodeBytesList(inner.state_ids);
158
- const finalizeStateIds = await func.bufferingConfig.combine(stateIds, params);
159
- const out = finalizeStateIds.map((fid, i) => {
160
- if (!(fid instanceof Uint8Array)) {
161
- throw new Error(
162
- `${func.meta.name}.combine() returned non-Uint8Array finalize_state_id at index ${i}`,
163
- );
164
- }
165
- return fid;
166
- });
167
- return wrapResult(
168
- { finalize_state_ids: out },
169
- TableBufferingCombineResultSchema,
170
- );
171
- },
172
- });
138
+ const tableBufferingCombine: VgiService["tableBufferingCombine"] = async (rpcParams, ctx?: any) => {
139
+ const inner = unwrapRequest(rpcParams.request);
140
+ const clientLog = makeClientLog(ctx);
141
+ const { func, params } = await loadParams(inner, clientLog, ctx);
142
+ const stateIds = decodeBytesList(inner.state_ids);
143
+ const finalizeStateIds = await func.bufferingConfig.combine(stateIds, params);
144
+ const out = finalizeStateIds.map((fid, i) => {
145
+ if (!(fid instanceof Uint8Array)) {
146
+ throw new Error(
147
+ `${func.meta.name}.combine() returned non-Uint8Array finalize_state_id at index ${i}`,
148
+ );
149
+ }
150
+ return fid;
151
+ });
152
+ return wrapResult(
153
+ { finalize_state_ids: out },
154
+ TableBufferingCombineResultSchema,
155
+ );
156
+ };
173
157
 
174
158
  // ------------------------------------------------------------------------
175
159
  // table_buffering_destructor — best-effort end-of-query cleanup (unary)
176
160
  // ------------------------------------------------------------------------
177
- protocol.unary("table_buffering_destructor", {
178
- params: TableBufferingDestructorParamsSchema,
179
- result: RESULT_BINARY_SCHEMA,
180
- handler: async (rpcParams) => {
181
- try {
182
- const inner = unwrapRequest(rpcParams.request);
183
- const executionId = toUint8Array(inner.execution_id);
184
- const bound = new BoundStorage(defaultStorage, executionId);
185
- await bound.executionClear();
186
- } catch {
187
- // Teardown path — swallow; the entry is wiped when the worker exits.
188
- }
189
- return wrapResult({}, TableBufferingDestructorResultSchema);
190
- },
191
- });
161
+ const tableBufferingDestructor: VgiService["tableBufferingDestructor"] = async (rpcParams) => {
162
+ try {
163
+ const inner = unwrapRequest(rpcParams.request);
164
+ const executionId = toUint8Array(inner.execution_id);
165
+ const bound = new BoundStorage(defaultStorage, executionId);
166
+ await bound.executionClear();
167
+ } catch {
168
+ // Teardown path — swallow; the entry is wiped when the worker exits.
169
+ }
170
+ return wrapResult({}, TableBufferingDestructorResultSchema);
171
+ };
172
+
173
+ return { tableBufferingProcess, tableBufferingCombine, tableBufferingDestructor };
192
174
  }
193
175
 
194
176
  function makeClientLog(ctx: any): (level: string, message: string) => void {
@@ -4,71 +4,27 @@
4
4
  // "The protocol is the unit of optionality": every SDK hosts EVERY vgi.v2
5
5
  // method with exactly the reference's schemas, so vgi_rpc.Reflection.v1
6
6
  // reports one vgi.v2 protocol hash everywhere (see VGI_V2_PROTOCOL_HASH in
7
- // ../dispatch.ts). A method this port has no implementation for is still
8
- // registered -- with the reference's params/result schemas -- and answers
9
- // every call with vgi-rpc's MethodNotImplementedError: error_code
10
- // UNIMPLEMENTED, error_kind method_not_implemented. Never a silent success.
7
+ // ../dispatch.ts). The registration and the UNIMPLEMENTED default for every
8
+ // method are generated (src/generated/vgi-service.ts); this module only names
9
+ // the methods whose generated default this worker leaves in place, so a test
10
+ // can assert each one refuses with UNIMPLEMENTED.
11
11
  //
12
- // When one of these gains a real implementation, move it out of this list
13
- // into its handler module; the schemas must not change (the hash test fails
14
- // if they do).
12
+ // To implement one, add its handler to a group in this directory; it drops off
13
+ // this list by itself.
15
14
 
16
- import { MethodNotImplementedError, type Protocol } from "@query-farm/vgi-rpc";
17
- import type { VgiSchema } from "../../arrow/index.js";
15
+ import { FunctionRegistry } from "../../functions/registry.js";
18
16
  import {
19
- CatalogIndexCreateParamsSchema,
20
- CatalogIndexDropParamsSchema,
21
- CatalogTableColumnCommentSetParamsSchema,
22
- CatalogTableDeleteFunctionGetParamsSchema,
23
- CatalogTableInsertFunctionGetParamsSchema,
24
- CatalogTableUpdateFunctionGetParamsSchema,
25
- } from "../../generated/vgi-protocol-schemas.js";
26
- import { REQUEST_PARAMS_SCHEMA, RESULT_BINARY_SCHEMA } from "./shared.js";
27
- import { emptyResultSchema } from "./catalog/shared.js";
17
+ UNIMPLEMENTED_VGI_SERVICE,
18
+ VGI_V2_METHODS,
19
+ type VgiMethodSpec,
20
+ } from "../../generated/vgi-service.js";
21
+ import { buildVgiService } from "../dispatch.js";
28
22
 
29
- /** One unimplemented method: its wire name and the reference's schemas. */
30
- interface UnimplementedMethod {
31
- readonly name: string;
32
- readonly params: VgiSchema;
33
- readonly result: VgiSchema;
34
- }
23
+ export { unimplementedMessage } from "../../generated/vgi-service.js";
35
24
 
36
- /**
37
- * The vgi.v2 methods (of the reference's 72) this SDK does not implement.
38
- * Exported so a test can assert each one refuses with UNIMPLEMENTED.
39
- */
40
- export const UNIMPLEMENTED_METHODS: readonly UnimplementedMethod[] = [
41
- // Streaming and window aggregates: request/result are wrapped ASD blobs.
42
- { name: "aggregate_streaming_open", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
43
- { name: "aggregate_streaming_chunk", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
44
- { name: "aggregate_streaming_close", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
45
- { name: "aggregate_window_init", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
46
- { name: "aggregate_window", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
47
- { name: "aggregate_window_batch", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
48
- { name: "aggregate_window_destructor", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
49
- // Index DDL and per-column comments.
50
- { name: "catalog_index_create", params: CatalogIndexCreateParamsSchema, result: emptyResultSchema },
51
- { name: "catalog_index_drop", params: CatalogIndexDropParamsSchema, result: emptyResultSchema },
52
- { name: "catalog_table_column_comment_set", params: CatalogTableColumnCommentSetParamsSchema, result: emptyResultSchema },
53
- // Per-table DML function getters.
54
- { name: "catalog_table_insert_function_get", params: CatalogTableInsertFunctionGetParamsSchema, result: RESULT_BINARY_SCHEMA },
55
- { name: "catalog_table_update_function_get", params: CatalogTableUpdateFunctionGetParamsSchema, result: RESULT_BINARY_SCHEMA },
56
- { name: "catalog_table_delete_function_get", params: CatalogTableDeleteFunctionGetParamsSchema, result: RESULT_BINARY_SCHEMA },
57
- ];
25
+ const service = buildVgiService({ registry: new FunctionRegistry() });
58
26
 
59
- /** The message every unimplemented method answers with. */
60
- export function unimplementedMessage(method: string): string {
61
- return `${method} is not implemented by this worker`;
62
- }
63
-
64
- export function registerUnimplementedMethods(protocol: Protocol): void {
65
- for (const m of UNIMPLEMENTED_METHODS) {
66
- protocol.unary(m.name, {
67
- params: m.params,
68
- result: m.result,
69
- handler: async () => {
70
- throw new MethodNotImplementedError(unimplementedMessage(m.name));
71
- },
72
- });
73
- }
74
- }
27
+ /** The vgi.v2 methods (of the reference's 72) this SDK leaves at the generated default. */
28
+ export const UNIMPLEMENTED_METHODS: readonly VgiMethodSpec[] = VGI_V2_METHODS.filter(
29
+ (m) => service[m.key] === UNIMPLEMENTED_VGI_SERVICE[m.key],
30
+ );
@@ -72,7 +72,9 @@ export function serializeBindRequest(req: BindRequest): VgiBatch {
72
72
  return buildSingleRowBatch(BIND_REQUEST_SCHEMA, {
73
73
  function_name: req.function_name,
74
74
  arguments: serializeArguments(req.arguments),
75
- function_type: req.function_type,
75
+ // The wire carries the Python enum member NAME ("TABLE"), not the TS
76
+ // enum's value ("table"); each FunctionType value upper-cases to its name.
77
+ function_type: req.function_type == null ? req.function_type : String(req.function_type).toUpperCase(),
76
78
  input_schema: req.input_schema ? serializeSchema(req.input_schema) : null,
77
79
  settings: req.settings ? serializeBatch(req.settings) : null,
78
80
  secrets: req.secrets ? serializeBatch(req.secrets) : null,
@@ -8,6 +8,7 @@ import {
8
8
  batchFromColumns,
9
9
  } from "../../arrow/index.js";
10
10
  import { toUint8Array as toUint8ArrayBase } from "../../util/bytes.js";
11
+ import { conformRecord } from "../../codec/conform.js";
11
12
 
12
13
  /**
13
14
  * Coerce a wire value (string / Uint8Array / Buffer / etc.) to a Uint8Array.
@@ -25,7 +26,8 @@ export function toUint8Array(val: any): Uint8Array {
25
26
 
26
27
  /**
27
28
  * 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
+ * given schema. Auto-coerces JS numbers to BigInt for Int64 fields, and
30
+ * applies the protocol's container rules (`conformRecord`).
29
31
  */
30
32
  export function buildSingleRowBatch(
31
33
  schema: VgiSchema,
@@ -35,8 +37,9 @@ export function buildSingleRowBatch(
35
37
  // declares one — vectorFromArray/columnFromArray rejects plain Number for
36
38
  // 64-bit ints under both backends.
37
39
  const cols: Record<string, any[]> = {};
40
+ const row = conformRecord(schema, values);
38
41
  for (const f of schema.fields) {
39
- let val = values[f.name];
42
+ let val = row[f.name];
40
43
  if (isInt(f.type) && (f.type as any).bitWidth === 64 && typeof val === "number") {
41
44
  val = BigInt(val);
42
45
  }
package/src/rpc-server.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  // 2. The worker's `hostedProtocols()`, in the order returned -- on EVERY
14
14
  // transport.
15
15
  // 3. `vgi_rpc.Reflection.v1` -- on every transport.
16
- // 4. `vgi_rpc.Identity.v1` -- HTTP only, and only when the worker supplies
16
+ // 4. `vgi_rpc.Identity.v2` -- HTTP only, and only when the worker supplies
17
17
  // `resolveToken` and/or `mintGrant` (or grant keys are configured).
18
18
  // 5. `vgi.attach_tickets.v1` -- HTTP only, and only when the signing key was
19
19
  // configured explicitly (never a generated one) *and* the worker can
@@ -44,7 +44,7 @@ export type ServerTransport = "pipe" | "unix" | "tcp" | "iroh" | "stream" | "htt
44
44
  const TRANSPORTS: ReadonlySet<string> = new Set<ServerTransport>(["pipe", "unix", "tcp", "iroh", "stream", "http"]);
45
45
 
46
46
  /** Transports that authenticate their callers, and so may host
47
- * `vgi_rpc.Identity.v1`. Identity answers questions about *other* callers'
47
+ * `vgi_rpc.Identity.v2`. Identity answers questions about *other* callers'
48
48
  * credentials; its allowlist is a list of principals, which a transport with
49
49
  * no caller identity cannot check. */
50
50
  const IDENTITY_TRANSPORTS: ReadonlySet<string> = new Set<ServerTransport>(["http"]);
@@ -78,14 +78,14 @@ export interface HostingOptions {
78
78
  *
79
79
  * Each needs a distinct wire name, which may not be `vgi.v2` and may not
80
80
  * use the reserved `vgi_rpc.` prefix: reflection is hosted automatically and
81
- * `vgi_rpc.Identity.v1` is enabled through {@link resolveToken} /
81
+ * `vgi_rpc.Identity.v2` is enabled through {@link resolveToken} /
82
82
  * {@link mintGrant}. Violations are a startup error naming this hook.
83
83
  */
84
84
  hostedProtocols?: () => Iterable<Protocol>;
85
85
 
86
86
  /**
87
87
  * Resolve an opaque bearer credential to the identity it authenticates as.
88
- * Supplying it hosts `vgi_rpc.Identity.v1`'s `introspect_token` -- on HTTP,
88
+ * Supplying it hosts `vgi_rpc.Identity.v2`'s `introspect_token` -- on HTTP,
89
89
  * the transport that authenticates callers. Absent, the method is absent
90
90
  * (not hosted-and-refusing), which keeps a dependency upgrade from growing a
91
91
  * credential-to-identity oracle on every existing worker.
@@ -232,7 +232,7 @@ function validatedHostedProtocols(hosting: HostingOptions, primary: Protocol): P
232
232
  throw new Error(
233
233
  `${owner} entry ${index} is named '${name}', which claims the reserved '${RESERVED_PREFIX}' prefix. ` +
234
234
  "Framework protocols are not supplied through this hook: reflection is hosted automatically, and " +
235
- "vgi_rpc.Identity.v1 is enabled by supplying resolveToken and/or mintGrant.",
235
+ "vgi_rpc.Identity.v2 is enabled by supplying resolveToken and/or mintGrant.",
236
236
  );
237
237
  }
238
238
  if (!/^[A-Za-z_][A-Za-z0-9_.]*$/.test(name)) {
@@ -259,7 +259,7 @@ function validatedHostedProtocols(hosting: HostingOptions, primary: Protocol): P
259
259
  return out;
260
260
  }
261
261
 
262
- /** Build the `vgi_rpc.Identity.v1` implementation, or `undefined`.
262
+ /** Build the `vgi_rpc.Identity.v2` implementation, or `undefined`.
263
263
  *
264
264
  * `undefined` unless the worker supplies at least one hook -- and that is the
265
265
  * point: the server then does not host the protocol at all, rather than
@@ -329,7 +329,7 @@ function resolveIntrospectPrincipals(
329
329
  }
330
330
  if (principals.length === 0) {
331
331
  throw new Error(
332
- "This worker supplies resolveToken, which hosts the vgi_rpc.Identity.v1 protocol, but no " +
332
+ "This worker supplies resolveToken, which hosts the vgi_rpc.Identity.v2 protocol, but no " +
333
333
  `introspector allowlist was configured. Set ${INTROSPECT_PRINCIPALS_ENV} (comma-separated) or pass ` +
334
334
  "introspectPrincipals.\n\n" +
335
335
  "There is no permissive default on purpose: introspection is a separate capability from " +