deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (hnrss.org)

SQLBraid 1.0 keeps raw SQL in TypeScript with typed binds and result mapping

SQLBraid, a SQL-first TypeScript data-access toolkit, hit 1.0 after a Show HN debut. It keeps plain SQL in tagged templates while adding type-safe parameter binds, dynamic SQL directives, and Standard Schema result validation.

SQLBraid 1.0 keeps raw SQL in TypeScript with typed binds and result mapping

SQLBraid, a SQL-first data-access toolkit for TypeScript, appeared on the Hacker News front page this week as a Show HN post linking to its GitHub repository. The project has reached version 1.0.0, which its README describes as a stable public API. The pitch is straightforward: write SQL directly and skip the query-builder translation layer, keeping ordinary SQL visible while adding safe value binding, readable dynamic SQL, explicit result contracts, and Standard Schema result mapping.

How queries are written

Developers author queries as tagged template literals. A call such as sql.rows with a UserRow type parameter attaches a row type to a SELECT statement, and every interpolated value expression becomes a parameter bind — never literal SQL text. Identifiers, fragments, lists and raw SQL instead go through explicit helpers like sql.ident, sql.fragment, sql.list, sql.join and sql.raw, a separation the README calls the core boundary between data and structure.

Dynamic SQL uses @braid directives — if, choose, when, otherwise, where, set and trim — written as comments inside the statement and lowered by SQLBraid's compiler. Inactive branches stay lazy, so a conditional WHERE clause reads close to the final SQL a database will receive. The renderer produces a single immutable RenderedStatement in which a rendered parameter can never become SQL, an identifier, a nested query or a driver fragment, which is the mechanism guarding against injection. Placeholder syntax such as $1, ? or :1 is left to the selected adapter, keeping the logical statement shape independent of the transport.

Result contracts and validation

Queries carry an explicit result kind: sql.rows for row-returning statements, sql.command for updates, sql.call for stored procedures with typed result sets, or plain sql for driver-specific statements whose shape is unknown. Runtime methods map onto those contracts. According to the README, db.all, db.one, db.maybeOne and db.stream require a sql.rows query, while db.execute accepts row, command or unknown queries and checks the actual result kind after execution. A mismatch surfaces as a BRAID_RESULT_KIND error, which cannot undo a side effect already applied at the database.

Row validation uses Standard Schema: a schema can be attached to the query itself or passed per execution, and mapping is strictly one row to one application value. SQLBraid deliberately does not hydrate relations, maintain identity maps, or infer types from arbitrary SELECT and JOIN shapes — a contrast with full ORMs.

Connections, sessions and transactions

The public runtime surface is intentionally small: execute, all, one, maybeOne, call, batch, bulk, stream, session and tx. All execution methods accept trailing options, including an AbortSignal. Cancellation is capability-driven; an already-aborted signal rejects with its reason, and where an adapter lacks a cancel capability the operation fails with UnsupportedFeatureError rather than pretending cancellation worked.

A pooled database wraps a ConnectionProvider that hands out leases rather than modeling the pool itself as an executor. Each pooled root operation acquires one lease, performs the physical I/O, releases the lease, and only then maps the materialized results; streams keep their lease until the driver resource closes. db.session pins a single physical connection for the duration of a callback and reuses it for nested operations, and db.tx inside a session uses that same lease instead of reacquiring. Nested transactions fall back to savepoints when the executor exposes them, and unsupported isolation or read-only options fail explicitly with capability errors instead of being silently ignored.

Drivers and release status

The toolkit ships combined driver and dialect entry points for PostgreSQL (pg), MySQL (mysql2), MariaDB, node:sqlite, better-sqlite3, libsql, sqlite-wasm, Cloudflare D1, oracledb and tedious for SQL Server. The root facade has no implicit default dialect. Capabilities vary by adapter: the README notes that SQLite adapters do not support db.call, that PostgreSQL refcursors require an existing transaction, and that unsupported routine channels fail explicitly rather than being emulated. Versioned support records certify each database, driver, profile and runtime tuple, and changed revisions require fresh runtime, documentation and release gates rather than inheriting certification from neighboring versions.

Why it matters

TypeScript data access has largely split into two camps: raw drivers with hand-built SQL strings, and query builders or ORMs that hide SQL behind a DSL. SQLBraid targets the gap between them. Developers keep writing the SQL they already know, binds are parameterised by construction, results are validated against schemas, and conditional clauses get a structured directive mechanism instead of string concatenation. With a declared stable 1.0 API and a broad adapter list spanning Node, serverless and browser SQLite targets, it is a serious option for teams that want type safety and injection resistance without adopting a query-builder vocabulary.

  • #sql
  • #typescript
  • #databases
  • #open-source
  • #developer-tools

Related posts