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

@batadata/serverless

v0.5.2

Published

BataDB serverless driver — SQL-over-HTTP for edge functions (Turbine ORM PgCompatPool-conformant)

Readme

@batadata/serverless

SQL-over-HTTP driver for BataDB — a serverless Postgres platform. Query your database from edge functions, serverless runtimes, or any environment with fetch, with no TCP connection to manage.

Works in Node.js, Vercel Edge Functions, Cloudflare Workers, Deno, and the browser.

Install

npm install @batadata/serverless

Quick start

import { neon } from '@batadata/serverless';

const sql = neon(
  'postgresql://user:[email protected]/mydb',
  {
    apiKey: process.env.BATA_API_KEY, // bata_...  (mint one with `bata api-keys create`)
    projectId: 'proj_...',
    branchId: 'br_...',
  },
);

// Tagged-template queries are automatically parameterized ($1, $2, ...).
const userId = 42;
const rows = await sql`SELECT * FROM users WHERE id = ${userId}`;

apiKey and apiUrl also fall back to the BATA_API_KEY and BATA_API_URL environment variables, so in most deployments you only pass projectId and branchId:

const sql = neon(connectionString, { projectId, branchId });

Explicit queries and full result metadata

The tagged template returns just the rows. For row counts, the executed command, or column names, use .query():

const result = await sql.query('SELECT id, email FROM users WHERE active = $1', [true]);
result.rows;     // Array of row objects
result.rowCount; // number of rows
result.columns;  // ['id', 'email']
result.command;  // 'SELECT'

Pool

A Pool is provided for API compatibility with pg/@neondatabase/serverless. Over HTTP there is no real connection to pool, so end() is a no-op — it exists so you can swap drivers without changing call sites.

import { Pool } from '@batadata/serverless';

const pool = new Pool({
  connectionString: 'postgresql://user:[email protected]/mydb',
  apiKey: process.env.BATA_API_KEY,
  projectId: 'proj_...',
  branchId: 'br_...',
});

const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
await pool.end();

Configuration

neon(connectionString, config) and new Pool(config) accept:

| Option | Type | Default | Description | |----------------|-----------------------|-----------------------------|-------------| | apiKey | string | BATA_API_KEY env | API key, sent as Authorization: Bearer <apiKey>. | | apiUrl | string | BATA_API_URL env, else https://api.batadata.com | Control-plane base URL. The SQL endpoint is ${apiUrl}/v1/sql. | | projectId | string | — | Target project (x-project-id header). | | branchId | string | — | Target branch (x-branch-id header). | | timeout | number | 10000 | Per-request timeout in ms. | | fetchFunction| typeof fetch | globalThis.fetch | Custom fetch (testing / runtimes without a global). | | headers | Record<string,string> | — | Extra headers on every request. | | fullResults | boolean | true | Request column/command metadata from the server. | | wakeRetry | { enabled?, maxWaitMs? } | { enabled: true, maxWaitMs: 30000 } | Auto-retry a 503 compute-wake response with backoff. See below. |

Pool additionally accepts connectionString, or discrete host / port / database / user / password / ssl fields.

Compute-wake auto-retry

BataDB branches scale to zero when idle, so the first query after a while can land on a compute that's still booting. The control plane reports this as a retryable 503 (code: 'COMPUTE_STARTING') with a Retry-After hint — and neon() / Pool retry it for you automatically, so "a client that retries just works" is true out of the box:

const sql = neon(connectionString, {
  projectId,
  branchId,
  wakeRetry: {
    enabled: true,   // default
    maxWaitMs: 30000, // default: total retry budget in ms
  },
});

// If the branch is asleep, this call transparently backs off and retries
// instead of throwing — it only surfaces an error if the compute is still
// not ready after the full `maxWaitMs` budget.
const rows = await sql`SELECT * FROM users`;

Backoff uses a fast exponential schedule (250ms → 500ms → 1s → …). A small server Retry-After hint (≤2s — "the compute is just waking") caps each wait but doesn't slow the schedule down, so a wake that finishes in a few hundred milliseconds isn't padded to a full second; a larger hint (>2s — deliberate throttling) is honored verbatim. Retries continue until the total wait budget is spent. Set wakeRetry: { enabled: false } to disable it and get the old throw-immediately behavior. Only 503 compute-wake responses are retried — real SQL errors (syntax errors, constraint violations, timeouts) always throw immediately, on the first attempt.

Error handling

Failed queries throw a BataError carrying the HTTP status code:

import { BataError } from '@batadata/serverless';

try {
  await sql`SELECT 1`;
} catch (err) {
  if (err instanceof BataError) {
    console.error(err.statusCode, err.message);
    // e.g. 401  Query failed with status 401: {"error":"Invalid API key","code":"AUTH_INVALID_KEY"}
  }
}

TypeScript

Ships with type definitions. Row types are generic:

interface User { id: number; email: string }
const users = await sql<User>`SELECT id, email FROM users`;

License

MIT