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.
Install
Section titled “Install”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.
Start here
Section titled “Start here”Three things to know up front
Section titled “Three things to know up front”- 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 itsparamsare the SQL signature. - Columns are erased.
batch.getChildAt(i)yieldsunknownvalues, because the SDK runs on two different Arrow backends. Cast at the use site, and go throughiterRowsfor 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.