The missing browser client for Apache Arrow Flight — a browser implementation of Apache Arrow Flight and Flight SQL over gRPC-web. Works with any Apache Arrow Flight or Flight SQL server.
Status ✔ on npm:
npm install @sparrowflight/js· ✔ powers the live demo at sparrowflight.io · 0.x — API may still move Supports ✔ Flight ✔ Flight SQL ✔ browser ✔ Node (same transport) ✔ gRPC-web Validated against ✔ Dremio OSS ✔ InfluxDB 3 Core ✔ GizmoSQL (DuckDB) ✔ Sparrow Flight
This is the live demo. Your browser opens a Flight SQL connection to a 136-million-row production server — ten full-history series, one call, half a second. No REST gateway. No JSON backend. (The second timing line is one, though: the REST+JSON control group racing on the same page — 1.6× slower, 4.8 MB of JSON against 1.7 MB of Arrow.)
Arrow Flight was designed for high-performance analytics. Browsers were left behind — they never learned gRPC, so most Flight deployments end the same way: a REST gateway converting Arrow back into JSON before a chart can touch it. sparrowJS removes that translation layer.
Flight over gRPC-web, decoded by Apache Arrow's JavaScript library, streaming record batches directly into the page — no JSON, no REST gateway in between.
The same auth + discovery pattern is validated against GizmoSQL (DuckDB), InfluxDB 3 Core, Dremio OSS, and the EnergyScope production server (Sparrow).
import { connect } from "@sparrowflight/js";
const client = await connect({
endpoint: "/flight",
user: "demo", // Basic in, Bearer adopted automatically
});
// Flight SQL — the everyday path. One method, two consumption styles:
for await (const batch of client.query("SELECT period, value FROM series_data WHERE …")) {
chart.append(batch); // Apache Arrow JS RecordBatch, as it arrives
}
const { table, stats } = await client.query("SELECT …");
// stats: { planMs, firstBatchMs, streamMs, rows, wireBytes, mbitPerSec, … }
// typed builder, metadata, int64 policy:
await client.from("series_data").select("period", "value").limit(1000).query();
client.capabilities(); // GetSqlInfo decoded — vendor, substrait, readOnly…
await client.tables(); // GetTables discovery (the portable path)
await client.query(sql, { bigIntMode: "string" }); // int64 without 2^53 surprises
// raw Flight against any server: client.getFlightInfo(desc) → client.doGet(ticket)
// 1-RTT pull — servers that accept client-made JSON tickets (Sparrow does)
// skip GetFlightInfo entirely: measured 143 ms vs 224 ms for the same
// 10,217-row series over the same wire. Flight at REST's latency floor.
await client.pull(["PET.RWTC.D"], { start: "2020-01-01" });
// …and query() takes the same shortcut BY ITSELF where the server
// advertises the "sql" direct-ticket template (SqlInfo 10100): the whole
// statement rides the ticket, arbitrary SQL at 1 RTT. stats.route tells
// you which path ran; { direct: false } forces the planned 2-RTT path.
const { stats } = await client.query("SELECT …"); // stats.route → "direct"Full surface + design rationale: docs/api-m1.md. connect() fails
fast on a bad endpoint or credentials, adopts the Bearer, and seeds capabilities()
in the same round trip. Every result carries wire stats — the same anatomy
sparrowCLI's --stats prints. The live demo runs this exact library.
npm install @sparrowflight/js apache-arrowapache-arrow is a peer dependency (≥17) — your bundle shares one copy.
The library is 44 kB gzipped, ESM, tree-shakeable, TypeScript types included.
your browser
│ sparrowjs
│ gRPC-web
▼
Envoy grpc_web filter ← config, not code
│ native gRPC (h2c)
▼
any Apache Arrow Flight SQL server
(Sparrow · GizmoSQL/DuckDB · ROAPI/DataFusion · Dremio · InfluxDB 3 · …)
- Transport —
@connectrpc/connect-web(fetch + ReadableStream). Browsers can't speak gRPC, so a translation layer sits at the edge: any Envoy with the standardgrpc_webfilter (or a TraefikgrpcWebmiddleware) in front of your Flight server. The one powering the live demo runs nginx → Envoygrpc_web→ Flight server. - Decode —
FlightDataframes are reassembled into an Arrow IPC stream (continuation marker + padded header + padded body + EOS) and handed toapache-arrow. Arrow columns are exposed as typed arrays after decode. - View types — Arrow JS can't decode
Utf8View/BinaryView(the types modern DataFusion servers emit for every parquet-sourced string column), so sparrowJS transcodes them to classicUtf8/Binaryat the IPC layer before Arrow JS sees them. DataFusion/ROAPI servers work as-is — noschema_force_view_typesconfig, no asking the server's operator. To our knowledge no other JS Flight client does this. - Auth — Basic bootstrap, then Bearer adoption: many Flight servers (GizmoSQL-style) mint a Bearer from your Basic credentials and bind the session to it, so the client adopts the token from the response headers — the same silent trick the ADBC drivers do.
We also built the control experiment: the same 136M-row snapshot behind a conventional REST+JSON API, raced button-against-button on the live demo. Measured July 2026:
| query | Arrow Flight | REST+JSON | winner |
|---|---|---|---|
| 1 series · 10,217 rows (2-RTT SQL) | 345 ms · 201 KB | 240 ms · 56 KB gz | REST — one round trip beats Flight's two |
1 series · same, via pull() (1-RTT ticket) |
143 ms · 201 KB | 149 ms · 56 KB gz | dead heat — pull() removes the extra round trip (2026-07-17) |
1 series · same, query() auto-routed (1-RTT sql ticket) |
137 ms · 201 KB | 149 ms · 56 KB gz | arbitrary SQL at the same floor — no code change (0.4.0) |
| 10 series · 71,979 rows | 588 ms · 1.7 MB | 850 ms · 4.8 MB JSON | Arrow — the backend's JSON factory becomes the bottleneck |
Small queries favour REST; the gap flips and grows with payload because the server stops spending CPU manufacturing JSON. Under load the difference is structural — 16 concurrent clients on the same 2-vCPU box: Arrow 13.8 req/s (p95 1.4 s) vs REST 3.1 req/s (p95 8.4 s). Full write-up on sparrowflight.io/js.
Streaming is real, not aspirational: the client decodes record batches as they
arrive (onBatch callback). On the demo's "watch it stream" button the first chart
paints at ~270 ms — before the entire REST response has landed — and all ten charts
complete by ~900 ms across 36 record batches.
Good JavaScript Flight SQL clients exist — targeting Node, riding native gRPC, which browsers cannot speak. Verified July 2026:
| Intended runtime | Transport | Flight SQL | |
|---|---|---|---|
apache-arrow (JS) |
browser + Node | — (Arrow IPC decode only; no Flight transport) | — |
gizmodata/gizmosql-client-js |
Node ≥ 20 | native gRPC (@grpc/grpc-js) |
✅ |
lancedb/flight-sql-js-client |
Node ("currently all testing is done on Node") | native gRPC | ✅ (experimental) |
@sparrowflight/js |
browser-first (proven in Node too) | gRPC-web | ✅ |
In the browser, the standard answer is still a REST/JSON backend in front of Flight. sparrowJS implements the transport stack browsers lack — connect-web → gRPC-web → Flight RPC → Flight SQL → Arrow IPC → typed arrays — so the page talks to the Flight server itself.
npm install
npm run gen # regenerate Flight/FlightSql stubs from the Apache protos (buf)
# proof script — the full chain in Node using the *browser* transport:
SPARROW_ENDPOINT=http://your-grpc-web-edge:8890 \
SPARROW_USER=user SPARROW_PASS=pass \
node src/m0.mjs "SELECT 42 AS answer"Bundle for a page (this is exactly how the live demo is built):
npx esbuild src/demo-entry.js --bundle --minify --format=iife \
--global-name=SparrowJS --target=es2020 --outfile=sparrow-demo.jsThe pipeline runs in production. What's left is product work, not research:
-
FlightClientAPI surface —connect()/query()(async-iterate OR await it) /from()builder /capabilities()/tables()/schema()/ rawgetFlightInfo()+doGet()— see docs/api-m1.md - multi-batch and dictionary IPC (multi-endpoint FlightInfo, per-endpoint readers)
- test coverage — unit + live integration against three dialects
(Sparrow · GizmoSQL · ROAPI) + an automated headless-Chrome smoke (
npm run test:browser) - npm packaging and publishing (M4 — the API above ships as written)
One transport, many clients: Sparrow (the Flight server) · sparrowXL (Excel) · sparrowCLI (terminal) · sparrowMCP (AI agents) · sparrowJS (the browser).
