turbine-orm 0.5.0 → 0.7.1

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 (50) hide show
  1. package/README.md +292 -26
  2. package/dist/cjs/cli/config.js +5 -15
  3. package/dist/cjs/cli/index.js +311 -43
  4. package/dist/cjs/cli/loader.js +129 -0
  5. package/dist/cjs/cli/migrate.js +96 -47
  6. package/dist/cjs/cli/ui.js +5 -9
  7. package/dist/cjs/client.js +158 -49
  8. package/dist/cjs/errors.js +424 -0
  9. package/dist/cjs/generate.js +145 -14
  10. package/dist/cjs/index.js +43 -20
  11. package/dist/cjs/introspect.js +3 -5
  12. package/dist/cjs/pipeline.js +9 -2
  13. package/dist/cjs/query.js +544 -115
  14. package/dist/cjs/schema-builder.js +150 -30
  15. package/dist/cjs/schema-sql.js +241 -37
  16. package/dist/cjs/schema.js +5 -2
  17. package/dist/cjs/serverless.js +88 -176
  18. package/dist/cli/config.js +6 -16
  19. package/dist/cli/index.js +316 -48
  20. package/dist/cli/loader.d.ts +45 -0
  21. package/dist/cli/loader.js +91 -0
  22. package/dist/cli/migrate.d.ts +13 -2
  23. package/dist/cli/migrate.js +97 -48
  24. package/dist/cli/ui.d.ts +1 -1
  25. package/dist/cli/ui.js +5 -9
  26. package/dist/client.d.ts +92 -4
  27. package/dist/client.js +158 -49
  28. package/dist/errors.d.ts +225 -0
  29. package/dist/errors.js +405 -0
  30. package/dist/generate.d.ts +7 -1
  31. package/dist/generate.js +148 -18
  32. package/dist/index.d.ts +11 -9
  33. package/dist/index.js +16 -12
  34. package/dist/introspect.d.ts +1 -1
  35. package/dist/introspect.js +4 -6
  36. package/dist/pipeline.d.ts +1 -1
  37. package/dist/pipeline.js +9 -2
  38. package/dist/query.d.ts +374 -38
  39. package/dist/query.js +545 -116
  40. package/dist/schema-builder.d.ts +38 -5
  41. package/dist/schema-builder.js +150 -31
  42. package/dist/schema-sql.d.ts +7 -3
  43. package/dist/schema-sql.js +241 -37
  44. package/dist/schema.d.ts +1 -1
  45. package/dist/schema.js +5 -2
  46. package/dist/serverless.d.ts +92 -139
  47. package/dist/serverless.js +87 -173
  48. package/package.json +33 -16
  49. package/dist/types.d.ts +0 -93
  50. package/dist/types.js +0 -126
@@ -56,13 +56,14 @@ var __importStar = (this && this.__importStar) || (function () {
56
56
  Object.defineProperty(exports, "__esModule", { value: true });
57
57
  const node_fs_1 = require("node:fs");
58
58
  const node_path_1 = require("node:path");
59
- const introspect_js_1 = require("../introspect.js");
59
+ const node_url_1 = require("node:url");
60
60
  const generate_js_1 = require("../generate.js");
61
+ const introspect_js_1 = require("../introspect.js");
61
62
  const schema_sql_js_1 = require("../schema-sql.js");
62
63
  const config_js_1 = require("./config.js");
64
+ const loader_js_1 = require("./loader.js");
63
65
  const migrate_js_1 = require("./migrate.js");
64
66
  const ui_js_1 = require("./ui.js");
65
- const node_url_1 = require("node:url");
66
67
  function parseArgs() {
67
68
  const args = process.argv.slice(2);
68
69
  const result = {
@@ -110,6 +111,12 @@ function parseArgs() {
110
111
  case '--dry-run':
111
112
  result.dryRun = true;
112
113
  break;
114
+ case '--auto':
115
+ result.auto = true;
116
+ break;
117
+ case '--allow-drift':
118
+ result.allowDrift = true;
119
+ break;
113
120
  case '--force':
114
121
  case '-f':
115
122
  result.force = true;
@@ -118,6 +125,10 @@ function parseArgs() {
118
125
  case '-v':
119
126
  result.verbose = true;
120
127
  break;
128
+ case '--help':
129
+ case '-h':
130
+ result.help = true;
131
+ break;
121
132
  default:
122
133
  if (!arg.startsWith('-')) {
123
134
  result.positional.push(arg);
@@ -128,6 +139,36 @@ function parseArgs() {
128
139
  return result;
129
140
  }
130
141
  // ---------------------------------------------------------------------------
142
+ // TypeScript loader — user-facing error helper
143
+ // ---------------------------------------------------------------------------
144
+ /**
145
+ * Print a friendly error explaining how to install tsx, then exit.
146
+ * Called when we know we need to load a `.ts` file but the loader isn't available.
147
+ */
148
+ function failMissingTsLoader(filePath, reason) {
149
+ (0, ui_js_1.newline)();
150
+ (0, ui_js_1.error)(`Cannot load TypeScript file: ${filePath}`);
151
+ (0, ui_js_1.newline)();
152
+ if (reason === 'unsupported') {
153
+ console.log(` ${(0, ui_js_1.dim)('Your Node.js version does not support')} ${(0, ui_js_1.cyan)('module.register()')}.`);
154
+ console.log(` ${(0, ui_js_1.dim)('Upgrade to Node.js')} ${(0, ui_js_1.cyan)('20.6+')} ${(0, ui_js_1.dim)('or use a')} ${(0, ui_js_1.cyan)('.js')} ${(0, ui_js_1.dim)('/')} ${(0, ui_js_1.cyan)('.mjs')} ${(0, ui_js_1.dim)('config file.')}`);
155
+ }
156
+ else {
157
+ console.log(` ${(0, ui_js_1.dim)('Loading .ts config / schema files requires')} ${(0, ui_js_1.cyan)('tsx')} ${(0, ui_js_1.dim)('to be installed.')}`);
158
+ (0, ui_js_1.newline)();
159
+ console.log(` ${(0, ui_js_1.dim)('Install it as a dev dependency:')}`);
160
+ console.log(` ${(0, ui_js_1.cyan)('npm install --save-dev tsx')}`);
161
+ console.log(` ${(0, ui_js_1.dim)('or')}`);
162
+ console.log(` ${(0, ui_js_1.cyan)('pnpm add -D tsx')}`);
163
+ console.log(` ${(0, ui_js_1.dim)('or')}`);
164
+ console.log(` ${(0, ui_js_1.cyan)('yarn add -D tsx')}`);
165
+ (0, ui_js_1.newline)();
166
+ console.log(` ${(0, ui_js_1.dim)('Alternatively, rename your file to')} ${(0, ui_js_1.cyan)('.js')} ${(0, ui_js_1.dim)('or')} ${(0, ui_js_1.cyan)('.mjs')}.`);
167
+ }
168
+ (0, ui_js_1.newline)();
169
+ process.exit(1);
170
+ }
171
+ // ---------------------------------------------------------------------------
131
172
  // Helpers
132
173
  // ---------------------------------------------------------------------------
133
174
  function requireUrl(config) {
@@ -150,6 +191,15 @@ async function loadSchemaFile(schemaFile) {
150
191
  console.log(` ${(0, ui_js_1.dim)('Create one with:')} ${(0, ui_js_1.cyan)('turbine init')}`);
151
192
  process.exit(1);
152
193
  }
194
+ // If this is a TypeScript file, ensure the tsx ESM loader is registered
195
+ // before we attempt the dynamic import. Without this, Node throws
196
+ // ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
197
+ if ((0, loader_js_1.needsTsLoader)(absPath)) {
198
+ const status = await (0, loader_js_1.registerTsLoader)();
199
+ if (status === 'missing' || status === 'unsupported') {
200
+ failMissingTsLoader(schemaFile, status);
201
+ }
202
+ }
153
203
  try {
154
204
  const fileUrl = (0, node_url_1.pathToFileURL)(absPath).href;
155
205
  const mod = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
@@ -164,6 +214,11 @@ async function loadSchemaFile(schemaFile) {
164
214
  (0, ui_js_1.error)(`Failed to load schema file: ${schemaFile}`);
165
215
  if (err instanceof Error) {
166
216
  console.log(` ${(0, ui_js_1.dim)(err.message)}`);
217
+ // If the error is the classic ERR_UNKNOWN_FILE_EXTENSION, give a hint.
218
+ if (err.message.includes('ERR_UNKNOWN_FILE_EXTENSION') || err.message.includes('Unknown file extension')) {
219
+ (0, ui_js_1.newline)();
220
+ console.log(` ${(0, ui_js_1.dim)('Hint: install')} ${(0, ui_js_1.cyan)('tsx')} ${(0, ui_js_1.dim)('to load .ts files:')} ${(0, ui_js_1.cyan)('npm install --save-dev tsx')}`);
221
+ }
167
222
  }
168
223
  process.exit(1);
169
224
  }
@@ -175,7 +230,7 @@ async function cmdInit(args, config) {
175
230
  (0, ui_js_1.banner)();
176
231
  (0, ui_js_1.header)('Initializing Turbine project');
177
232
  // Detect environment
178
- const envUrl = process.env['DATABASE_URL'];
233
+ const envUrl = process.env.DATABASE_URL;
179
234
  const hasEnvFile = (0, node_fs_1.existsSync)('.env');
180
235
  const hasEnvLocal = (0, node_fs_1.existsSync)('.env.local');
181
236
  if (envUrl) {
@@ -214,7 +269,7 @@ async function cmdInit(args, config) {
214
269
  (0, node_fs_1.mkdirSync)(migrDir, { recursive: true });
215
270
  // Create .gitkeep
216
271
  (0, node_fs_1.writeFileSync)(`${migrDir}/.gitkeep`, '', 'utf-8');
217
- (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(migrDir + '/')}`);
272
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${migrDir}/`)}`);
218
273
  }
219
274
  else {
220
275
  (0, ui_js_1.info)(`Migrations dir already exists: ${(0, ui_js_1.dim)(migrDir)}`);
@@ -222,7 +277,7 @@ async function cmdInit(args, config) {
222
277
  // Create output directory
223
278
  if (!(0, node_fs_1.existsSync)(config.out)) {
224
279
  (0, node_fs_1.mkdirSync)(config.out, { recursive: true });
225
- (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(config.out + '/')}`);
280
+ (0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
226
281
  }
227
282
  // Create seed file template
228
283
  const seedDir = config.seedFile.substring(0, config.seedFile.lastIndexOf('/'));
@@ -266,7 +321,7 @@ async function cmdInit(args, config) {
266
321
  * Define your database schema in TypeScript.
267
322
  * Use \`npx turbine push\` to sync it to your database.
268
323
  *
269
- * @see https://batadata.com/docs/turbine/schema
324
+ * @see https://turbineorm.dev
270
325
  */
271
326
 
272
327
  import { defineSchema } from 'turbine-orm';
@@ -317,15 +372,15 @@ export default defineSchema({
317
372
  spinner.succeed(`Found ${(0, ui_js_1.bold)(String(tableCount))} tables`);
318
373
  const genSpinner = new ui_js_1.Spinner('Generating TypeScript client').start();
319
374
  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 + '/')}`);
375
+ genSpinner.succeed(`Generated ${(0, ui_js_1.bold)(String(result.files.length))} files to ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
321
376
  }
322
377
  catch (err) {
323
378
  spinner.fail('Could not connect to database');
324
379
  if (err instanceof Error) {
325
- console.log(` ${(0, ui_js_1.dim)(err.message)}`);
380
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
326
381
  }
327
382
  (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'));
383
+ (0, ui_js_1.info)(`You can run generation later with: ${(0, ui_js_1.cyan)('npx turbine generate')}`);
329
384
  }
330
385
  }
331
386
  // Next steps
@@ -401,7 +456,7 @@ async function cmdGenerate(args, config) {
401
456
  genSpinner.succeed(`Generated ${(0, ui_js_1.bold)(String(result.files.length))} files in ${(0, ui_js_1.elapsed)(startTime)}`);
402
457
  // List files
403
458
  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)}`);
459
+ console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.teeEnd)} ${(0, ui_js_1.cyan)(`${result.outDir}/${file}`)}`);
405
460
  }
406
461
  // Usage hint
407
462
  (0, ui_js_1.newline)();
@@ -449,9 +504,11 @@ async function cmdPush(args, config) {
449
504
  for (const a of diff.alter) {
450
505
  console.log(` ${(0, ui_js_1.yellow)(ui_js_1.symbols.arrowRight)} ${a.table}`);
451
506
  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('_', ' '));
507
+ const actionLabel = col.action === 'add'
508
+ ? (0, ui_js_1.green)('+ add')
509
+ : col.action === 'drop'
510
+ ? (0, ui_js_1.red)('- drop')
511
+ : (0, ui_js_1.yellow)(`~ ${col.action.replace('_', ' ')}`);
455
512
  console.log(` ${actionLabel} ${col.column}`);
456
513
  }
457
514
  }
@@ -505,17 +562,21 @@ async function cmdMigrate(args, config) {
505
562
  console.log(` ${(0, ui_js_1.bold)('turbine migrate')} ${(0, ui_js_1.dim)('— SQL-first migration system')}`);
506
563
  (0, ui_js_1.newline)();
507
564
  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`);
565
+ console.log(` ${(0, ui_js_1.cyan)('create <name>')} Create a new migration file`);
566
+ console.log(` ${(0, ui_js_1.cyan)('create <name> --auto')} Auto-generate from schema diff`);
567
+ console.log(` ${(0, ui_js_1.cyan)('up')} Apply pending migrations`);
568
+ console.log(` ${(0, ui_js_1.cyan)('down')} Rollback last migration`);
569
+ console.log(` ${(0, ui_js_1.cyan)('status')} Show migration status`);
512
570
  (0, ui_js_1.newline)();
513
571
  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`);
572
+ console.log(` ${(0, ui_js_1.cyan)('--auto')} Auto-generate UP/DOWN SQL from schema diff`);
573
+ console.log(` ${(0, ui_js_1.cyan)('--step, -n')} Number of migrations to apply/rollback`);
574
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
575
+ console.log(` ${(0, ui_js_1.cyan)('--allow-drift')} Bypass checksum validation on ${(0, ui_js_1.cyan)('migrate up')} ${(0, ui_js_1.dim)('(advanced)')}`);
516
576
  (0, ui_js_1.newline)();
517
577
  console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
518
578
  console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create add_users_table')}`);
579
+ console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create add_email_index --auto')}`);
519
580
  console.log(` ${(0, ui_js_1.dim)('npx turbine migrate up')}`);
520
581
  console.log(` ${(0, ui_js_1.dim)('npx turbine migrate down --step 2')}`);
521
582
  (0, ui_js_1.newline)();
@@ -549,9 +610,61 @@ async function cmdMigrateCreate(args, config) {
549
610
  (0, ui_js_1.newline)();
550
611
  console.log(` ${(0, ui_js_1.dim)('Usage:')} ${(0, ui_js_1.cyan)('npx turbine migrate create <name>')}`);
551
612
  console.log(` ${(0, ui_js_1.dim)('Example:')} ${(0, ui_js_1.cyan)('npx turbine migrate create add_users_table')}`);
613
+ console.log(` ${(0, ui_js_1.dim)('Auto:')} ${(0, ui_js_1.cyan)('npx turbine migrate create my_change --auto')}`);
552
614
  (0, ui_js_1.newline)();
553
615
  process.exit(1);
554
616
  }
617
+ if (args.auto) {
618
+ // Auto-generate migration from schema diff
619
+ const url = requireUrl(config);
620
+ (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
621
+ (0, ui_js_1.label)('Schema file', config.schemaFile);
622
+ (0, ui_js_1.newline)();
623
+ const schemaDef = await loadSchemaFile(config.schemaFile);
624
+ const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
625
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
626
+ if (diff.statements.length === 0) {
627
+ diffSpinner.succeed('Database is already in sync — nothing to migrate');
628
+ (0, ui_js_1.newline)();
629
+ return;
630
+ }
631
+ diffSpinner.succeed(`Found ${(0, ui_js_1.bold)(String(diff.statements.length))} change(s)`);
632
+ (0, ui_js_1.newline)();
633
+ const upSQL = diff.statements.join('\n');
634
+ const downSQL = diff.reverseStatements.join('\n');
635
+ const file = (0, migrate_js_1.createMigration)(config.migrationsDir, name, { up: upSQL, down: downSQL });
636
+ const relPath = (0, node_path_1.relative)(process.cwd(), file.path);
637
+ (0, ui_js_1.success)(`Created auto-migration: ${(0, ui_js_1.bold)(file.filename)}`);
638
+ (0, ui_js_1.newline)();
639
+ console.log(` ${(0, ui_js_1.dim)('File:')} ${(0, ui_js_1.cyan)(relPath)}`);
640
+ (0, ui_js_1.newline)();
641
+ // Show summary of changes
642
+ if (diff.create.length > 0) {
643
+ console.log(` ${(0, ui_js_1.green)('+ Create')} ${diff.create.length} table(s): ${diff.create.map((t) => t.name).join(', ')}`);
644
+ }
645
+ if (diff.alter.length > 0) {
646
+ console.log(` ${(0, ui_js_1.yellow)('~ Alter')} ${diff.alter.length} table(s):`);
647
+ for (const a of diff.alter) {
648
+ for (const col of a.columns) {
649
+ const actionLabel = col.action === 'add'
650
+ ? (0, ui_js_1.green)('+ add')
651
+ : col.action === 'drop'
652
+ ? (0, ui_js_1.red)('- drop')
653
+ : col.action === 'add_unique'
654
+ ? (0, ui_js_1.green)('+ unique')
655
+ : col.action === 'drop_unique'
656
+ ? (0, ui_js_1.red)('- unique')
657
+ : (0, ui_js_1.yellow)(`~ ${col.action.replace(/_/g, ' ')}`);
658
+ console.log(` ${actionLabel} ${a.table}.${col.column}`);
659
+ }
660
+ }
661
+ }
662
+ (0, ui_js_1.newline)();
663
+ console.log(` ${(0, ui_js_1.dim)('Review the migration, then run:')}`);
664
+ console.log(` ${(0, ui_js_1.cyan)('npx turbine migrate up')}`);
665
+ (0, ui_js_1.newline)();
666
+ return;
667
+ }
555
668
  const file = (0, migrate_js_1.createMigration)(config.migrationsDir, name);
556
669
  const relPath = (0, node_path_1.relative)(process.cwd(), file.path);
557
670
  (0, ui_js_1.success)(`Created migration: ${(0, ui_js_1.bold)(file.filename)}`);
@@ -575,9 +688,18 @@ async function cmdMigrateUp(args, config) {
575
688
  (0, ui_js_1.newline)();
576
689
  return;
577
690
  }
691
+ // Big, loud warning when bypassing drift detection — this is a deliberately
692
+ // dangerous operation and the user should see it on every invocation.
693
+ if (args.allowDrift) {
694
+ (0, ui_js_1.warn)('--allow-drift is set — checksum validation is DISABLED for this run.');
695
+ console.log(` ${(0, ui_js_1.dim)('Applied migrations may have been modified or deleted on disk.')}`);
696
+ console.log(` ${(0, ui_js_1.dim)('Proceed only if you are intentionally rewriting migration history.')}`);
697
+ (0, ui_js_1.newline)();
698
+ }
578
699
  const spinner = new ui_js_1.Spinner('Applying migrations').start();
579
700
  const result = await (0, migrate_js_1.migrateUp)(url, config.migrationsDir, {
580
701
  step: args.step,
702
+ allowDrift: args.allowDrift,
581
703
  });
582
704
  if (result.applied.length === 0 && result.errors.length === 0) {
583
705
  spinner.succeed('All migrations are up to date');
@@ -633,7 +755,7 @@ async function cmdMigrateDown(args, config) {
633
755
  }
634
756
  (0, ui_js_1.newline)();
635
757
  }
636
- async function cmdMigrateStatus(args, config) {
758
+ async function cmdMigrateStatus(_args, config) {
637
759
  (0, ui_js_1.banner)();
638
760
  const url = requireUrl(config);
639
761
  (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
@@ -663,19 +785,22 @@ async function cmdMigrateStatus(args, config) {
663
785
  const rows = statuses.map((s) => {
664
786
  let status;
665
787
  if (s.applied && s.checksumValid === false) {
666
- status = (0, ui_js_1.red)(ui_js_1.symbols.warning + ' Drifted');
788
+ status = (0, ui_js_1.red)(`${ui_js_1.symbols.warning} Drifted`);
667
789
  }
668
790
  else if (s.applied) {
669
- status = (0, ui_js_1.green)(ui_js_1.symbols.check + ' Applied');
791
+ status = (0, ui_js_1.green)(`${ui_js_1.symbols.check} Applied`);
670
792
  }
671
793
  else {
672
- status = (0, ui_js_1.yellow)(ui_js_1.symbols.dot + ' Pending');
794
+ status = (0, ui_js_1.yellow)(`${ui_js_1.symbols.dot} Pending`);
673
795
  }
674
796
  return [
675
797
  status,
676
798
  s.file.filename,
677
799
  s.appliedAt
678
- ? (0, ui_js_1.dim)(s.appliedAt.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC'))
800
+ ? (0, ui_js_1.dim)(s.appliedAt
801
+ .toISOString()
802
+ .replace('T', ' ')
803
+ .replace(/\.\d+Z$/, ' UTC'))
679
804
  : (0, ui_js_1.dim)('—'),
680
805
  ];
681
806
  });
@@ -689,7 +814,7 @@ async function cmdMigrateStatus(args, config) {
689
814
  // ---------------------------------------------------------------------------
690
815
  // Command: seed
691
816
  // ---------------------------------------------------------------------------
692
- async function cmdSeed(args, config) {
817
+ async function cmdSeed(_args, config) {
693
818
  (0, ui_js_1.banner)();
694
819
  const seedFile = (0, node_path_1.resolve)(config.seedFile);
695
820
  (0, ui_js_1.label)('Seed file', config.seedFile);
@@ -705,22 +830,20 @@ async function cmdSeed(args, config) {
705
830
  const spinner = new ui_js_1.Spinner('Running seed file').start();
706
831
  try {
707
832
  // Use child_process to run the seed file via tsx or node
708
- const { execSync } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
833
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
709
834
  // Try tsx first (most compatible with .ts files), fall back to node --experimental-strip-types
710
835
  const runners = [
711
- { cmd: 'npx tsx', name: 'tsx' },
712
- { cmd: 'node --experimental-strip-types', name: 'node' },
836
+ { cmd: 'npx', args: ['tsx', seedFile], name: 'tsx' },
837
+ { cmd: 'node', args: ['--experimental-strip-types', seedFile], name: 'node' },
713
838
  ];
714
- // Shell-escape the seed file path to prevent injection
715
- const escapedSeedFile = seedFile.replace(/'/g, "'\\''");
716
839
  let ran = false;
717
840
  for (const runner of runners) {
718
841
  try {
719
- execSync(`${runner.cmd} '${escapedSeedFile}'`, {
842
+ execFileSync(runner.cmd, runner.args, {
720
843
  stdio: 'inherit',
721
844
  env: {
722
845
  ...process.env,
723
- DATABASE_URL: config.url || process.env['DATABASE_URL'],
846
+ DATABASE_URL: config.url || process.env.DATABASE_URL,
724
847
  },
725
848
  });
726
849
  ran = true;
@@ -742,7 +865,7 @@ async function cmdSeed(args, config) {
742
865
  catch (err) {
743
866
  spinner.fail('Seed failed');
744
867
  if (err instanceof Error) {
745
- console.log(` ${(0, ui_js_1.dim)(err.message)}`);
868
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
746
869
  }
747
870
  (0, ui_js_1.newline)();
748
871
  process.exit(1);
@@ -752,7 +875,7 @@ async function cmdSeed(args, config) {
752
875
  // ---------------------------------------------------------------------------
753
876
  // Command: status
754
877
  // ---------------------------------------------------------------------------
755
- async function cmdStatus(args, config) {
878
+ async function cmdStatus(_args, config) {
756
879
  (0, ui_js_1.banner)();
757
880
  const url = requireUrl(config);
758
881
  (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
@@ -770,7 +893,7 @@ async function cmdStatus(args, config) {
770
893
  (0, ui_js_1.newline)();
771
894
  for (const tbl of Object.values(schema.tables)) {
772
895
  const relCount = Object.keys(tbl.relations).length;
773
- const pk = tbl.primaryKey.join(', ') || (0, ui_js_1.dim)('(none)');
896
+ const _pk = tbl.primaryKey.join(', ') || (0, ui_js_1.dim)('(none)');
774
897
  console.log(` ${(0, ui_js_1.bold)((0, ui_js_1.cyan)(tbl.name))}`);
775
898
  for (let i = 0; i < tbl.columns.length; i++) {
776
899
  const col = tbl.columns[i];
@@ -779,7 +902,7 @@ async function cmdStatus(args, config) {
779
902
  const nullable = col.nullable ? (0, ui_js_1.dim)('?') : '';
780
903
  const def = col.hasDefault ? (0, ui_js_1.dim)(' (default)') : '';
781
904
  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)}`);
905
+ 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
906
  }
784
907
  const rels = Object.entries(tbl.relations);
785
908
  if (rels.length > 0) {
@@ -812,11 +935,139 @@ async function cmdStudio(_args, _config) {
812
935
  'A local web UI for browsing your database,',
813
936
  'exploring relations, and managing data.',
814
937
  '',
815
- `Follow ${(0, ui_js_1.cyan)('@batadata')} for updates.`,
938
+ `Follow ${(0, ui_js_1.cyan)('@turbineorm')} for updates.`,
816
939
  ].join('\n'), { title: (0, ui_js_1.bold)((0, ui_js_1.cyan)('Studio')), padding: 2 }));
817
940
  (0, ui_js_1.newline)();
818
941
  }
819
942
  // ---------------------------------------------------------------------------
943
+ // Subcommand help
944
+ // ---------------------------------------------------------------------------
945
+ function showSubcommandHelp(command) {
946
+ const helpMap = {
947
+ init: showInitHelp,
948
+ generate: showGenerateHelp,
949
+ pull: showGenerateHelp,
950
+ push: showPushHelp,
951
+ migrate: showMigrateHelp,
952
+ migration: showMigrateHelp,
953
+ seed: showSeedHelp,
954
+ status: showStatusHelp,
955
+ };
956
+ const fn = helpMap[command];
957
+ if (fn) {
958
+ fn();
959
+ return true;
960
+ }
961
+ return false;
962
+ }
963
+ function showInitHelp() {
964
+ (0, ui_js_1.banner)();
965
+ console.log(` ${(0, ui_js_1.bold)('turbine init')} — Initialize a Turbine project`);
966
+ (0, ui_js_1.newline)();
967
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
968
+ console.log(` npx turbine init ${(0, ui_js_1.dim)('[options]')}`);
969
+ (0, ui_js_1.newline)();
970
+ console.log(` Creates ${(0, ui_js_1.cyan)('turbine.config.ts')}, migrations directory, seed file template,`);
971
+ console.log(` and schema file template.`);
972
+ (0, ui_js_1.newline)();
973
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
974
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string to embed in config`);
975
+ console.log(` ${(0, ui_js_1.cyan)('--force, -f')} Overwrite existing config file`);
976
+ (0, ui_js_1.newline)();
977
+ }
978
+ function showGenerateHelp() {
979
+ (0, ui_js_1.banner)();
980
+ console.log(` ${(0, ui_js_1.bold)('turbine generate')} — Introspect database and generate TypeScript types`);
981
+ (0, ui_js_1.newline)();
982
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
983
+ console.log(` npx turbine generate ${(0, ui_js_1.dim)('[options]')}`);
984
+ (0, ui_js_1.newline)();
985
+ console.log(` Connects to your database, reads the schema, and generates:`);
986
+ console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('types.ts')} — Entity interfaces, Create/Update input types`);
987
+ console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('metadata.ts')} — Runtime schema metadata`);
988
+ console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('index.ts')} — Configured client with typed table accessors`);
989
+ (0, ui_js_1.newline)();
990
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
991
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
992
+ 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)')}`);
993
+ console.log(` ${(0, ui_js_1.cyan)('--schema, -s')} ${(0, ui_js_1.dim)('<name>')} Postgres schema ${(0, ui_js_1.dim)('(default: public)')}`);
994
+ console.log(` ${(0, ui_js_1.cyan)('--include')} ${(0, ui_js_1.dim)('<tables>')} Comma-separated tables to include`);
995
+ console.log(` ${(0, ui_js_1.cyan)('--exclude')} ${(0, ui_js_1.dim)('<tables>')} Comma-separated tables to exclude`);
996
+ (0, ui_js_1.newline)();
997
+ }
998
+ function showPushHelp() {
999
+ (0, ui_js_1.banner)();
1000
+ console.log(` ${(0, ui_js_1.bold)('turbine push')} — Apply schema-builder definitions to database`);
1001
+ (0, ui_js_1.newline)();
1002
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
1003
+ console.log(` npx turbine push ${(0, ui_js_1.dim)('[options]')}`);
1004
+ (0, ui_js_1.newline)();
1005
+ console.log(` Reads your ${(0, ui_js_1.cyan)('turbine/schema.ts')} file, diffs against the live database,`);
1006
+ console.log(` and applies CREATE/ALTER statements.`);
1007
+ (0, ui_js_1.newline)();
1008
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
1009
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
1010
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
1011
+ console.log(` ${(0, ui_js_1.cyan)('--verbose, -v')} Show detailed output`);
1012
+ (0, ui_js_1.newline)();
1013
+ }
1014
+ function showMigrateHelp() {
1015
+ (0, ui_js_1.banner)();
1016
+ console.log(` ${(0, ui_js_1.bold)('turbine migrate')} — SQL migration management`);
1017
+ (0, ui_js_1.newline)();
1018
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
1019
+ console.log(` npx turbine migrate ${(0, ui_js_1.cyan)('<subcommand>')} ${(0, ui_js_1.dim)('[options]')}`);
1020
+ (0, ui_js_1.newline)();
1021
+ console.log(` ${(0, ui_js_1.bold)('Subcommands:')}`);
1022
+ console.log(` ${(0, ui_js_1.cyan)('create')} ${(0, ui_js_1.dim)('<name>')} Create a new migration file`);
1023
+ console.log(` ${(0, ui_js_1.cyan)('up')} Apply pending migrations`);
1024
+ console.log(` ${(0, ui_js_1.cyan)('down')} Rollback last migration`);
1025
+ console.log(` ${(0, ui_js_1.cyan)('status')} Show applied/pending migrations`);
1026
+ (0, ui_js_1.newline)();
1027
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
1028
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
1029
+ console.log(` ${(0, ui_js_1.cyan)('--step, -n')} ${(0, ui_js_1.dim)('<N>')} Number of migrations to apply/rollback`);
1030
+ console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
1031
+ console.log(` ${(0, ui_js_1.cyan)('--allow-drift')} Bypass checksum validation ${(0, ui_js_1.dim)('(migrate up only — advanced)')}`);
1032
+ console.log(` ${(0, ui_js_1.cyan)('--verbose, -v')} Show detailed output`);
1033
+ (0, ui_js_1.newline)();
1034
+ console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
1035
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create add_users_table`);
1036
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate up`);
1037
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate down --step 2`);
1038
+ console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate status`);
1039
+ (0, ui_js_1.newline)();
1040
+ }
1041
+ function showSeedHelp() {
1042
+ (0, ui_js_1.banner)();
1043
+ console.log(` ${(0, ui_js_1.bold)('turbine seed')} — Run seed file`);
1044
+ (0, ui_js_1.newline)();
1045
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
1046
+ console.log(` npx turbine seed ${(0, ui_js_1.dim)('[options]')}`);
1047
+ (0, ui_js_1.newline)();
1048
+ console.log(` Runs the seed file specified in ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
1049
+ console.log(` ${(0, ui_js_1.dim)('(default: ./turbine/seed.ts)')}`);
1050
+ (0, ui_js_1.newline)();
1051
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
1052
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
1053
+ (0, ui_js_1.newline)();
1054
+ }
1055
+ function showStatusHelp() {
1056
+ (0, ui_js_1.banner)();
1057
+ console.log(` ${(0, ui_js_1.bold)('turbine status')} — Show database schema summary`);
1058
+ (0, ui_js_1.newline)();
1059
+ console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
1060
+ console.log(` npx turbine status ${(0, ui_js_1.dim)('[options]')}`);
1061
+ (0, ui_js_1.newline)();
1062
+ console.log(` Introspects your database and displays tables, columns,`);
1063
+ console.log(` types, relations, and indexes.`);
1064
+ (0, ui_js_1.newline)();
1065
+ console.log(` ${(0, ui_js_1.bold)('Options:')}`);
1066
+ console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
1067
+ console.log(` ${(0, ui_js_1.cyan)('--schema, -s')} ${(0, ui_js_1.dim)('<name>')} Postgres schema ${(0, ui_js_1.dim)('(default: public)')}`);
1068
+ (0, ui_js_1.newline)();
1069
+ }
1070
+ // ---------------------------------------------------------------------------
820
1071
  // Help
821
1072
  // ---------------------------------------------------------------------------
822
1073
  function showHelp() {
@@ -875,10 +1126,27 @@ async function main() {
875
1126
  showHelp();
876
1127
  return;
877
1128
  }
1129
+ // Subcommand help: e.g. `turbine migrate --help`
1130
+ if (args.help) {
1131
+ if (showSubcommandHelp(args.command))
1132
+ return;
1133
+ showHelp();
1134
+ return;
1135
+ }
878
1136
  if (args.command === 'version' || args.command === '--version' || args.command === '-V') {
879
1137
  showVersion();
880
1138
  return;
881
1139
  }
1140
+ // If the user has a TypeScript config file, register the tsx ESM loader
1141
+ // before we attempt to import it. Otherwise Node throws
1142
+ // ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
1143
+ const configPath = (0, config_js_1.findConfigFile)();
1144
+ if ((0, loader_js_1.needsTsLoader)(configPath)) {
1145
+ const status = await (0, loader_js_1.registerTsLoader)();
1146
+ if (status === 'missing' || status === 'unsupported') {
1147
+ failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
1148
+ }
1149
+ }
882
1150
  // Load config file
883
1151
  let fileConfig = {};
884
1152
  try {
@@ -940,7 +1208,7 @@ async function main() {
940
1208
  if (err.message.includes('ECONNREFUSED') || err.message.includes('connection')) {
941
1209
  (0, ui_js_1.newline)();
942
1210
  (0, ui_js_1.error)(`Could not connect to database`);
943
- console.log(` ${(0, ui_js_1.dim)(err.message)}`);
1211
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
944
1212
  (0, ui_js_1.newline)();
945
1213
  console.log(` ${(0, ui_js_1.dim)('Check that:')}`);
946
1214
  console.log(` ${(0, ui_js_1.dim)('1.')} Your database is running`);
@@ -950,25 +1218,25 @@ async function main() {
950
1218
  else if (err.message.includes('authentication')) {
951
1219
  (0, ui_js_1.newline)();
952
1220
  (0, ui_js_1.error)(`Authentication failed`);
953
- console.log(` ${(0, ui_js_1.dim)(err.message)}`);
1221
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
954
1222
  }
955
1223
  else if (err.message.includes('does not exist')) {
956
1224
  (0, ui_js_1.newline)();
957
1225
  (0, ui_js_1.error)(`Database or schema not found`);
958
- console.log(` ${(0, ui_js_1.dim)(err.message)}`);
1226
+ console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
959
1227
  }
960
1228
  else {
961
1229
  (0, ui_js_1.newline)();
962
- (0, ui_js_1.error)(err.message);
1230
+ (0, ui_js_1.error)((0, ui_js_1.redactUrl)(err.message));
963
1231
  if (args.verbose && err.stack) {
964
1232
  (0, ui_js_1.newline)();
965
- console.log((0, ui_js_1.dim)(err.stack));
1233
+ console.log((0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.stack)));
966
1234
  }
967
1235
  }
968
1236
  }
969
1237
  else {
970
1238
  (0, ui_js_1.newline)();
971
- (0, ui_js_1.error)(`Unexpected error: ${String(err)}`);
1239
+ (0, ui_js_1.error)(`Unexpected error: ${(0, ui_js_1.redactUrl)(String(err))}`);
972
1240
  }
973
1241
  (0, ui_js_1.newline)();
974
1242
  process.exit(1);