GitHub - Clickin/SQLBraid: Write SQL. Keep TypeScript. Skip the query-builder translation layer.
Pangram verdict · v3.3
We believe that this entire text is AI.
AI likelihood · overall
AIArticle text · 1,281 words · 1 segments analyzed
Write SQL. Keep TypeScript. Skip the query-builder translation layer. SQLBraid is a SQL-first data-access toolkit for TypeScript. It keeps ordinary SQL visible while adding safe value binds, readable dynamic SQL, explicit result contracts, Standard Schema result mapping, physical connection ownership, and driver-owned transports. pnpm add sqlbraid On Node.js 22.18 or newer, save this as quickstart.mts and run node quickstart.mts. It creates and closes its own in-memory database: import { createNodeSqliteDatabase, sql } from "sqlbraid/node-sqlite"; import { DatabaseSync } from "node:sqlite"; interface UserRow { id: string; name: string; } const native = new DatabaseSync(":memory:"); try { native.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"); native.prepare("INSERT INTO users (name) VALUES (?)").run("Ada"); const db = createNodeSqliteDatabase(native); const userId = 1; const users = await db.all(sql.rows<UserRow>` SELECT id, name FROM users WHERE id = ${userId} `); console.log(users); // [{ id: "1", name: "Ada" }] } finally { native.close(); } Application code installs the unscoped sqlbraid facade and imports a combined driver+dialect/query subpath such as sqlbraid/pg, sqlbraid/mysql2, sqlbraid/mariadb, sqlbraid/node-sqlite, sqlbraid/better-sqlite3, sqlbraid/libsql, sqlbraid/sqlite-wasm, sqlbraid/d1, sqlbraid/oracledb, or sqlbraid/tedious. The facade has no implicit default dialect; its root exports only common runtime contracts. The granular @sqlbraid/* packages remain available for custom integrations and tooling. Release status: 1.0.0 — stable public API. GA means stable public contracts, not universal driver capabilities. Versioned support records identify each certified database/driver/profile/runtime tuple, implementation revision, and workflow evidence. Changed revisions require fresh exact-SHA Runtime, Documentation, and Release gates; neighboring versions do not inherit certification. No tag, npm publication, Pages deployment, or release authorization is implied. Get started · Documentation · Architecture mental model · Data representations · Public API audit · Driver-author guide The core boundary Ordinary ${value} interpolation is always a value bind. Structural SQL uses explicit helpers such as sql.ident, sql.fragment, sql.list, sql.join, sql.raw, and sql.empty. @braid directives (if, choose, when, otherwise, where, set, trim) are lowered by the compiler; inactive branches stay lazy. The renderer produces one immutable logical RenderedStatement: segments.length === parameters.length + 1. A rendered parameter is never SQL, an identifier, a nested query, or a driver fragment. The selected adapter owns placeholder materialization. $1, ?, :1, @p1, and native value-template syntax are transport details, not logical shape identity. const query = sql.rows<UserRow>` SELECT id, name FROM users /*@braid where*/ /*@braid if ${teamId != null}*/ AND team_id = ${teamId} /*@braid end*/ /*@braid end*/ `; Use sql.bind(value, hint) only when a first-party adapter documents the database parameter metadata. Hints are not application codecs or Standard Schema validators; an adapter honors a hint or rejects it before I/O. Result contracts and mapping sql.rows<UserRow>`SELECT ...`; sql.command`UPDATE ...`; sql.call({ resultSets: [UserSchema] as const })`CALL ...`; sql`driver-specific SQL`; // unknown result kind db.all, db.one, db.maybeOne, and db.stream require sql.rows. db.execute accepts row, command, or unknown queries and checks the actual result kind after execution. db.call accepts sql.call and returns output, ordered heterogeneous resultSets, and an optional returnValue. sql.out(name, hint?) is valid for sql.call and Oracle row-returning DML. sql.inOut(name, value, hint?) is call-only. Cursor and emitted result sets are materialized and closed before asynchronous mapping; raw cursors, portals, requests, and carrier rows do not escape. A result-kind mismatch is BRAID_RESULT_KIND after execution and cannot undo a root side effect. Routine channels depend on the adapter: mysql2 supports emitted CALL result sets, but not OUT/INOUT descriptor carriers; SQLite adapters do not support db.call (routine.call), which is an API capability limit, not a restriction on authored SQLite SQL. PostgreSQL refcursors require an existing db.tx; direct SQL Server cursor OUT is unsupported. Unsupported routine channels fail explicitly rather than being emulated; see the support records. callStream is reserved and unimplemented, not a callable API. Attach a Standard Schema to a row query or pass one per execution: const eventQuery = sql.rows(EventSchema)`SELECT created_at, payload FROM events`; const event = await db.one(eventQuery, { schema: EventSchema }); Mapping is one row to one application value. SQLBraid does not hydrate relations, maintain identity maps, or infer arbitrary SELECT/JOIN result types. Runtime API The public runtime surface is intentionally small: interface ExecutionOptions { signal?: AbortSignal } interface RowValidationOptions<Row> extends ExecutionOptions { schema?: StandardSchemaV1<unknown, Row> } interface StreamOptions<Row> extends RowValidationOptions<Row> {} type TransactionIsolation = | "read-uncommitted" | "read-committed" | "repeatable-read" | "serializable"; interface TransactionOptions { isolation?: TransactionIsolation; readOnly?: boolean; } await db.execute(query, options?); await db.all(rows, options?); await db.one(rows, options?); await db.maybeOne(rows, options?); await db.call(call, options?); await db.batch(queries, options?); await db.bulk(inputs, factory, options?); await db.environment(options?); db.stream(rows, options?); await db.session(callback); await db.tx(callback); await db.tx(transactionOptions, callback); All execution methods accept options in the trailing position. signal is an AbortSignal; cancellation is capability-driven. An already-aborted signal rejects with its reason. An active signal requires the adapter's statement.cancel capability; otherwise the operation fails with UnsupportedFeatureError (BRAID_CANCEL_UNSUPPORTED) rather than pretending cancellation is supported. Sessions, providers, and physical leases A direct database wraps one physical executor. A pooled database wraps a ConnectionProvider: interface ConnectionProvider { readonly statementBinding: StatementBindingAdapter; acquire(): Promise<ConnectionLease>; } interface ConnectionLease extends QueryExecutor { release(options?: { discard?: boolean }): void | Promise<void>; } A provider is a source of leases; it is not itself a physical connection and must not be modeled as a fake executor whose transaction commands can land on unrelated connections. Each pooled root operation acquires one lease, performs physical I/O, releases it, and then maps materialized results. A stream keeps its lease until the driver resource closes. The application owns pool shutdown. db.session(async (session) => ...) acquires one lease for the callback and reuses that physical lease for nested operations and nested sessions. db.tx(...) inside a session uses the session lease; it does not reacquire. The root database must not be used to escape the session. An unavailable session primitive fails with BRAID_SESSION_UNSUPPORTED; root misuse, closed callback handles, and sibling/parent transaction handles fail with the runtime's scope errors. db.tx begins and ends one physical transaction. If transactions are absent, the operation fails with BRAID_TX_UNSUPPORTED. Nested transactions use savepoints when the executor exposes them. Explicit transaction options are supported only when the adapter advertises the matching capability; valid but unsupported isolation/access options fail with UnsupportedFeatureError (BRAID_TX_OPTION_UNSUPPORTED, feature transaction.isolation.<level> or transaction.read-only). Malformed runtime values fail before acquisition with TypeError / BRAID_TX_OPTIONS_INVALID. Nested tx(options, callback) is rejected with BRAID_TX_OPTIONS_NESTED rather than silently changing an active transaction. If options are omitted, the database/driver default remains in force; SQLBraid does not guess a profile or reset a session. await db.tx({ isolation: "serializable", readOnly: true }, async (tx) => { await tx.all(sql.rows<{ id: string }>`SELECT id FROM accounts`); }); Transaction-control uncertainty poisons a direct resource or discards a pooled lease. Use the innermost callback handle while a savepoint is active. A successful callback is not sufficient for transaction success: PostgreSQL and Bun PostgreSQL reject with BRAID_TX_NOT_COMMITTED if the server reports that COMMIT actually rolled back. Prepared queries prepare accepts an input factory (required input by default, or explicitly { input: "required" }) or a zero-input factory that explicitly declares { input: "none" }. The factory is evaluated per execution, renders once, and is locked to the first logical shape: result kind, canonical segments, ordered hint/direction/output metadata, and dialect. Values may change; shape changes fail before driver I/O with BRAID_PREPARED_SHAPE. const byId = db.prepare( "user-by-id", (id: string) => sql.rows<UserRow>` SELECT id, name FROM users WHERE id = ${id} `, ); await byId.execute("u_1", { signal }); await byId.all("u_1", { schema: UserSchema }); await byId.one("u_1"); await byId.maybeOne("u_1"); for await (const row of byId.stream("u_1", { signal })) console.log(row); A zero-input prepared query is declared with db.prepare("users", () => query, { input: "none" }) and takes options as its only execution argument (prepared.all({ signal })). Row queries expose execute, all, one, maybeOne, and stream; command/unknown queries expose execute; call queries expose call. Prepared means a stable SQLBraid application shape, not a universal native/server prepared cache. The adapter reports effective reuse. Observers and diagnostics