Skip to content

JavaScript client

The pondra package is a small client for a node’s HTTP API. It has no dependencies and runs on Node 18 or later, or on any runtime with fetch. It is an ES module. connect reaches a node that is running, and local starts one on a folder and connects to it. JavaScript has no frames: SQL is its API, so files and other engines’ tables are SQL’s read_* functions and COPY … TO. See names.

import { connect } from "pondra";
const db = connect("http://localhost:8080");
console.log(await db.sql("SELECT 1 AS one"));
[ { one: 1 } ]
Call Meaning
connect(url = "http://127.0.0.1:8080", { token, onNotice, retrySecs }) a connection to a node, or to any of several (an array of URLs)
await local(dir = "lake", { port, token, flags, timeoutMs, python, onNotice }) start a node on a lake here (a folder or s3://bucket/prefix) and connect to it
binary() the path of the pondra executable: $PONDRA_BIN, or the one npm installed for this platform
Option Default Meaning
token none sent as Authorization: Bearer …; the token’s role decides what the connection may do
user, password none sign in as a user instead: its name and password (or one of its tokens)
onNotice console.log called with each line a procedure printed. null keeps them quiet
port a free port the port local’s node listens on (127.0.0.1)
flags [] more pondra serve options, such as ["--memory-gb", "2"]
timeoutMs 120000 how long local waits for the node to start
python true give the node a Python, for Python functions and procedures, if this machine has one with the pondra package (--python auto); false for none
retrySecs 60 how long a call keeps trying while no node answers it (0: once)

Given a cluster’s nodes, a connection rides out one going away, as the Python client does: a call no node ran goes to the next node, and so does one that can’t be applied twice (a query, append, a single INSERT, UPDATE, DELETE or MERGE, sent with a job). While a node holds the session (a transaction, temporary tables, settings, variables), calls go to it alone; if it goes, the next call fails with sqlstate 08006.

const db = connect(["http://10.0.0.1:8080", "http://10.0.0.2:8080", "http://10.0.0.3:8080"]);

local stops its node on db.close() or when the process exits, and the lake stays. Because this program started the node, its SQL may read and write files on this machine (SELECT * FROM read_csv('jan.csv')).

import { local } from "pondra";
const db = await local("scratch");
console.log(await db.sql("SELECT 1 AS one"));
await db.close();
Method Returns
await db.sql(query, params) a query’s rows, as an array of objects. For other statements (CREATE, INSERT, UPDATE, DELETE, CALL, several at once), the last one’s outcome. params gives values for $name in the query
await db.run(file, params = {}) a .sql file on this machine, or else a .sql, .py or notebook file of the lake’s, run on the node (Run files as jobs); or SQL text. $name takes params.name

A parameter is a JSON value: a number, string, boolean, null or an array.

Method Returns
await db.vars() this connection’s SQL variables (DECLARE $day …, $day = …: Variables) as an object of their values
await db.getVariable(name) one variable’s value, null if it has none
await db.setVariable(name, value) sets it, as $name = … in SQL (the value bound, never pasted in)
await db.resetVariable(name) forgets it (RESET $name)
await db.parameters(file) a file of the lake’s parameters: each DECLARE PARAMETER’s name, type, default and comment, and each $name it needs
import { connect } from "pondra";
const db = connect("http://localhost:8080");
await db.sql("DECLARE $since DATE = DATE '2026-09-01'");
await db.setVariable("top", 2);
console.log(await db.vars());
console.log(await db.sql("SELECT $since + 1 AS next, $top * 10 AS t"));
{ since: '2026-09-01', top: 2 }
[ { next: '2026-09-02', t: 20 } ]
import { connect } from "pondra";
const db = connect("http://localhost:8080");
await db.sql("CREATE TABLE orders (id BIGINT, item VARCHAR, amount DOUBLE)");
console.log(await db.append("orders", [{ id: 1, item: "tea", amount: 7.0 }, { id: 2, item: "cake", amount: 4.5 }]));
console.log(await db.sql("SELECT item, sum(amount) AS total FROM orders WHERE amount > $min GROUP BY item ORDER BY item", { min: 1 }));
console.log(await db.run("SELECT $x + 1 AS y", { x: 41 }));
{ seg: 2, duplicate: false, conflict: false, row: 0, ms: 0 }
[ { item: 'cake', total: 4.5 }, { item: 'tea', total: 7 } ]
[ { y: 42 } ]
Method Returns
await db.append(table, rows, retries = 10) appends an array of objects exactly once: a retry after a lost answer is recognised and not applied twice. Answers with { seg, duplicate, conflict, row, ms }. An error the node refuses with (an HTTP status) is not retried
await db.lookup(table, key) the current row of one key of a keyed table, or null

Other writes are SQL: INSERT, UPDATE, DELETE, MERGE, COPY.

import { connect } from "pondra";
const db = connect("http://localhost:8080");
await db.sql("CREATE TABLE users (id BIGINT PRIMARY KEY, name VARCHAR)");
await db.append("users", [{ id: 7, name: "ann" }]);
console.log(await db.lookup("users", 7), await db.lookup("users", 8));
{ id: 7, name: 'ann' } null
Method Returns
await db.call(name, ...args) CALL name(…): its rows or outcome. What the procedure printed is in db.notices, and goes to onNotice
await db.start(name, ...args) starts the procedure on the node without waiting, and answers with the run’s id. The run’s row in pondra.runs says how it went
await db.callProcedure(name, ...args) the same as call, from 0.22
await db.task(name, sql, { schedule, after, when, retries, retryDelay, timeout, onFailure }) CREATE TASK: sql run on a schedule, or after other tasks
await db.executeTask(name, values) EXECUTE TASK: its graph runs now, with these values for its tasks’ $names; answers with the run’s id
await db.wait(id) waits for a run (start’s or executeTask’s) to end, as SQL’s AWAIT 'id'; throws its error if it failed

The arguments are passed by position. A function is called from SQL.

import { connect } from "pondra";
const db = connect("http://localhost:8080");
await db.sql("CREATE PROCEDURE report(day VARCHAR) LANGUAGE sql AS $$ SELECT count(*) AS n, $day AS day FROM orders; $$");
console.log(await db.call("report", "2026-09-27"));
const run = await db.start("report", "2026-09-28");
console.log(typeof run);
[ { n: 2, day: '2026-09-27' } ]
string

await db.view(name, sql, { materialized, replace = true, ...options }) makes a view others read by name.

Option Meaning
materialized: true keep it up to date with each new row (CREATE MATERIALIZED VIEW) instead of running the query when it is read
replace for a stored view, false refuses to replace one that exists
other options the options of a materialized view: window, size_secs, slide_secs, lateness_secs, session, gap_secs, join, time, within_secs. They need materialized: true. See table options
import { connect } from "pondra";
const db = connect("http://localhost:8080");
console.log(await db.view("big_orders", "SELECT * FROM orders WHERE amount > 5"));
console.log(await db.view("spend", "SELECT item, sum(amount) AS total FROM orders GROUP BY item", { materialized: true }));
{ view: 'big_orders' }
{ view: 'spend', materialized: true }
Method Yields
db.watch(table, { after, changes }) an async generator of a table’s new rows as they commit, one object each. For a keyed table, every upsert and delete. after replays from that position first. changes: true also yields UPDATE’s and DELETE’s rows, each with _change_type, _row_id and _version
db.live(sql, { params, everyMs }) an async generator of a query’s answer now, and again each time a commit changes it: an array of rows each time. An answer that comes out the same isn’t sent again. A busy table is queried at most every everyMs (default 100)

After an answer, db.position is the commit it is as of. Both run until you leave the loop: a break ends the stream on the node.

import { connect } from "pondra";
const db = connect("http://localhost:8080");
for await (const row of db.watch("orders", { after: 0 })) {
console.log(row);
break;
}
for await (const rows of db.live("SELECT count(*) AS n FROM orders")) {
console.log(rows);
break;
}
{ id: 1, item: 'tea', amount: 7 }
[ { n: 2 } ]
Member Meaning
db.url, db.token where the node is, and the token in use
db.notices what the last statement’s procedures printed, as an array of strings
db.onNotice the function each notice goes to
db.producer, db.seq the name append writes under, and its last sequence number
db.session this connection’s session id: its temporary tables and views, and the variables of its DO LANGUAGE python blocks, live under it
db.position the commit the last live answer is as of
await db.request(method, path, body, type) one HTTP request to the node, as the methods above make them. Answers the fetch response, and throws on an error status with the text status: message; the error’s sqlstate is Postgres’s code for it (23514, 40001…)

await db.close(timeoutMs = 15000) ends the connection’s session, so its temporary tables and views go, and so do the variables of its DO LANGUAGE python blocks. It also stops the node local started: it waits for the node to stop, and kills it after timeoutMs.

import { connect } from "pondra";
const db = connect("http://localhost:8080");
await db.sql("CREATE TEMP TABLE picked AS SELECT * FROM orders WHERE amount > 5");
console.log(await db.sql("SELECT count(*) AS n FROM picked"));
await db.close();
[ { n: 1 } ]