JSPM

@batadata/serverless

0.5.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 541
  • Score
    100M100P100Q86398F
  • License MIT

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

Package Exports

  • @batadata/serverless

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:pass@ep-xxx.us-east-1.db.batadata.com/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:pass@ep-xxx.db.batadata.com/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