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 } ]Connect
Section titled “Connect”| 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();Queries
Section titled “Queries”| 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 } ]Writing rows
Section titled “Writing rows”| 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' } nullProcedures
Section titled “Procedures”| 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' } ]stringawait 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 }Following changes
Section titled “Following changes”| 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 } ]Members
Section titled “Members”| 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 } ]