# Turbine ORM, full reference for LLMs > Turbine is a Postgres-first TypeScript ORM with a Prisma-like API. Nested relations compile to a single SQL statement via PostgreSQL json_agg. The client is fully typed and generated from your live schema. One runtime dependency (pg). Optional SQLite / MySQL / SQL Server / PowDB engines share the same typed API behind subpath exports. This file is a condensed API reference for coding agents. It is accurate to the documented API; when in doubt, prefer the full docs at https://turbineorm.dev. Everything below is real, current API, do not invent methods, operators, or options that are not listed here. ## Setup and codegen flow The flow is deterministic and agent-friendly: introspect the database, generate a typed client, query through it. ```bash npm install turbine-orm npm install --save-dev tsx # CLI loads .ts config/schema/seeds via tsx npx turbine init # writes turbine.config.ts + turbine/ npx turbine generate # introspect an existing DB -> typed client # or, code-first: edit turbine/schema.ts, then: npx turbine push # apply defineSchema to the DB (fast local path) npx turbine generate # emit the typed client from the live schema ``` `generate` writes three files to `./generated/turbine/` (or the config `out`): - `types.ts`, entity interfaces plus `*Create` / `*Update` input types - `metadata.ts`, runtime schema metadata - `index.ts`, a typed `TurbineClient` subclass with `db.` accessors and a `turbine()` factory Requirements: Node.js >= 20 (SQLite engine needs Node >= 22.5), PostgreSQL 14+ for the default path. ESM-first; `"type": "module"` recommended. The library never reads `.env`, populate `process.env.DATABASE_URL` yourself (e.g. `node --env-file=.env app.js`); the CLI auto-loads `.env`. ```ts import { turbine } from './generated/turbine'; const db = turbine(); // reads DATABASE_URL; or turbine({ connectionString }) const users = await db.users.findMany({ where: { role: 'admin' }, orderBy: { createdAt: 'desc' }, limit: 10, }); await db.disconnect(); ``` ## Reads ### findMany(args) Returns an array. Supports `where`, `with`, `orderBy`, `limit`/`take`, `offset`, `cursor`, `distinct`, `select`, `omit`. ```ts const users = await db.users.findMany({ where: { orgId: 1 }, with: { posts: { where: { published: true }, orderBy: { createdAt: 'desc' }, limit: 5, with: { comments: true } } }, }); // the whole graph resolves in ONE SQL statement; users[0].posts[0].comments[0] is fully typed ``` - `select` / `omit`, pick or drop columns (one or the other, not both): `select: { id: true, email: true }`. - Pagination: `limit`/`take` + `offset`, or keyset `cursor` (pass the last row's ordered column values; the cursor row is excluded). `take` is a Prisma-compatible alias for `limit`; when both are present `take` wins. - `distinct: ['userId']`, DISTINCT ON; which row survives per group is governed by `orderBy`. - `orderBy`, `{ field: 'asc' | 'desc' }`, or a spec `{ field: { sort: 'desc', nulls: 'last' } }`. Order by a to-many relation's `_count` (`orderBy: { posts: { _count: 'desc' } }`) or a to-one relation's column (`orderBy: { author: { name: 'asc' } }`). - `warnOnUnlimited: false` silences the unlimited-query warning per call. ### findUnique / findUniqueOrThrow Look up by primary key or any unique column. `findUnique` returns `T | null`; the `OrThrow` form throws `NotFoundError` (E001). Composite PKs pass all key columns: `where: { userId: 1, orgId: 2 }`. ### findFirst / findFirstOrThrow First matching row by `where` + `orderBy` (non-unique lookups). ### findManyStream(args) Async iterable backed by a server-side cursor. Constant memory, supports nested `with`, safe to `break` early (cursor + connection cleaned up deterministically). `batchSize` defaults to 1000. ### count / aggregate / groupBy ```ts await db.users.count({ where: { role: 'admin' } }); await db.posts.aggregate({ where: { orgId: 1 }, _sum: { viewCount: true }, _avg: { score: true }, _count: true }); await db.posts.groupBy({ by: ['userId'], _count: true, _sum: { viewCount: true }, having: { _count: { gt: 1 } } }); ``` Aggregate functions: `_count`, `_sum`, `_avg`, `_min`, `_max`. `groupBy` also supports `where` (filter rows before grouping), `having` (filter groups; operators gt/gte/lt/lte/in/notIn), `orderBy`, JSON-path group keys/targets, and Postgres-only `distinctOn`. ### explain(args) Compiles the exact statement `findMany(args)` would run and returns the engine plan as `string[]`. Use it to confirm the emitted query hits the index you expect. ## WHERE operators Equality: bare literal (`{ email: 'a@b.com' }`), `equals`, `not` (also `not: null` for IS NOT NULL). Sets: `in`, `notIn`. Comparison: `gt`, `gte`, `lt`, `lte`. String: `contains`, `startsWith`, `endsWith`, and `mode: 'insensitive'` (switches to ILIKE). Wildcards in input are escaped. Array columns: `has`, `hasEvery`, `hasSome`. Full-text: `search` (with optional `config`, default 'english'). JSON (`json`/`jsonb`): `path` (drill into keys), `equals`, `contains` (containment @>), `hasKey`, and `gt`/`gte`/`lt`/`lte` (require `path`). Column-to-column: `{ col: 'otherField' }` inside `equals`/`not`/`gt`/`gte`/`lt`/`lte`. Relation filters: `some`, `every`, `none` (any operator composes inside them). Combinators: `AND`, `OR`, `NOT`. ```ts where: { AND: [{ orgId: 1 }, { role: 'admin' }], OR: [{ role: 'owner' }, { role: 'admin' }], NOT: { deletedAt: { not: null } }, } ``` ## Writes ```ts await db.users.create({ data: { email: 'a@b.com', name: 'Alice', orgId: 1 } }); // returns the full row await db.users.createMany({ data: [ /* ... */ ] }); // single INSERT ... UNNEST await db.users.update({ where: { id: 42 }, data: { name: 'Alice Updated' } }); await db.users.updateMany({ where: { role: 'guest' }, data: { role: 'member' } }); await db.users.delete({ where: { id: 42 } }); await db.users.deleteMany({ where: { createdAt: { lt: cutoff } } }); await db.users.upsert({ where: { email: 'a@b.com' }, create: { /* ... */ }, update: { /* ... */ } }); ``` Atomic update operators on numeric columns (race-safe `col = col + $n`): `set`, `increment`, `decrement`, `multiply`, `divide`. ```ts await db.posts.update({ where: { id: 1 }, data: { viewCount: { increment: 1 } } }); ``` Safety: `update`/`delete` with an empty `where` (`{}` or all-undefined) throws `ValidationError` (E003). Pass `{ allowFullTableScan: true }` to override. Nested writes: relation fields in `create`/`update` `data` support `create`, `connect`, `connectOrCreate`, `disconnect`, `set`, `delete`, `update`, `upsert`, resolved in one transaction (depth-capped at 10). ## Transactions, pipeline, raw SQL ```ts await db.$transaction(async (tx) => { const user = await tx.users.create({ data: { /* ... */ } }); await tx.posts.create({ data: { userId: user.id, /* ... */ } }); }, { isolationLevel: 'Serializable', timeout: 5000 }); ``` `tx` has the same typed accessors as `db`. Nested `$transaction` calls become SAVEPOINTs. Isolation levels: `ReadCommitted`, `RepeatableRead`, `Serializable`. Pipeline, N independent queries in one round-trip via the Postgres extended-query pipeline protocol: ```ts const [user, postCount, recent] = await db.pipeline([ db.users.buildFindUnique({ where: { id: 1 } }), db.posts.buildCount({ where: { orgId: 1 } }), db.posts.buildFindMany({ where: { userId: 1 }, limit: 5 }), ]); ``` `build*` methods return DeferredQuery objects (sql + params + transform) without executing. Pipelines run in a transaction by default; `{ transactional: false }` isolates errors (inspect `PipelineError`). HTTP/serverless pools fall back to sequential; probe with `db.pipelineSupported()`. Raw SQL, every `${value}` becomes a bound `$N` parameter (injection impossible): ```ts const rows = await db.raw<{ day: Date; count: number }>`SELECT ... WHERE org_id = ${orgId}`; const users = await db.sql<{ id: number; name: string }>`SELECT id, name FROM users WHERE id = ${42}`; // .one(), .scalar() ``` Middleware, `db.$use(fn)` runs after SQL generation: observe params/timing and transform results, but it cannot rewrite the query. ## Typed errors Every error extends `TurbineError` and carries a `code`. Branch on the class/code, never on the message. - E001 NotFoundError, OrThrow lookups - E002 TimeoutError - E003 ValidationError, unknown column, invalid operator, empty-where guard - E004 ConnectionError - E005 RelationError, unknown relation in `with` - E006 MigrationError - E007 CircularRelationError, `with` nesting depth > 10 - E008 UniqueConstraintError (pg 23505) - E009 ForeignKeyError (23503) - E010 NotNullViolationError (23502) - E011 CheckConstraintError (23514) - E012 DeadlockError (40P01), `isRetryable = true` - E013 SerializationFailureError (40001), `isRetryable = true` - E014 PipelineError - E015 OptimisticLockError - E016 ExclusionConstraintError (23P01) - E017 UnsupportedFeatureError, a Postgres-only feature invoked on an engine that lacks it - E018 ReadOnlyError, a write refused on a read-only database ```ts import { SerializationFailureError } from 'turbine-orm'; try { await doWork(); } catch (err) { if (err instanceof SerializationFailureError && err.isRetryable) { /* retry */ } } ``` ## Migrations SQL-first migration files (timestamp-prefixed, `-- UP` / `-- DOWN` sections) tracked in `_turbine_migrations` with SHA-256 checksums and a `pg_try_advisory_lock()`. Each runs in its own transaction; modified files are detected by checksum. Destructive statements need a typed confirmation or `--allow-destructive`. For fast local iteration use `turbine push` (applies `defineSchema` directly); prefer SQL migrations for production. ## Engines Default is PostgreSQL. Optional engines share the same typed API via subpath exports and load their drivers lazily (Postgres users still pull only `pg`): - `turbine-orm/sqlite`, `turbineSqlite(...)`, uses Node's built-in `node:sqlite` (Node >= 22.5) - `turbine-orm/mysql`, `await turbineMysql(...)` (optional peer `mysql2`) - `turbine-orm/mssql`, `await turbineMssql(...)` (optional peer `mssql`) - `turbine-orm/powdb`, `await turbinePowDB(...)` (PowDB, its own query language under the same API) Several flagship features stay Postgres-only (pgvector, LISTEN/NOTIFY, RLS session context); calling them on an engine that lacks the capability throws `UnsupportedFeatureError` (E017) instead of emitting broken SQL. See https://turbineorm.dev/engines for the capability matrix. ## Agent integration point The canonical way to teach an agent about Turbine is to point it at https://turbineorm.dev/llms.txt (index) and this file (full reference), and to add a short instruction block to the project's CLAUDE.md / AGENTS.md, see https://turbineorm.dev/ai-agents for a copy-pasteable snippet. Turbine also ships a read-only MCP server (`npx turbine mcp`) that exposes schema, migrations, doctor, EXPLAIN, and sample rows to MCP clients like Claude Code and Cursor.