@deltex/client
v1.4.2
Published
Official TypeScript/JavaScript client for Deltex — edge-native SQL database
Maintainers
Readme
@deltex/client
Official TypeScript/JavaScript client for Deltex — edge-native SQL database.
Install
npm install @deltex/client
# or
pnpm add @deltex/clientQuick start
import { createClient } from "@deltex/client";
// Auto-reads DELTEX_API_KEY from environment
const db = createClient();
type User = { id: number; name: string; email: string };
// Tagged template literals — primary API (injection-safe)
const users = await db<User[]>`SELECT id, name FROM users WHERE active = ${true}`;
// Single row
const user = await db.one<User>`SELECT * FROM users WHERE email = ${"[email protected]"}`;
if (!user) throw new Error("Not found");
// Mutations
const n = await db.exec`
UPDATE users SET last_login = NOW()
WHERE id = ${user.id}
`;
console.log(`${n} row updated`);Postgres-compatible drivers (edge-native)
Deltex is HTTP-only at the edge, so there's no raw TCP
Postgres port. Instead, @deltex/client/serverless exposes the familiar shapes of
@neondatabase/serverless (neon())
and pg (Client / Pool) — each query is a single
HTTPS round-trip, so it works from any edge or serverless runtime (Cloudflare
Workers, Vercel Edge, Deno, Bun, Node ≥18) with no connection pool and no gateway.
neon() — Neon-compatible
import { neon } from "@deltex/client/serverless";
const sql = neon(process.env.DATABASE_URL); // or neon({ apiKey })
const users = await sql`SELECT * FROM users WHERE id = ${id}`;
const r = await sql.query("SELECT * FROM users WHERE id = $1", [id]); // { rows, rowCount, fields, command }The connection string's password is used as the Deltex API key:
postgresql://user:[email protected]/db.
pg-compatible Client / Pool
import { Client, Pool } from "@deltex/client/serverless";
const client = new Client({ apiKey: process.env.DELTEX_API_KEY });
await client.connect(); // no-op (connectionless)
const { rows } = await client.query("SELECT * FROM users WHERE id = $1", [id]);
// Atomic transaction (statements are committed together via /v1/transaction)
await client.transaction(async (tx) => {
await tx.query("INSERT INTO orders (user_id, total) VALUES ($1, $2)", [id, 99]);
await tx.query("UPDATE users SET order_count = order_count + 1 WHERE id = $1", [id]);
});Drizzle ORM
import { drizzle } from "@deltex/client/drizzle"; // requires `drizzle-orm`
import { pgTable, integer, text } from "drizzle-orm/pg-core";
import { eq } from "drizzle-orm";
const users = pgTable("users", { id: integer("id").primaryKey(), name: text("name") });
const db = drizzle(process.env.DATABASE_URL); // or drizzle({ apiKey })
const rows = await db.select().from(users).where(eq(users.id, 1));Backed by drizzle-orm/pg-proxy — fully edge-native, no TCP.
API
createClient(options?)
// Node/Deno/Bun — reads DELTEX_API_KEY and DELTEX_ENDPOINT from env
const db = createClient();
// Cloudflare Workers, edge runtimes (no process.env)
const db = createClient({ apiKey: env.DELTEX_API_KEY });
// Custom endpoint + write mode
const db = createClient({
apiKey: env.DELTEX_API_KEY,
endpoint: "https://db.deltex.dev",
writeMode: "sync", // "sync" (default, durable) | "edge" | "async"
});| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | DELTEX_API_KEY env | API key from Deltex dashboard |
| endpoint | string | DELTEX_ENDPOINT env or https://db.deltex.dev | Engine URL |
| writeMode | "sync" \| "edge" \| "async" | "sync" | Write durability mode |
| timeoutMs | number | 30000 | Request timeout |
| fetch | typeof fetch | globalThis.fetch | Custom fetch (Node < 18) |
Tagged template literal — db\sql``
Primary API. SQL is split at interpolation boundaries — values are never concatenated as raw strings.
const id = 42;
const users = await db<User[]>`SELECT * FROM users WHERE id = ${id}`;
// ^^^
// Safely formatted as: ... WHERE id = 42db.one\sql`` — Single row
Returns the first row or undefined.
const user = await db.one<User>`SELECT * FROM users WHERE id = ${id}`;
// user: User | undefineddb.exec\sql`` — Mutations
Returns rows affected count.
const n = await db.exec`DELETE FROM sessions WHERE expires_at < NOW()`;
// n: numberdb.raw\sql`` — Full result envelope
const result = await db.raw`SELECT * FROM users`;
// result.rows: Row[]
// result.columns: string[]
// result.rowsAffected: number
// result.executionMs: number | nulldb.transaction(fn) — Transactions
Runs BEGIN → your function → COMMIT on success, ROLLBACK on error.
const order = await db.transaction(async (tx) => {
const [account] = await tx<Account[]>`
SELECT balance FROM accounts WHERE id = ${userId} FOR UPDATE
`;
if (account.balance < amount) throw new Error("Insufficient funds");
await tx`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${userId}`;
await tx`INSERT INTO transactions (user_id, amount) VALUES (${userId}, ${amount})`;
const [created] = await tx<Transaction[]>`
SELECT * FROM transactions ORDER BY id DESC LIMIT 1
`;
return created;
});db.batch(statements) — Fastest bulk write
Applies an array of SQL statements in one round-trip, committed atomically. Returns the total rows affected.
const n = await db.batch([
"INSERT INTO products (name, price) VALUES ('Apple', 0.99)",
"INSERT INTO products (name, price) VALUES ('Banana', 0.59)",
"UPDATE inventory SET stock = stock - 1 WHERE sku = 'A1'",
]);
// n === 3Looping execute makes one durable commit per statement. batch() (and a
single multi-row INSERT) coalesce them into one commit — O(1) instead of
O(N) — so it's far faster for bulk writes. Reach for it whenever you're writing
many rows at once.
batch()takes raw SQL strings (no parameter binding). For untrusted values, build statements safely or usetransactionwith tagged templates — both commit in one round-trip.
db.withWriteMode(mode) — Per-operation write mode
const syncDb = db.withWriteMode("sync"); // Wait for the durable write
const fastDb = db.withWriteMode("async"); // Fire and forget
await syncDb.exec`INSERT INTO audit_log (event) VALUES (${"payment"})`;db.query(sql, params?) — Positional parameters
For dynamic SQL generation where tagged templates aren't convenient:
const cols = ["id", "name", "email"].join(", ");
const users = await db.query<User>(`SELECT ${cols} FROM users WHERE id = $1`, [42]);Write Modes
| Mode | Durability | Use When |
|------|------------|----------|
| sync (default) | Durable commit | Everything by default; never loses an acked write |
| edge | CAS-protected async, eventual durability | Caches, sessions, idempotent upserts — not loss-critical data |
| async | Fire-and-forget | High-volume events, telemetry |
Error Handling
import { DeltexError } from "@deltex/client";
try {
await db`SELECT * FROM nonexistent_table`;
} catch (err) {
if (err instanceof DeltexError) {
console.error(err.message); // "Table does not exist"
console.error(err.engineMessage); // Raw engine error
console.error(err.status); // HTTP status (400)
console.error(err.sql); // The SQL that failed
}
}Examples
Next.js App Router
// lib/db.ts
import { createClient } from "@deltex/client";
export const db = createClient(); // reads DELTEX_API_KEY from env
// app/users/[id]/page.tsx
import { db } from "@/lib/db";
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await db.one`SELECT * FROM users WHERE id = ${Number(params.id)}`;
if (!user) notFound();
return <div>{user.name}</div>;
}Cloudflare Workers
import { createClient } from "@deltex/client";
export default {
async fetch(req: Request, env: Env) {
const db = createClient({ apiKey: env.DELTEX_API_KEY });
const products = await db`SELECT id, name, price FROM products LIMIT 20`;
return Response.json(products);
},
};Batch insert
For untrusted values, use a transaction with tagged templates — writes are collected and committed in one round-trip, safely parameterized:
const items = [{ name: "Apple", price: 0.99 }, { name: "Banana", price: 0.59 }];
await db.transaction(async (tx) => {
for (const item of items) {
await tx`INSERT INTO products (name, price) VALUES (${item.name}, ${item.price})`;
}
});If you already hold an array of trusted SQL strings, db.batch(statements) does
the same in one call.
License
MIT
