Skip to content

feat: add try(), which returns the database's error as a value - #88

Merged
colinhacks merged 2 commits into
mainfrom
run-safe
Oct 23, 2026
Merged

colinhacks merged 2 commits into
mainfrom
run-safe

Conversation

@colinhacks

Copy link
Copy Markdown
Owner

.try() runs a query as .run() does and resolves to { value }, or to { error } when the database refuses the query. The result is Standard Schema's shape, so if (result.error) narrows either way. .trySync() is the same on the synchronous SQLite clients.

const result = await createUser.try(db, { name: "Ada", email });
if (result.error?.condition === "unique_violation") {
  result.error.constraint; // "users_email_key" …
}

The error

  • One shape per database, whichever driver ran the query, typed with every code the database defines:
    • Postgres: code, the SQLSTATE; condition, its name; constraint, table, column, schema, detail, hint, position, where.
    • MySQL: code (ER_DUP_ENTRY), errno, sqlState; key, constraint, table, column, read from the message.
    • SQLite: code, the most specific result code the driver gives; errcode; table, columns, constraint, read from the message.
  • cause is the driver's own error. The object is an Error named QueryError, so throwing it keeps a stack.
  • Only an error the database raised is returned. A refused connection, a client Reylo can't run on, a value Reylo refuses, or a row-count error still rejects, as from .run().
  • Each .sql module exports the type as QueryError.

Code lists

  • scripts/error-codes.ts writes src/error-codes-{postgres,mysql,sqlite}.ts from each database's own source:
    • Postgres 18's errcodes.txt: 251 codes.
    • MySQL 8.4.11's messages_to_clients.txt: 1,777 numbers, the same as the installed mysqld_error.h.
    • SQLite 3.53.4's sqlite3.h: 108 codes.
  • The Postgres and MySQL lists load lazily, the first time an error needs one, so a bundle keeps them out of the main chunk.
  • A Postgres function can raise any five-character code, so code also takes any string. condition is the strict union, so compare it to have TypeScript catch a typo.

Drivers

The driver matrix gains S6c and S6d, run through every job and client shape: a unique violation and a syntax error each resolve to the database's error, read into the dialect's fields; rows resolve to the value; Reylo's own row-count error rejects. On a synchronous client, trySync must give the same. The new rows are merged into RESULTS.md; compare.ts finds no difference. Where drivers differ:

  • Bun.sql keeps the SQLSTATE in errno and writes its own stack column over column.
  • mariadb numbers its own errors 45001 to 45999, with a state.
  • @planetscale/database writes errno and sqlstate into the message.
  • sqlite3 and the sqlite package give only the primary code.
  • D1 and Durable Objects append the codes to the message.
  • Turso's engine gives the primary code before 0.8, and none from 0.8 on.

Checks

  • tests/run-safe.test.ts:
    • every driver's measured error shape;
    • live constraint, syntax and custom-code errors on pg, postgres.js, mysql2 and mariadb;
    • trySync on node:sqlite;
    • type tests that a misspelled code or condition is an error.
  • The type check, the loader's tests (235, none skipped), the package build, the docs site, every lab, the driver matrix with its containers, and every example pass.

Docs: a .try() section under Usage, a Database errors section on each database's page, and the agent skill.

Copilot AI balanced review requested due to automatic review settings October 23, 2026 21:13
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Oct 23, 2026 9:22pm UTC

Request Review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues. One docs gap is worth closing before users reach for .try() inside a transaction.

Reviewed changes

I reviewed the whole of the initial commit: the new .try()/.trySync() API, the error reader, the generated code lists, the type exports, the driver-matrix scenarios, the docs and the tests. tests/query-error.test.ts passes locally. Its two live tests skip here because no database is available.

  • .try() and .trySync() (src/index.ts): wraps run/runSync. It resolves to { value }, or to { error } when readQueryError recognises the rejection as the database's. Every other error is rethrown unchanged, including Reylo's row-count, parameter and decode errors.
  • Error reader (src/query-error.ts): reads each driver's error into one QueryError shape per dialect, with the driver's error as cause. Postgres reads pg's names and postgres.js's protocol names, and takes the SQLSTATE from errno on Bun.sql. MySQL reads errno/sqlState, or parses them from a PlanetScale message, and leaves out mariadb's own 45xxx errors. SQLite takes the most specific code from code/extendedCode/errcode/rawCode/errno, or from the message tail on D1 and Turso.
  • Code lists (scripts/error-codes.ts → src/error-codes-*.ts): generated from Postgres 18, MySQL 8.4.11 and SQLite 3.53.4 sources. The Postgres and MySQL lists load lazily, and tsc's rewriteRelativeImportExtensions rewrites those dynamic .ts imports in dist/.
  • Types: TryResult, QueryError<D> and the per-dialect code unions are exported, and each .sql module now exports QueryError. The mapper, its no-engine fallback and site/scripts/capture-types.ts were all updated together.
  • Driver matrix: S6c and S6d check .try() (and .trySync() on sync clients) on every job for a unique violation, a syntax error, rows, and Reylo's own row-count error.
  • Docs: a .try() section under Usage, a "Database errors" section on each dialect's page, and the updated skill line.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread site/content/sections/02-usage.mdx Resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants