JavaScript / TypeScript — HTTP Driver
@arcadedb/driver is ArcadeDB’s TypeScript/JavaScript HTTP client, generated from the server’s
OpenAPI contract.
|
Node.js 20+, ESM only. The package requires Node.js 20 or later, and it ships ESM only — there is no CommonJS
build and no |
Connect and query
import { ArcadeDBError, basicAuth, createClient } from "@arcadedb/driver";
const server = createClient({
baseUrl: "http://localhost:2480",
auth: basicAuth("root", "playwithdata"),
});
const db = server.db("mydb");
const envelope = await db.query({
language: "sql",
command: "SELECT FROM Person WHERE age > ?",
params: { 0: 21 },
});
console.log(envelope.result, envelope.returned, envelope.truncated);
[
{
'@rid': '#1:0',
'@type': 'Person',
'@cat': 'v',
name: 'Alice',
age: 30
},
{
'@rid': '#1:1',
'@type': 'Person',
'@cat': 'v',
name: 'Bob',
age: 25
}
] 2 false
|
Positional parameters are zero-indexed — Positional parameters are keyed by their zero-based index. For the single placeholder above,
the key is |
|
There is no timeout option, and
The Python driver takes an explicit |
basicAuth("root", "playwithdata") builds the Authorization header for HTTP basic auth;
bearerAuth("AU-…") substitutes directly for it wherever you already hold a session token or
other bearer credential; see Sessions and API tokens for getting one.
The result envelope
query and command do not return bare rows. They return the whole response envelope:
interface QueryEnvelope<T> {
result: T[];
limit: number;
returned: number;
truncated: boolean;
explain?: string; // EXPLAIN / PROFILE only
explainPlan?: Record<string, unknown>; // present exactly when `explain` is
}
See Native Drivers for why
truncated matters and what to do when it is true.
An EXPLAIN or PROFILE statement answers with its execution plan instead of rows: explain
holds it as indented text and explainPlan as the same plan in structured form, while result
is empty and returned is 0. An empty result there is not "no rows matched".
Transactions
const total = await db.transaction(async (tx) => {
await tx.command({ language: "sql", command: "INSERT INTO Account SET balance = 100" });
const { result } = await tx.query({ language: "sql", command: "SELECT sum(balance) AS total FROM Account" });
return result[0].total;
});
console.log("total:", total);
total: 200
(The total above reflects balances already present in this database from earlier examples on
this page’s server; a fresh Account type would show 100.)
Every call inside the callback must go through the tx handle it receives, not the outer db — calls made through db while a transaction is open auto-commit individually, outside it. The
contract:
-
transactioncommits when the callback resolves, and returns the callback’s own return value. -
It rolls back and re-throws when the callback throws or rejects.
-
The caller always sees the callback’s own error. If the rollback itself also fails, that failure is attached to the error as
err.causerather than replacing it. -
If the commit itself fails,
transactionissues a best-effort rollback first, to release the server-side session, before re-throwing the commit’s error.
Two error models
Facade methods — query, command, transaction, and others — throw ArcadeDBError on any
non-2xx response:
try {
await db.query({ language: "sql", command: "SELECT FROM NoSuchType" });
} catch (err) {
if (err instanceof ArcadeDBError) console.error(err.status, err.error, err.detail, err.requestId);
}
500 Internal error Type with name 'NoSuchType' was not found 1a9b928039f2a6cd-30
requestId is the server’s request identifier for that call and differs on every request; use
it to correlate a failure with the server log. Two more fields come from the response’s headers:
retryAfter, the seconds the server asked you to wait before retrying (on a 503, or a 409 for an
identical request still in flight), and commitIndex, the Raft index to send back as
X-ArcadeDB-Read-After on a later read. Both are undefined when the server sent none, and 0
is a valid retryAfter, so compare against undefined.
|
A 409 has two meanings. It is either "an identical request is still in flight", which is
retryable after |
A rejected time-series write throws TimeSeriesWriteError, a subclass of ArcadeDBError that
says how many samples were written and dropped and why. A write is not atomic: the written
samples landed, so re-sending the whole batch writes them twice.
server.raw — the underlying openapi-fetch client
beneath the facade — takes the opposite approach: it never throws. It returns { data, error }
and leaves handling the error to the caller. Use the facade for the ergonomics of try/catch; use
raw when you would rather branch on { data, error } without exceptions.
Beyond query, command and transaction, a database handle also exposes db.ts,
db.grafana and db.promql for time-series ingestion, Grafana panel queries and a
PromQL-compatible query surface (including db.promql.labelValues), and db.vector for vector,
hybrid and full-text search — see the package README linked below for those.
Labelling work with a query tag
query, command, queryStream, commandStream, batchLoad and batchLoadStream take an
optional queryTag, and transaction takes one as its second argument. The tag travels as the
X-ArcadeDB-Query-Tag header, so you can list and terminate the work by it:
await db.query({ language: "sql", command: "SELECT FROM Orders", queryTag: "nightly-report" });
await db.transaction(
async (tx) => {
await tx.command({ language: "sql", command: "UPDATE Orders SET archived = true" });
},
{ queryTag: "archive-run" },
);
A transaction’s tag is sent on the begin request only; the server applies it to every statement in the transaction that carries no tag of its own.
Duplex insert session
db.insertSession() opens an insert session on the server’s /ws endpoint. Unlike batchLoad,
whose commit policy is fixed before the load starts, a session lets you read each chunk’s
acknowledgement before deciding what to send next, and whether to commit at all:
const session = await db.insertSession({ targetType: "Person" });
try {
const ack = await session.sendChunk([{ name: "a" }, { name: "b" }]);
console.log(ack.inserted, ack.failed, ack.errors); // nothing is durable yet
const done = await session.commit(); // or: await session.rollback()
console.log(done.outcome, done.summary.inserted);
} finally {
await session.close(); // rolls back if still open
}
It uses the runtime’s global WebSocket (Node 22+); on Node 20, pass the ws package’s class as
createClient’s `WebSocket option.
Administration
createDatabase, dropDatabase, createUser, updateUser, dropUser, listUsers,
saveGroup, deleteGroup and listGroups are on the server handle, and serverCommand runs any
other server command. All of them are root-only:
await server.createDatabase("tenant_a");
await server.createUser("alice", "a-long-password", { databases: { tenant_a: ["admin"] } });
const users = await server.listUsers();
await server.dropUser("alice");
await server.dropDatabase("tenant_a");
A user or database that already exists is a 409 on ArcadeDB 26.10.2 and later. Older servers answer 403 for a duplicate user and 400 for a duplicate database.
Sessions and API tokens
// Log in as each user once, then keep a client that holds only that user's token.
const login = await server.loginAs("alice", "a-long-password"); // or server.login()
const alice = server.withToken(login.token); // server is unchanged
await alice.listDatabases(); // only what alice may see
await alice.logout();
// A long-lived machine credential (root only).
const created = await server.createAPIToken("ci", { database: "tenant_a" });
store(created.token); // the only time the plaintext exists
await server.deleteAPIToken(created.tokenHash); // by hash, never by token
listSessions and listAPITokens list what exists. Each user holds at most 100 sessions, and a
further login silently evicts the oldest, so log in once per user and reuse the token rather than
logging in per request. Wrong Basic credentials are answered 403, not 401; 401 is what an invalid,
expired or logged-out token gets.
Running work
import { isQueryTerminated } from "@arcadedb/driver";
const running = await server.listQueries({ tag: "nightly-report" }); // or { database }
const results = await server.terminateQueriesByTag("nightly-report", { waitMs: 3000 });
await server.terminateQuery(running[0].id);
const open = await server.listTransactions();
await server.terminateTransaction(open[0].id);
const progress = await db.progress(); // long-running maintenance operations
Any authenticated user may call these: root sees and stops every statement, another user only
their own. Each terminate answer carries a status, and which one you get decides whether you have
anything to undo:
status |
Meaning |
|---|---|
|
It stopped, and what it wrote was rolled back. |
|
It finished before it noticed, so what it did stands. |
|
It had not stopped when the wait ended; the request’s own outcome is the answer. |
|
No such statement on this server, or another user’s. |
|
Next steps
-
Native Drivers — the result envelope and why
truncatedmatters, and how this driver compares to the others. -
JavaScript / TypeScript — gRPC Driver — the gRPC counterpart for throughput-sensitive server-to-server work.
-
The package README for the full method list and every failure mode of the methods above, including
db.ts,db.grafana,db.promqlanddb.vector.