Skip to content

JavaScript

Hardcharger from JavaScript, by GizmoData

@gizmodata/hardcharger is the TypeScript client for Hardcharger™. It runs wherever fetch does (Node, browsers, Deno, Bun, Cloudflare Workers) with no native code, takes the same hardcharger:// URLs as the Python and Go drivers, and returns apache-arrow Tables or streams of record batches.

It is a thin layer on @gizmodata/warp-sql, the generic JavaScript client for any Warp SQL server, which it depends on and re-exports: import everything from @gizmodata/hardcharger.

import { tableFromArrays } from 'apache-arrow';
import { HardchargerError, connect } from '@gizmodata/hardcharger';
const conn = await connect({ url: 'hardcharger://localhost?tls=false', username: 'hardcharger', password: 'change-me' });
// A query with a bind parameter: an Arrow Table back.
const table = await conn.query('SELECT 42 AS answer, ?::VARCHAR AS greeting', ['hello']);
console.log(table.toArray().map((row) => row.toJSON()));
// A big result, streamed batch by batch (and read over several connections at once).
let rows = 0;
for await (const batch of conn.stream('SELECT * FROM range(10000000)')) rows += batch.numRows;
console.log(`streamed ${rows} rows`);
// Bulk ingest from Arrow, then a prepared statement run once per row.
await conn.ingest('events', tableFromArrays({ id: Int32Array.from([1, 2, 3]) }), { mode: 'replace' });
const insert = await conn.prepare('INSERT INTO events VALUES (?)');
console.log(`inserted ${await insert.executeBatch([[4], [5]])} rows`);
// Errors carry the server's code; an AbortSignal cancels the statement on the server.
try {
await conn.query('SELECT sum(a.range * b.range) FROM range(100000000) a, range(100000) b', undefined, {
signal: AbortSignal.timeout(1000),
});
} catch (err) {
if (!(err instanceof HardchargerError)) throw err;
console.log(`${err.code}: ${err.message}`);
}
await conn.close();
[ { answer: 42, greeting: 'hello' } ]
streamed 10000000 rows
inserted 2 rows
statement_timeout: the operation timed out (its AbortSignal fired)
Call Returns
connect({ url, username, password }) (or token, tokenProvider, subjectToken for SSO) a Connection
conn.query(sql, params?, options?) an Arrow Table (empty for DDL and DML)
conn.stream(sql, params?, options?) a stream to for await over: schema(), rowsAffected(), toTable(), close()
conn.execute(sql, params?) the rows changed
conn.prepare(sql) a prepared statement: query, stream, execute, executeBatch(rows), close()
conn.ingest(table, data, { mode, catalog, schema, temporary }) the rows written
conn.metadata.catalogs(), tables(), columns(), … Arrow tables in Flight SQL’s schemas
conn.transaction(async (tx) => …) commits, or rolls back on a throw

Statement options (per call, or as defaults in connect): signal, async, retainResult, statementTimeoutMs, queryTag, resultPartitions (8 in Node, 4 in browsers), resultOrder, ipcCompression: 'zstd'. Every error is a HardchargerError (the Warp SQL client’s WarpSqlError, under Hardcharger’s name) with the server’s stable code, status, sqlstate and requestId. A result stream that ends without Arrow’s end-of-stream marker throws: a short table is never taken for a complete one.

URL Meaning
hardcharger://host HTTPS to host:31337
hardcharger://host:port HTTPS to another port
hardcharger://host?tls=false Plain HTTP (development, loopback)
hardcharger://host:port/custom/prefix The server mounts Warp SQL under /custom/prefix
warp://…, https://…, http://… The Warp SQL client’s own spellings

hardcharger+http://, credentials in the URL and unknown parameters (useEncryption included) are refused before anything is sent, with an invalid_argument error that says what to write.

In Node, Deno and Bun, connect({ url: 'hardcharger://db.example.com', tls: { ca, cert, key } }) takes PEM text for a private CA and mutual TLS. With authType: 'client_certificate' (or 'mtls') and no password, the client certificate is the credential, on a server with --mtls-certificate-sign-in (TLS). Browsers use the system’s trust store.

The same code runs in a page. The server must allow the page’s origin with cors_origins: an explicit list, off by default, never *:

Terminal window
$ curl -s -o /dev/null -D - -X OPTIONS localhost:31337/warp/v1/auth/token \
-H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' \
| grep -i '^access-control-allow-origin' | tr -d '\r'
Access-Control-Allow-Origin: https://app.example.com
$ curl -s -o /dev/null -D - -X OPTIONS localhost:31337/warp/v1/auth/token \
-H 'Origin: https://elsewhere.example.com' -H 'Access-Control-Request-Method: POST' \
| grep -i '^access-control-allow-origin' || echo "no CORS headers: the browser refuses"
no CORS headers: the browser refuses
  • Credentials are bearer tokens in a header, never cookies, so cross-site request forgery has nothing to ride on. Keep tokens in memory, not in localStorage.
  • Serve the page and Hardcharger over HTTPS: browsers block mixed content.
  • For people who sign in with your identity provider, pass its ID token as subjectToken (SSO).

Warp SQL is plain HTTP, so fetch and apache-arrow alone work too, as in your first query and the plain HTTP page.