Import .sql files into your TypeScript code with full type inference
If you have a file like this:
-- get_author.sql
select u.id, u.name, count(p.id)::int as post_count
from users u
left join posts p on p.user_id = u.id
where u.id = :id::int
group by u.idYou can import it directly from a TypeScript file like this:
// main.ts
import postgres from "postgres";
import getAuthor from "./get_author.sql";
const db = postgres(process.env.DATABASE_URL);
const author = await getAuthor.run(db, { id: 1 });
// { id: number; name: string; post_count: number } | nullReylo is powered by TypeScript content mappers, introduced in TypeScript 7.1 (currently in beta), which let you dynamically define static types for external file types and integrate them directly into TypeScript compilation.
Reylo is the perfect option for the agent era:
- 💪 The full power of SQL — any statement, function, operator or syntax your database supports, in its own dialect.
- 🌵 DRY — the types come from the SQL itself: no hand-written row types, no schema file to keep in sync.
- 🎯 Checked against your database — a misspelled column, a wrong parameter or a dropped table is a type error, in
tscand in your editor. - 🤖 Agent-friendly — each fact lives in one place, so an agent cannot leave a stale type behind, and the type check finds every caller it missed.
- 🧩 Postgres, MySQL and SQLite — bring your own driver:
pg, postgres.js,mysql2,better-sqlite3,Bun.sql, Cloudflare D1 and more. - ⚡ No codegen — the type checker asks Reylo directly: no generated files, no watcher.
To install Reylo, you'll need:
- TypeScript 7.1 (currently in beta). The first release with content mappers.
- Node 24 or later.
- A running dev database. Postgres, MySQL 8.0.22 or later, or a SQLite file: Reylo reads your current schema from it.
Install Reylo, the engine for your database and TypeScript 7.1:
npm install reylo
npm install -D @reylosql/postgres # or mysql, sqlite
npm install -D typescript@betaAdd Reylo to tsconfig.json, for .sql files and for its project file, .reylo.yaml or .reylo.yml:
Then type-check with --runExternalCode, which lets tsc run content mappers:
// package.json
{
"scripts": {
"typecheck": "tsc --noEmit --runExternalCode"
}
}Point Reylo at your dev database with DATABASE_URL, in your environment or in .env. Its scheme tells Reylo which database you use:
# .env
DATABASE_URL=postgres://localhost:5432/app # or mysql://…, or file:./app.db for SQLiteTo make .sql imports work at run time too, load Reylo where your code runs. On Node:
node --env-file=.env --import reylo/register main.tsFor Bun, Nub, Vite, Next.js, esbuild, webpack, Rspack, Cloudflare Workers and Jest, add the plugin for your setup, or copy its complete example project. To see the types as you type, set up your editor.
Write a query in a .sql file, import it, and run it with your database driver:
-- get_user.sql
select id, name, email from users where id = :id::intimport getUser from "./get_user.sql";
const user = await getUser.run(db, { id: 1 });
// { id: number; name: string; email: string | null } | nullName each parameter with a colon, and give it a type with a cast where the query doesn't pin one down. Pass .run() your driver's client: a pool, a client, or a connection or transaction taken from one.
It resolves to the rows, shaped by how many the query can return:
| The query | Resolves to |
|---|---|
A match on a primary or unique key, or limit 1 |
Row | null |
An aggregate without group by, a one-row insert … returning |
Row |
An insert, update or delete without returning |
void |
| Anything else | Row[] |
An imported query also has .runSync(), .with(), .sql and .raw (Usage).
To type a query, Reylo has your dev database prepare it, without running it, and reads back the type of every column and parameter. On top of that, it infers:
- Nulls — a column is nullable unless its schema, a join or a
wheretest rules null out. - Cardinality — a match on a unique key is one row or
null, and an aggregate is exactly one row. - Unions — an enum, or a column whose check constraint lists its values, is a union of them.
- JSON — JSON the query builds, and a JSON column with a JSON Schema check, keep their shape.
Each has examples in Inference.
Reylo is bring-your-own-driver. Pass .run() a client from any of these, or a Drizzle database over one:
- Postgres —
pg, postgres.js,Bun.sql, PGlite, Neon,pg-promise,@vercel/postgres - MySQL —
mysql2,mariadb, PlanetScale,Bun.sql - SQLite —
better-sqlite3,node:sqlite,Bun.sql, libSQL, Turso, Cloudflare D1,sqlite3,sqlite
reylo.dev covers the rest: every plugin and editor, configuration, errors, each database's data types and edge cases, and advanced examples. Agents can read it all as Markdown at reylo.dev/llms.txt.
