turbine-orm 0.4.0 → 0.7.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.
Files changed (57) hide show
  1. package/README.md +243 -26
  2. package/dist/cjs/cli/config.js +151 -0
  3. package/dist/cjs/cli/index.js +1176 -0
  4. package/dist/cjs/cli/migrate.js +446 -0
  5. package/dist/cjs/cli/ui.js +233 -0
  6. package/dist/cjs/client.js +512 -0
  7. package/dist/cjs/errors.js +293 -0
  8. package/dist/cjs/generate.js +321 -0
  9. package/dist/cjs/index.js +94 -0
  10. package/dist/cjs/introspect.js +287 -0
  11. package/dist/cjs/package.json +1 -0
  12. package/dist/cjs/pipeline.js +78 -0
  13. package/dist/cjs/query.js +1891 -0
  14. package/dist/cjs/schema-builder.js +238 -0
  15. package/dist/cjs/schema-sql.js +509 -0
  16. package/dist/cjs/schema.js +140 -0
  17. package/dist/cjs/serverless.js +110 -0
  18. package/dist/cli/config.js +6 -16
  19. package/dist/cli/index.js +256 -49
  20. package/dist/cli/migrate.d.ts +35 -6
  21. package/dist/cli/migrate.js +124 -76
  22. package/dist/cli/ui.js +5 -9
  23. package/dist/client.d.ts +87 -3
  24. package/dist/client.js +122 -46
  25. package/dist/errors.d.ts +138 -0
  26. package/dist/errors.js +278 -0
  27. package/dist/generate.js +37 -11
  28. package/dist/index.d.ts +10 -8
  29. package/dist/index.js +15 -11
  30. package/dist/introspect.js +3 -5
  31. package/dist/pipeline.js +8 -1
  32. package/dist/query.d.ts +310 -45
  33. package/dist/query.js +565 -237
  34. package/dist/schema-builder.js +91 -23
  35. package/dist/schema-sql.d.ts +6 -2
  36. package/dist/schema-sql.js +180 -26
  37. package/dist/schema.js +4 -1
  38. package/dist/serverless.d.ts +91 -139
  39. package/dist/serverless.js +86 -173
  40. package/package.json +44 -21
  41. package/dist/cli/config.d.ts.map +0 -1
  42. package/dist/cli/index.d.ts.map +0 -1
  43. package/dist/cli/migrate.d.ts.map +0 -1
  44. package/dist/cli/ui.d.ts.map +0 -1
  45. package/dist/client.d.ts.map +0 -1
  46. package/dist/generate.d.ts.map +0 -1
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/introspect.d.ts.map +0 -1
  49. package/dist/pipeline.d.ts.map +0 -1
  50. package/dist/query.d.ts.map +0 -1
  51. package/dist/schema-builder.d.ts.map +0 -1
  52. package/dist/schema-sql.d.ts.map +0 -1
  53. package/dist/schema.d.ts.map +0 -1
  54. package/dist/serverless.d.ts.map +0 -1
  55. package/dist/types.d.ts +0 -93
  56. package/dist/types.d.ts.map +0 -1
  57. package/dist/types.js +0 -126
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # turbine-orm
2
2
 
3
- The performance-first Postgres ORM. Prisma-compatible API, 2-3x faster nested queries, zero runtime overhead beyond `pg`.
3
+ Postgres-native TypeScript ORM runs on **Neon, Vercel Postgres, Cloudflare, Supabase**, and any pg-compatible driver. Streaming cursors, typed errors, single-query nested relations. 1 dependency, ~110KB.
4
4
 
5
5
  ```
6
6
  npm install turbine-orm
@@ -8,30 +8,35 @@ npm install turbine-orm
8
8
 
9
9
  ## Why Turbine?
10
10
 
11
- ORMs like Prisma fetch nested relations with N+1 queries -- one query per nesting level. Turbine uses Postgres `json_agg` to resolve the entire object graph in **a single SQL query**. The result is fewer round-trips, less connection overhead, and significantly lower latency.
11
+ Turbine is a PostgreSQL-native TypeScript ORM with features no other ORM offers together: **cursor-based streaming** through nested relations, **typed error classes** with PostgreSQL constraint mapping, **pipeline batching** (N queries, 1 round-trip), **middleware**, and a driver-agnostic core that plugs into any pg-compatible pool so it runs on Vercel Edge, Cloudflare Workers, Deno Deploy, and similar environments. It resolves nested relations in a single SQL query using `json_agg` an approach now shared by Prisma 7+ and Drizzle v2, but Turbine does it with 1 runtime dependency (`pg`) and ~110KB on npm.
12
12
 
13
- **One query instead of N+1.** When you write `db.users.findMany({ with: { posts: { with: { comments: true } } } })`, Turbine generates a single SQL statement that returns fully-nested JSON. Prisma sends 3 separate queries. Drizzle uses LATERAL joins which are competitive, but Turbine still wins on median latency.
13
+ **One query for nested relations.** When you write `db.users.findMany({ with: { posts: { with: { comments: true } } } })`, Turbine generates a single SQL statement using correlated subqueries with `json_agg`. Modern ORMs like Prisma 7+ and Drizzle v2 use similar single-query approaches (LATERAL JOINs). Turbine's advantage is architectural simplicity: 1 dependency, no code generation DSL, and PostgreSQL-native depth.
14
14
 
15
15
  ## Benchmarks
16
16
 
17
- Production results from Vercel Serverless hitting Neon Postgres (20 iterations, warm):
17
+ Tested against **Prisma 7.6** (adapter-pg, relationJoins) and **Drizzle 0.45** (relational queries) on the same PostgreSQL database. 200 iterations, 20 warmup, Node v22.
18
18
 
19
- | Scenario | Turbine | Drizzle | Prisma |
19
+ | Scenario | Turbine | Prisma 7 | Drizzle v2 |
20
20
  |---|---|---|---|
21
- | **Nested L3 (median)** | **5.3ms** | 6.5ms | 7.4ms |
22
- | Nested L3 (min) | **4.4ms** | 5.7ms | 6.0ms |
23
- | Nested L2 | **6.5ms** | 9.1ms | 10.2ms |
24
- | Simple select | 5.6ms | 7.1ms | 3.9ms |
25
-
26
- Local Docker results (50K iterations, HDR histograms):
27
-
28
- | Scenario | Turbine | Drizzle | Prisma |
21
+ | **findMany 100 rows (flat)** | **0.39 ms** | 0.58 ms | 0.44 ms |
22
+ | **findMany L2 nested (users + posts)** | **1.29 ms** | 1.84 ms | 1.30 ms |
23
+ | **findMany L3 nested (users → posts → comments)** | **0.50 ms** | 0.91 ms | 0.69 ms |
24
+ | **findUnique by PK** | **0.08 ms** | 0.13 ms | 0.14 ms |
25
+ | **findUnique — L3 nested** | **0.18 ms** | 0.32 ms | 0.34 ms |
26
+ | **count** | **0.06 ms** | 0.10 ms | 0.08 ms |
27
+
28
+ | Scenario | Turbine | Prisma 7 | Drizzle v2 |
29
29
  |---|---|---|---|
30
- | **L2 nested p50** | **201us** | 523us | 835us |
31
- | **L2 nested RPS (c=50)** | **24,041** | 6,360 | 3,784 |
32
- | L2 nested memory | 109MB | 117MB | 233MB |
30
+ | findMany flat | **1.00x** | 1.51x | 1.15x |
31
+ | findMany — L2 nested | **1.00x** | 1.43x | 1.01x |
32
+ | findMany — L3 nested | **1.00x** | 1.81x | 1.38x |
33
+ | findUnique by PK | **1.00x** | 1.67x | 1.69x |
34
+ | findUnique — L3 nested | **1.00x** | 1.81x | 1.93x |
35
+ | count | **1.00x** | 1.70x | 1.38x |
36
+
37
+ Turbine is fastest in every scenario. The advantage is largest on deep nesting (1.8x vs Prisma, up to 1.9x vs Drizzle) and single-record lookups (1.7x). All three ORMs now use single-query approaches for nested relations — Turbine's advantage comes from lower per-query overhead (minimal JS object allocation, no query plan compilation layer, direct pg driver access).
33
38
 
34
- Turbine is 2.6x faster than Drizzle and 4.2x faster than Prisma on nested queries at p50. Throughput is 3.8x higher than Drizzle and 6.3x higher than Prisma.
39
+ > Reproduce: `cd benchmarks && npm install && npx prisma generate && DATABASE_URL=... npx tsx bench.ts`
35
40
 
36
41
  ## Quick Start
37
42
 
@@ -46,6 +51,16 @@ npx turbine init --url postgres://user:pass@localhost:5432/mydb
46
51
  npx turbine generate
47
52
  ```
48
53
 
54
+ Works with both ESM and CommonJS:
55
+
56
+ ```typescript
57
+ // ESM
58
+ import { turbine } from './generated/turbine';
59
+
60
+ // CommonJS
61
+ const { turbine } = require('./generated/turbine');
62
+ ```
63
+
49
64
  This introspects your database and generates a fully-typed client at `./generated/turbine/`.
50
65
 
51
66
  ```typescript
@@ -167,6 +182,52 @@ const stats = await db.raw<{ day: Date; count: number }>`
167
182
  `;
168
183
  ```
169
184
 
185
+ ### Case-insensitive search
186
+
187
+ ```typescript
188
+ const users = await db.users.findMany({
189
+ where: {
190
+ email: { contains: 'alice', mode: 'insensitive' },
191
+ },
192
+ });
193
+ // Generates: WHERE email ILIKE '%alice%'
194
+ ```
195
+
196
+ ### Streaming large result sets
197
+
198
+ ```typescript
199
+ // Stream rows using PostgreSQL cursors — constant memory, no matter how many rows
200
+ for await (const user of db.users.findManyStream({
201
+ where: { orgId: 1 },
202
+ batchSize: 500, // internal FETCH batch size (default: 100)
203
+ orderBy: { id: 'asc' },
204
+ with: { posts: true }, // nested relations work too
205
+ })) {
206
+ process.stdout.write(`${user.email}\n`);
207
+ }
208
+ ```
209
+
210
+ Uses `DECLARE CURSOR` under the hood — rows are fetched in batches on a dedicated connection, parsed individually, and yielded via `AsyncGenerator`. Safe to `break` early; the cursor and connection are cleaned up automatically.
211
+
212
+ ### Query timeout
213
+
214
+ ```typescript
215
+ const users = await db.users.findMany({
216
+ where: { orgId: 1 },
217
+ timeout: 5000, // 5 second timeout
218
+ });
219
+ ```
220
+
221
+ ### Default limit
222
+
223
+ ```typescript
224
+ // Set a default limit for all queries on a model
225
+ const db = turbine({
226
+ connectionString: process.env.DATABASE_URL,
227
+ defaultLimit: 100,
228
+ });
229
+ ```
230
+
170
231
  ### Middleware
171
232
 
172
233
  ```typescript
@@ -187,6 +248,31 @@ db.$use(async (params, next) => {
187
248
  });
188
249
  ```
189
250
 
251
+ ### Error handling
252
+
253
+ Turbine throws typed errors you can catch programmatically:
254
+
255
+ ```typescript
256
+ import { NotFoundError, ValidationError, TimeoutError } from 'turbine-orm';
257
+
258
+ try {
259
+ const user = await db.users.findUniqueOrThrow({ where: { id: 999 } });
260
+ } catch (err) {
261
+ if (err instanceof NotFoundError) {
262
+ // err.code === 'TURBINE_E001'
263
+ console.log('User not found');
264
+ } else if (err instanceof TimeoutError) {
265
+ // err.code === 'TURBINE_E002'
266
+ console.log('Query timed out');
267
+ } else if (err instanceof ValidationError) {
268
+ // err.code === 'TURBINE_E003'
269
+ console.log('Invalid query:', err.message);
270
+ }
271
+ }
272
+ ```
273
+
274
+ Error codes: `TURBINE_E001` (NotFound), `TURBINE_E002` (Timeout), `TURBINE_E003` (Validation), `TURBINE_E004` (Connection), `TURBINE_E005` (Relation), `TURBINE_E006` (Migration), `TURBINE_E007` (CircularRelation).
275
+
190
276
  ## CLI
191
277
 
192
278
  ```
@@ -196,17 +282,19 @@ Commands:
196
282
  init Initialize a Turbine project (creates config, dirs, templates)
197
283
  generate | pull Introspect database and generate TypeScript types + client
198
284
  push Apply schema-builder definitions to database
199
- migrate create <name> Create a new SQL migration file
200
- migrate up Apply pending migrations
201
- migrate down Rollback last migration
202
- migrate status Show applied/pending migrations
203
- seed Run seed file
204
- status Show database schema summary
285
+ migrate create <name> Create a new SQL migration file
286
+ migrate create <name> --auto Auto-generate from schema diff
287
+ migrate up Apply pending migrations
288
+ migrate down Rollback last migration
289
+ migrate status Show applied/pending migrations
290
+ seed Run seed file
291
+ status Show database schema summary
205
292
 
206
293
  Options:
207
294
  --url, -u <url> Postgres connection string
208
295
  --out, -o <dir> Output directory (default: ./generated/turbine)
209
296
  --schema, -s <name> Postgres schema (default: public)
297
+ --auto Auto-generate migration from schema diff
210
298
  --dry-run Show SQL without executing
211
299
  --verbose, -v Detailed output
212
300
  ```
@@ -239,12 +327,97 @@ npx turbine generate # Regenerate typed client
239
327
  ### Migration workflow
240
328
 
241
329
  ```bash
330
+ # Create a blank migration (write SQL manually)
242
331
  npx turbine migrate create add_users_table
243
- # Edit the generated .sql file with UP and DOWN sections
332
+
333
+ # Auto-generate migration from schema diff (compares defineSchema() vs live DB)
334
+ npx turbine migrate create add_email_index --auto
335
+ # -> Generates UP (ALTER/CREATE) and DOWN (reverse) SQL automatically
336
+
337
+ # Apply all pending migrations
244
338
  npx turbine migrate up
339
+
340
+ # Rollback the last applied migration
341
+ npx turbine migrate down
342
+
343
+ # Check migration status (applied vs pending)
245
344
  npx turbine migrate status
246
345
  ```
247
346
 
347
+ ## Serverless / Edge
348
+
349
+ Turbine's core is driver-agnostic: pass any pg-compatible pool to `TurbineConfig.pool` (or use the `turbineHttp()` factory) and Turbine runs on **Vercel Edge**, **Cloudflare Workers**, **Deno Deploy**, **Netlify Edge**, or any other environment where a direct TCP connection is unavailable. No new dependencies — install whichever driver you already use.
350
+
351
+ ### Neon Serverless (HTTP / WebSocket)
352
+
353
+ ```ts
354
+ // app/api/users/route.ts
355
+ import { Pool } from '@neondatabase/serverless';
356
+ import { turbineHttp } from 'turbine-orm/serverless';
357
+ import { schema } from '@/generated/turbine/metadata';
358
+
359
+ export const runtime = 'edge';
360
+
361
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
362
+ const db = turbineHttp(pool, schema);
363
+
364
+ export async function GET() {
365
+ const users = await db.table('users').findMany({
366
+ with: { posts: { with: { comments: true } } },
367
+ limit: 10,
368
+ });
369
+ return Response.json(users);
370
+ }
371
+ ```
372
+
373
+ ### Vercel Postgres
374
+
375
+ ```ts
376
+ import { createPool } from '@vercel/postgres';
377
+ import { turbineHttp } from 'turbine-orm/serverless';
378
+ import { schema } from './generated/turbine/metadata.js';
379
+
380
+ const pool = createPool({ connectionString: process.env.POSTGRES_URL });
381
+ const db = turbineHttp(pool, schema);
382
+ ```
383
+
384
+ ### Supabase (direct Postgres — no HTTP proxy needed)
385
+
386
+ ```ts
387
+ import { TurbineClient } from 'turbine-orm';
388
+ import { schema } from './generated/turbine/metadata.js';
389
+
390
+ const db = new TurbineClient({
391
+ connectionString: process.env.SUPABASE_DB_URL,
392
+ ssl: { rejectUnauthorized: false },
393
+ }, schema);
394
+ ```
395
+
396
+ ### Cloudflare Workers
397
+
398
+ ```ts
399
+ import { Pool } from '@neondatabase/serverless';
400
+ import { turbineHttp } from 'turbine-orm/serverless';
401
+ import { schema } from './generated/turbine/metadata';
402
+
403
+ export default {
404
+ async fetch(req: Request, env: Env) {
405
+ const pool = new Pool({ connectionString: env.DATABASE_URL });
406
+ const db = turbineHttp(pool, schema);
407
+ const users = await db.table('users').findMany({ limit: 10 });
408
+ return Response.json(users);
409
+ },
410
+ };
411
+ ```
412
+
413
+ ### Limitations on HTTP drivers
414
+
415
+ - **Streaming cursors** (`findManyStream`) require `DECLARE CURSOR`, which most HTTP drivers don't support. Use `findMany` with `limit` + pagination instead.
416
+ - **LISTEN/NOTIFY** is not available over HTTP.
417
+ - Transactions work but hold an HTTP connection for their duration — keep them short.
418
+
419
+ When Turbine receives an external pool, `db.disconnect()` is a no-op: the caller owns the pool's lifecycle.
420
+
248
421
  ## Configuration
249
422
 
250
423
  Create `turbine.config.ts` in your project root (or run `npx turbine init`):
@@ -283,12 +456,56 @@ SELECT u.*,
283
456
  FROM users u WHERE u.org_id = 1
284
457
  ```
285
458
 
286
- This resolves the entire 3-level object graph in one database round-trip. Prisma would send 3 queries. The performance difference scales with nesting depth and network latency.
459
+ This resolves the entire 3-level object graph in one database round-trip. Prisma 7+ and Drizzle v2 also use single-query approaches (LATERAL JOINs), but Turbine's correlated subquery strategy has lower per-query overhead — see [Benchmarks](#benchmarks).
460
+
461
+ ## Type Mapping
462
+
463
+ Turbine maps Postgres types to TypeScript:
464
+
465
+ | Postgres | TypeScript | Notes |
466
+ |---|---|---|
467
+ | `int2`, `int4`, `float4`, `float8` | `number` | Standard numeric types |
468
+ | `int8` / `bigint` | `number` | Values > `Number.MAX_SAFE_INTEGER` (2^53 - 1) are returned as `string` at runtime to avoid precision loss. This affects < 0.01% of use cases (auto-increment IDs, counts, etc. are all safe). |
469
+ | `numeric`, `money` | `string` | Arbitrary precision — kept as string to avoid JS float issues |
470
+ | `text`, `varchar`, `uuid`, `citext` | `string` | |
471
+ | `timestamptz`, `timestamp`, `date` | `Date` | |
472
+ | `boolean` | `boolean` | |
473
+ | `json`, `jsonb` | `unknown` | |
474
+ | `bytea` | `Buffer` | |
475
+ | Array types | `T[]` | e.g. `_text` → `string[]` |
476
+
477
+ ## Comparison
478
+
479
+ | | **Turbine** | **Prisma** | **Drizzle** | **Kysely** |
480
+ |---|---|---|---|---|
481
+ | **Nested relations** | 1 query (`json_agg`) | 1 query (LATERAL JOIN + json_agg, since v5.8) | 1 query (LATERAL JOINs) | Manual (`jsonArrayFrom`) |
482
+ | **API style** | `findMany`, `with` | `findMany`, `include` | SQL-like + relational | SQL builder |
483
+ | **Schema** | TypeScript | Custom DSL (`.prisma`) | TypeScript | Manual interfaces |
484
+ | **Runtime deps** | 1 (`pg`) | `@prisma/client` + adapter | 0 | 0 |
485
+ | **Multi-DB** | PostgreSQL only | PG, MySQL, SQLite, MSSQL | PG, MySQL, SQLite | PG, MySQL, SQLite |
486
+ | **Code generation** | `turbine generate` | `prisma generate` | Not needed | Not needed |
487
+
488
+ All three ORMs now use single-query approaches for nested relations. Turbine uses correlated subqueries with `json_agg`, Prisma 7 uses LATERAL JOIN + `json_agg`, and Drizzle uses LATERAL JOINs. Turbine is 1.4–1.9x faster due to lower per-query overhead — minimal JS object allocation, no query plan compilation layer, and direct pg driver access. See [Benchmarks](#benchmarks) for full results.
489
+
490
+ ## Limitations
491
+
492
+ Turbine is focused and opinionated. Here's what it doesn't do:
493
+
494
+ - **PostgreSQL only.** No MySQL, SQLite, or MSSQL. This is by design — the `json_agg` approach is PostgreSQL-specific, and going deep on one database enables the performance advantage.
495
+ - **No incremental updates.** Prisma's `{ count: { increment: 1 } }` syntax is not yet supported. Use raw SQL for atomic increments.
496
+ - **No full-text search operators.** TSVECTOR/TSQUERY are not exposed in the query builder. Use `db.raw` for full-text queries.
497
+ - **Large nested result sets.** `json_agg` builds the entire JSON array in PostgreSQL memory. For relations with 10K+ rows, always use `limit` in your `with` clause to cap the aggregation size.
498
+ - **No admin UI.** Turbine Studio is planned but not yet available.
499
+
500
+ ## Examples
501
+
502
+ - **[Next.js](./examples/nextjs/)** — Server-rendered app with nested relations, streaming, and live code demos
287
503
 
288
504
  ## Requirements
289
505
 
290
- - Node.js >= 22.0.0
506
+ - Node.js >= 18.0.0
291
507
  - PostgreSQL >= 14
508
+ - Works with both ESM (`import`) and CommonJS (`require`)
292
509
 
293
510
  ## License
294
511
 
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /**
3
+ * turbine-orm CLI — Configuration file support
4
+ *
5
+ * Loads turbine.config.ts (or .js/.mjs) via dynamic import.
6
+ * Falls back to CLI args and environment variables.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.loadConfig = loadConfig;
43
+ exports.findConfigFile = findConfigFile;
44
+ exports.resolveConfig = resolveConfig;
45
+ exports.configTemplate = configTemplate;
46
+ const node_fs_1 = require("node:fs");
47
+ const node_path_1 = require("node:path");
48
+ const node_url_1 = require("node:url");
49
+ // ---------------------------------------------------------------------------
50
+ // Config file names, in priority order
51
+ // ---------------------------------------------------------------------------
52
+ const CONFIG_FILES = ['turbine.config.ts', 'turbine.config.mts', 'turbine.config.js', 'turbine.config.mjs'];
53
+ // ---------------------------------------------------------------------------
54
+ // Load config
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Attempt to load a turbine config file from the current directory.
58
+ * Returns the config if found, or an empty object.
59
+ */
60
+ async function loadConfig(cwd) {
61
+ const dir = cwd ?? process.cwd();
62
+ for (const filename of CONFIG_FILES) {
63
+ const filePath = (0, node_path_1.join)(dir, filename);
64
+ if (!(0, node_fs_1.existsSync)(filePath))
65
+ continue;
66
+ try {
67
+ const absPath = (0, node_path_1.resolve)(filePath);
68
+ const fileUrl = (0, node_url_1.pathToFileURL)(absPath).href;
69
+ // For .ts files, we need to rely on Node's --experimental-strip-types
70
+ // or the tsx loader. Dynamic import handles .js/.mjs natively.
71
+ const mod = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
72
+ const config = mod.default ?? mod;
73
+ return config;
74
+ }
75
+ catch (err) {
76
+ // If importing a .ts file fails, try the next one
77
+ if (filename.endsWith('.ts') || filename.endsWith('.mts')) {
78
+ continue;
79
+ }
80
+ throw new Error(`Failed to load config from ${filename}: ${err instanceof Error ? err.message : String(err)}`);
81
+ }
82
+ }
83
+ return {};
84
+ }
85
+ /**
86
+ * Find the config file path (for display purposes).
87
+ * Returns null if no config file is found.
88
+ */
89
+ function findConfigFile(cwd) {
90
+ const dir = cwd ?? process.cwd();
91
+ for (const filename of CONFIG_FILES) {
92
+ const filePath = (0, node_path_1.join)(dir, filename);
93
+ if ((0, node_fs_1.existsSync)(filePath))
94
+ return filePath;
95
+ }
96
+ return null;
97
+ }
98
+ /**
99
+ * Merge config file values with CLI overrides and env vars.
100
+ * Priority: CLI flags > env vars > config file > defaults.
101
+ */
102
+ function resolveConfig(fileConfig, overrides) {
103
+ return {
104
+ url: overrides.url ?? process.env.DATABASE_URL ?? fileConfig.url ?? '',
105
+ out: overrides.out ?? fileConfig.out ?? './generated/turbine',
106
+ schema: overrides.schema ?? fileConfig.schema ?? 'public',
107
+ include: overrides.include ?? fileConfig.include ?? [],
108
+ exclude: overrides.exclude ?? fileConfig.exclude ?? [],
109
+ migrationsDir: fileConfig.migrationsDir ?? './turbine/migrations',
110
+ seedFile: fileConfig.seedFile ?? './turbine/seed.ts',
111
+ schemaFile: fileConfig.schemaFile ?? './turbine/schema.ts',
112
+ };
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Config file template (for `turbine init`)
116
+ // ---------------------------------------------------------------------------
117
+ function configTemplate(connectionString) {
118
+ const _url = connectionString ?? 'process.env.DATABASE_URL';
119
+ const urlLine = connectionString ? ` url: '${connectionString}',` : ` url: process.env.DATABASE_URL,`;
120
+ return `import type { TurbineCliConfig } from 'turbine-orm/cli';
121
+
122
+ /**
123
+ * Turbine configuration
124
+ * @see https://turbineorm.dev
125
+ */
126
+ const config: TurbineCliConfig = {
127
+ /** Postgres connection string */
128
+ ${urlLine}
129
+
130
+ /** Output directory for generated types + client */
131
+ out: './generated/turbine',
132
+
133
+ /** Postgres schema to introspect (default: public) */
134
+ schema: 'public',
135
+
136
+ /** Tables to exclude from generation */
137
+ // exclude: ['_migrations', '_sessions'],
138
+
139
+ /** Directory for SQL migration files */
140
+ migrationsDir: './turbine/migrations',
141
+
142
+ /** Path to seed file */
143
+ seedFile: './turbine/seed.ts',
144
+
145
+ /** Path to schema builder file (for turbine push) */
146
+ schemaFile: './turbine/schema.ts',
147
+ };
148
+
149
+ export default config;
150
+ `;
151
+ }