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
@@ -0,0 +1,1176 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * turbine-orm CLI
5
+ *
6
+ * Commands:
7
+ * turbine init — Initialize a Turbine project
8
+ * turbine generate | pull — Introspect database and generate TypeScript types
9
+ * turbine push — Apply schema-builder definitions to database
10
+ * turbine migrate create <name> — Create a new SQL migration file
11
+ * turbine migrate up — Apply pending migrations
12
+ * turbine migrate down — Rollback last migration
13
+ * turbine migrate status — Show migration status
14
+ * turbine seed — Run seed file
15
+ * turbine status — Show schema summary
16
+ * turbine studio — Launch web UI (coming soon)
17
+ *
18
+ * Usage:
19
+ * DATABASE_URL=postgres://... npx turbine generate
20
+ * npx turbine init --url postgres://...
21
+ * npx turbine migrate create add_users_table
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ const node_fs_1 = require("node:fs");
58
+ const node_path_1 = require("node:path");
59
+ const node_url_1 = require("node:url");
60
+ const generate_js_1 = require("../generate.js");
61
+ const introspect_js_1 = require("../introspect.js");
62
+ const schema_sql_js_1 = require("../schema-sql.js");
63
+ const config_js_1 = require("./config.js");
64
+ const migrate_js_1 = require("./migrate.js");
65
+ const ui_js_1 = require("./ui.js");
66
+ function parseArgs() {
67
+ const args = process.argv.slice(2);
68
+ const result = {
69
+ command: args[0] ?? 'help',
70
+ positional: [],
71
+ };
72
+ let i = 1;
73
+ // Check for subcommand (e.g. "migrate create")
74
+ if (i < args.length && args[i] && !args[i].startsWith('-')) {
75
+ result.subcommand = args[i];
76
+ i++;
77
+ }
78
+ for (; i < args.length; i++) {
79
+ const arg = args[i];
80
+ const next = args[i + 1];
81
+ switch (arg) {
82
+ case '--url':
83
+ case '-u':
84
+ result.url = next;
85
+ i++;
86
+ break;
87
+ case '--out':
88
+ case '-o':
89
+ result.out = next;
90
+ i++;
91
+ break;
92
+ case '--schema':
93
+ case '-s':
94
+ result.schema = next;
95
+ i++;
96
+ break;
97
+ case '--include':
98
+ result.include = next?.split(',');
99
+ i++;
100
+ break;
101
+ case '--exclude':
102
+ result.exclude = next?.split(',');
103
+ i++;
104
+ break;
105
+ case '--step':
106
+ case '-n':
107
+ result.step = next ? parseInt(next, 10) : undefined;
108
+ i++;
109
+ break;
110
+ case '--dry-run':
111
+ result.dryRun = true;
112
+ break;
113
+ case '--auto':
114
+ result.auto = true;
115
+ break;
116
+ case '--force':
117
+ case '-f':
118
+ result.force = true;
119
+ break;
120
+ case '--verbose':
121
+ case '-v':
122
+ result.verbose = true;
123
+ break;
124
+ case '--help':
125
+ case '-h':
126
+ result.help = true;
127
+ break;
128
+ default:
129
+ if (!arg.startsWith('-')) {
130
+ result.positional.push(arg);
131
+ }
132
+ break;
133
+ }
134
+ }
135
+ return result;
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Helpers
139
+ // ---------------------------------------------------------------------------
140
+ function requireUrl(config) {
141
+ if (!config.url) {
142
+ (0, ui_js_1.error)('No database URL provided.');
143
+ (0, ui_js_1.newline)();
144
+ console.log(` ${(0, ui_js_1.dim)('Set it in one of these ways:')}`);
145
+ console.log(` ${(0, ui_js_1.dim)('1.')} Add ${(0, ui_js_1.cyan)('url')} to ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
146
+ console.log(` ${(0, ui_js_1.dim)('2.')} Set ${(0, ui_js_1.cyan)('DATABASE_URL')} environment variable`);
147
+ console.log(` ${(0, ui_js_1.dim)('3.')} Pass ${(0, ui_js_1.cyan)('--url')} flag`);
148
+ (0, ui_js_1.newline)();
149
+ process.exit(1);
150
+ }
151
+ return config.url;
152
+ }
153
+ async function loadSchemaFile(schemaFile) {
154
+ const absPath = (0, node_path_1.resolve)(schemaFile);
155
+ if (!(0, node_fs_1.existsSync)(absPath)) {
156
+ (0, ui_js_1.error)(`Schema file not found: ${schemaFile}`);
157
+ console.log(` ${(0, ui_js_1.dim)('Create one with:')} ${(0, ui_js_1.cyan)('turbine init')}`);
158
+ process.exit(1);
159
+ }
160
+ try {
161
+ const fileUrl = (0, node_url_1.pathToFileURL)(absPath).href;
162
+ const mod = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
163
+ const schema = mod.default ?? mod;
164
+ if (!schema.tables) {
165
+ (0, ui_js_1.error)('Schema file must export a SchemaDef with a "tables" property.');
166
+ process.exit(1);
167
+ }
168
+ return schema;
169
+ }
170
+ catch (err) {
171
+ (0, ui_js_1.error)(`Failed to load schema file: ${schemaFile}`);
172
+ if (err instanceof Error) {
173
+ console.log(` ${(0, ui_js_1.dim)(err.message)}`);
174
+ }
175
+ process.exit(1);
176
+ }
177
+ }
178
+ // ---------------------------------------------------------------------------
179
+ // Command: init
180
+ // ---------------------------------------------------------------------------
181
+ async function cmdInit(args, config) {
182
+ (0, ui_js_1.banner)();
183
+ (0, ui_js_1.header)('Initializing Turbine project');
184
+ // Detect environment
185
+ const envUrl = process.env.DATABASE_URL;
186
+ const hasEnvFile = (0, node_fs_1.existsSync)('.env');
187
+ const hasEnvLocal = (0, node_fs_1.existsSync)('.env.local');
188
+ if (envUrl) {
189
+ (0, ui_js_1.success)(`Detected ${(0, ui_js_1.cyan)('DATABASE_URL')} in environment`);
190
+ }
191
+ else if (hasEnvLocal) {
192
+ (0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env.local')} — Turbine will use ${(0, ui_js_1.cyan)('DATABASE_URL')} from it if set`);
193
+ }
194
+ else if (hasEnvFile) {
195
+ (0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env')} — Turbine will use ${(0, ui_js_1.cyan)('DATABASE_URL')} from it if set`);
196
+ }
197
+ else {
198
+ (0, ui_js_1.info)(`No ${(0, ui_js_1.cyan)('DATABASE_URL')} found in environment`);
199
+ }
200
+ (0, ui_js_1.newline)();
201
+ const configPath = (0, config_js_1.findConfigFile)();
202
+ // Create config file
203
+ if (configPath && !args.force) {
204
+ (0, ui_js_1.warn)(`Config file already exists: ${(0, ui_js_1.dim)(configPath)}`);
205
+ console.log(` ${(0, ui_js_1.dim)('Run with')} ${(0, ui_js_1.cyan)('--force')} ${(0, ui_js_1.dim)('to overwrite')}`);
206
+ }
207
+ else {
208
+ const urlForConfig = args.url ?? undefined;
209
+ const configContent = (0, config_js_1.configTemplate)(urlForConfig);
210
+ (0, node_fs_1.writeFileSync)('turbine.config.ts', configContent, 'utf-8');
211
+ if (configPath) {
212
+ (0, ui_js_1.success)(`Overwrote ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
213
+ }
214
+ else {
215
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
216
+ }
217
+ }
218
+ // Create migrations directory
219
+ const migrDir = config.migrationsDir;
220
+ if (!(0, node_fs_1.existsSync)(migrDir)) {
221
+ (0, node_fs_1.mkdirSync)(migrDir, { recursive: true });
222
+ // Create .gitkeep
223
+ (0, node_fs_1.writeFileSync)(`${migrDir}/.gitkeep`, '', 'utf-8');
224
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${migrDir}/`)}`);
225
+ }
226
+ else {
227
+ (0, ui_js_1.info)(`Migrations dir already exists: ${(0, ui_js_1.dim)(migrDir)}`);
228
+ }
229
+ // Create output directory
230
+ if (!(0, node_fs_1.existsSync)(config.out)) {
231
+ (0, node_fs_1.mkdirSync)(config.out, { recursive: true });
232
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
233
+ }
234
+ // Create seed file template
235
+ const seedDir = config.seedFile.substring(0, config.seedFile.lastIndexOf('/'));
236
+ if (!(0, node_fs_1.existsSync)(config.seedFile)) {
237
+ if (!(0, node_fs_1.existsSync)(seedDir)) {
238
+ (0, node_fs_1.mkdirSync)(seedDir, { recursive: true });
239
+ }
240
+ (0, node_fs_1.writeFileSync)(config.seedFile, `/**
241
+ * Turbine seed file
242
+ *
243
+ * Run with: npx turbine seed
244
+ */
245
+
246
+ // import { turbine } from '${config.out.replace('./', '')}';
247
+ //
248
+ // const db = turbine({ connectionString: process.env.DATABASE_URL });
249
+ //
250
+ // async function seed() {
251
+ // console.log('Seeding database...');
252
+ //
253
+ // // Add your seed data here:
254
+ // // await db.users.create({ data: { email: 'admin@example.com', name: 'Admin' } });
255
+ //
256
+ // console.log('Done!');
257
+ // await db.disconnect();
258
+ // }
259
+ //
260
+ // seed();
261
+ `, 'utf-8');
262
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(config.seedFile)}`);
263
+ }
264
+ // Create schema builder template
265
+ if (!(0, node_fs_1.existsSync)(config.schemaFile)) {
266
+ const schemaDir = config.schemaFile.substring(0, config.schemaFile.lastIndexOf('/'));
267
+ if (!(0, node_fs_1.existsSync)(schemaDir)) {
268
+ (0, node_fs_1.mkdirSync)(schemaDir, { recursive: true });
269
+ }
270
+ (0, node_fs_1.writeFileSync)(config.schemaFile, `/**
271
+ * Turbine schema definition
272
+ *
273
+ * Define your database schema in TypeScript.
274
+ * Use \`npx turbine push\` to sync it to your database.
275
+ *
276
+ * @see https://turbineorm.dev
277
+ */
278
+
279
+ import { defineSchema } from 'turbine-orm';
280
+
281
+ export default defineSchema({
282
+ // Example:
283
+ // users: {
284
+ // id: { type: 'serial', primaryKey: true },
285
+ // email: { type: 'text', notNull: true, unique: true },
286
+ // name: { type: 'text', notNull: true },
287
+ // created_at: { type: 'timestamptz', default: 'NOW()' },
288
+ // },
289
+ });
290
+ `, 'utf-8');
291
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(config.schemaFile)}`);
292
+ }
293
+ // Add .gitignore entries for generated output and config (may contain connection strings)
294
+ const gitignorePath = '.gitignore';
295
+ if ((0, node_fs_1.existsSync)(gitignorePath)) {
296
+ const gitignoreContent = (0, node_fs_1.readFileSync)(gitignorePath, 'utf-8');
297
+ const additions = [];
298
+ if (!gitignoreContent.includes('generated/turbine')) {
299
+ additions.push('generated/turbine/');
300
+ }
301
+ if (!gitignoreContent.includes('turbine.config.ts')) {
302
+ additions.push('turbine.config.ts');
303
+ }
304
+ if (additions.length > 0) {
305
+ (0, node_fs_1.appendFileSync)(gitignorePath, `\n# Turbine generated client & config\n${additions.join('\n')}\n`);
306
+ (0, ui_js_1.success)(`Added ${(0, ui_js_1.cyan)(additions.join(', '))} to ${(0, ui_js_1.cyan)('.gitignore')}`);
307
+ }
308
+ }
309
+ // If we have a URL, run initial generate
310
+ const url = args.url ?? envUrl ?? config.url;
311
+ if (url) {
312
+ (0, ui_js_1.newline)();
313
+ (0, ui_js_1.divider)();
314
+ (0, ui_js_1.newline)();
315
+ const spinner = new ui_js_1.Spinner('Introspecting database').start();
316
+ try {
317
+ const schema = await (0, introspect_js_1.introspect)({
318
+ connectionString: url,
319
+ schema: config.schema,
320
+ include: config.include.length ? config.include : undefined,
321
+ exclude: config.exclude.length ? config.exclude : undefined,
322
+ });
323
+ const tableCount = Object.keys(schema.tables).length;
324
+ spinner.succeed(`Found ${(0, ui_js_1.bold)(String(tableCount))} tables`);
325
+ const genSpinner = new ui_js_1.Spinner('Generating TypeScript client').start();
326
+ const result = (0, generate_js_1.generate)({ schema, outDir: config.out, connectionString: url });
327
+ genSpinner.succeed(`Generated ${(0, ui_js_1.bold)(String(result.files.length))} files to ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
328
+ }
329
+ catch (err) {
330
+ spinner.fail('Could not connect to database');
331
+ if (err instanceof Error) {
332
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
333
+ }
334
+ (0, ui_js_1.newline)();
335
+ (0, ui_js_1.info)(`You can run generation later with: ${(0, ui_js_1.cyan)('npx turbine generate')}`);
336
+ }
337
+ }
338
+ // Next steps
339
+ (0, ui_js_1.newline)();
340
+ (0, ui_js_1.divider)();
341
+ (0, ui_js_1.newline)();
342
+ console.log(` ${(0, ui_js_1.bold)('Next steps:')}`);
343
+ (0, ui_js_1.newline)();
344
+ if (!url) {
345
+ console.log(` ${(0, ui_js_1.dim)('1.')} Set your database URL in ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
346
+ if (!hasEnvFile && !hasEnvLocal) {
347
+ console.log(` ${(0, ui_js_1.dim)('or create a')} ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('file with')} ${(0, ui_js_1.cyan)('DATABASE_URL=postgres://...')}`);
348
+ }
349
+ console.log(` ${(0, ui_js_1.dim)('2.')} Run ${(0, ui_js_1.cyan)('npx turbine generate')} to introspect your DB`);
350
+ }
351
+ else {
352
+ console.log(` ${(0, ui_js_1.dim)('1.')} Import the generated client:`);
353
+ console.log(` ${(0, ui_js_1.cyan)(`import { turbine } from './${config.out.replace('./', '')}';`)}`);
354
+ (0, ui_js_1.newline)();
355
+ console.log(` ${(0, ui_js_1.dim)('2.')} Create a connection and query:`);
356
+ console.log(` ${(0, ui_js_1.dim)('const db = turbine();')}`);
357
+ console.log(` ${(0, ui_js_1.dim)('const users = await db.users.findMany();')}`);
358
+ }
359
+ (0, ui_js_1.newline)();
360
+ console.log(` ${(0, ui_js_1.dim)('3.')} Create migrations: ${(0, ui_js_1.cyan)('npx turbine migrate create <name>')}`);
361
+ console.log(` ${(0, ui_js_1.dim)('4.')} Run migrations: ${(0, ui_js_1.cyan)('npx turbine migrate up')}`);
362
+ console.log(` ${(0, ui_js_1.dim)('5.')} Seed your database: ${(0, ui_js_1.cyan)('npx turbine seed')}`);
363
+ (0, ui_js_1.newline)();
364
+ }
365
+ // ---------------------------------------------------------------------------
366
+ // Command: generate (pull)
367
+ // ---------------------------------------------------------------------------
368
+ async function cmdGenerate(args, config) {
369
+ (0, ui_js_1.banner)();
370
+ const url = requireUrl(config);
371
+ const startTime = performance.now();
372
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
373
+ (0, ui_js_1.label)('Schema', config.schema);
374
+ (0, ui_js_1.label)('Output', config.out);
375
+ (0, ui_js_1.newline)();
376
+ // Introspect
377
+ const spinner = new ui_js_1.Spinner('Introspecting database schema').start();
378
+ const schema = await (0, introspect_js_1.introspect)({
379
+ connectionString: url,
380
+ schema: config.schema,
381
+ include: config.include.length ? config.include : undefined,
382
+ exclude: config.exclude.length ? config.exclude : undefined,
383
+ });
384
+ const tableNames = Object.keys(schema.tables);
385
+ const totalColumns = Object.values(schema.tables).reduce((sum, t) => sum + t.columns.length, 0);
386
+ const totalRelations = Object.values(schema.tables).reduce((sum, t) => sum + Object.keys(t.relations).length, 0);
387
+ spinner.succeed(`Found ${(0, ui_js_1.bold)(String(tableNames.length))} tables, ${(0, ui_js_1.bold)(String(totalColumns))} columns, ${(0, ui_js_1.bold)(String(totalRelations))} relations`);
388
+ // Print table summary
389
+ if (args.verbose) {
390
+ (0, ui_js_1.newline)();
391
+ for (const tbl of Object.values(schema.tables)) {
392
+ const relCount = Object.keys(tbl.relations).length;
393
+ const pk = tbl.primaryKey.join(', ') || '(none)';
394
+ console.log(` ${ui_js_1.symbols.tee} ${(0, ui_js_1.bold)(tbl.name)} ${(0, ui_js_1.dim)(`${tbl.columns.length} cols, PK: ${pk}`)}${relCount > 0 ? (0, ui_js_1.dim)(`, ${relCount} rels`) : ''}`);
395
+ }
396
+ (0, ui_js_1.newline)();
397
+ }
398
+ if (Object.keys(schema.enums).length > 0) {
399
+ (0, ui_js_1.info)(`Enums: ${Object.keys(schema.enums).join(', ')}`);
400
+ }
401
+ // Generate
402
+ const genSpinner = new ui_js_1.Spinner('Generating TypeScript client').start();
403
+ const result = (0, generate_js_1.generate)({
404
+ schema,
405
+ outDir: config.out,
406
+ connectionString: url,
407
+ });
408
+ genSpinner.succeed(`Generated ${(0, ui_js_1.bold)(String(result.files.length))} files in ${(0, ui_js_1.elapsed)(startTime)}`);
409
+ // List files
410
+ for (const file of result.files) {
411
+ console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.teeEnd)} ${(0, ui_js_1.cyan)(`${result.outDir}/${file}`)}`);
412
+ }
413
+ // Usage hint
414
+ (0, ui_js_1.newline)();
415
+ (0, ui_js_1.divider)();
416
+ (0, ui_js_1.newline)();
417
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
418
+ (0, ui_js_1.newline)();
419
+ console.log(` ${(0, ui_js_1.cyan)(`import { turbine } from './${config.out.replace('./', '')}';`)}`);
420
+ console.log(` ${(0, ui_js_1.dim)('const db = turbine({ connectionString: process.env.DATABASE_URL });')}`);
421
+ console.log(` ${(0, ui_js_1.dim)('const user = await db.users.findUnique({ where: { id: 1 } });')}`);
422
+ (0, ui_js_1.newline)();
423
+ }
424
+ // ---------------------------------------------------------------------------
425
+ // Command: push
426
+ // ---------------------------------------------------------------------------
427
+ async function cmdPush(args, config) {
428
+ (0, ui_js_1.banner)();
429
+ const url = requireUrl(config);
430
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
431
+ (0, ui_js_1.label)('Schema file', config.schemaFile);
432
+ (0, ui_js_1.newline)();
433
+ const schemaDef = await loadSchemaFile(config.schemaFile);
434
+ const tableCount = Object.keys(schemaDef.tables).length;
435
+ (0, ui_js_1.info)(`Schema defines ${(0, ui_js_1.bold)(String(tableCount))} tables`);
436
+ // Compute diff
437
+ const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
438
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
439
+ if (diff.statements.length === 0 && diff.drop.length === 0) {
440
+ diffSpinner.succeed('Database is already in sync');
441
+ (0, ui_js_1.newline)();
442
+ return;
443
+ }
444
+ diffSpinner.succeed('Found changes');
445
+ (0, ui_js_1.newline)();
446
+ // Show what will happen
447
+ if (diff.create.length > 0) {
448
+ console.log(` ${(0, ui_js_1.green)('+ Create')} ${(0, ui_js_1.bold)(String(diff.create.length))} table(s):`);
449
+ for (const t of diff.create) {
450
+ console.log(` ${(0, ui_js_1.green)(ui_js_1.symbols.arrowRight)} ${t.name}`);
451
+ }
452
+ (0, ui_js_1.newline)();
453
+ }
454
+ if (diff.alter.length > 0) {
455
+ console.log(` ${(0, ui_js_1.yellow)('~ Alter')} ${(0, ui_js_1.bold)(String(diff.alter.length))} table(s):`);
456
+ for (const a of diff.alter) {
457
+ console.log(` ${(0, ui_js_1.yellow)(ui_js_1.symbols.arrowRight)} ${a.table}`);
458
+ for (const col of a.columns) {
459
+ const actionLabel = col.action === 'add'
460
+ ? (0, ui_js_1.green)('+ add')
461
+ : col.action === 'drop'
462
+ ? (0, ui_js_1.red)('- drop')
463
+ : (0, ui_js_1.yellow)(`~ ${col.action.replace('_', ' ')}`);
464
+ console.log(` ${actionLabel} ${col.column}`);
465
+ }
466
+ }
467
+ (0, ui_js_1.newline)();
468
+ }
469
+ if (diff.drop.length > 0) {
470
+ console.log(` ${(0, ui_js_1.red)('- Extra tables')} in database (not in schema):`);
471
+ for (const t of diff.drop) {
472
+ console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.arrowRight)} ${t} ${(0, ui_js_1.dim)('(not dropped automatically)')}`);
473
+ }
474
+ (0, ui_js_1.newline)();
475
+ }
476
+ // Show SQL
477
+ if (diff.statements.length > 0) {
478
+ console.log(` ${(0, ui_js_1.bold)('SQL to execute:')}`);
479
+ (0, ui_js_1.newline)();
480
+ for (const stmt of diff.statements) {
481
+ for (const line of stmt.split('\n')) {
482
+ console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.vertLine)} ${(0, ui_js_1.cyan)(line)}`);
483
+ }
484
+ console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.vertLine)}`);
485
+ }
486
+ (0, ui_js_1.newline)();
487
+ }
488
+ if (args.dryRun) {
489
+ (0, ui_js_1.info)('Dry run — no changes applied.');
490
+ (0, ui_js_1.newline)();
491
+ return;
492
+ }
493
+ // Execute
494
+ const pushSpinner = new ui_js_1.Spinner('Applying changes').start();
495
+ const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url);
496
+ pushSpinner.succeed(`Applied ${(0, ui_js_1.bold)(String(result.statementsExecuted))} statement(s)`);
497
+ if (result.tablesCreated.length > 0) {
498
+ (0, ui_js_1.success)(`Created: ${result.tablesCreated.join(', ')}`);
499
+ }
500
+ if (result.tablesAltered.length > 0) {
501
+ (0, ui_js_1.success)(`Altered: ${result.tablesAltered.join(', ')}`);
502
+ }
503
+ (0, ui_js_1.newline)();
504
+ (0, ui_js_1.info)(`Run ${(0, ui_js_1.cyan)('npx turbine generate')} to update your TypeScript types.`);
505
+ (0, ui_js_1.newline)();
506
+ }
507
+ // ---------------------------------------------------------------------------
508
+ // Command: migrate
509
+ // ---------------------------------------------------------------------------
510
+ async function cmdMigrate(args, config) {
511
+ const sub = args.subcommand;
512
+ if (!sub || sub === 'help') {
513
+ (0, ui_js_1.banner)();
514
+ console.log(` ${(0, ui_js_1.bold)('turbine migrate')} ${(0, ui_js_1.dim)('— SQL-first migration system')}`);
515
+ (0, ui_js_1.newline)();
516
+ console.log(` ${(0, ui_js_1.bold)('Commands:')}`);
517
+ console.log(` ${(0, ui_js_1.cyan)('create <name>')} Create a new migration file`);
518
+ console.log(` ${(0, ui_js_1.cyan)('create <name> --auto')} Auto-generate from schema diff`);
519
+ console.log(` ${(0, ui_js_1.cyan)('up')} Apply pending migrations`);
520
+ console.log(` ${(0, ui_js_1.cyan)('down')} Rollback last migration`);
521
+ console.log(` ${(0, ui_js_1.cyan)('status')} Show migration status`);
522
+ (0, ui_js_1.newline)();
523
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
524
+ console.log(` ${(0, ui_js_1.cyan)('--auto')} Auto-generate UP/DOWN SQL from schema diff`);
525
+ console.log(` ${(0, ui_js_1.cyan)('--step, -n')} Number of migrations to apply/rollback`);
526
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
527
+ (0, ui_js_1.newline)();
528
+ console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
529
+ console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create add_users_table')}`);
530
+ console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create add_email_index --auto')}`);
531
+ console.log(` ${(0, ui_js_1.dim)('npx turbine migrate up')}`);
532
+ console.log(` ${(0, ui_js_1.dim)('npx turbine migrate down --step 2')}`);
533
+ (0, ui_js_1.newline)();
534
+ return;
535
+ }
536
+ switch (sub) {
537
+ case 'create':
538
+ await cmdMigrateCreate(args, config);
539
+ break;
540
+ case 'up':
541
+ await cmdMigrateUp(args, config);
542
+ break;
543
+ case 'down':
544
+ await cmdMigrateDown(args, config);
545
+ break;
546
+ case 'status':
547
+ case 'list':
548
+ await cmdMigrateStatus(args, config);
549
+ break;
550
+ default:
551
+ (0, ui_js_1.error)(`Unknown migrate subcommand: ${sub}`);
552
+ console.log(` ${(0, ui_js_1.dim)('Run')} ${(0, ui_js_1.cyan)('npx turbine migrate help')} ${(0, ui_js_1.dim)('for usage.')}`);
553
+ process.exit(1);
554
+ }
555
+ }
556
+ async function cmdMigrateCreate(args, config) {
557
+ (0, ui_js_1.banner)();
558
+ const name = args.positional[0];
559
+ if (!name) {
560
+ (0, ui_js_1.error)('Migration name is required.');
561
+ (0, ui_js_1.newline)();
562
+ console.log(` ${(0, ui_js_1.dim)('Usage:')} ${(0, ui_js_1.cyan)('npx turbine migrate create <name>')}`);
563
+ console.log(` ${(0, ui_js_1.dim)('Example:')} ${(0, ui_js_1.cyan)('npx turbine migrate create add_users_table')}`);
564
+ console.log(` ${(0, ui_js_1.dim)('Auto:')} ${(0, ui_js_1.cyan)('npx turbine migrate create my_change --auto')}`);
565
+ (0, ui_js_1.newline)();
566
+ process.exit(1);
567
+ }
568
+ if (args.auto) {
569
+ // Auto-generate migration from schema diff
570
+ const url = requireUrl(config);
571
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
572
+ (0, ui_js_1.label)('Schema file', config.schemaFile);
573
+ (0, ui_js_1.newline)();
574
+ const schemaDef = await loadSchemaFile(config.schemaFile);
575
+ const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
576
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
577
+ if (diff.statements.length === 0) {
578
+ diffSpinner.succeed('Database is already in sync — nothing to migrate');
579
+ (0, ui_js_1.newline)();
580
+ return;
581
+ }
582
+ diffSpinner.succeed(`Found ${(0, ui_js_1.bold)(String(diff.statements.length))} change(s)`);
583
+ (0, ui_js_1.newline)();
584
+ const upSQL = diff.statements.join('\n');
585
+ const downSQL = diff.reverseStatements.join('\n');
586
+ const file = (0, migrate_js_1.createMigration)(config.migrationsDir, name, { up: upSQL, down: downSQL });
587
+ const relPath = (0, node_path_1.relative)(process.cwd(), file.path);
588
+ (0, ui_js_1.success)(`Created auto-migration: ${(0, ui_js_1.bold)(file.filename)}`);
589
+ (0, ui_js_1.newline)();
590
+ console.log(` ${(0, ui_js_1.dim)('File:')} ${(0, ui_js_1.cyan)(relPath)}`);
591
+ (0, ui_js_1.newline)();
592
+ // Show summary of changes
593
+ if (diff.create.length > 0) {
594
+ console.log(` ${(0, ui_js_1.green)('+ Create')} ${diff.create.length} table(s): ${diff.create.map((t) => t.name).join(', ')}`);
595
+ }
596
+ if (diff.alter.length > 0) {
597
+ console.log(` ${(0, ui_js_1.yellow)('~ Alter')} ${diff.alter.length} table(s):`);
598
+ for (const a of diff.alter) {
599
+ for (const col of a.columns) {
600
+ const actionLabel = col.action === 'add'
601
+ ? (0, ui_js_1.green)('+ add')
602
+ : col.action === 'drop'
603
+ ? (0, ui_js_1.red)('- drop')
604
+ : col.action === 'add_unique'
605
+ ? (0, ui_js_1.green)('+ unique')
606
+ : col.action === 'drop_unique'
607
+ ? (0, ui_js_1.red)('- unique')
608
+ : (0, ui_js_1.yellow)(`~ ${col.action.replace(/_/g, ' ')}`);
609
+ console.log(` ${actionLabel} ${a.table}.${col.column}`);
610
+ }
611
+ }
612
+ }
613
+ (0, ui_js_1.newline)();
614
+ console.log(` ${(0, ui_js_1.dim)('Review the migration, then run:')}`);
615
+ console.log(` ${(0, ui_js_1.cyan)('npx turbine migrate up')}`);
616
+ (0, ui_js_1.newline)();
617
+ return;
618
+ }
619
+ const file = (0, migrate_js_1.createMigration)(config.migrationsDir, name);
620
+ const relPath = (0, node_path_1.relative)(process.cwd(), file.path);
621
+ (0, ui_js_1.success)(`Created migration: ${(0, ui_js_1.bold)(file.filename)}`);
622
+ (0, ui_js_1.newline)();
623
+ console.log(` ${(0, ui_js_1.dim)('File:')} ${(0, ui_js_1.cyan)(relPath)}`);
624
+ (0, ui_js_1.newline)();
625
+ console.log(` ${(0, ui_js_1.dim)('Edit the file to add your SQL, then run:')}`);
626
+ console.log(` ${(0, ui_js_1.cyan)('npx turbine migrate up')}`);
627
+ (0, ui_js_1.newline)();
628
+ }
629
+ async function cmdMigrateUp(args, config) {
630
+ (0, ui_js_1.banner)();
631
+ const url = requireUrl(config);
632
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
633
+ (0, ui_js_1.label)('Migrations', config.migrationsDir);
634
+ (0, ui_js_1.newline)();
635
+ const allFiles = (0, migrate_js_1.listMigrationFiles)(config.migrationsDir);
636
+ if (allFiles.length === 0) {
637
+ (0, ui_js_1.warn)('No migration files found.');
638
+ console.log(` ${(0, ui_js_1.dim)('Create one with:')} ${(0, ui_js_1.cyan)('npx turbine migrate create <name>')}`);
639
+ (0, ui_js_1.newline)();
640
+ return;
641
+ }
642
+ const spinner = new ui_js_1.Spinner('Applying migrations').start();
643
+ const result = await (0, migrate_js_1.migrateUp)(url, config.migrationsDir, {
644
+ step: args.step,
645
+ });
646
+ if (result.applied.length === 0 && result.errors.length === 0) {
647
+ spinner.succeed('All migrations are up to date');
648
+ (0, ui_js_1.newline)();
649
+ return;
650
+ }
651
+ if (result.applied.length > 0) {
652
+ spinner.succeed(`Applied ${(0, ui_js_1.bold)(String(result.applied.length))} migration(s)`);
653
+ for (const file of result.applied) {
654
+ console.log(` ${(0, ui_js_1.green)(ui_js_1.symbols.check)} ${file.filename}`);
655
+ }
656
+ }
657
+ if (result.errors.length > 0) {
658
+ spinner.fail('Migration failed');
659
+ for (const { file, error: msg } of result.errors) {
660
+ console.log(` ${(0, ui_js_1.red)(ui_js_1.symbols.cross)} ${file.filename}`);
661
+ console.log(` ${(0, ui_js_1.dim)(msg)}`);
662
+ }
663
+ (0, ui_js_1.newline)();
664
+ process.exit(1);
665
+ }
666
+ (0, ui_js_1.newline)();
667
+ }
668
+ async function cmdMigrateDown(args, config) {
669
+ (0, ui_js_1.banner)();
670
+ const url = requireUrl(config);
671
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
672
+ (0, ui_js_1.label)('Migrations', config.migrationsDir);
673
+ (0, ui_js_1.newline)();
674
+ const spinner = new ui_js_1.Spinner('Rolling back migration(s)').start();
675
+ const result = await (0, migrate_js_1.migrateDown)(url, config.migrationsDir, {
676
+ step: args.step ?? 1,
677
+ });
678
+ if (result.rolledBack.length === 0 && result.errors.length === 0) {
679
+ spinner.succeed('No migrations to roll back');
680
+ (0, ui_js_1.newline)();
681
+ return;
682
+ }
683
+ if (result.rolledBack.length > 0) {
684
+ spinner.succeed(`Rolled back ${(0, ui_js_1.bold)(String(result.rolledBack.length))} migration(s)`);
685
+ for (const file of result.rolledBack) {
686
+ console.log(` ${(0, ui_js_1.yellow)(ui_js_1.symbols.arrowRight)} ${file.filename}`);
687
+ }
688
+ }
689
+ if (result.errors.length > 0) {
690
+ spinner.fail('Rollback failed');
691
+ for (const { file, error: msg } of result.errors) {
692
+ console.log(` ${(0, ui_js_1.red)(ui_js_1.symbols.cross)} ${file.filename}`);
693
+ console.log(` ${(0, ui_js_1.dim)(msg)}`);
694
+ }
695
+ (0, ui_js_1.newline)();
696
+ process.exit(1);
697
+ }
698
+ (0, ui_js_1.newline)();
699
+ }
700
+ async function cmdMigrateStatus(_args, config) {
701
+ (0, ui_js_1.banner)();
702
+ const url = requireUrl(config);
703
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
704
+ (0, ui_js_1.label)('Migrations', config.migrationsDir);
705
+ (0, ui_js_1.newline)();
706
+ const allFiles = (0, migrate_js_1.listMigrationFiles)(config.migrationsDir);
707
+ if (allFiles.length === 0) {
708
+ (0, ui_js_1.warn)('No migration files found.');
709
+ console.log(` ${(0, ui_js_1.dim)('Create one with:')} ${(0, ui_js_1.cyan)('npx turbine migrate create <name>')}`);
710
+ (0, ui_js_1.newline)();
711
+ return;
712
+ }
713
+ const statuses = await (0, migrate_js_1.migrateStatus)(url, config.migrationsDir);
714
+ const appliedCount = statuses.filter((s) => s.applied).length;
715
+ const pendingCount = statuses.filter((s) => !s.applied).length;
716
+ (0, ui_js_1.info)(`${(0, ui_js_1.bold)(String(appliedCount))} applied, ${pendingCount > 0 ? (0, ui_js_1.yellow)((0, ui_js_1.bold)(String(pendingCount))) : (0, ui_js_1.bold)(String(pendingCount))} pending`);
717
+ (0, ui_js_1.newline)();
718
+ // Check for checksum mismatches
719
+ const driftCount = statuses.filter((s) => s.checksumValid === false).length;
720
+ if (driftCount > 0) {
721
+ (0, ui_js_1.warn)(`${(0, ui_js_1.bold)(String(driftCount))} migration(s) have been modified after application!`);
722
+ console.log(` ${(0, ui_js_1.dim)('Applied migrations should be immutable. Modifying them can cause drift.')}`);
723
+ (0, ui_js_1.newline)();
724
+ }
725
+ // Format as table
726
+ const headers = ['Status', 'Migration', 'Applied at'];
727
+ const rows = statuses.map((s) => {
728
+ let status;
729
+ if (s.applied && s.checksumValid === false) {
730
+ status = (0, ui_js_1.red)(`${ui_js_1.symbols.warning} Drifted`);
731
+ }
732
+ else if (s.applied) {
733
+ status = (0, ui_js_1.green)(`${ui_js_1.symbols.check} Applied`);
734
+ }
735
+ else {
736
+ status = (0, ui_js_1.yellow)(`${ui_js_1.symbols.dot} Pending`);
737
+ }
738
+ return [
739
+ status,
740
+ s.file.filename,
741
+ s.appliedAt
742
+ ? (0, ui_js_1.dim)(s.appliedAt
743
+ .toISOString()
744
+ .replace('T', ' ')
745
+ .replace(/\.\d+Z$/, ' UTC'))
746
+ : (0, ui_js_1.dim)('—'),
747
+ ];
748
+ });
749
+ console.log((0, ui_js_1.table)(headers, rows));
750
+ (0, ui_js_1.newline)();
751
+ if (pendingCount > 0) {
752
+ console.log(` ${(0, ui_js_1.dim)('Run')} ${(0, ui_js_1.cyan)('npx turbine migrate up')} ${(0, ui_js_1.dim)('to apply pending migrations.')}`);
753
+ (0, ui_js_1.newline)();
754
+ }
755
+ }
756
+ // ---------------------------------------------------------------------------
757
+ // Command: seed
758
+ // ---------------------------------------------------------------------------
759
+ async function cmdSeed(_args, config) {
760
+ (0, ui_js_1.banner)();
761
+ const seedFile = (0, node_path_1.resolve)(config.seedFile);
762
+ (0, ui_js_1.label)('Seed file', config.seedFile);
763
+ (0, ui_js_1.newline)();
764
+ if (!(0, node_fs_1.existsSync)(seedFile)) {
765
+ (0, ui_js_1.error)(`Seed file not found: ${config.seedFile}`);
766
+ (0, ui_js_1.newline)();
767
+ console.log(` ${(0, ui_js_1.dim)('Create one with:')} ${(0, ui_js_1.cyan)('npx turbine init')}`);
768
+ console.log(` ${(0, ui_js_1.dim)('Or set a custom path in')} ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
769
+ (0, ui_js_1.newline)();
770
+ process.exit(1);
771
+ }
772
+ const spinner = new ui_js_1.Spinner('Running seed file').start();
773
+ try {
774
+ // Use child_process to run the seed file via tsx or node
775
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
776
+ // Try tsx first (most compatible with .ts files), fall back to node --experimental-strip-types
777
+ const runners = [
778
+ { cmd: 'npx', args: ['tsx', seedFile], name: 'tsx' },
779
+ { cmd: 'node', args: ['--experimental-strip-types', seedFile], name: 'node' },
780
+ ];
781
+ let ran = false;
782
+ for (const runner of runners) {
783
+ try {
784
+ execFileSync(runner.cmd, runner.args, {
785
+ stdio: 'inherit',
786
+ env: {
787
+ ...process.env,
788
+ DATABASE_URL: config.url || process.env.DATABASE_URL,
789
+ },
790
+ });
791
+ ran = true;
792
+ break;
793
+ }
794
+ catch (err) {
795
+ // If tsx not found, try next runner
796
+ if (err instanceof Error && 'status' in err && err.status === null) {
797
+ continue;
798
+ }
799
+ throw err;
800
+ }
801
+ }
802
+ if (!ran) {
803
+ throw new Error('Could not find tsx or compatible Node.js version to run .ts files');
804
+ }
805
+ spinner.succeed('Seed completed');
806
+ }
807
+ catch (err) {
808
+ spinner.fail('Seed failed');
809
+ if (err instanceof Error) {
810
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
811
+ }
812
+ (0, ui_js_1.newline)();
813
+ process.exit(1);
814
+ }
815
+ (0, ui_js_1.newline)();
816
+ }
817
+ // ---------------------------------------------------------------------------
818
+ // Command: status
819
+ // ---------------------------------------------------------------------------
820
+ async function cmdStatus(_args, config) {
821
+ (0, ui_js_1.banner)();
822
+ const url = requireUrl(config);
823
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
824
+ (0, ui_js_1.label)('Schema', config.schema);
825
+ (0, ui_js_1.newline)();
826
+ const spinner = new ui_js_1.Spinner('Introspecting database').start();
827
+ const schema = await (0, introspect_js_1.introspect)({
828
+ connectionString: url,
829
+ schema: config.schema,
830
+ include: config.include.length ? config.include : undefined,
831
+ exclude: config.exclude.length ? config.exclude : undefined,
832
+ });
833
+ const tableNames = Object.keys(schema.tables);
834
+ spinner.succeed(`Found ${(0, ui_js_1.bold)(String(tableNames.length))} tables`);
835
+ (0, ui_js_1.newline)();
836
+ for (const tbl of Object.values(schema.tables)) {
837
+ const relCount = Object.keys(tbl.relations).length;
838
+ const _pk = tbl.primaryKey.join(', ') || (0, ui_js_1.dim)('(none)');
839
+ console.log(` ${(0, ui_js_1.bold)((0, ui_js_1.cyan)(tbl.name))}`);
840
+ for (let i = 0; i < tbl.columns.length; i++) {
841
+ const col = tbl.columns[i];
842
+ const isLast = i === tbl.columns.length - 1 && relCount === 0;
843
+ const prefix = isLast ? ui_js_1.symbols.teeEnd : ui_js_1.symbols.tee;
844
+ const nullable = col.nullable ? (0, ui_js_1.dim)('?') : '';
845
+ const def = col.hasDefault ? (0, ui_js_1.dim)(' (default)') : '';
846
+ const pkLabel = tbl.primaryKey.includes(col.name) ? ` ${(0, ui_js_1.magenta)('PK')}` : '';
847
+ console.log(` ${(0, ui_js_1.dim)(prefix)} ${col.field}${nullable}: ${(0, ui_js_1.green)(col.tsType)}${pkLabel}${def} ${(0, ui_js_1.gray)(`${ui_js_1.symbols.arrow} ${col.pgType}`)}`);
848
+ }
849
+ const rels = Object.entries(tbl.relations);
850
+ if (rels.length > 0) {
851
+ for (let i = 0; i < rels.length; i++) {
852
+ const [relName, rel] = rels[i];
853
+ const isLast = i === rels.length - 1;
854
+ const prefix = isLast ? ui_js_1.symbols.teeEnd : ui_js_1.symbols.tee;
855
+ const relColor = rel.type === 'hasMany' ? ui_js_1.blue : ui_js_1.yellow;
856
+ console.log(` ${(0, ui_js_1.dim)(prefix)} ${relColor(relName)} ${(0, ui_js_1.dim)(ui_js_1.symbols.arrow)} ${rel.to} ${(0, ui_js_1.dim)(`(${rel.type}, FK: ${rel.foreignKey})`)}`);
857
+ }
858
+ }
859
+ (0, ui_js_1.newline)();
860
+ }
861
+ if (Object.keys(schema.enums).length > 0) {
862
+ console.log(` ${(0, ui_js_1.bold)('Enums:')}`);
863
+ for (const [enumName, labels] of Object.entries(schema.enums)) {
864
+ console.log(` ${(0, ui_js_1.cyan)(enumName)}: ${labels.map((l) => (0, ui_js_1.green)(`'${l}'`)).join((0, ui_js_1.dim)(' | '))}`);
865
+ }
866
+ (0, ui_js_1.newline)();
867
+ }
868
+ }
869
+ // ---------------------------------------------------------------------------
870
+ // Command: studio (scaffold)
871
+ // ---------------------------------------------------------------------------
872
+ async function cmdStudio(_args, _config) {
873
+ (0, ui_js_1.banner)();
874
+ console.log((0, ui_js_1.box)([
875
+ `${(0, ui_js_1.bold)('Turbine Studio')} ${(0, ui_js_1.dim)('— coming soon')}`,
876
+ '',
877
+ 'A local web UI for browsing your database,',
878
+ 'exploring relations, and managing data.',
879
+ '',
880
+ `Follow ${(0, ui_js_1.cyan)('@turbineorm')} for updates.`,
881
+ ].join('\n'), { title: (0, ui_js_1.bold)((0, ui_js_1.cyan)('Studio')), padding: 2 }));
882
+ (0, ui_js_1.newline)();
883
+ }
884
+ // ---------------------------------------------------------------------------
885
+ // Subcommand help
886
+ // ---------------------------------------------------------------------------
887
+ function showSubcommandHelp(command) {
888
+ const helpMap = {
889
+ init: showInitHelp,
890
+ generate: showGenerateHelp,
891
+ pull: showGenerateHelp,
892
+ push: showPushHelp,
893
+ migrate: showMigrateHelp,
894
+ migration: showMigrateHelp,
895
+ seed: showSeedHelp,
896
+ status: showStatusHelp,
897
+ };
898
+ const fn = helpMap[command];
899
+ if (fn) {
900
+ fn();
901
+ return true;
902
+ }
903
+ return false;
904
+ }
905
+ function showInitHelp() {
906
+ (0, ui_js_1.banner)();
907
+ console.log(` ${(0, ui_js_1.bold)('turbine init')} — Initialize a Turbine project`);
908
+ (0, ui_js_1.newline)();
909
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
910
+ console.log(` npx turbine init ${(0, ui_js_1.dim)('[options]')}`);
911
+ (0, ui_js_1.newline)();
912
+ console.log(` Creates ${(0, ui_js_1.cyan)('turbine.config.ts')}, migrations directory, seed file template,`);
913
+ console.log(` and schema file template.`);
914
+ (0, ui_js_1.newline)();
915
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
916
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string to embed in config`);
917
+ console.log(` ${(0, ui_js_1.cyan)('--force, -f')} Overwrite existing config file`);
918
+ (0, ui_js_1.newline)();
919
+ }
920
+ function showGenerateHelp() {
921
+ (0, ui_js_1.banner)();
922
+ console.log(` ${(0, ui_js_1.bold)('turbine generate')} — Introspect database and generate TypeScript types`);
923
+ (0, ui_js_1.newline)();
924
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
925
+ console.log(` npx turbine generate ${(0, ui_js_1.dim)('[options]')}`);
926
+ (0, ui_js_1.newline)();
927
+ console.log(` Connects to your database, reads the schema, and generates:`);
928
+ console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('types.ts')} — Entity interfaces, Create/Update input types`);
929
+ console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('metadata.ts')} — Runtime schema metadata`);
930
+ console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('index.ts')} — Configured client with typed table accessors`);
931
+ (0, ui_js_1.newline)();
932
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
933
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
934
+ console.log(` ${(0, ui_js_1.cyan)('--out, -o')} ${(0, ui_js_1.dim)('<dir>')} Output directory ${(0, ui_js_1.dim)('(default: ./generated/turbine)')}`);
935
+ console.log(` ${(0, ui_js_1.cyan)('--schema, -s')} ${(0, ui_js_1.dim)('<name>')} Postgres schema ${(0, ui_js_1.dim)('(default: public)')}`);
936
+ console.log(` ${(0, ui_js_1.cyan)('--include')} ${(0, ui_js_1.dim)('<tables>')} Comma-separated tables to include`);
937
+ console.log(` ${(0, ui_js_1.cyan)('--exclude')} ${(0, ui_js_1.dim)('<tables>')} Comma-separated tables to exclude`);
938
+ (0, ui_js_1.newline)();
939
+ }
940
+ function showPushHelp() {
941
+ (0, ui_js_1.banner)();
942
+ console.log(` ${(0, ui_js_1.bold)('turbine push')} — Apply schema-builder definitions to database`);
943
+ (0, ui_js_1.newline)();
944
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
945
+ console.log(` npx turbine push ${(0, ui_js_1.dim)('[options]')}`);
946
+ (0, ui_js_1.newline)();
947
+ console.log(` Reads your ${(0, ui_js_1.cyan)('turbine/schema.ts')} file, diffs against the live database,`);
948
+ console.log(` and applies CREATE/ALTER statements.`);
949
+ (0, ui_js_1.newline)();
950
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
951
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
952
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
953
+ console.log(` ${(0, ui_js_1.cyan)('--verbose, -v')} Show detailed output`);
954
+ (0, ui_js_1.newline)();
955
+ }
956
+ function showMigrateHelp() {
957
+ (0, ui_js_1.banner)();
958
+ console.log(` ${(0, ui_js_1.bold)('turbine migrate')} — SQL migration management`);
959
+ (0, ui_js_1.newline)();
960
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
961
+ console.log(` npx turbine migrate ${(0, ui_js_1.cyan)('<subcommand>')} ${(0, ui_js_1.dim)('[options]')}`);
962
+ (0, ui_js_1.newline)();
963
+ console.log(` ${(0, ui_js_1.bold)('Subcommands:')}`);
964
+ console.log(` ${(0, ui_js_1.cyan)('create')} ${(0, ui_js_1.dim)('<name>')} Create a new migration file`);
965
+ console.log(` ${(0, ui_js_1.cyan)('up')} Apply pending migrations`);
966
+ console.log(` ${(0, ui_js_1.cyan)('down')} Rollback last migration`);
967
+ console.log(` ${(0, ui_js_1.cyan)('status')} Show applied/pending migrations`);
968
+ (0, ui_js_1.newline)();
969
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
970
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
971
+ console.log(` ${(0, ui_js_1.cyan)('--step, -n')} ${(0, ui_js_1.dim)('<N>')} Number of migrations to apply/rollback`);
972
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
973
+ console.log(` ${(0, ui_js_1.cyan)('--verbose, -v')} Show detailed output`);
974
+ (0, ui_js_1.newline)();
975
+ console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
976
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create add_users_table`);
977
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate up`);
978
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate down --step 2`);
979
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate status`);
980
+ (0, ui_js_1.newline)();
981
+ }
982
+ function showSeedHelp() {
983
+ (0, ui_js_1.banner)();
984
+ console.log(` ${(0, ui_js_1.bold)('turbine seed')} — Run seed file`);
985
+ (0, ui_js_1.newline)();
986
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
987
+ console.log(` npx turbine seed ${(0, ui_js_1.dim)('[options]')}`);
988
+ (0, ui_js_1.newline)();
989
+ console.log(` Runs the seed file specified in ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
990
+ console.log(` ${(0, ui_js_1.dim)('(default: ./turbine/seed.ts)')}`);
991
+ (0, ui_js_1.newline)();
992
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
993
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
994
+ (0, ui_js_1.newline)();
995
+ }
996
+ function showStatusHelp() {
997
+ (0, ui_js_1.banner)();
998
+ console.log(` ${(0, ui_js_1.bold)('turbine status')} — Show database schema summary`);
999
+ (0, ui_js_1.newline)();
1000
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
1001
+ console.log(` npx turbine status ${(0, ui_js_1.dim)('[options]')}`);
1002
+ (0, ui_js_1.newline)();
1003
+ console.log(` Introspects your database and displays tables, columns,`);
1004
+ console.log(` types, relations, and indexes.`);
1005
+ (0, ui_js_1.newline)();
1006
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
1007
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
1008
+ console.log(` ${(0, ui_js_1.cyan)('--schema, -s')} ${(0, ui_js_1.dim)('<name>')} Postgres schema ${(0, ui_js_1.dim)('(default: public)')}`);
1009
+ (0, ui_js_1.newline)();
1010
+ }
1011
+ // ---------------------------------------------------------------------------
1012
+ // Help
1013
+ // ---------------------------------------------------------------------------
1014
+ function showHelp() {
1015
+ (0, ui_js_1.banner)();
1016
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
1017
+ console.log(` npx turbine ${(0, ui_js_1.cyan)('<command>')} ${(0, ui_js_1.dim)('[options]')}`);
1018
+ (0, ui_js_1.newline)();
1019
+ console.log(` ${(0, ui_js_1.bold)('Commands:')}`);
1020
+ console.log(` ${(0, ui_js_1.cyan)('init')} Initialize a Turbine project`);
1021
+ console.log(` ${(0, ui_js_1.cyan)('generate')} ${(0, ui_js_1.dim)('| pull')} Introspect database ${ui_js_1.symbols.arrow} generate types`);
1022
+ console.log(` ${(0, ui_js_1.cyan)('push')} Apply schema definitions to database`);
1023
+ console.log(` ${(0, ui_js_1.cyan)('migrate')} ${(0, ui_js_1.dim)('<sub>')} SQL migration management`);
1024
+ console.log(` ${(0, ui_js_1.dim)('create <name>')} Create a new migration file`);
1025
+ console.log(` ${(0, ui_js_1.dim)('up')} Apply pending migrations`);
1026
+ console.log(` ${(0, ui_js_1.dim)('down')} Rollback last migration`);
1027
+ console.log(` ${(0, ui_js_1.dim)('status')} Show applied/pending migrations`);
1028
+ console.log(` ${(0, ui_js_1.cyan)('seed')} Run seed file`);
1029
+ console.log(` ${(0, ui_js_1.cyan)('status')} ${(0, ui_js_1.dim)('| info')} Show schema summary`);
1030
+ console.log(` ${(0, ui_js_1.cyan)('studio')} Launch web UI (coming soon)`);
1031
+ (0, ui_js_1.newline)();
1032
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
1033
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
1034
+ console.log(` ${(0, ui_js_1.cyan)('--out, -o')} ${(0, ui_js_1.dim)('<dir>')} Output directory ${(0, ui_js_1.dim)('(default: ./generated/turbine)')}`);
1035
+ console.log(` ${(0, ui_js_1.cyan)('--schema, -s')} ${(0, ui_js_1.dim)('<name>')} Postgres schema ${(0, ui_js_1.dim)('(default: public)')}`);
1036
+ console.log(` ${(0, ui_js_1.cyan)('--include')} ${(0, ui_js_1.dim)('<tables>')} Comma-separated tables to include`);
1037
+ console.log(` ${(0, ui_js_1.cyan)('--exclude')} ${(0, ui_js_1.dim)('<tables>')} Comma-separated tables to exclude`);
1038
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
1039
+ console.log(` ${(0, ui_js_1.cyan)('--verbose, -v')} Show detailed output`);
1040
+ console.log(` ${(0, ui_js_1.cyan)('--force, -f')} Overwrite existing files`);
1041
+ (0, ui_js_1.newline)();
1042
+ console.log(` ${(0, ui_js_1.bold)('Config file:')}`);
1043
+ console.log(` ${(0, ui_js_1.dim)('Create')} ${(0, ui_js_1.cyan)('turbine.config.ts')} ${(0, ui_js_1.dim)('with')} ${(0, ui_js_1.cyan)('npx turbine init')}`);
1044
+ console.log(` ${(0, ui_js_1.dim)('CLI flags override config file values.')}`);
1045
+ (0, ui_js_1.newline)();
1046
+ console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
1047
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine init --url postgres://user:pass@host/db`);
1048
+ console.log(` ${(0, ui_js_1.dim)('$')} DATABASE_URL=postgres://... npx turbine generate`);
1049
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create add_users_table`);
1050
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate up`);
1051
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine push --dry-run`);
1052
+ (0, ui_js_1.newline)();
1053
+ }
1054
+ // ---------------------------------------------------------------------------
1055
+ // Version
1056
+ // ---------------------------------------------------------------------------
1057
+ function showVersion() {
1058
+ console.log(`turbine-orm v0.5.0`);
1059
+ }
1060
+ // ---------------------------------------------------------------------------
1061
+ // Main
1062
+ // ---------------------------------------------------------------------------
1063
+ async function main() {
1064
+ const args = parseArgs();
1065
+ // Quick exits that don't need config
1066
+ if (args.command === 'help' || args.command === '--help' || args.command === '-h') {
1067
+ showHelp();
1068
+ return;
1069
+ }
1070
+ // Subcommand help: e.g. `turbine migrate --help`
1071
+ if (args.help) {
1072
+ if (showSubcommandHelp(args.command))
1073
+ return;
1074
+ showHelp();
1075
+ return;
1076
+ }
1077
+ if (args.command === 'version' || args.command === '--version' || args.command === '-V') {
1078
+ showVersion();
1079
+ return;
1080
+ }
1081
+ // Load config file
1082
+ let fileConfig = {};
1083
+ try {
1084
+ fileConfig = await (0, config_js_1.loadConfig)();
1085
+ }
1086
+ catch (err) {
1087
+ if (args.command !== 'init') {
1088
+ (0, ui_js_1.warn)(`Could not load config: ${err instanceof Error ? err.message : String(err)}`);
1089
+ }
1090
+ }
1091
+ const overrides = {
1092
+ url: args.url,
1093
+ out: args.out,
1094
+ schema: args.schema,
1095
+ include: args.include,
1096
+ exclude: args.exclude,
1097
+ };
1098
+ const config = (0, config_js_1.resolveConfig)(fileConfig, overrides);
1099
+ try {
1100
+ switch (args.command) {
1101
+ case 'init':
1102
+ await cmdInit(args, config);
1103
+ break;
1104
+ case 'generate':
1105
+ case 'gen':
1106
+ case 'g':
1107
+ case 'pull':
1108
+ await cmdGenerate(args, config);
1109
+ break;
1110
+ case 'push':
1111
+ await cmdPush(args, config);
1112
+ break;
1113
+ case 'migrate':
1114
+ case 'migration':
1115
+ case 'm':
1116
+ await cmdMigrate(args, config);
1117
+ break;
1118
+ case 'seed':
1119
+ case 's':
1120
+ await cmdSeed(args, config);
1121
+ break;
1122
+ case 'status':
1123
+ case 'info':
1124
+ await cmdStatus(args, config);
1125
+ break;
1126
+ case 'studio':
1127
+ await cmdStudio(args, config);
1128
+ break;
1129
+ default:
1130
+ (0, ui_js_1.error)(`Unknown command: ${(0, ui_js_1.bold)(args.command)}`);
1131
+ (0, ui_js_1.newline)();
1132
+ console.log(` ${(0, ui_js_1.dim)('Run')} ${(0, ui_js_1.cyan)('npx turbine help')} ${(0, ui_js_1.dim)('for available commands.')}`);
1133
+ (0, ui_js_1.newline)();
1134
+ process.exit(1);
1135
+ }
1136
+ }
1137
+ catch (err) {
1138
+ if (err instanceof Error) {
1139
+ if (err.message.includes('ECONNREFUSED') || err.message.includes('connection')) {
1140
+ (0, ui_js_1.newline)();
1141
+ (0, ui_js_1.error)(`Could not connect to database`);
1142
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
1143
+ (0, ui_js_1.newline)();
1144
+ console.log(` ${(0, ui_js_1.dim)('Check that:')}`);
1145
+ console.log(` ${(0, ui_js_1.dim)('1.')} Your database is running`);
1146
+ console.log(` ${(0, ui_js_1.dim)('2.')} The connection string is correct`);
1147
+ console.log(` ${(0, ui_js_1.dim)('3.')} Network/firewall allows the connection`);
1148
+ }
1149
+ else if (err.message.includes('authentication')) {
1150
+ (0, ui_js_1.newline)();
1151
+ (0, ui_js_1.error)(`Authentication failed`);
1152
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
1153
+ }
1154
+ else if (err.message.includes('does not exist')) {
1155
+ (0, ui_js_1.newline)();
1156
+ (0, ui_js_1.error)(`Database or schema not found`);
1157
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
1158
+ }
1159
+ else {
1160
+ (0, ui_js_1.newline)();
1161
+ (0, ui_js_1.error)((0, ui_js_1.redactUrl)(err.message));
1162
+ if (args.verbose && err.stack) {
1163
+ (0, ui_js_1.newline)();
1164
+ console.log((0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.stack)));
1165
+ }
1166
+ }
1167
+ }
1168
+ else {
1169
+ (0, ui_js_1.newline)();
1170
+ (0, ui_js_1.error)(`Unexpected error: ${(0, ui_js_1.redactUrl)(String(err))}`);
1171
+ }
1172
+ (0, ui_js_1.newline)();
1173
+ process.exit(1);
1174
+ }
1175
+ }
1176
+ main();