@query-farm/vgi 0.42.0 → 0.43.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 (35) hide show
  1. package/dist/generated/vgi-service.d.ts +1315 -0
  2. package/dist/index.js +1845 -813
  3. package/dist/index.js.map +16 -16
  4. package/dist/protocol/dispatch.d.ts +14 -5
  5. package/dist/protocol/handlers/aggregate.d.ts +3 -2
  6. package/dist/protocol/handlers/catalog/admin.d.ts +2 -2
  7. package/dist/protocol/handlers/catalog/index.d.ts +3 -2
  8. package/dist/protocol/handlers/catalog/index_methods.d.ts +2 -2
  9. package/dist/protocol/handlers/catalog/macro.d.ts +2 -2
  10. package/dist/protocol/handlers/catalog/shared.d.ts +7 -14
  11. package/dist/protocol/handlers/catalog/table.d.ts +2 -2
  12. package/dist/protocol/handlers/catalog/view.d.ts +2 -2
  13. package/dist/protocol/handlers/function.d.ts +3 -2
  14. package/dist/protocol/handlers/shared.d.ts +0 -1
  15. package/dist/protocol/handlers/table-buffering.d.ts +3 -2
  16. package/dist/protocol/handlers/unimplemented.d.ts +4 -17
  17. package/dist/serve-entry.js +1844 -813
  18. package/dist/serve-entry.js.map +16 -16
  19. package/dist/worker-cf-entry.js +1845 -813
  20. package/dist/worker-cf-entry.js.map +16 -16
  21. package/package.json +1 -1
  22. package/src/generated/vgi-service.ts +2678 -0
  23. package/src/protocol/dispatch.ts +40 -29
  24. package/src/protocol/handlers/aggregate.ts +205 -232
  25. package/src/protocol/handlers/catalog/admin.ts +228 -299
  26. package/src/protocol/handlers/catalog/index.ts +17 -16
  27. package/src/protocol/handlers/catalog/index_methods.ts +26 -36
  28. package/src/protocol/handlers/catalog/macro.ts +54 -77
  29. package/src/protocol/handlers/catalog/shared.ts +30 -47
  30. package/src/protocol/handlers/catalog/table.ts +205 -270
  31. package/src/protocol/handlers/catalog/view.ts +59 -85
  32. package/src/protocol/handlers/function.ts +127 -156
  33. package/src/protocol/handlers/shared.ts +0 -4
  34. package/src/protocol/handlers/table-buffering.ts +53 -71
  35. package/src/protocol/handlers/unimplemented.ts +18 -62
@@ -1,115 +1,89 @@
1
1
  // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
2
  // Catalog view handlers: view_get/create/drop/rename/comment_set.
3
3
 
4
- import { type VgiSchema, schema, type VgiField, field, type VgiDataType, binary, utf8 } from "../../../arrow/index.js";
5
- import { Protocol } from "@query-farm/vgi-rpc";
4
+ import type { VgiService } from "../../../generated/vgi-service.js";
6
5
  import { encodeViewInfo } from "../../../generated/vgi-client.js";
7
6
  import {
8
- CatalogViewCommentSetParamsSchema,
9
- CatalogViewCreateParamsSchema,
10
- CatalogViewDropParamsSchema,
11
- CatalogViewGetParamsSchema,
12
7
  CatalogViewGetResultSchema,
13
- CatalogViewRenameParamsSchema,
14
8
  } from "../../../generated/vgi-protocol-schemas.js";
15
9
  import { toUint8Array } from "../../../util/bytes.js";
16
10
  import {
17
- RESULT_BINARY_SCHEMA,
18
11
  wrapResult,
19
12
  } from "../shared.js";
20
13
  import {
21
14
  type GetCatalog,
22
- emptyResultSchema,
23
- catalogUnary,
15
+ catalogHandler,
24
16
  } from "./shared.js";
25
17
 
26
- export function registerCatalogViewMethods(protocol: Protocol, getCatalog: GetCatalog, signingKey?: Uint8Array): void {
18
+ export function catalogViewHandlers(getCatalog: GetCatalog, signingKey?: Uint8Array): Partial<VgiService> {
27
19
  // catalog_view_get
28
- catalogUnary(protocol, signingKey, "catalog_view_get", {
29
- params: CatalogViewGetParamsSchema,
30
- result: RESULT_BINARY_SCHEMA,
31
- handler: async (params) => {
32
- const cat = getCatalog();
33
- const info = await cat.viewGet(
34
- toUint8Array(params.attach_opaque_data),
35
- params.schema_path,
36
- params.name,
37
- params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
38
- );
39
- return wrapResult({
40
- items: info ? [encodeViewInfo(info)] : [],
41
- }, CatalogViewGetResultSchema);
42
- },
20
+ const catalogViewGet = catalogHandler(signingKey, async (params) => {
21
+ const cat = getCatalog();
22
+ const info = await cat.viewGet(
23
+ toUint8Array(params.attach_opaque_data),
24
+ params.schema_path,
25
+ params.name,
26
+ params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
27
+ );
28
+ return wrapResult({
29
+ items: info ? [encodeViewInfo(info)] : [],
30
+ }, CatalogViewGetResultSchema);
43
31
  });
44
32
 
45
33
  // catalog_view_create
46
- catalogUnary(protocol, signingKey, "catalog_view_create", {
47
- params: CatalogViewCreateParamsSchema,
48
- result: emptyResultSchema,
49
- handler: async (params) => {
50
- const cat = getCatalog();
51
- await cat.viewCreate(
52
- toUint8Array(params.attach_opaque_data),
53
- params.schema_path,
54
- params.name,
55
- params.definition,
56
- params.on_conflict,
57
- params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
58
- );
59
- return {};
60
- },
34
+ const catalogViewCreate = catalogHandler(signingKey, async (params) => {
35
+ const cat = getCatalog();
36
+ await cat.viewCreate(
37
+ toUint8Array(params.attach_opaque_data),
38
+ params.schema_path,
39
+ params.name,
40
+ params.definition,
41
+ params.on_conflict,
42
+ params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
43
+ );
44
+ return {};
61
45
  });
62
46
 
63
47
  // catalog_view_drop
64
- catalogUnary(protocol, signingKey, "catalog_view_drop", {
65
- params: CatalogViewDropParamsSchema,
66
- result: emptyResultSchema,
67
- handler: async (params) => {
68
- const cat = getCatalog();
69
- await cat.viewDrop(
70
- toUint8Array(params.attach_opaque_data),
71
- params.schema_path,
72
- params.name,
73
- params.ignore_not_found,
74
- params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
75
- );
76
- return {};
77
- },
48
+ const catalogViewDrop = catalogHandler(signingKey, async (params) => {
49
+ const cat = getCatalog();
50
+ await cat.viewDrop(
51
+ toUint8Array(params.attach_opaque_data),
52
+ params.schema_path,
53
+ params.name,
54
+ params.ignore_not_found,
55
+ params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
56
+ );
57
+ return {};
78
58
  });
79
59
 
80
60
  // catalog_view_rename
81
- catalogUnary(protocol, signingKey, "catalog_view_rename", {
82
- params: CatalogViewRenameParamsSchema,
83
- result: emptyResultSchema,
84
- handler: async (params) => {
85
- const cat = getCatalog();
86
- await cat.viewRename(
87
- toUint8Array(params.attach_opaque_data),
88
- params.schema_path,
89
- params.name,
90
- params.new_name,
91
- params.ignore_not_found,
92
- params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
93
- );
94
- return {};
95
- },
61
+ const catalogViewRename = catalogHandler(signingKey, async (params) => {
62
+ const cat = getCatalog();
63
+ await cat.viewRename(
64
+ toUint8Array(params.attach_opaque_data),
65
+ params.schema_path,
66
+ params.name,
67
+ params.new_name,
68
+ params.ignore_not_found,
69
+ params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
70
+ );
71
+ return {};
96
72
  });
97
73
 
98
74
  // catalog_view_comment_set
99
- catalogUnary(protocol, signingKey, "catalog_view_comment_set", {
100
- params: CatalogViewCommentSetParamsSchema,
101
- result: emptyResultSchema,
102
- handler: async (params) => {
103
- const cat = getCatalog();
104
- await cat.viewCommentSet(
105
- toUint8Array(params.attach_opaque_data),
106
- params.schema_path,
107
- params.name,
108
- params.comment,
109
- params.ignore_not_found,
110
- params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
111
- );
112
- return {};
113
- },
75
+ const catalogViewCommentSet = catalogHandler(signingKey, async (params) => {
76
+ const cat = getCatalog();
77
+ await cat.viewCommentSet(
78
+ toUint8Array(params.attach_opaque_data),
79
+ params.schema_path,
80
+ params.name,
81
+ params.comment,
82
+ params.ignore_not_found,
83
+ params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
84
+ );
85
+ return {};
114
86
  });
87
+
88
+ return { catalogViewGet, catalogViewCreate, catalogViewDrop, catalogViewRename, catalogViewCommentSet };
115
89
  }
@@ -4,7 +4,8 @@
4
4
  // implements VgiFunction.bind/globalInit/createStreamHandlers.
5
5
 
6
6
  import { type VgiSchema, schema, type VgiField, field, type VgiDataType, binary, int64 } from "../../arrow/index.js";
7
- import { Protocol, type AuthContext } from "@query-farm/vgi-rpc";
7
+ import type { AuthContext } from "@query-farm/vgi-rpc";
8
+ import { exchangeStream, type VgiService } from "../../generated/vgi-service.js";
8
9
  import type { FunctionRegistry } from "../../functions/registry.js";
9
10
  import type { StreamHandlers, HandlerState } from "../../functions/types.js";
10
11
  import {
@@ -20,25 +21,11 @@ import { batchToScalarDict, deserializeBatch, adoptArrowJsShape } from "../../ut
20
21
  import { toUint8Array } from "../../util/bytes.js";
21
22
  import { serializeColumnStatistics } from "../../util/statistics.js";
22
23
  import {
23
- BindParamsSchema,
24
24
  BindResultSchema,
25
- InitParamsSchema,
26
- TableFunctionCardinalityParamsSchema,
27
25
  TableFunctionCardinalityResultSchema,
28
- TableFunctionDynamicToStringParamsSchema,
29
26
  TableFunctionDynamicToStringResultSchema,
30
- TableFunctionPlanParamsSchema,
31
- TableFunctionStatisticsParamsSchema,
32
27
  } from "../../generated/vgi-protocol-schemas.js";
33
- import {
34
- REQUEST_PARAMS_SCHEMA,
35
- RESULT_BINARY_SCHEMA,
36
- RESULT_BINARY_NULLABLE_SCHEMA,
37
- unwrapRequest,
38
- wrapResult,
39
- overloadContext,
40
- } from "./shared.js";
41
- import { GLOBAL_INIT_RESPONSE_SCHEMA } from "../serializers/init.js";
28
+ import { unwrapRequest, wrapResult, overloadContext } from "./shared.js";
42
29
  import { parseCarriedInitRequest } from "../carried-init-request.js";
43
30
  import { openAttach } from "./catalog/shared.js";
44
31
  import { currentRequestAuth } from "../../request-auth.js";
@@ -197,7 +184,8 @@ async function resolveSplitPayloads(
197
184
  }
198
185
  }
199
186
 
200
- export function registerFunctionMethods(protocol: Protocol, config: FunctionHandlerConfig): void {
187
+ /** The per-function lifecycle's `vgi.v2` handlers. */
188
+ export function functionHandlers(config: FunctionHandlerConfig): Partial<VgiService> {
201
189
  const { registry, signingKey, catalogInterface } = config;
202
190
 
203
191
  // The framework mints every attach as uuid(16) || catalog_bytes (sealed on
@@ -232,10 +220,6 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
232
220
  return overloadContext({ ...bindCall, attach_opaque_data: attach }, catalogInterface);
233
221
  }
234
222
 
235
- // Shared with the serializer that actually emits the header, so the
236
- // advertised shape and the emitted shape cannot drift apart.
237
- const initHeaderSchema = GLOBAL_INIT_RESPONSE_SCHEMA;
238
-
239
223
  const emptySchema = schema([]);
240
224
 
241
225
  // Dummy non-empty schema for the init exchange registration.
@@ -249,25 +233,20 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
249
233
  // --------------------------------------------------------------------------
250
234
  // bind (unary)
251
235
  // --------------------------------------------------------------------------
252
- protocol.unary("bind", {
253
- params: BindParamsSchema,
254
- result: RESULT_BINARY_SCHEMA,
255
- handler: async (params, ctx) => {
256
- const innerParams = unwrapRequest(params.request);
257
- const request = deserializeBindRequest(innerParams);
258
- request.attach_opaque_data = await stripAttach(request.attach_opaque_data, ctx);
259
- const func = registry.get(request.function_name, overloadContext(request, catalogInterface));
260
- const response = await func.bind(request);
261
- const serialized = serializeBindResponse(response);
262
- return wrapResult(serialized, BindResultSchema);
263
- },
264
- });
236
+ const bind: VgiService["bind"] = async (params, ctx) => {
237
+ const innerParams = unwrapRequest(params.request);
238
+ const request = deserializeBindRequest(innerParams);
239
+ request.attach_opaque_data = await stripAttach(request.attach_opaque_data, ctx);
240
+ const func = registry.get(request.function_name, overloadContext(request, catalogInterface));
241
+ const response = await func.bind(request);
242
+ const serialized = serializeBindResponse(response);
243
+ return wrapResult(serialized, BindResultSchema);
244
+ };
265
245
 
266
246
  // --------------------------------------------------------------------------
267
247
  // init (streaming) - dynamically produces either producer or exchange streams
268
248
  // --------------------------------------------------------------------------
269
- protocol.exchange("init", {
270
- params: InitParamsSchema,
249
+ const init = exchangeStream({
271
250
  inputSchema: dummyInputSchema,
272
251
  outputSchema: emptySchema,
273
252
  init: async (params, ctx?: any) => {
@@ -457,7 +436,6 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
457
436
  // the number of predicate ids, not the number of turns).
458
437
  state.filterHistory = handlerState?.filterHistory ?? null;
459
438
  },
460
- headerSchema: initHeaderSchema,
461
439
  headerInit: (params: any, state: any, ctx: any) => {
462
440
  // During init, _initResponse is still in memory.
463
441
  // For exchange, this is never called (headers are only in init response).
@@ -471,22 +449,18 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
471
449
  // --------------------------------------------------------------------------
472
450
  // table_function_cardinality (unary)
473
451
  // --------------------------------------------------------------------------
474
- protocol.unary("table_function_cardinality", {
475
- params: TableFunctionCardinalityParamsSchema,
476
- result: RESULT_BINARY_SCHEMA,
477
- handler: async (params) => {
478
- const innerParams = unwrapRequest(params.request);
479
- const request = deserializeCardinalityRequest(innerParams);
480
- const func = registry.get(request.bind_call.function_name, await strippedContext(request.bind_call));
481
- let cardResult: Record<string, any>;
482
- if (func.cardinality) {
483
- cardResult = serializeTableCardinality(await func.cardinality(request));
484
- } else {
485
- cardResult = { estimate: null, max: null };
486
- }
487
- return wrapResult(cardResult, TableFunctionCardinalityResultSchema);
488
- },
489
- });
452
+ const tableFunctionCardinality: VgiService["tableFunctionCardinality"] = async (params) => {
453
+ const innerParams = unwrapRequest(params.request);
454
+ const request = deserializeCardinalityRequest(innerParams);
455
+ const func = registry.get(request.bind_call.function_name, await strippedContext(request.bind_call));
456
+ let cardResult: Record<string, any>;
457
+ if (func.cardinality) {
458
+ cardResult = serializeTableCardinality(await func.cardinality(request));
459
+ } else {
460
+ cardResult = { estimate: null, max: null };
461
+ }
462
+ return wrapResult(cardResult, TableFunctionCardinalityResultSchema);
463
+ };
490
464
 
491
465
  // --------------------------------------------------------------------------
492
466
  // table_function_plan (unary)
@@ -499,77 +473,73 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
499
473
  // is one unit of work. That keeps every existing worker serving unchanged under
500
474
  // protocol 1.4.0, so splits stay opt-in rather than something every worker must
501
475
  // now implement.
502
- protocol.unary("table_function_plan", {
503
- params: TableFunctionPlanParamsSchema,
504
- result: RESULT_BINARY_SCHEMA,
505
- handler: async (params, ctx?: any) => {
506
- const innerParams = unwrapRequest(params.request);
507
- const request = deserializePlanRequest(innerParams);
508
- const func = registry.get(
509
- request.bind_call.function_name,
510
- await strippedContext(request.bind_call),
511
- );
476
+ const tableFunctionPlan: VgiService["tableFunctionPlan"] = async (params, ctx?: any) => {
477
+ const innerParams = unwrapRequest(params.request);
478
+ const request = deserializePlanRequest(innerParams);
479
+ const func = registry.get(
480
+ request.bind_call.function_name,
481
+ await strippedContext(request.bind_call),
482
+ );
512
483
 
513
- const plan: PlanResult = func.plan
514
- ? await func.plan(request)
515
- : { splits: [{ payload: new Uint8Array(0) }] };
516
-
517
- // The framework stamps every token: an author cannot forget the
518
- // consistency anchor, cannot mis-bind the fingerprint, and never writes
519
- // crypto — and the envelope stays a private implementation detail whose
520
- // layout can change without touching worker code in five languages.
521
- const fp = fingerprintInputs(toUint8Array(innerParams.bind_call));
522
- const fingerprint = await bindFingerprint(
523
- fp.schemaPath,
524
- fp.functionName,
525
- fp.args,
526
- fp.settings,
527
- // projection_ids is not a bind-call field (it rides the init request),
528
- // so it feeds in empty — matching the reference implementation, which
529
- // reads it off the bind call and likewise finds nothing.
530
- new Uint8Array(0),
531
- );
532
- // A worker that names its version is taken at its word — it knows which
533
- // snapshot it planned against. One that leaves it unset gets the LIVE
534
- // version, never 0: minting 0 while redemption compares against the live
535
- // counter refuses every token, and is invisible on a catalog whose version
536
- // happens to be 0, which is most fixtures.
537
- const anchor =
538
- plan.catalogVersion != null
539
- ? splitAnchor(plan.catalogVersion)
540
- : ((await liveSplitAnchor(catalogInterface, request.bind_call as any, ctx?.auth, signingKey)) ??
541
- splitAnchor(0));
542
-
543
- const blobs: Uint8Array[] = [];
544
- for (const split of plan.splits) {
545
- const token = await buildSplitToken({
546
- payload: split.payload,
547
- fingerprint,
548
- anchor,
549
- signingKey,
550
- // Bind the caller. Without it every token seals under the anonymous
551
- // identity, so one tenant's splits are replayable by another — the
552
- // exact replay the AAD exists to stop.
553
- auth: ctx?.auth,
554
- });
555
- blobs.push(
556
- serializeBatch(
557
- batchFromColumns(
558
- Object.fromEntries(
559
- SCAN_SPLIT_SCHEMA.fields.map((f) => [
560
- f.name,
561
- [scanSplitRow(split, token)[f.name] ?? null],
562
- ]),
563
- ),
564
- SCAN_SPLIT_SCHEMA,
484
+ const plan: PlanResult = func.plan
485
+ ? await func.plan(request)
486
+ : { splits: [{ payload: new Uint8Array(0) }] };
487
+
488
+ // The framework stamps every token: an author cannot forget the
489
+ // consistency anchor, cannot mis-bind the fingerprint, and never writes
490
+ // crypto — and the envelope stays a private implementation detail whose
491
+ // layout can change without touching worker code in five languages.
492
+ const fp = fingerprintInputs(toUint8Array(innerParams.bind_call));
493
+ const fingerprint = await bindFingerprint(
494
+ fp.schemaPath,
495
+ fp.functionName,
496
+ fp.args,
497
+ fp.settings,
498
+ // projection_ids is not a bind-call field (it rides the init request),
499
+ // so it feeds in empty — matching the reference implementation, which
500
+ // reads it off the bind call and likewise finds nothing.
501
+ new Uint8Array(0),
502
+ );
503
+ // A worker that names its version is taken at its word — it knows which
504
+ // snapshot it planned against. One that leaves it unset gets the LIVE
505
+ // version, never 0: minting 0 while redemption compares against the live
506
+ // counter refuses every token, and is invisible on a catalog whose version
507
+ // happens to be 0, which is most fixtures.
508
+ const anchor =
509
+ plan.catalogVersion != null
510
+ ? splitAnchor(plan.catalogVersion)
511
+ : ((await liveSplitAnchor(catalogInterface, request.bind_call as any, ctx?.auth, signingKey)) ??
512
+ splitAnchor(0));
513
+
514
+ const blobs: Uint8Array[] = [];
515
+ for (const split of plan.splits) {
516
+ const token = await buildSplitToken({
517
+ payload: split.payload,
518
+ fingerprint,
519
+ anchor,
520
+ signingKey,
521
+ // Bind the caller. Without it every token seals under the anonymous
522
+ // identity, so one tenant's splits are replayable by another — the
523
+ // exact replay the AAD exists to stop.
524
+ auth: ctx?.auth,
525
+ });
526
+ blobs.push(
527
+ serializeBatch(
528
+ batchFromColumns(
529
+ Object.fromEntries(
530
+ SCAN_SPLIT_SCHEMA.fields.map((f) => [
531
+ f.name,
532
+ [scanSplitRow(split, token)[f.name] ?? null],
533
+ ]),
565
534
  ),
535
+ SCAN_SPLIT_SCHEMA,
566
536
  ),
567
- );
568
- }
537
+ ),
538
+ );
539
+ }
569
540
 
570
- return wrapResult(planResponseRow(plan, blobs), PLAN_RESPONSE_SCHEMA);
571
- },
572
- });
541
+ return wrapResult(planResponseRow(plan, blobs), PLAN_RESPONSE_SCHEMA);
542
+ };
573
543
 
574
544
  // --------------------------------------------------------------------------
575
545
  // table_function_statistics (unary)
@@ -578,19 +548,15 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
578
548
  // the function declared a statistics() hook and it produced a non-empty
579
549
  // list, else null. DuckDB uses the bounds for plan-time filter elimination
580
550
  // (folds impossible filters to EMPTY_RESULT).
581
- protocol.unary("table_function_statistics", {
582
- params: TableFunctionStatisticsParamsSchema,
583
- result: RESULT_BINARY_NULLABLE_SCHEMA,
584
- handler: async (params) => {
585
- const innerParams = unwrapRequest(params.request);
586
- const request = deserializeCardinalityRequest(innerParams);
587
- const func = registry.get(request.bind_call.function_name, await strippedContext(request.bind_call));
588
- if (!func.statistics) return { result: null };
589
- const stats = func.statistics(request);
590
- if (!stats || stats.length === 0) return { result: null };
591
- return { result: serializeColumnStatistics(stats) };
592
- },
593
- });
551
+ const tableFunctionStatistics: VgiService["tableFunctionStatistics"] = async (params) => {
552
+ const innerParams = unwrapRequest(params.request);
553
+ const request = deserializeCardinalityRequest(innerParams);
554
+ const func = registry.get(request.bind_call.function_name, await strippedContext(request.bind_call));
555
+ if (!func.statistics) return { result: null };
556
+ const stats = func.statistics(request);
557
+ if (!stats || stats.length === 0) return { result: null };
558
+ return { result: serializeColumnStatistics(stats) };
559
+ };
594
560
 
595
561
  // --------------------------------------------------------------------------
596
562
  // table_function_dynamic_to_string (unary)
@@ -601,27 +567,32 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
601
567
  // intrinsics (Function, Rows Read, Threads). When the function doesn't
602
568
  // declare dynamicToString, return empty maps so the C++ side falls back
603
569
  // to intrinsics only.
604
- protocol.unary("table_function_dynamic_to_string", {
605
- params: TableFunctionDynamicToStringParamsSchema,
606
- result: RESULT_BINARY_SCHEMA,
607
- handler: async (params) => {
608
- const innerParams = unwrapRequest(params.request);
609
- const bindCallBytes = toUint8Array(innerParams.bind_call);
610
- const bindCallBatch = deserializeBatch(bindCallBytes);
611
- // Single-row bind_call batch -> dict via the codec/canonical path.
612
- const bindParams = batchToScalarDict(bindCallBatch);
613
- const bindCall = deserializeBindRequest(bindParams);
614
- const globalExecutionId = toUint8Array(innerParams.global_execution_id);
615
- const bindOpaqueData = innerParams.bind_opaque_data
616
- ? toUint8Array(innerParams.bind_opaque_data)
617
- : null;
618
- const func = registry.get(bindCall.function_name, await strippedContext(bindCall));
619
- const map = func.dynamicToString
620
- ? await func.dynamicToString({ bindCall, bindOpaqueData, globalExecutionId })
621
- : {};
622
- const keys = Object.keys(map);
623
- const values = keys.map((k) => map[k] ?? "");
624
- return wrapResult({ keys, values }, TableFunctionDynamicToStringResultSchema);
625
- },
626
- });
570
+ const tableFunctionDynamicToString: VgiService["tableFunctionDynamicToString"] = async (params) => {
571
+ const innerParams = unwrapRequest(params.request);
572
+ const bindCallBytes = toUint8Array(innerParams.bind_call);
573
+ const bindCallBatch = deserializeBatch(bindCallBytes);
574
+ // Single-row bind_call batch -> dict via the codec/canonical path.
575
+ const bindParams = batchToScalarDict(bindCallBatch);
576
+ const bindCall = deserializeBindRequest(bindParams);
577
+ const globalExecutionId = toUint8Array(innerParams.global_execution_id);
578
+ const bindOpaqueData = innerParams.bind_opaque_data
579
+ ? toUint8Array(innerParams.bind_opaque_data)
580
+ : null;
581
+ const func = registry.get(bindCall.function_name, await strippedContext(bindCall));
582
+ const map = func.dynamicToString
583
+ ? await func.dynamicToString({ bindCall, bindOpaqueData, globalExecutionId })
584
+ : {};
585
+ const keys = Object.keys(map);
586
+ const values = keys.map((k) => map[k] ?? "");
587
+ return wrapResult({ keys, values }, TableFunctionDynamicToStringResultSchema);
588
+ };
589
+
590
+ return {
591
+ bind,
592
+ init,
593
+ tableFunctionCardinality,
594
+ tableFunctionPlan,
595
+ tableFunctionStatistics,
596
+ tableFunctionDynamicToString,
597
+ };
627
598
  }
@@ -19,10 +19,6 @@ export const REQUEST_PARAMS_SCHEMA = schema([
19
19
  field("request", binary(), false),
20
20
  ]);
21
21
 
22
- export const RESULT_BINARY_NULLABLE_SCHEMA = schema([
23
- field("result", binary(), true),
24
- ]);
25
-
26
22
  /**
27
23
  * Unwrap a "request" Binary column: deserialize the inner Arrow IPC batch
28
24
  * and return flat columns as a dict (row 0).