Tenon ORM
A PostgreSQL ORM designed for coding agents. Built for Node.js and TypeScript.
Tenon keeps database work in forms coding agents can read, write, and check: explicit SQL, ordinary TypeScript functions, and SQL migrations. Queries carry their result types and execution options together, so an agent and the person reviewing its work can see what an operation does in one place.
npm install @kequtech/tenon-orm
Requires Node.js 24+, PostgreSQL 16+, and ESM. TypeScript applications need TypeScript 6+ and Node.js typings. The PostgreSQL driver, pgclear, is included.
Query with SQL
This example assumes a public.users table with integer id and text email columns and a
main export in your shared configuration:
import { createTenon, sql } from '@kequtech/tenon-orm';
import { main } from './tenon.config.ts';
const db = createTenon(main);
try {
const email = '[email protected]';
const user = await db.query(
sql<{ id: number; email: string }>`
SELECT id, email FROM public.users WHERE email = ${email}
`,
{ returns: 'oneOrNone' },
);
// user: { id: number; email: string } | undefined
} finally {
await db.close();
}
${email} becomes $1 in the SQL, with the value passed separately. sql<Row> describes one
returned row. returns chooses how Tenon gives those rows back:
returns | You receive | Required rows |
|---|---|---|
many | readonly Row[] | Any number |
oneOrNone | Row | undefined | Zero or one |
one | Row | Exactly one |
native (default) | Driver result with rows, rowCount, and fields | Any number |
A row-count mismatch throws. The modes do not add LIMIT or change the SQL. In a server, reuse one
database instance and close it during shutdown. Supply environment variables through your application;
Tenon does not load .env files.
Designed for coding agents
Tenon's API gives coding agents concrete code to work with and explicit contracts to preserve:
- Readable SQL. Selected columns, joins, filters, and conflict handling are visible in the query. An agent can inspect the operation directly and use PostgreSQL's existing vocabulary.
- Ordinary TypeScript. Reusable queries are functions. An agent can follow imports, change a function, and pass the same query code a database or transaction client.
- Explicit write limits. A query can require exactly one affected row or set a maximum. Tenon rolls back the operation if that bound is violated.
- Actionable checks. Generated table types describe storage contracts;
tenon checkreports contradictions between recoverable SQL and declared types. Freshness checks catch stale generated declarations, and database tests verify results and effects.
The guides and changelog ship with the package, giving an agent documentation for the installed version. For work in an existing project, start with the agent guide.
How Tenon fits together
- SQL defines the operation. Use PostgreSQL joins, aggregates, constraints, and upserts directly. Reuse queries with ordinary functions; use fragments when parts of the SQL need to vary.
- TypeScript describes the values. Declare query projections with
sql<Row>. Optionally generate table read/insert/update types withtenon generate. Capture a versioned schema fingerprint withtenon schemafor application-owned guards. - Transactions define what commits together. Use
db.transaction(async (tx) => { ... })and run every participating query throughtx. - Write guards check affected rows.
{ affectedRows: { exact: 1 } }rolls back a write unless exactly one row was affected.{ max: 1 }allows zero or one. - Migrations change the schema. Tenon's optional runner applies numbered SQL files. You can keep an existing schema and migration runner.
For object inputs, sql.insertValues(input) and sql.assignments(patch) build the column/value
parts of a statement. The table, conditions, expressions, and conflict behavior stay in your SQL.
There are no model instances or implicit relationship loading.
Query types are declarations. Tenon does not validate returned data against Row during execution.
The optional tenon check command compares recoverable source queries with TypeScript types and
PostgreSQL metadata. Keep database tests for actual results and write effects.
Some PostgreSQL values need care: bigint columns and count(*) read as exact strings, numeric and
temporal values also default to strings, and JSON/array inputs use explicit encoding helpers. Command
rowCount is a number or null. See value mappings before
choosing application types.
Share configuration with the CLI
Run tenon init to create tenon.config.ts. Put connection settings and codecs in its main export,
then reuse that object with createTenon(main). Initialization stays lazy, so builds that only import
the database instance need no credentials.
The CLI uses the same config for migrations, tenon generate, freshness checks, tenon check, and
tenon describe query.sql. For multiple databases, add named exports and select one with --database.
See shared configuration for the complete setup.
Documentation
Start with Getting started for a complete table-to-query example. Then choose the guide for your task:
| Guide | What it covers |
|---|---|
| Shared configuration | Runtime/CLI connection settings, codecs, command defaults, and overrides |
| Writing queries | Parameters, reusable functions, joins, aggregates, pagination, and fragments |
| Writing data | Inserts, patches, defaults, upserts, JSON, arrays, and batches |
| Schema types and values | Generation, read/write types, codecs, and domain validation |
| Migrations | Creating, applying, inspecting, and rolling back SQL migrations |
| Connections and driver access | Pool configuration, shutdown, custom codecs, and native features |
| Checking query types | Source-aware checks and their supported SQL composition |
| Inspecting and testing queries | PostgreSQL metadata and tests of real behavior |
| Adopting Tenon | Replacing an ORM and transferring migration ownership |
| Agent guide | A short implementation workflow for an existing project |
| API and CLI reference | Signatures, options, errors, commands, and troubleshooting |
The README, guides, and changelog ship with the npm package. Read the documentation for your installed version when upgrading. Library maintenance is covered in CONTRIBUTING.md.
ISC licensed. Maintained by Kequtech.