Skip to content
Query.Farm
Talk with Us

Vector Gateway Interface for TypeScript

Extend DuckDB with functions written in TypeScript — backed by Apache Arrow, callable straight from SQL.

@query-farm/vgi implements the Vector Gateway Interface (VGI) for TypeScript: you write a small worker exposing typed functions, and DuckDB calls them as if they were native. Data moves as Apache Arrow record batches, so it stays columnar across the boundary — no row-by-row marshalling.

bun add @query-farm/vgi @query-farm/apache-arrow @query-farm/vgi-rpc

All three go in explicitly: the latter two are peerDependencies, so a single shared copy is used rather than two that disagree. Requires Node.js 22.15+ or Bun.

A worker is an ordinary module. Define functions, hand them to a Worker, and run it:

import { Worker, defineScalarFunction, int } from "@query-farm/vgi";

const double = defineScalarFunction({
name: "double",
params: { n: int },
returns: int,
compute: (batch) => {
  const ns = batch.getChildAt(0)! as Iterable<bigint | null>;
  return Array.from(ns, (v) => (v == null ? null : v * 2n));
},
});

new Worker({
catalog: { name: "calc", schemas: [{ name: "main", functions: [double] }] },
}).run();
ATTACH 'calc' (TYPE vgi, LOCATION 'bun run ./worker.ts');
SELECT calc.double(21);   -- 42

LOCATION is the command DuckDB runs, not a path to a binary — which is why a TypeScript worker needs no build step at all.

  • Functions are declared, not subclassed. defineScalarFunction({ … }) and its four siblings take a config object. There is no base class to extend and no interface to satisfy — the config is the function, and its params are the SQL signature.
  • Columns are erased. batch.getChildAt(i) yields unknown values, because the SDK runs on two different Arrow backends. Cast at the use site, and go through iterRows for dates and decimals — see Value representations.
  • It runs where the engine runs. The same functions serve from Node or Bun, from a Cloudflare Worker at the edge, or behind an HTTP endpoint a browser talks to. See Runtimes.

Coming from another SDK? The protocol is identical and a TypeScript worker is wire-compatible with a Python, Go or Rust one — the same DuckDB extension drives all of them, so the concepts carry over unchanged.