turbine-orm 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,25 @@
1
1
  # turbine-orm
2
2
 
3
- Postgres ORM built for the edge. One runtime dependency. Built-in read-only Studio. Code-first and DB-first schema workflows in the same CLI. Runs on **Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase**, and any pg-compatible driver.
3
+ 110 KB. One runtime dependency. The Postgres ORM that ships light and locks tight.
4
4
 
5
5
  ```
6
6
  npm install turbine-orm
7
7
  ```
8
8
 
9
+ **Full docs: [turbineorm.dev](https://turbineorm.dev)** — [Quick Start](https://turbineorm.dev/quickstart) · [API Reference](https://turbineorm.dev/queries) · [Relations](https://turbineorm.dev/relations) · [Transactions & Pipelines](https://turbineorm.dev/transactions) · [Serverless & Edge](https://turbineorm.dev/serverless) · [Typed Errors](https://turbineorm.dev/errors) · [Benchmarks](https://turbineorm.dev/benchmarks)
10
+
9
11
  ## Why Turbine?
10
12
 
11
- The elevator pitch: **a Prisma-like DX without Prisma's engine, a Drizzle-class runtime footprint, and the only built-in read-only Studio in the TS ORM ecosystem.** Four things bundled together that no other ORM bundles:
13
+ Prisma ships a 1.6 MB WASM engine. Drizzle ships zero runtime but no Studio, no typed errors, no migration checksums. Turbine ships **one dependency (`pg`), ~110 KB**, and bundles six things no other TS ORM has together:
12
14
 
13
- 1. **One runtime dependency (`pg`).** No engine binary, no WASM adapter, no code-generation DSL. ~110 KB on npm. 5 KB on the edge entry.
14
- 2. **First-class edge support.** `turbineHttp(pool, schema)` one import swap and the full API runs on Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase. No extra adapter package, no WASM bundle.
15
- 3. **Built-in read-only Studio.** `npx turbine studio` spins up a loopback-bound, token-authed web UI with `BEGIN READ ONLY` + statement-stacking guard. DBA-approvable. No separate install.
16
- 4. **Code-first and DB-first in the same CLI.** `defineSchema()` in TypeScript *or* `npx turbine pull` against a live database same generated client, same migrations runner. Prisma forces the DSL; Drizzle is code-only.
15
+ 1. **One runtime dependency (`pg`).** No engine binary, no WASM adapter, no adapter packages to keep in lockstep. ~110 KB on npm. 5 KB on the edge entry. Prisma's WASM engine alone is 1.6 MB.
16
+ 2. **Built-in read-only Studio.** `npx turbine studio` spins up a loopback-bound web UI with 192-bit auth tokens, `BEGIN READ ONLY` transactions, and a statement-stacking guard. The only TS ORM Studio that physically cannot mutate your database. DBA-approvable.
17
+ 3. **PII-safe error messages.** Turbine errors show WHERE keys, not values. A `UniqueConstraintError` says which column violated the constraint never the actual user data. Safe to log, safe to surface to monitoring, no scrubbing needed.
18
+ 4. **SQL-first migrations with drift detection.** Write real SQL. SHA-256 checksums catch modified migration files. `pg_try_advisory_lock()` prevents concurrent runs. Each migration in its own transaction. No shadow database, no magic DSL.
19
+ 5. **Edge-native — one import swap.** `turbineHttp(pool, schema)` — same API on Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase. No WASM bundle, no adapter package, no separate serverless build.
20
+ 6. **Pipeline batching via wire protocol.** Real Parse/Bind/Execute pipeline — not queries wrapped in a transaction. N independent queries in one round-trip.
17
21
 
18
- Plus the architectural bits you'd expect: pipeline batching (N queries in one round-trip via the pg extended-query protocol), `json_agg`-based nested relation loading (same technique Drizzle and Prisma 7 use no N+1), deep `with` type inference, streaming cursors, typed error hierarchy with `isRetryable` discriminants, middleware.
22
+ Every ORM claims single-query nested loads now (Prisma 7 and Drizzle v2 both use json_agg). Turbine does too see [How It Works](#how-it-works). The differentiator isn't the query strategy; it's the 110 KB footprint, the read-only Studio, and the error messages that never leak user data.
19
23
 
20
24
  ## Benchmarks
21
25
 
@@ -39,7 +43,7 @@ Tested against **Prisma 7.6** (adapter-pg, relationJoins preview on) and **Drizz
39
43
  - **Streaming 50K rows.** Turbine's optimized streaming (speculative first fetch + batch size 1000) matches Prisma at ~3.1–3.2 s. Drizzle's keyset pagination is 1.49× slower at 4.6 s. Turbine's cursor still gives you correctness on any `orderBy` and clean early-`break` semantics.
40
44
  - **Pipeline batching** puts 5 independent queries through a single round-trip using the Postgres extended-query pipeline protocol — all three ORMs are tied here since each runs 5 queries sequentially in a transaction.
41
45
 
42
- Beyond the numbers, Turbine's real strengths are: **one runtime dependency** (`pg`, ~110 KB), a **single import swap** for edge runtimes (`turbine-orm/serverless`), **typed Postgres errors** with a `readonly isRetryable` const for retry loops, and the **read-only Studio** web UI that ships in the CLI the only one in the TS ORM ecosystem that physically cannot mutate your database. And deep type inference through `with` clauses works end-to-end: write `db.users.findMany({ with: { posts: { with: { comments: true } } } })` and `users[0].posts[0].comments[0].body` autocompletes — no manual assertion, no `*With*` helper interfaces.
46
+ Performance is at parity with Prisma and Drizzle the real reasons to choose Turbine are elsewhere: **110 KB install** (vs Prisma's 1.6 MB WASM), the **only read-only Studio** in the TS ORM ecosystem, **PII-safe error messages** that never leak user data, and **SQL-first migrations** with SHA-256 drift detection. Deep type inference through `with` clauses works end-to-end: write `db.users.findMany({ with: { posts: { with: { comments: true } } } })` and `users[0].posts[0].comments[0].body` autocompletes — no manual assertion, no helper annotation.
43
47
 
44
48
  > Full analysis with p50/p95/p99 and methodology notes: [`benchmarks/RESULTS.md`](./benchmarks/RESULTS.md).
45
49
  > Reproduce: `cd benchmarks && npm install && npx prisma generate && DATABASE_URL=... npx tsx bench.ts`
@@ -303,6 +307,8 @@ try {
303
307
 
304
308
  Error codes: `TURBINE_E001` (NotFound), `TURBINE_E002` (Timeout), `TURBINE_E003` (Validation), `TURBINE_E004` (Connection), `TURBINE_E005` (Relation), `TURBINE_E006` (Migration), `TURBINE_E007` (CircularRelation), `TURBINE_E008` (UniqueConstraint), `TURBINE_E009` (ForeignKey), `TURBINE_E010` (NotNullViolation), `TURBINE_E011` (CheckConstraint), `TURBINE_E012` (Deadlock), `TURBINE_E013` (SerializationFailure), `TURBINE_E014` (Pipeline).
305
309
 
310
+ Full reference with `wrapPgError()` translation, retry patterns for `DeadlockError` / `SerializationFailureError`, and safe vs verbose message modes: **[turbineorm.dev/errors](https://turbineorm.dev/errors)**.
311
+
306
312
  ## WHERE Operator Reference
307
313
 
308
314
  Every operator supported by the `where` clause. Operators compose freely with `AND`, `OR`, `NOT`, and the relation filters `some` / `every` / `none`.
@@ -565,9 +571,9 @@ Priority order: CLI flags > environment variables (`DATABASE_URL`) > config file
565
571
 
566
572
  ## How It Works
567
573
 
568
- Turbine resolves the entire object graph in a single database round-trip, regardless of nesting depth. The runtime nests relations for you via `json_agg` one round-trip, no N+1, no client-side stitching. And the `with` clause is fully type-inferred: the generator emits branded `*Relations` interfaces with `RelationDescriptor` phantom fields, and a recursive `WithResult<T, R, W>` conditional type walks an arbitrarily-deep `with` literal to produce the exact nested return shape. Write `db.users.findMany({ with: { posts: { with: { comments: { with: { author: true } } } } } })` and `users[0].posts[0].comments[0].author.name` autocompletes no manual assertion, no `*With*` helper annotation.
574
+ Turbine resolves nested relations the same way Prisma 7 and Drizzle v2 do: correlated subqueries with `json_agg` + `json_build_object`, evaluated by PostgreSQL in a single round-trip. No N+1, no client-side stitching, no separate queries per relation. The `with` clause is fully type-inferred end-to-end write `db.users.findMany({ with: { posts: { with: { comments: { with: { author: true } } } } } })` and `users[0].posts[0].comments[0].author.name` autocompletes with zero manual annotation.
569
575
 
570
- Prisma 7+ and Drizzle v2 also do single-query nested loads. Turbine's advantage isn't query latency (see [Benchmarks](#benchmarks) all three are within noise over a real pooled database); it's architectural simplicity plus the read-only Studio. One runtime dependency (`pg`), no DSL compiler, no driver adapter shim for edge, and the only TS ORM Studio your DBA will approve.
576
+ The query strategy is table stakes now. What isn't table stakes: the 110 KB footprint that makes it possible, the read-only Studio your DBA will approve, the error messages that never leak PII, and the SQL-first migrations with SHA-256 drift detection. See [Why Turbine?](#why-turbine) for the full breakdown.
571
577
 
572
578
  ## Type Mapping
573
579
 
@@ -589,14 +595,18 @@ Turbine maps Postgres types to TypeScript:
589
595
 
590
596
  | | **Turbine** | **Prisma** | **Drizzle** | **Kysely** |
591
597
  |---|---|---|---|---|
592
- | **Nested relations** | 1 query, deep type inference | 1 query (since v5.8), shallow inference | 1 query, requires `relations()` re-declaration | Manual (`jsonArrayFrom`) |
593
- | **API style** | `findMany`, `with` | `findMany`, `include` | SQL-like + relational | SQL builder |
594
- | **Schema** | TypeScript | Custom DSL (`.prisma`) | TypeScript | Manual interfaces |
598
+ | **Install size** | ~110 KB (`pg` only) | ~1.6 MB (WASM engine) | ~0 KB (no runtime) | ~0 KB (no runtime) |
595
599
  | **Runtime deps** | 1 (`pg`) | `@prisma/client` + adapter | 0 | 0 |
600
+ | **Studio** | Read-only, 192-bit auth | Full CRUD, cloud-hosted | Paid tier | None |
601
+ | **Error PII safety** | Keys only by default | Values in messages | Raw pg errors | Raw pg errors |
602
+ | **Migrations** | SQL-first, SHA-256 checksums | DSL-generated, shadow DB | SQL or Drizzle Kit | None |
603
+ | **Edge runtime** | One import swap, 5 KB | 1.6 MB WASM adapter | Native | Native |
604
+ | **Pipeline batching** | Parse/Bind/Execute protocol | Sequential in txn | Sequential | Manual |
605
+ | **Typed errors** | `isRetryable` discriminant | Error codes only | None | None |
606
+ | **Nested relations** | 1 query, deep type inference | 1 query, shallow inference | 1 query, `relations()` re-declaration | Manual (`jsonArrayFrom`) |
596
607
  | **Multi-DB** | PostgreSQL only | PG, MySQL, SQLite, MSSQL | PG, MySQL, SQLite | PG, MySQL, SQLite |
597
- | **Code generation** | `turbine generate` | `prisma generate` | Not needed | Not needed |
598
608
 
599
- All three ORMs now do single-query nested loads. Over a real pooled database (Neon, US-East) most single-query scenarios land within noise — but Turbine's SQL template caching and prepared statements give it a consistent edge, particularly on L2 nested reads (1.59× faster than Drizzle) and streaming (at parity with Prisma, 1.49× faster than Drizzle). Turbine's differentiators are both architectural and performance: one runtime dependency, one import swap for edge, typed errors with `isRetryable`, deep `with` type inference, and real Postgres pipeline protocol support. See [Benchmarks](#benchmarks) and [`benchmarks/RESULTS.md`](./benchmarks/RESULTS.md) for the full breakdown.
609
+ All three ORMs now do single-query nested loads that's table stakes. Turbine's real differentiators: the smallest install of any full-featured ORM (110 KB vs Prisma's 1.6 MB), the only read-only Studio in the ecosystem, error messages that never leak PII, and SQL-first migrations with SHA-256 drift detection. See [Benchmarks](#benchmarks) for performance numbers most scenarios are within noise over a real pooled database.
600
610
 
601
611
  ## Limitations
602
612
 
@@ -624,7 +634,15 @@ Turbine is focused and opinionated. Here's what it doesn't do:
624
634
 
625
635
  ## Guides
626
636
 
627
- - **[Migrating from Prisma](./docs/migrate-from-prisma.md)** — API mapping table, side-by-side `findMany`, and notes on the differences
637
+ - **[Quick Start](https://turbineorm.dev/quickstart)** — zero-to-first-query in five minutes
638
+ - **[API Reference](https://turbineorm.dev/queries)** — every `findMany` / `findUnique` / `create` / `update` / `delete` option, the full operator table, and `pipeline()` semantics
639
+ - **[Relations](https://turbineorm.dev/relations)** — deep `with` clause, nested options, relation filters (`some` / `every` / `none`), payload-size guidance
640
+ - **[Transactions & Pipelines](https://turbineorm.dev/transactions)** — isolation levels, nested SAVEPOINTs, retry loops for `DeadlockError` and `SerializationFailureError`
641
+ - **[Schema & Migrations](https://turbineorm.dev/schema)** — `defineSchema()`, auto-diff migrations, checksum validation
642
+ - **[Serverless & Edge](https://turbineorm.dev/serverless)** — Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase walkthroughs
643
+ - **[CLI](https://turbineorm.dev/cli)** — every command, flag, and config option
644
+ - **[Typed Errors](https://turbineorm.dev/errors)** — error code reference, `wrapPgError()` translation, retry patterns
645
+ - **[Migrating from Prisma](https://turbineorm.dev/migrate-from-prisma)** — API mapping table, side-by-side `findMany`, and notes on the differences
628
646
 
629
647
  ## Requirements
630
648
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * turbine-orm — CockroachDB adapter
3
+ *
4
+ * CockroachDB speaks the PostgreSQL wire protocol but has key differences:
5
+ *
6
+ * 1. **No advisory locks** — `pg_try_advisory_lock()` is not supported.
7
+ * This adapter uses a `_turbine_lock` table with `SELECT FOR UPDATE NOWAIT`
8
+ * as a concurrency mechanism for migrations.
9
+ *
10
+ * 2. **No `SET LOCAL statement_timeout`** — CockroachDB uses
11
+ * `SET transaction_timeout` (v23.1+) for per-transaction time limits.
12
+ *
13
+ * 3. **`pg_indexes` view** — CockroachDB supports `pg_indexes` since v22.1
14
+ * but the `indexdef` column may not match Postgres exactly. We use
15
+ * `SHOW INDEXES` as a more reliable alternative.
16
+ *
17
+ * 4. **`pg_class.reltuples`** — Not reliable in CockroachDB. We use
18
+ * `crdb_internal.table_row_statistics` for row estimates.
19
+ *
20
+ * Known limitations with Turbine on CockroachDB:
21
+ * - `json_agg` works but NULL ordering within aggregates may differ
22
+ * - `SERIAL` columns use `unique_rowid()` instead of sequences
23
+ * - Schema introspection via information_schema works for tables, columns,
24
+ * constraints; pg_catalog has gaps for some metadata
25
+ * - Pipeline batching works (extended query protocol is supported)
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { cockroachdb } from 'turbine-orm/adapters';
30
+ *
31
+ * // In turbine.config.ts:
32
+ * export default {
33
+ * url: process.env.DATABASE_URL,
34
+ * adapter: cockroachdb,
35
+ * };
36
+ * ```
37
+ */
38
+ import type { DatabaseAdapter } from './index.js';
39
+ export declare const cockroachdb: DatabaseAdapter;
40
+ //# sourceMappingURL=cockroachdb.d.ts.map
@@ -0,0 +1,172 @@
1
+ /**
2
+ * turbine-orm — CockroachDB adapter
3
+ *
4
+ * CockroachDB speaks the PostgreSQL wire protocol but has key differences:
5
+ *
6
+ * 1. **No advisory locks** — `pg_try_advisory_lock()` is not supported.
7
+ * This adapter uses a `_turbine_lock` table with `SELECT FOR UPDATE NOWAIT`
8
+ * as a concurrency mechanism for migrations.
9
+ *
10
+ * 2. **No `SET LOCAL statement_timeout`** — CockroachDB uses
11
+ * `SET transaction_timeout` (v23.1+) for per-transaction time limits.
12
+ *
13
+ * 3. **`pg_indexes` view** — CockroachDB supports `pg_indexes` since v22.1
14
+ * but the `indexdef` column may not match Postgres exactly. We use
15
+ * `SHOW INDEXES` as a more reliable alternative.
16
+ *
17
+ * 4. **`pg_class.reltuples`** — Not reliable in CockroachDB. We use
18
+ * `crdb_internal.table_row_statistics` for row estimates.
19
+ *
20
+ * Known limitations with Turbine on CockroachDB:
21
+ * - `json_agg` works but NULL ordering within aggregates may differ
22
+ * - `SERIAL` columns use `unique_rowid()` instead of sequences
23
+ * - Schema introspection via information_schema works for tables, columns,
24
+ * constraints; pg_catalog has gaps for some metadata
25
+ * - Pipeline batching works (extended query protocol is supported)
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { cockroachdb } from 'turbine-orm/adapters';
30
+ *
31
+ * // In turbine.config.ts:
32
+ * export default {
33
+ * url: process.env.DATABASE_URL,
34
+ * adapter: cockroachdb,
35
+ * };
36
+ * ```
37
+ */
38
+ // ---------------------------------------------------------------------------
39
+ // Lock table SQL
40
+ // ---------------------------------------------------------------------------
41
+ const LOCK_TABLE = '_turbine_lock';
42
+ /**
43
+ * DDL for the table-based lock mechanism. The table holds a single row per
44
+ * lock ID. `SELECT ... FOR UPDATE NOWAIT` on this row serves as a mutex.
45
+ */
46
+ const CREATE_LOCK_TABLE_SQL = `
47
+ CREATE TABLE IF NOT EXISTS "${LOCK_TABLE}" (
48
+ lock_id INT PRIMARY KEY,
49
+ acquired_at TIMESTAMPTZ NOT NULL DEFAULT now(),
50
+ acquired_by TEXT
51
+ )
52
+ `;
53
+ // ---------------------------------------------------------------------------
54
+ // Introspection query overrides
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * CockroachDB index introspection via SHOW INDEXES.
58
+ * This returns a different shape than pg_indexes, so the consumer
59
+ * needs to handle the transformation. However, since we're providing this
60
+ * as a drop-in SQL string for the existing introspection flow, we use
61
+ * CockroachDB's pg_indexes compatibility view but with a fallback query
62
+ * that produces the same columns.
63
+ *
64
+ * CockroachDB's pg_indexes is compatible since v22.1 — we keep this
65
+ * override for older versions or when indexdef is incomplete.
66
+ */
67
+ const SQL_INDEXES_CRDB = `
68
+ SELECT
69
+ tablename,
70
+ indexname,
71
+ indexdef
72
+ FROM pg_indexes
73
+ WHERE schemaname = $1
74
+ `;
75
+ /**
76
+ * Row estimates using crdb_internal. Falls back gracefully if the view
77
+ * doesn't exist (permissions issue) — returns 0 rows in that case.
78
+ */
79
+ const SQL_ROW_ESTIMATES_CRDB = `
80
+ SELECT
81
+ t.name AS relname,
82
+ COALESCE(s.estimated_row_count, 0)::text AS reltuples
83
+ FROM crdb_internal.tables t
84
+ LEFT JOIN crdb_internal.table_row_statistics s
85
+ ON s.table_id = t.table_id
86
+ WHERE t.schema_name = $1
87
+ AND t.database_name = current_database()
88
+ `;
89
+ /**
90
+ * Enum introspection — CockroachDB supports pg_type/pg_enum since v20.2.
91
+ * The standard query works, but we include it explicitly so the override
92
+ * mechanism is complete.
93
+ */
94
+ const SQL_ENUMS_CRDB = `
95
+ SELECT t.typname, e.enumlabel
96
+ FROM pg_type t
97
+ JOIN pg_enum e ON t.oid = e.enumtypid
98
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
99
+ WHERE n.nspname = $1
100
+ ORDER BY t.typname, e.enumsortorder
101
+ `;
102
+ // ---------------------------------------------------------------------------
103
+ // Adapter implementation
104
+ // ---------------------------------------------------------------------------
105
+ export const cockroachdb = {
106
+ name: 'cockroachdb',
107
+ createLockTableSQL() {
108
+ return CREATE_LOCK_TABLE_SQL;
109
+ },
110
+ async acquireLock(client, lockId) {
111
+ // Ensure the lock table exists
112
+ await client.query(CREATE_LOCK_TABLE_SQL);
113
+ // Insert the lock row if it doesn't exist (idempotent)
114
+ await client.query(`INSERT INTO "${LOCK_TABLE}" (lock_id) VALUES ($1) ON CONFLICT (lock_id) DO NOTHING`, [lockId]);
115
+ // Try to acquire the row lock with NOWAIT — fails immediately if held
116
+ try {
117
+ await client.query('BEGIN');
118
+ await client.query(`SELECT lock_id FROM "${LOCK_TABLE}" WHERE lock_id = $1 FOR UPDATE NOWAIT`, [lockId]);
119
+ // Update the acquired metadata
120
+ await client.query(`UPDATE "${LOCK_TABLE}" SET acquired_at = now(), acquired_by = current_user WHERE lock_id = $1`, [lockId]);
121
+ // Note: we leave the transaction OPEN — the lock is held until
122
+ // releaseLock() commits or rolls back.
123
+ return true;
124
+ }
125
+ catch (err) {
126
+ // NOWAIT throws error code 55P03 (lock_not_available) if the row is locked
127
+ const pgErr = err;
128
+ if (pgErr.code === '55P03') {
129
+ try {
130
+ await client.query('ROLLBACK');
131
+ }
132
+ catch {
133
+ /* ignore rollback errors */
134
+ }
135
+ return false;
136
+ }
137
+ // Any other error — rollback and re-throw
138
+ try {
139
+ await client.query('ROLLBACK');
140
+ }
141
+ catch {
142
+ /* ignore */
143
+ }
144
+ throw err;
145
+ }
146
+ },
147
+ async releaseLock(client, _lockId) {
148
+ // Commit the transaction opened in acquireLock to release the FOR UPDATE lock
149
+ try {
150
+ await client.query('COMMIT');
151
+ }
152
+ catch {
153
+ // If commit fails, try rollback (either way, lock is released)
154
+ try {
155
+ await client.query('ROLLBACK');
156
+ }
157
+ catch {
158
+ /* ignore */
159
+ }
160
+ }
161
+ },
162
+ introspectionOverrides: {
163
+ indexes: SQL_INDEXES_CRDB,
164
+ enums: SQL_ENUMS_CRDB,
165
+ rowEstimates: SQL_ROW_ESTIMATES_CRDB,
166
+ },
167
+ statementTimeout(seconds) {
168
+ // CockroachDB v23.1+ supports transaction_timeout
169
+ return `SET transaction_timeout = '${seconds}s'`;
170
+ },
171
+ };
172
+ //# sourceMappingURL=cockroachdb.js.map
@@ -0,0 +1,107 @@
1
+ /**
2
+ * turbine-orm — Database adapter interface
3
+ *
4
+ * Adapters allow Turbine to work with PostgreSQL-compatible databases that
5
+ * have subtle differences (e.g. CockroachDB, YugabyteDB). The default
6
+ * behavior remains standard PostgreSQL — adapters only override specific
7
+ * operations where compatibility gaps exist.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { cockroachdb } from 'turbine-orm/adapters';
12
+ *
13
+ * // Pass to TurbineCliConfig or migration functions
14
+ * const config = { url: process.env.DATABASE_URL, adapter: cockroachdb };
15
+ * ```
16
+ */
17
+ import type { PgCompatPoolClient } from '../client.js';
18
+ /**
19
+ * Override individual introspection SQL queries for databases where
20
+ * pg_catalog or information_schema behaves differently.
21
+ */
22
+ export interface IntrospectionOverrides {
23
+ /** Override the pg_indexes query (CockroachDB uses crdb_internal or show indexes) */
24
+ indexes: string;
25
+ /** Override the pg_enum query */
26
+ enums: string;
27
+ /** Override the row count estimation query (pg_class reltuples) */
28
+ rowEstimates: string;
29
+ }
30
+ /**
31
+ * A DatabaseAdapter encapsulates dialect-specific behavior for databases
32
+ * that speak the PostgreSQL wire protocol but differ in implementation.
33
+ *
34
+ * All methods are optional except `name`. Turbine falls through to standard
35
+ * PostgreSQL behavior for any method not provided by the adapter.
36
+ */
37
+ export interface DatabaseAdapter {
38
+ /** Identifier for the adapter (e.g. 'postgresql', 'cockroachdb') */
39
+ readonly name: string;
40
+ /**
41
+ * Acquire a concurrency lock for migrations.
42
+ * PostgreSQL uses `pg_try_advisory_lock`. CockroachDB uses a lock table.
43
+ *
44
+ * @returns `true` if the lock was successfully acquired.
45
+ */
46
+ acquireLock(client: PgCompatPoolClient, lockId: number): Promise<boolean>;
47
+ /**
48
+ * Release the concurrency lock acquired by `acquireLock`.
49
+ */
50
+ releaseLock(client: PgCompatPoolClient, lockId: number): Promise<void>;
51
+ /**
52
+ * Optional overrides for introspection queries where the default
53
+ * information_schema/pg_catalog queries don't work.
54
+ */
55
+ introspectionOverrides?: Partial<IntrospectionOverrides>;
56
+ /**
57
+ * Generate the SQL to set a statement timeout within a transaction.
58
+ * PostgreSQL uses `SET LOCAL statement_timeout = '<n>s'`.
59
+ * CockroachDB uses `SET transaction_timeout = '<n>s'` (v23.1+).
60
+ *
61
+ * @param seconds — timeout in seconds
62
+ * @returns the SQL string to execute
63
+ */
64
+ statementTimeout?(seconds: number): string;
65
+ /**
66
+ * SQL to create the lock table used by table-based locking adapters.
67
+ * Called during `ensureTrackingTable` when the adapter uses table locks.
68
+ */
69
+ createLockTableSQL?(): string;
70
+ }
71
+ /**
72
+ * The default PostgreSQL adapter. Uses pg_try_advisory_lock, standard
73
+ * pg_catalog queries, and SET LOCAL statement_timeout.
74
+ */
75
+ export declare const postgresql: DatabaseAdapter;
76
+ /**
77
+ * Google AlloyDB adapter. AlloyDB is PostgreSQL with Google's columnar storage
78
+ * engine. It is wire-protocol and catalog-compatible — no adapter overrides
79
+ * are needed. All Turbine features (json_agg, advisory locks, introspection,
80
+ * migrations) work identically to standard PostgreSQL.
81
+ *
82
+ * This export exists for documentation and explicit configuration:
83
+ *
84
+ * ```ts
85
+ * import { alloydb } from 'turbine-orm/adapters';
86
+ * const config = { url: process.env.DATABASE_URL, adapter: alloydb };
87
+ * ```
88
+ */
89
+ export declare const alloydb: DatabaseAdapter;
90
+ /**
91
+ * TimescaleDB adapter. Timescale is a PostgreSQL extension that adds
92
+ * hypertables, continuous aggregates, and time-series optimizations.
93
+ * Standard tables and hypertables both introspect via information_schema
94
+ * identically. Advisory locks, json_agg, and all other Turbine features
95
+ * work without modification.
96
+ *
97
+ * This export exists for documentation and explicit configuration:
98
+ *
99
+ * ```ts
100
+ * import { timescale } from 'turbine-orm/adapters';
101
+ * const config = { url: process.env.DATABASE_URL, adapter: timescale };
102
+ * ```
103
+ */
104
+ export declare const timescale: DatabaseAdapter;
105
+ export { cockroachdb } from './cockroachdb.js';
106
+ export { yugabytedb } from './yugabytedb.js';
107
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * turbine-orm — Database adapter interface
3
+ *
4
+ * Adapters allow Turbine to work with PostgreSQL-compatible databases that
5
+ * have subtle differences (e.g. CockroachDB, YugabyteDB). The default
6
+ * behavior remains standard PostgreSQL — adapters only override specific
7
+ * operations where compatibility gaps exist.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { cockroachdb } from 'turbine-orm/adapters';
12
+ *
13
+ * // Pass to TurbineCliConfig or migration functions
14
+ * const config = { url: process.env.DATABASE_URL, adapter: cockroachdb };
15
+ * ```
16
+ */
17
+ // ---------------------------------------------------------------------------
18
+ // Default PostgreSQL adapter (no-op — standard behavior)
19
+ // ---------------------------------------------------------------------------
20
+ /**
21
+ * The default PostgreSQL adapter. Uses pg_try_advisory_lock, standard
22
+ * pg_catalog queries, and SET LOCAL statement_timeout.
23
+ */
24
+ export const postgresql = {
25
+ name: 'postgresql',
26
+ async acquireLock(client, lockId) {
27
+ const result = await client.query(`SELECT pg_try_advisory_lock($1) AS locked`, [lockId]);
28
+ return result.rows[0]?.locked ?? false;
29
+ },
30
+ async releaseLock(client, lockId) {
31
+ await client.query(`SELECT pg_advisory_unlock($1)`, [lockId]);
32
+ },
33
+ statementTimeout(seconds) {
34
+ return `SET LOCAL statement_timeout = '${seconds}s'`;
35
+ },
36
+ };
37
+ // ---------------------------------------------------------------------------
38
+ // AlloyDB — fully PostgreSQL-compatible, no adapter logic needed
39
+ // ---------------------------------------------------------------------------
40
+ /**
41
+ * Google AlloyDB adapter. AlloyDB is PostgreSQL with Google's columnar storage
42
+ * engine. It is wire-protocol and catalog-compatible — no adapter overrides
43
+ * are needed. All Turbine features (json_agg, advisory locks, introspection,
44
+ * migrations) work identically to standard PostgreSQL.
45
+ *
46
+ * This export exists for documentation and explicit configuration:
47
+ *
48
+ * ```ts
49
+ * import { alloydb } from 'turbine-orm/adapters';
50
+ * const config = { url: process.env.DATABASE_URL, adapter: alloydb };
51
+ * ```
52
+ */
53
+ export const alloydb = {
54
+ ...postgresql,
55
+ name: 'alloydb',
56
+ };
57
+ // ---------------------------------------------------------------------------
58
+ // TimescaleDB — PostgreSQL extension, fully compatible
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * TimescaleDB adapter. Timescale is a PostgreSQL extension that adds
62
+ * hypertables, continuous aggregates, and time-series optimizations.
63
+ * Standard tables and hypertables both introspect via information_schema
64
+ * identically. Advisory locks, json_agg, and all other Turbine features
65
+ * work without modification.
66
+ *
67
+ * This export exists for documentation and explicit configuration:
68
+ *
69
+ * ```ts
70
+ * import { timescale } from 'turbine-orm/adapters';
71
+ * const config = { url: process.env.DATABASE_URL, adapter: timescale };
72
+ * ```
73
+ */
74
+ export const timescale = {
75
+ ...postgresql,
76
+ name: 'timescale',
77
+ };
78
+ // ---------------------------------------------------------------------------
79
+ // Re-exports
80
+ // ---------------------------------------------------------------------------
81
+ export { cockroachdb } from './cockroachdb.js';
82
+ export { yugabytedb } from './yugabytedb.js';
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * turbine-orm — YugabyteDB adapter
3
+ *
4
+ * YugabyteDB is a distributed SQL database that speaks the PostgreSQL wire
5
+ * protocol. It supports most PostgreSQL features including json_agg,
6
+ * subqueries, CTEs, and the standard information_schema.
7
+ *
8
+ * Key differences from PostgreSQL that this adapter addresses:
9
+ *
10
+ * 1. **Advisory locks are per-node** — `pg_try_advisory_lock()` is supported
11
+ * but only scoped to the tserver node handling the connection. In a
12
+ * multi-node cluster, two concurrent `turbine migrate` runs routed to
13
+ * different nodes would both acquire the "same" advisory lock. This adapter
14
+ * provides a table-based distributed lock using `SELECT FOR UPDATE NOWAIT`
15
+ * which is cluster-wide via YugabyteDB's distributed transactions.
16
+ *
17
+ * 2. **Sequences may have gaps** — YugabyteDB uses distributed sequences.
18
+ * SERIAL/BIGSERIAL columns work correctly but may produce non-contiguous
19
+ * IDs under concurrent inserts. This is purely cosmetic and does not affect
20
+ * Turbine's behavior.
21
+ *
22
+ * 3. **pg_catalog** — Mostly complete. `pg_indexes`, `pg_type`, `pg_enum`,
23
+ * `information_schema.columns` all work. Row estimate via `pg_class.reltuples`
24
+ * may be stale or zero on recently created tables (YugabyteDB's stats
25
+ * collection is asynchronous). This adapter provides an override that
26
+ * falls back to `yb_table_properties` when available.
27
+ *
28
+ * Features that work identically to PostgreSQL (no adapter override needed):
29
+ * - `json_agg` / `json_build_object` — fully supported
30
+ * - Correlated subqueries — fully supported
31
+ * - `COALESCE`, `LIMIT`, `OFFSET`, `ORDER BY` — fully supported
32
+ * - `information_schema` for table/column/constraint introspection
33
+ * - Extended query protocol (parameterized queries, pipeline batching)
34
+ * - Transactions with `SAVEPOINT` (nested transactions)
35
+ * - `FOR UPDATE` / `FOR SHARE` row-level locking
36
+ * - All WHERE operators (LIKE, ILIKE, IN, etc.)
37
+ * - Array and JSON column types
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { yugabytedb } from 'turbine-orm/adapters';
42
+ *
43
+ * // In turbine.config.ts:
44
+ * export default {
45
+ * url: process.env.DATABASE_URL,
46
+ * adapter: yugabytedb,
47
+ * };
48
+ * ```
49
+ */
50
+ import type { DatabaseAdapter } from './index.js';
51
+ export declare const yugabytedb: DatabaseAdapter;
52
+ //# sourceMappingURL=yugabytedb.d.ts.map