@query-farm/vgi 0.23.0 → 0.24.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@query-farm/vgi",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "TypeScript framework for extending DuckDB with user-defined functions via the Vector Gateway Interface (VGI) over Arrow IPC.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Query Farm LLC (https://query.farm)",
@@ -69,15 +69,18 @@
69
69
  "files": [
70
70
  "dist",
71
71
  "src",
72
+ "scripts/postinstall.cjs",
72
73
  "!src/**/__tests__/**",
73
74
  "!src/**/*.test.ts",
74
75
  "!src/**/*.spec.ts"
75
76
  ],
77
+ "//imports": "`browser` maps to arrow-js DELIBERATELY, not flechette. Measured on the client surface (VgiClient + Arguments + batch helpers), a minified browser bundle is 84 KB gzip via arrow-js vs 116 KB via flechette — arrow-js tree-shakes to the subset the client touches, while flechette links as a unit. Flechette still wins for `worker-cf`, which exercises far more of the Arrow API and cannot take a peer dep. Keep `browser` AFTER `workerd`/`worker`: conditions match in key order, so a workerd build (which sets both) must hit flechette first.",
76
78
  "imports": {
77
79
  "#arrow-impl": {
78
80
  "flechette": "./src/arrow/impl-flechette/index.ts",
79
81
  "workerd": "./src/arrow/impl-flechette/index.ts",
80
82
  "worker": "./src/arrow/impl-flechette/index.ts",
83
+ "browser": "./src/arrow/impl-arrowjs/index.ts",
81
84
  "default": "./src/arrow/impl-arrowjs/index.ts"
82
85
  }
83
86
  },
@@ -87,12 +90,12 @@
87
90
  },
88
91
  "peerDependencies": {
89
92
  "@query-farm/apache-arrow": "^21.1.1",
90
- "@query-farm/vgi-rpc": "^0.15.4"
93
+ "@query-farm/vgi-rpc": ">=0.18.1 <0.19.0"
91
94
  },
92
95
  "devDependencies": {
93
96
  "@duckdb/node-api": "^1.5.2-r.1",
94
97
  "@query-farm/apache-arrow": "^21.1.1",
95
- "@query-farm/vgi-rpc": "^0.15.4",
98
+ "@query-farm/vgi-rpc": "^0.18.1",
96
99
  "@types/bun": "latest",
97
100
  "typescript": "^6.0.3"
98
101
  },
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
3
+ // Postinstall: deduplicate @query-farm/apache-arrow across @query-farm/vgi-rpc
4
+ // to avoid dual-package instanceof issues.
5
+ //
6
+ // This is a LOCAL DEVELOPMENT-only fixup for the vgi-typescript monorepo, where
7
+ // @query-farm/vgi-rpc is a `file:` link with its own nested copy of apache-arrow.
8
+ // It must NOT run when this package is installed as a dependency of someone
9
+ // else's project — so it no-ops unless the sibling vgi-rpc-typescript checkout
10
+ // is present next to this repo. (Published consumers get a single hoisted
11
+ // apache-arrow from the registry, so no dedup is needed there.)
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+
15
+ const PKG = "@query-farm/apache-arrow";
16
+
17
+ // Resolve everything relative to the repo root (this file lives in <repo>/scripts),
18
+ // not the process cwd, so behavior doesn't depend on where the installer runs us.
19
+ const repoRoot = path.resolve(__dirname, "..");
20
+
21
+ // Dev-monorepo signal: the sibling vgi-rpc source checkout. When this package is
22
+ // installed as a dependency, that path resolves inside the consumer's node_modules
23
+ // and won't exist — so we bail and leave their tree untouched.
24
+ const devSibling = path.resolve(repoRoot, "..", "vgi-rpc-typescript", "package.json");
25
+ if (!fs.existsSync(devSibling)) {
26
+ process.exit(0);
27
+ }
28
+
29
+ // Deduplicate: replace vgi-rpc's nested copy with a symlink to ours.
30
+ // Without this, two separate DataType classes exist at runtime and
31
+ // instanceof checks fail across package boundaries.
32
+ // Bun uses per-file symlinks for file: deps, so module resolution follows
33
+ // the real path. We must target the REAL node_modules of vgi-rpc-typescript.
34
+ const ourArrow = path.join(repoRoot, "node_modules", PKG);
35
+ const rpcPkgLink = path.join(repoRoot, "node_modules", "@query-farm", "vgi-rpc", "package.json");
36
+ try {
37
+ const realPkgPath = fs.realpathSync(rpcPkgLink);
38
+ const rpcRoot = path.dirname(realPkgPath);
39
+ const rpcArrow = path.join(rpcRoot, "node_modules", PKG);
40
+ const st = fs.lstatSync(rpcArrow);
41
+ if (!st.isSymbolicLink()) {
42
+ fs.rmSync(rpcArrow, { recursive: true });
43
+ fs.symlinkSync(ourArrow, rpcArrow);
44
+ }
45
+ } catch (e) {
46
+ if (e.code !== "ENOENT") throw e;
47
+ }
@@ -173,4 +173,22 @@ export interface CatalogDescriptor {
173
173
  * advertises a null `source_url`.
174
174
  */
175
175
  sourceUrl?: string;
176
+ /**
177
+ * Functions this catalog asks the client to *additionally* publish into its
178
+ * global (DuckDB `system.main`) function namespace, on top of the normal
179
+ * schema-qualified registration. Surfaced as IPC-serialized `FunctionInfo`
180
+ * records on `catalog_attach.global_functions`.
181
+ *
182
+ * Every entry must also be declared in exactly one of this catalog's
183
+ * schemas — the `FunctionInfo.schema_name` it carries is the bind-dispatch
184
+ * key, not a sentinel. Mirrors vgi-python's `Catalog.global_functions`.
185
+ */
186
+ globalFunctions?: VgiFunction[];
187
+ /**
188
+ * Prefix applied client-side to every `globalFunctions` entry's name when it
189
+ * is published globally (e.g. prefix `vgi_example` + `global_scalar` →
190
+ * `vgi_example_global_scalar`). The advertised `FunctionInfo.name` stays
191
+ * unprefixed. Surfaced as `catalog_attach.global_function_prefix`.
192
+ */
193
+ globalFunctionPrefix?: string;
176
194
  }
@@ -17,6 +17,7 @@ import {
17
17
  type IndexConstraintType,
18
18
  type CopyFromFormatInfo,
19
19
  encodeAttachCatalogInfo,
20
+ encodeFunctionInfo,
20
21
  } from "./interface.js";
21
22
  import type { CatalogDescriptor, SchemaDescriptor, TableDescriptor, ViewDescriptor, MacroDescriptor, SettingDescriptor, SecretTypeDescriptor, ForeignKeyDef, DefaultValue } from "./descriptors.js";
22
23
  import { serializeColumnStatistics } from "../util/statistics.js";
@@ -119,6 +120,11 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
119
120
  // Advertise true so DuckDB routes catalog_table_column_statistics_get
120
121
  // for tables whose TableInfo.supports_column_statistics is also true.
121
122
  supports_column_statistics: true,
123
+ // Functions the client should ALSO publish into its global (system.main)
124
+ // namespace, under `global_function_prefix` + "_" + name. Empty unless
125
+ // the descriptor opts in via `globalFunctions`.
126
+ global_functions: this._globalFunctionInfos().map(encodeFunctionInfo),
127
+ global_function_prefix: this._descriptor.globalFunctionPrefix ?? "",
122
128
  resolved_data_version: null,
123
129
  resolved_implementation_version: null,
124
130
  };
@@ -374,7 +380,18 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
374
380
  if (t === "AGGREGATE_FUNCTION") return (f.kind as string) === "aggregate";
375
381
  return true;
376
382
  })
377
- .map((f) => {
383
+ .map((f) => this._functionToInfo(f, name));
384
+ }
385
+
386
+ /**
387
+ * Build the wire `FunctionInfo` for one declared function.
388
+ *
389
+ * `schemaName` is the schema the function actually lives in — it is the
390
+ * bind-dispatch key, so a function advertised through more than one route
391
+ * (schema contents *and* `global_functions`) must carry the same value in
392
+ * both. Shared by `schemaContentsFunctions` and `_globalFunctionInfos`.
393
+ */
394
+ private _functionToInfo(f: VgiFunction, name: string): FunctionInfo {
378
395
  const meta = resolveMetadata(f);
379
396
  const argSchema = argumentSpecsToSchema(f.argumentSpecs);
380
397
  const argBytes = serializeSchema(argSchema);
@@ -474,7 +491,39 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
474
491
  secret_name: s.secret_name,
475
492
  })),
476
493
  };
477
- });
494
+ }
495
+
496
+ /**
497
+ * Build `FunctionInfo` records for `CatalogDescriptor.globalFunctions`.
498
+ *
499
+ * Each entry's `schema_name` is the schema it actually lives in — the
500
+ * globally visible name is derived client-side from
501
+ * `global_function_prefix`, so `name` stays unprefixed here. An entry not
502
+ * declared in any schema is a descriptor bug, so it throws rather than
503
+ * advertising a function no bind can resolve. Mirrors vgi-python's
504
+ * `_global_function_infos`.
505
+ */
506
+ private _globalFunctionInfos(): FunctionInfo[] {
507
+ const globals = this._descriptor.globalFunctions ?? [];
508
+ if (globals.length === 0) return [];
509
+
510
+ const schemaOf = new Map<VgiFunction, string>();
511
+ for (const sch of this._descriptor.schemas) {
512
+ for (const fn of sch.functions ?? []) {
513
+ if (!schemaOf.has(fn)) schemaOf.set(fn, sch.name);
514
+ }
515
+ }
516
+
517
+ return globals.map((fn) => {
518
+ const schemaName = schemaOf.get(fn);
519
+ if (schemaName === undefined) {
520
+ throw new Error(
521
+ `Catalog '${this._descriptor.name}': '${fn.meta.name}' is listed in ` +
522
+ `globalFunctions but is not declared in any schema.`,
523
+ );
524
+ }
525
+ return this._functionToInfo(fn, schemaName);
526
+ });
478
527
  }
479
528
 
480
529
  override schemaContentsMacros(
@@ -628,6 +628,10 @@ export class VgiClient {
628
628
  comment: inner.comment ?? null,
629
629
  tags: deserializeTags(inner.tags),
630
630
  supports_column_statistics: inner.supports_column_statistics ?? false,
631
+ global_functions: inner.global_functions
632
+ ? (Array.isArray(inner.global_functions) ? inner.global_functions : [...inner.global_functions]).map(toUint8Array)
633
+ : [],
634
+ global_function_prefix: inner.global_function_prefix ?? "",
631
635
  resolved_data_version: inner.resolved_data_version ?? null,
632
636
  resolved_implementation_version: inner.resolved_implementation_version ?? null,
633
637
  };
@@ -2,7 +2,7 @@
2
2
  // GENERATED by vgi.codegen.ts_client. DO NOT EDIT BY HAND.
3
3
  //
4
4
  // Generator: vgi-gen-ts-client v1
5
- // Content hash: 80ec56060e88
5
+ // Content hash: e0862da99312
6
6
  //
7
7
  // To regenerate:
8
8
  // uv run --project ~/Development/vgi-python vgi-gen-ts-client \
@@ -384,6 +384,8 @@ export interface CatalogAttachResult {
384
384
  comment?: string | null;
385
385
  tags: Record<string, string>;
386
386
  supports_column_statistics: boolean;
387
+ global_functions: Uint8Array[];
388
+ global_function_prefix: string;
387
389
  resolved_data_version?: string | null;
388
390
  resolved_implementation_version?: string | null;
389
391
  }
@@ -2,7 +2,7 @@
2
2
  // GENERATED by vgi.codegen.ts_schemas. DO NOT EDIT BY HAND.
3
3
  //
4
4
  // Generator: vgi-gen-ts-schemas v2
5
- // Content hash: 214d149e8f9c
5
+ // Content hash: ae533ce2196c
6
6
  //
7
7
  // To regenerate:
8
8
  // uv run --project ~/Development/vgi-python vgi-gen-ts-schemas \
@@ -265,6 +265,8 @@ export const CatalogAttachResultSchema = schema([
265
265
  field("comment", utf8(), true),
266
266
  field("tags", map(field("key", utf8(), false), field("value", utf8(), true), false), false),
267
267
  field("supports_column_statistics", bool(), false),
268
+ field("global_functions", list(field("item", binary(), true)), false),
269
+ field("global_function_prefix", utf8(), false),
268
270
  field("resolved_data_version", utf8(), true),
269
271
  field("resolved_implementation_version", utf8(), true),
270
272
  ]);
@@ -108,6 +108,12 @@ export function registerCatalogAdminMethods(protocol: Protocol, getCatalog: GetC
108
108
  // is also true. Catalogs that never serve column stats can override
109
109
  // this in attach() via CatalogAttachResult.supports_column_statistics.
110
110
  supports_column_statistics: result.supports_column_statistics ?? true,
111
+ // Protocol 1.3.0: functions the worker asks the client to publish into
112
+ // its global namespace. Empty unless the catalog advertises them —
113
+ // but the columns must still be present, since the extension pins the
114
+ // full CatalogAttachResult field list on the response schema.
115
+ global_functions: result.global_functions ?? [],
116
+ global_function_prefix: result.global_function_prefix ?? "",
111
117
  resolved_data_version: result.resolved_data_version ?? null,
112
118
  resolved_implementation_version: result.resolved_implementation_version ?? null,
113
119
  }, CatalogAttachResultSchema);