npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scopedb

v0.2.2

Published

A TypeScript-first ScopeDB client for server-side JavaScript runtimes

Readme

ScopeDB JavaScript SDK

This package provides a TypeScript-first ScopeDB client for trusted server-side JavaScript runtimes. It is ESM-only and has no runtime dependencies.

Runtime support

| Environment | Recommended usage | | --- | --- | | Node.js 20+ | Native ESM; Node 20 is a compatibility floor, so use a maintained Node LTS release in production | | Next.js | Route Handlers, Server Actions, and other server-only modules; prefer the Node runtime | | Bun | Use the same ESM API and bun add scopedb; no Bun-specific adapter | | Cloudflare Workers | Use secret bindings and Web APIs; nodejs_compat is not required | | CommonJS | Load the ESM package with dynamic import() |

Do not use this SDK from browser code, Next.js Client Components, or other untrusted clients. A ScopeDB API key grants server access and must not be included in a browser bundle. The Next.js and Cloudflare Worker templates show the intended boundary.

ScopeQL documentation

This SDK executes ScopeQL statements; the language is documented separately:

Installation

pnpm add scopedb
# or: npm install scopedb
# or: bun add scopedb

Create a Client

import { Client } from "scopedb";

const client = new Client(process.env.SCOPEDB_ENDPOINT!, {
  apiKey: process.env.SCOPEDB_API_KEY!,
});

The SDK compresses JSON request bodies and AppendStream batches with gzip by default using the Web Compression API. Direct caller-encoded table appends remain identity-encoded.

Run a Statement

import { Client } from "scopedb";

const client = new Client(process.env.SCOPEDB_ENDPOINT!, {
  apiKey: process.env.SCOPEDB_API_KEY!,
});

const result = await client.query("SELECT 1 AS ready");
console.log(result.toObjects());

For a detached or long-running statement, keep its handle and choose between a local snapshot, one remote status request, or waiting for the result:

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

// Synchronous and local: the snapshot updated by submit(), status(), or wait().
console.log(handle.lastStatus()?.status);

// Asynchronous: requests the latest remote status while the statement is active.
const latest = await handle.status();
console.log(latest.status);

// Polls until the statement terminates and returns its result set.
const result = await handle.wait();

status() returns the cached snapshot without another request once the handle has reached a terminal state. Use client.statementHandle(id) to resume this lifecycle from a previously stored statement ID. wait(), query(), and execute() accept WaitOptions when polling delays or cancellation need to be configured.

Integer Representation

int and uint cells default to JS bigint to preserve full I64 precision. This is the safe default but is not directly JSON-serializable — JSON.stringify(rowWithBigInt) throws TypeError: Do not know how to serialize a BigInt.

toValues(), toObjects(), and first() accept an optional { integerMode } to opt in to a different representation:

// Default: bigint (lossless, NOT JSON-safe)
const rowsBigint = result.toObjects();

// JSON-safe number. Loses precision for |x| > Number.MAX_SAFE_INTEGER
// (i.e. 2**53 - 1). Safe for typical count() / bounded counters.
const rowsNumber = result.toObjects({ integerMode: "number" });
JSON.stringify(rowsNumber); // ok

// Decimal string. Always safe, always JSON-safe.
// Recommended for unbounded I64 identifiers.
const rowsString = result.toObjects({ integerMode: "string" });

The option only affects int / uint columns; other types are unchanged.

Table Helper

import { Client } from "scopedb";

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

const description = await table.describe();
console.log(description.columns);

Streaming Writes with NDJSON

The streaming write API accepts newline-delimited JSON. The table helper uses scopedb and public when the database or schema is not specified. The destination table must already exist. Use an explicit disposable table for the snippets before pointing any write path at production.

import { Client } from "scopedb";

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

const result = await table.append(
  [
    JSON.stringify({ id: 1, name: "first" }),
    JSON.stringify({ id: 2, name: "second" }),
  ].join("\n"),
);

console.log(result.num_rows_inserted);

For continuous producers, use the asynchronous append stream. It serializes each record as one NDJSON line, batches by size or time, applies byte-based backpressure, and sends a bounded number of append requests concurrently.

const stream = table
  .appendStream()
  .targetBatchBytes(4 * 1024 * 1024)
  .maxBatchRows(10_000)
  .flushIntervalMs(1_000)
  .maxConcurrentBatches(4)
  .maxBufferedBytes(64 * 1024 * 1024)
  .build();

const accepted = await stream.sendAll([
  { id: 1, name: "first" },
  { id: 2, name: "second" },
]);
console.log(accepted.acceptedRows);

// A commit barrier for all rows accepted before flush().
await stream.flush();

// Flushes remaining rows and waits for all in-flight requests. Repeated calls
// return the same promise.
await stream.shutdown();

send() waits only for local admission capacity; it does not wait for a remote commit. sendAll() consumes an iterable or async iterable one row at a time with the same admission backpressure. Avoid creating one promise per row with Promise.all(): those promises and their serialized rows can outgrow the stream's bounded buffer. flush() and shutdown() are the remote delivery barriers.

More precisely, a successful barrier in the default "stop" mode confirms that its accepted prefix committed. In "continue" mode it is a settlement barrier: inspect its report because some batches may be rejected, unknown, or dropped while later batches continue.

The default failurePolicy is "stop", which preserves fail-fast behavior and returns the existing AppendRowsResult | null from barriers. Best-effort telemetry must opt in when creating the stream with .appendStream({ failurePolicy: "continue" }). Its barriers return an AppendDeliveryReport with committed, failed, unknown, and locally dropped row counts. failedRows includes explicitly rejected rows and rows that a local fatal stream failure prevented from being delivered; ambiguous outcomes remain separate in unknownRows. outcome is "partial" when at least one row committed but others were lost or remain unknown. With no committed rows it is "unknown" if any batch may have committed, otherwise "failed"; only a loss-free report is "ok". Never blindly replay an "unknown" report. In every completed report:

acceptedRows = committedRows + failedRows + unknownRows

The stream automatically retries only the exact HTTP batch when its temporary error is explicitly marked append_state: "rejected". That does not make the whole stream or source safe to replay: other concurrent batches may already be committed. A transport error or attempt timeout is unknown; the SDK reports that batch without retrying it, then continue mode can process later batches. Continue mode releases a failed batch after reporting it; it is not an in-memory retry queue. Use an external spool/outbox when the payload must remain available for replay or reconciliation. Safe retries honor Retry-After, capped by the configured maximum backoff.

Choose a delivery path

| Workload | Admission and delivery | Example | | --- | --- | --- | | One exact NDJSON payload | Caller owns request boundaries | append.ts | | Basic asynchronous batching | SDK owns batch boundaries; default strict barriers | append-stream.ts | | Backfill or file import | Bounded backpressure and concurrent strict batches | bulk-import.ts | | Long-running logs and events | Continue-mode stream with observable loss | telemetry.ts | | Fetch-style Serverless | Warm stream settled through a lifecycle hook | serverless.ts | | Durable audit records | One durable attempt per request; ambiguous commits require reconciliation | audit-outbox.ts |

For long-running telemetry, trySend() attempts local admission without waiting; a true result still does not mean a remote commit. A false result can mean a full buffer, open circuit, invalid or oversized input, or a closed stream; stats().droppedByReason separates those causes. Continue mode's default circuit opens after five consecutive availability failures and probes again after 30 seconds. Its default attempt timeout is also 30 seconds.

For Serverless, register the real flush() promise with a lifecycle hook such as waitUntil(); a per-attempt attemptTimeoutMs() does not bound the whole barrier or a shared backlog. A report from a module-level stream can cover concurrent invocations, so it is not an attribution receipt for one event.

For audit data, an in-memory stream is not a durable queue and stable IDs do not automatically provide idempotency. One durable outbox checkpoint should map to one size-validated NDJSON request unless the application stores per-request receipts. An unknown result may already have committed and must not be blindly replayed. Persist READY -> ATTEMPTING before the request; after a crash, route an incomplete ATTEMPTING record to reconciliation instead of appending it again.

An AbortSignal passed to send() or sendAll() cancels only rows still waiting for local admission. Already accepted rows remain in the stream. For flush() and shutdown(), aborting stops the caller's wait but does not cancel an in-flight append, because doing so would create another unknown outcome. Lifetime results remain available from stats(); the latest completed continue-mode barrier is also exposed as stats().lastReport.

If sendAll() is cancelled or its input iterator throws, previously accepted rows are not rolled back and may already have been dispatched. Call shutdown() when the accepted prefix should still commit; there is no transactional stream-wide abort or rollback.

The default number of concurrent batches is 4. Set .maxConcurrentBatches(1) when batches must be submitted serially; concurrent batches do not have a defined commit order. AppendStream caps each uncompressed NDJSON request at 8 MiB and 200,000 rows, and splits automatically at either limit. Direct caller-encoded appends retain the endpoint's 16 MiB limit.

Remote append failures and ambiguous commit outcomes throw AppendRowsError. Its appendState, rowErrors, and rowErrorsTruncated fields preserve the structured response. An appendState of "unknown" means the commit outcome cannot be determined; retrying the same payload may insert duplicates. The stream retries only the exact temporary HTTP batch that the server explicitly marks as "rejected".

Browse the Catalog

The RESTful catalog methods return database, schema, table-summary, and full table resources. Use async iterators for the common path; list methods remain available when an application needs explicit page boundaries.

for await (const database of client.iterateDatabases({ pageSize: 100 })) {
  console.log(database.name);
}

for await (
  const table of client.iterateTables({
    database: "scopedb",
    schema: "public",
    pageSize: 100,
  })
) {
  console.log(table.name);
}

Errors

Server messages pass through unchanged. ScopeDBError adds structured diagnostics without requiring callers to parse the message:

import { ScopeDBError } from "scopedb";

try {
  await client.query("SELECT 1");
} catch (error) {
  if (error instanceof ScopeDBError) {
    console.error({
      message: error.message,
      httpStatus: error.httpStatus,
      requestId: error.requestId,
      retryable: error.retryable,
      retryAfterMs: error.retryAfterMs,
    });
  }
  throw error;
}

CommonJS applications

The package is ESM-only. CommonJS applications can load it with dynamic import:

async function main() {
  const { Client } = await import("scopedb");
  // ...
}

void main();

Batched JSON Ingest

This is also a write path: create the target first and use a disposable table while evaluating the example.

import { Client } from "scopedb";

const client = new Client(process.env.SCOPEDB_ENDPOINT!, {
  apiKey: process.env.SCOPEDB_API_KEY!,
});

const stream = client
  .ingestStream(`
    SELECT
      $0["ts"]::timestamp AS occurred_at,
      $0["name"]::string AS name
    INSERT INTO public.sdk_example_events (occurred_at, name)
  `)
  .build();

await stream.send({
  ts: "2026-03-13T12:00:00Z",
  name: "scopedb",
});

await stream.flush();
await stream.shutdown();

Examples

See the runnable instructions and delivery rules in examples/README.md.

All examples import the public scopedb package entry and are checked with:

pnpm run check:examples

Development

pnpm test
pnpm run build
pnpm run check

Delivery Notes

  • The package is TypeScript-first and emits declarations from src/index.ts.
  • Generated artifacts stay out of git; dist/, dist-test/, and node_modules/ are ignored.
  • prepack runs unit, type, example, and package-entry checks before creating a publishable tarball.