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