Skip to content
ScopeDB Docs
ScopeDB Docs

Node.js SDK

On this page

Use the Node.js SDK from server-side TypeScript or JavaScript applications.

Before you start

The examples use Node.js 20 or later. The SDK also supports Bun and Cloudflare Workers from trusted server-side code. Follow Connect an application, then set the endpoint and API key used by the examples:

export SCOPEDB_ENDPOINT="https://<endpoint>"
export SCOPEDB_API_KEY="<api-key>"

Install

npm install scopedb

If you use pnpm, run pnpm add scopedb instead. The package is ESM-only.

Verify the connection

Save this complete example as connect.mjs. The .mjs extension enables native ES modules and top-level await, without a TypeScript runner:

import { Client } from "scopedb";

const endpoint = process.env.SCOPEDB_ENDPOINT;
const apiKey = process.env.SCOPEDB_API_KEY;

if (!endpoint || !apiKey) {
  throw new Error("SCOPEDB_ENDPOINT and SCOPEDB_API_KEY are required");
}

const client = new Client(endpoint, { apiKey });

const ready = await client.query("SELECT 1 AS ready");
console.log(JSON.stringify(ready.toObjects({ integerMode: "string" })));

Run it from the same shell where you set the connection variables:

node connect.mjs

Expected output:

[{"ready":"1"}]

Keep the API key on the server. Do not create authenticated ScopeDB clients in browser code.

The SDK does not read scope login credentials. If the CLI works but the SDK does not, check that the application process receives both connection variables. Reuse this client when trying the examples below.

Execute a statement

First create and populate the events table from the ScopeQL quickstart. The connection test above does not require an application table.

const result = await client
  .statement(`
    FROM events
    WHERE service = 'checkout'
    ORDER BY time DESC
    LIMIT 10
  `)
  .execute();

execute() returns a finished result or throws when execution fails, is cancelled, or the request is aborted. It handles an active statement's polling internally.

Work with results

For most application code, convert rows to objects keyed by column name:

const rows = result.toObjects();
console.log(rows[0]);

An empty array means the query succeeded but no rows matched; rows[0] is then undefined.

Use first() for a lookup or aggregate that returns at most one row, and toValues() when positional arrays are more convenient.

Result value mapping

toValues(), toObjects(), and first() convert result cells according to their ScopeDB result type:

ScopeDB result typeJavaScript valueNotes
intbigintUse integerMode to return a number or decimal string instead.
uintbigintUse integerMode to return a number or decimal string instead.
floatnumberFinite values only; NaN and infinities cause conversion to throw.
binarystringHexadecimal representation.
stringstring
booleanboolean
timestampDateJavaScript Date preserves milliseconds, not nanoseconds.
intervalstringFixed-duration ISO 8601 representation.
arraystringJSON text; use JSON.parse() when an array is needed.
objectstringJSON text; use JSON.parse() when an object is needed.
anystringRaw textual value; the SDK does not infer its dynamic JavaScript type.
nullnullA null cell is null regardless of the field's declared type.

The null result type can appear in query metadata, such as for a literal NULL; it is not a table column type. See the data types reference for ScopeDB type semantics.

ScopeDB integers can exceed JavaScript's safe integer range. The SDK returns them as bigint by default, which preserves precision but cannot be passed to JSON.stringify directly. Choose the representation at the application boundary:

const jsonSafeRows = result.toObjects({ integerMode: "string" });

Use:

  • bigint for lossless JavaScript arithmetic;
  • string for JSON-safe identifiers or unbounded counters;
  • number only when values fit within JavaScript's safe integer range.

Converting a timestamp to Date discards sub-millisecond precision. Use jsonRows() when the application must retain the original result text.

array and object values contain JSON text. Do not assume that an any value is JSON: it remains raw text because its dynamic ScopeDB type cannot be reconstructed from the result. When parsing array or object values with JSON.parse(), nested integers outside JavaScript's safe range can lose precision. Parsing structured values does not recursively apply the SDK's result conversions; nested binary, timestamp, and interval values remain strings.

Control statement execution

Use submit() when you need a StatementHandle instead of waiting through execute(). The local snapshot does not make a network request; status() fetches the latest remote state while the statement is active:

const handle = await client.statement("SELECT 1 AS ok").submit();

console.log(handle.lastStatus()?.status);
const latest = await handle.status();
console.log(latest.status);
const result = await handle.wait();

An AbortSignal stops client-side requests and polling; it does not cancel a statement in ScopeDB. Call handle.cancel() to request server-side cancellation. See the HTTP API for the underlying state and cancellation contract.

Stream rows to a table

Use table().appendStream() to write rows to a table. The SDK groups rows into batches automatically. Create a table for this example:

scope query 'CREATE TABLE sdk_example_events (id int, name string)'

Run this command once using the ScopeDB CLI with the same endpoint and API key as your application.

const table = client.table("sdk_example_events", {
  database: "scopedb",
  schema: "public",
});

const stream = table.appendStream().build();
await stream.send({ id: 1, name: "first" });
await stream.send({ id: 2, name: "second" });
await stream.shutdown();

send() adds a row to the SDK's buffer. shutdown() sends the remaining rows, waits for the writes to finish, and closes the stream.

Use flush() when you want to wait for pending writes and then keep using the same stream.

After shutdown, read the committed rows:

const written = await client.query("FROM sdk_example_events ORDER BY id");
console.log(written.toObjects({ integerMode: "string" }));

The result includes IDs "1" and "2" with names "first" and "second". Each run adds rows to the table. See the Node.js SDK examples for bulk writes and logging.

Use ingestStream() when source JSON needs a server-side ScopeQL transformation before it matches the destination table. Follow Ingest data for that workflow.

Next steps