turbine-orm 0.5.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 (46) hide show
  1. package/README.md +194 -26
  2. package/dist/cjs/cli/config.js +5 -15
  3. package/dist/cjs/cli/index.js +240 -41
  4. package/dist/cjs/cli/migrate.js +71 -46
  5. package/dist/cjs/cli/ui.js +5 -9
  6. package/dist/cjs/client.js +109 -46
  7. package/dist/cjs/errors.js +293 -0
  8. package/dist/cjs/generate.js +33 -13
  9. package/dist/cjs/index.js +39 -20
  10. package/dist/cjs/introspect.js +3 -5
  11. package/dist/cjs/pipeline.js +9 -2
  12. package/dist/cjs/query.js +442 -109
  13. package/dist/cjs/schema-builder.js +93 -24
  14. package/dist/cjs/schema-sql.js +157 -19
  15. package/dist/cjs/schema.js +5 -2
  16. package/dist/cjs/serverless.js +87 -176
  17. package/dist/cli/config.js +6 -16
  18. package/dist/cli/index.js +245 -46
  19. package/dist/cli/migrate.d.ts +6 -1
  20. package/dist/cli/migrate.js +72 -47
  21. package/dist/cli/ui.js +5 -9
  22. package/dist/client.d.ts +77 -4
  23. package/dist/client.js +109 -46
  24. package/dist/errors.d.ts +138 -0
  25. package/dist/errors.js +278 -0
  26. package/dist/generate.d.ts +1 -1
  27. package/dist/generate.js +36 -16
  28. package/dist/index.d.ts +11 -9
  29. package/dist/index.js +16 -12
  30. package/dist/introspect.d.ts +1 -1
  31. package/dist/introspect.js +4 -6
  32. package/dist/pipeline.d.ts +1 -1
  33. package/dist/pipeline.js +9 -2
  34. package/dist/query.d.ts +257 -36
  35. package/dist/query.js +443 -110
  36. package/dist/schema-builder.d.ts +2 -2
  37. package/dist/schema-builder.js +93 -25
  38. package/dist/schema-sql.d.ts +7 -3
  39. package/dist/schema-sql.js +157 -19
  40. package/dist/schema.d.ts +1 -1
  41. package/dist/schema.js +5 -2
  42. package/dist/serverless.d.ts +91 -139
  43. package/dist/serverless.js +86 -173
  44. package/package.json +33 -16
  45. package/dist/types.d.ts +0 -93
  46. package/dist/types.js +0 -126
package/dist/cli/index.js CHANGED
@@ -19,15 +19,15 @@
19
19
  * npx turbine init --url postgres://...
20
20
  * npx turbine migrate create add_users_table
21
21
  */
22
- import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'node:fs';
23
- import { resolve, relative } from 'node:path';
24
- import { introspect } from '../introspect.js';
22
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
23
+ import { relative, resolve } from 'node:path';
24
+ import { pathToFileURL } from 'node:url';
25
25
  import { generate } from '../generate.js';
26
+ import { introspect } from '../introspect.js';
26
27
  import { schemaDiff, schemaPush } from '../schema-sql.js';
27
- import { loadConfig, resolveConfig, findConfigFile, configTemplate } from './config.js';
28
- import { createMigration, migrateUp, migrateDown, migrateStatus, listMigrationFiles, } from './migrate.js';
29
- import { bold, dim, red, green, yellow, blue, cyan, gray, magenta, greenBright, cyanBright, yellowBright, symbols, box, table as formatTable, Spinner, header, success, error, warn, info, label, newline, divider, banner, elapsed, redactUrl, } from './ui.js';
30
- import { pathToFileURL } from 'node:url';
28
+ import { configTemplate, findConfigFile, loadConfig, resolveConfig } from './config.js';
29
+ import { createMigration, listMigrationFiles, migrateDown, migrateStatus, migrateUp } from './migrate.js';
30
+ import { banner, blue, bold, box, cyan, dim, divider, elapsed, error, table as formatTable, gray, green, header, info, label, magenta, newline, red, redactUrl, Spinner, success, symbols, warn, yellow, } from './ui.js';
31
31
  function parseArgs() {
32
32
  const args = process.argv.slice(2);
33
33
  const result = {
@@ -75,6 +75,9 @@ function parseArgs() {
75
75
  case '--dry-run':
76
76
  result.dryRun = true;
77
77
  break;
78
+ case '--auto':
79
+ result.auto = true;
80
+ break;
78
81
  case '--force':
79
82
  case '-f':
80
83
  result.force = true;
@@ -83,6 +86,10 @@ function parseArgs() {
83
86
  case '-v':
84
87
  result.verbose = true;
85
88
  break;
89
+ case '--help':
90
+ case '-h':
91
+ result.help = true;
92
+ break;
86
93
  default:
87
94
  if (!arg.startsWith('-')) {
88
95
  result.positional.push(arg);
@@ -140,7 +147,7 @@ async function cmdInit(args, config) {
140
147
  banner();
141
148
  header('Initializing Turbine project');
142
149
  // Detect environment
143
- const envUrl = process.env['DATABASE_URL'];
150
+ const envUrl = process.env.DATABASE_URL;
144
151
  const hasEnvFile = existsSync('.env');
145
152
  const hasEnvLocal = existsSync('.env.local');
146
153
  if (envUrl) {
@@ -179,7 +186,7 @@ async function cmdInit(args, config) {
179
186
  mkdirSync(migrDir, { recursive: true });
180
187
  // Create .gitkeep
181
188
  writeFileSync(`${migrDir}/.gitkeep`, '', 'utf-8');
182
- success(`Created ${cyan(migrDir + '/')}`);
189
+ success(`Created ${cyan(`${migrDir}/`)}`);
183
190
  }
184
191
  else {
185
192
  info(`Migrations dir already exists: ${dim(migrDir)}`);
@@ -187,7 +194,7 @@ async function cmdInit(args, config) {
187
194
  // Create output directory
188
195
  if (!existsSync(config.out)) {
189
196
  mkdirSync(config.out, { recursive: true });
190
- success(`Created ${cyan(config.out + '/')}`);
197
+ success(`Created ${cyan(`${config.out}/`)}`);
191
198
  }
192
199
  // Create seed file template
193
200
  const seedDir = config.seedFile.substring(0, config.seedFile.lastIndexOf('/'));
@@ -231,7 +238,7 @@ async function cmdInit(args, config) {
231
238
  * Define your database schema in TypeScript.
232
239
  * Use \`npx turbine push\` to sync it to your database.
233
240
  *
234
- * @see https://batadata.com/docs/turbine/schema
241
+ * @see https://turbineorm.dev
235
242
  */
236
243
 
237
244
  import { defineSchema } from 'turbine-orm';
@@ -282,15 +289,15 @@ export default defineSchema({
282
289
  spinner.succeed(`Found ${bold(String(tableCount))} tables`);
283
290
  const genSpinner = new Spinner('Generating TypeScript client').start();
284
291
  const result = generate({ schema, outDir: config.out, connectionString: url });
285
- genSpinner.succeed(`Generated ${bold(String(result.files.length))} files to ${cyan(config.out + '/')}`);
292
+ genSpinner.succeed(`Generated ${bold(String(result.files.length))} files to ${cyan(`${config.out}/`)}`);
286
293
  }
287
294
  catch (err) {
288
295
  spinner.fail('Could not connect to database');
289
296
  if (err instanceof Error) {
290
- console.log(` ${dim(err.message)}`);
297
+ console.log(` ${dim(redactUrl(err.message))}`);
291
298
  }
292
299
  newline();
293
- info('You can run generation later with: ' + cyan('npx turbine generate'));
300
+ info(`You can run generation later with: ${cyan('npx turbine generate')}`);
294
301
  }
295
302
  }
296
303
  // Next steps
@@ -366,7 +373,7 @@ async function cmdGenerate(args, config) {
366
373
  genSpinner.succeed(`Generated ${bold(String(result.files.length))} files in ${elapsed(startTime)}`);
367
374
  // List files
368
375
  for (const file of result.files) {
369
- console.log(` ${dim(symbols.teeEnd)} ${cyan(result.outDir + '/' + file)}`);
376
+ console.log(` ${dim(symbols.teeEnd)} ${cyan(`${result.outDir}/${file}`)}`);
370
377
  }
371
378
  // Usage hint
372
379
  newline();
@@ -414,9 +421,11 @@ async function cmdPush(args, config) {
414
421
  for (const a of diff.alter) {
415
422
  console.log(` ${yellow(symbols.arrowRight)} ${a.table}`);
416
423
  for (const col of a.columns) {
417
- const actionLabel = col.action === 'add' ? green('+ add') :
418
- col.action === 'drop' ? red('- drop') :
419
- yellow('~ ' + col.action.replace('_', ' '));
424
+ const actionLabel = col.action === 'add'
425
+ ? green('+ add')
426
+ : col.action === 'drop'
427
+ ? red('- drop')
428
+ : yellow(`~ ${col.action.replace('_', ' ')}`);
420
429
  console.log(` ${actionLabel} ${col.column}`);
421
430
  }
422
431
  }
@@ -470,17 +479,20 @@ async function cmdMigrate(args, config) {
470
479
  console.log(` ${bold('turbine migrate')} ${dim('— SQL-first migration system')}`);
471
480
  newline();
472
481
  console.log(` ${bold('Commands:')}`);
473
- console.log(` ${cyan('create <name>')} Create a new migration file`);
474
- console.log(` ${cyan('up')} Apply pending migrations`);
475
- console.log(` ${cyan('down')} Rollback last migration`);
476
- console.log(` ${cyan('status')} Show migration status`);
482
+ console.log(` ${cyan('create <name>')} Create a new migration file`);
483
+ console.log(` ${cyan('create <name> --auto')} Auto-generate from schema diff`);
484
+ console.log(` ${cyan('up')} Apply pending migrations`);
485
+ console.log(` ${cyan('down')} Rollback last migration`);
486
+ console.log(` ${cyan('status')} Show migration status`);
477
487
  newline();
478
488
  console.log(` ${bold('Options:')}`);
489
+ console.log(` ${cyan('--auto')} Auto-generate UP/DOWN SQL from schema diff`);
479
490
  console.log(` ${cyan('--step, -n')} Number of migrations to apply/rollback`);
480
491
  console.log(` ${cyan('--dry-run')} Show SQL without executing`);
481
492
  newline();
482
493
  console.log(` ${bold('Examples:')}`);
483
494
  console.log(` ${dim('npx turbine migrate create add_users_table')}`);
495
+ console.log(` ${dim('npx turbine migrate create add_email_index --auto')}`);
484
496
  console.log(` ${dim('npx turbine migrate up')}`);
485
497
  console.log(` ${dim('npx turbine migrate down --step 2')}`);
486
498
  newline();
@@ -514,9 +526,61 @@ async function cmdMigrateCreate(args, config) {
514
526
  newline();
515
527
  console.log(` ${dim('Usage:')} ${cyan('npx turbine migrate create <name>')}`);
516
528
  console.log(` ${dim('Example:')} ${cyan('npx turbine migrate create add_users_table')}`);
529
+ console.log(` ${dim('Auto:')} ${cyan('npx turbine migrate create my_change --auto')}`);
517
530
  newline();
518
531
  process.exit(1);
519
532
  }
533
+ if (args.auto) {
534
+ // Auto-generate migration from schema diff
535
+ const url = requireUrl(config);
536
+ label('Database', redactUrl(url));
537
+ label('Schema file', config.schemaFile);
538
+ newline();
539
+ const schemaDef = await loadSchemaFile(config.schemaFile);
540
+ const diffSpinner = new Spinner('Computing schema diff').start();
541
+ const diff = await schemaDiff(schemaDef, url);
542
+ if (diff.statements.length === 0) {
543
+ diffSpinner.succeed('Database is already in sync — nothing to migrate');
544
+ newline();
545
+ return;
546
+ }
547
+ diffSpinner.succeed(`Found ${bold(String(diff.statements.length))} change(s)`);
548
+ newline();
549
+ const upSQL = diff.statements.join('\n');
550
+ const downSQL = diff.reverseStatements.join('\n');
551
+ const file = createMigration(config.migrationsDir, name, { up: upSQL, down: downSQL });
552
+ const relPath = relative(process.cwd(), file.path);
553
+ success(`Created auto-migration: ${bold(file.filename)}`);
554
+ newline();
555
+ console.log(` ${dim('File:')} ${cyan(relPath)}`);
556
+ newline();
557
+ // Show summary of changes
558
+ if (diff.create.length > 0) {
559
+ console.log(` ${green('+ Create')} ${diff.create.length} table(s): ${diff.create.map((t) => t.name).join(', ')}`);
560
+ }
561
+ if (diff.alter.length > 0) {
562
+ console.log(` ${yellow('~ Alter')} ${diff.alter.length} table(s):`);
563
+ for (const a of diff.alter) {
564
+ for (const col of a.columns) {
565
+ const actionLabel = col.action === 'add'
566
+ ? green('+ add')
567
+ : col.action === 'drop'
568
+ ? red('- drop')
569
+ : col.action === 'add_unique'
570
+ ? green('+ unique')
571
+ : col.action === 'drop_unique'
572
+ ? red('- unique')
573
+ : yellow(`~ ${col.action.replace(/_/g, ' ')}`);
574
+ console.log(` ${actionLabel} ${a.table}.${col.column}`);
575
+ }
576
+ }
577
+ }
578
+ newline();
579
+ console.log(` ${dim('Review the migration, then run:')}`);
580
+ console.log(` ${cyan('npx turbine migrate up')}`);
581
+ newline();
582
+ return;
583
+ }
520
584
  const file = createMigration(config.migrationsDir, name);
521
585
  const relPath = relative(process.cwd(), file.path);
522
586
  success(`Created migration: ${bold(file.filename)}`);
@@ -598,7 +662,7 @@ async function cmdMigrateDown(args, config) {
598
662
  }
599
663
  newline();
600
664
  }
601
- async function cmdMigrateStatus(args, config) {
665
+ async function cmdMigrateStatus(_args, config) {
602
666
  banner();
603
667
  const url = requireUrl(config);
604
668
  label('Database', redactUrl(url));
@@ -628,19 +692,22 @@ async function cmdMigrateStatus(args, config) {
628
692
  const rows = statuses.map((s) => {
629
693
  let status;
630
694
  if (s.applied && s.checksumValid === false) {
631
- status = red(symbols.warning + ' Drifted');
695
+ status = red(`${symbols.warning} Drifted`);
632
696
  }
633
697
  else if (s.applied) {
634
- status = green(symbols.check + ' Applied');
698
+ status = green(`${symbols.check} Applied`);
635
699
  }
636
700
  else {
637
- status = yellow(symbols.dot + ' Pending');
701
+ status = yellow(`${symbols.dot} Pending`);
638
702
  }
639
703
  return [
640
704
  status,
641
705
  s.file.filename,
642
706
  s.appliedAt
643
- ? dim(s.appliedAt.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC'))
707
+ ? dim(s.appliedAt
708
+ .toISOString()
709
+ .replace('T', ' ')
710
+ .replace(/\.\d+Z$/, ' UTC'))
644
711
  : dim('—'),
645
712
  ];
646
713
  });
@@ -654,7 +721,7 @@ async function cmdMigrateStatus(args, config) {
654
721
  // ---------------------------------------------------------------------------
655
722
  // Command: seed
656
723
  // ---------------------------------------------------------------------------
657
- async function cmdSeed(args, config) {
724
+ async function cmdSeed(_args, config) {
658
725
  banner();
659
726
  const seedFile = resolve(config.seedFile);
660
727
  label('Seed file', config.seedFile);
@@ -670,22 +737,20 @@ async function cmdSeed(args, config) {
670
737
  const spinner = new Spinner('Running seed file').start();
671
738
  try {
672
739
  // Use child_process to run the seed file via tsx or node
673
- const { execSync } = await import('node:child_process');
740
+ const { execFileSync } = await import('node:child_process');
674
741
  // Try tsx first (most compatible with .ts files), fall back to node --experimental-strip-types
675
742
  const runners = [
676
- { cmd: 'npx tsx', name: 'tsx' },
677
- { cmd: 'node --experimental-strip-types', name: 'node' },
743
+ { cmd: 'npx', args: ['tsx', seedFile], name: 'tsx' },
744
+ { cmd: 'node', args: ['--experimental-strip-types', seedFile], name: 'node' },
678
745
  ];
679
- // Shell-escape the seed file path to prevent injection
680
- const escapedSeedFile = seedFile.replace(/'/g, "'\\''");
681
746
  let ran = false;
682
747
  for (const runner of runners) {
683
748
  try {
684
- execSync(`${runner.cmd} '${escapedSeedFile}'`, {
749
+ execFileSync(runner.cmd, runner.args, {
685
750
  stdio: 'inherit',
686
751
  env: {
687
752
  ...process.env,
688
- DATABASE_URL: config.url || process.env['DATABASE_URL'],
753
+ DATABASE_URL: config.url || process.env.DATABASE_URL,
689
754
  },
690
755
  });
691
756
  ran = true;
@@ -707,7 +772,7 @@ async function cmdSeed(args, config) {
707
772
  catch (err) {
708
773
  spinner.fail('Seed failed');
709
774
  if (err instanceof Error) {
710
- console.log(` ${dim(err.message)}`);
775
+ console.log(` ${dim(redactUrl(err.message))}`);
711
776
  }
712
777
  newline();
713
778
  process.exit(1);
@@ -717,7 +782,7 @@ async function cmdSeed(args, config) {
717
782
  // ---------------------------------------------------------------------------
718
783
  // Command: status
719
784
  // ---------------------------------------------------------------------------
720
- async function cmdStatus(args, config) {
785
+ async function cmdStatus(_args, config) {
721
786
  banner();
722
787
  const url = requireUrl(config);
723
788
  label('Database', redactUrl(url));
@@ -735,7 +800,7 @@ async function cmdStatus(args, config) {
735
800
  newline();
736
801
  for (const tbl of Object.values(schema.tables)) {
737
802
  const relCount = Object.keys(tbl.relations).length;
738
- const pk = tbl.primaryKey.join(', ') || dim('(none)');
803
+ const _pk = tbl.primaryKey.join(', ') || dim('(none)');
739
804
  console.log(` ${bold(cyan(tbl.name))}`);
740
805
  for (let i = 0; i < tbl.columns.length; i++) {
741
806
  const col = tbl.columns[i];
@@ -744,7 +809,7 @@ async function cmdStatus(args, config) {
744
809
  const nullable = col.nullable ? dim('?') : '';
745
810
  const def = col.hasDefault ? dim(' (default)') : '';
746
811
  const pkLabel = tbl.primaryKey.includes(col.name) ? ` ${magenta('PK')}` : '';
747
- console.log(` ${dim(prefix)} ${col.field}${nullable}: ${green(col.tsType)}${pkLabel}${def} ${gray(symbols.arrow + ' ' + col.pgType)}`);
812
+ console.log(` ${dim(prefix)} ${col.field}${nullable}: ${green(col.tsType)}${pkLabel}${def} ${gray(`${symbols.arrow} ${col.pgType}`)}`);
748
813
  }
749
814
  const rels = Object.entries(tbl.relations);
750
815
  if (rels.length > 0) {
@@ -777,11 +842,138 @@ async function cmdStudio(_args, _config) {
777
842
  'A local web UI for browsing your database,',
778
843
  'exploring relations, and managing data.',
779
844
  '',
780
- `Follow ${cyan('@batadata')} for updates.`,
845
+ `Follow ${cyan('@turbineorm')} for updates.`,
781
846
  ].join('\n'), { title: bold(cyan('Studio')), padding: 2 }));
782
847
  newline();
783
848
  }
784
849
  // ---------------------------------------------------------------------------
850
+ // Subcommand help
851
+ // ---------------------------------------------------------------------------
852
+ function showSubcommandHelp(command) {
853
+ const helpMap = {
854
+ init: showInitHelp,
855
+ generate: showGenerateHelp,
856
+ pull: showGenerateHelp,
857
+ push: showPushHelp,
858
+ migrate: showMigrateHelp,
859
+ migration: showMigrateHelp,
860
+ seed: showSeedHelp,
861
+ status: showStatusHelp,
862
+ };
863
+ const fn = helpMap[command];
864
+ if (fn) {
865
+ fn();
866
+ return true;
867
+ }
868
+ return false;
869
+ }
870
+ function showInitHelp() {
871
+ banner();
872
+ console.log(` ${bold('turbine init')} — Initialize a Turbine project`);
873
+ newline();
874
+ console.log(` ${bold('Usage:')}`);
875
+ console.log(` npx turbine init ${dim('[options]')}`);
876
+ newline();
877
+ console.log(` Creates ${cyan('turbine.config.ts')}, migrations directory, seed file template,`);
878
+ console.log(` and schema file template.`);
879
+ newline();
880
+ console.log(` ${bold('Options:')}`);
881
+ console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string to embed in config`);
882
+ console.log(` ${cyan('--force, -f')} Overwrite existing config file`);
883
+ newline();
884
+ }
885
+ function showGenerateHelp() {
886
+ banner();
887
+ console.log(` ${bold('turbine generate')} — Introspect database and generate TypeScript types`);
888
+ newline();
889
+ console.log(` ${bold('Usage:')}`);
890
+ console.log(` npx turbine generate ${dim('[options]')}`);
891
+ newline();
892
+ console.log(` Connects to your database, reads the schema, and generates:`);
893
+ console.log(` ${dim('•')} ${cyan('types.ts')} — Entity interfaces, Create/Update input types`);
894
+ console.log(` ${dim('•')} ${cyan('metadata.ts')} — Runtime schema metadata`);
895
+ console.log(` ${dim('•')} ${cyan('index.ts')} — Configured client with typed table accessors`);
896
+ newline();
897
+ console.log(` ${bold('Options:')}`);
898
+ console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
899
+ console.log(` ${cyan('--out, -o')} ${dim('<dir>')} Output directory ${dim('(default: ./generated/turbine)')}`);
900
+ console.log(` ${cyan('--schema, -s')} ${dim('<name>')} Postgres schema ${dim('(default: public)')}`);
901
+ console.log(` ${cyan('--include')} ${dim('<tables>')} Comma-separated tables to include`);
902
+ console.log(` ${cyan('--exclude')} ${dim('<tables>')} Comma-separated tables to exclude`);
903
+ newline();
904
+ }
905
+ function showPushHelp() {
906
+ banner();
907
+ console.log(` ${bold('turbine push')} — Apply schema-builder definitions to database`);
908
+ newline();
909
+ console.log(` ${bold('Usage:')}`);
910
+ console.log(` npx turbine push ${dim('[options]')}`);
911
+ newline();
912
+ console.log(` Reads your ${cyan('turbine/schema.ts')} file, diffs against the live database,`);
913
+ console.log(` and applies CREATE/ALTER statements.`);
914
+ newline();
915
+ console.log(` ${bold('Options:')}`);
916
+ console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
917
+ console.log(` ${cyan('--dry-run')} Show SQL without executing`);
918
+ console.log(` ${cyan('--verbose, -v')} Show detailed output`);
919
+ newline();
920
+ }
921
+ function showMigrateHelp() {
922
+ banner();
923
+ console.log(` ${bold('turbine migrate')} — SQL migration management`);
924
+ newline();
925
+ console.log(` ${bold('Usage:')}`);
926
+ console.log(` npx turbine migrate ${cyan('<subcommand>')} ${dim('[options]')}`);
927
+ newline();
928
+ console.log(` ${bold('Subcommands:')}`);
929
+ console.log(` ${cyan('create')} ${dim('<name>')} Create a new migration file`);
930
+ console.log(` ${cyan('up')} Apply pending migrations`);
931
+ console.log(` ${cyan('down')} Rollback last migration`);
932
+ console.log(` ${cyan('status')} Show applied/pending migrations`);
933
+ newline();
934
+ console.log(` ${bold('Options:')}`);
935
+ console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
936
+ console.log(` ${cyan('--step, -n')} ${dim('<N>')} Number of migrations to apply/rollback`);
937
+ console.log(` ${cyan('--dry-run')} Show SQL without executing`);
938
+ console.log(` ${cyan('--verbose, -v')} Show detailed output`);
939
+ newline();
940
+ console.log(` ${bold('Examples:')}`);
941
+ console.log(` ${dim('$')} npx turbine migrate create add_users_table`);
942
+ console.log(` ${dim('$')} npx turbine migrate up`);
943
+ console.log(` ${dim('$')} npx turbine migrate down --step 2`);
944
+ console.log(` ${dim('$')} npx turbine migrate status`);
945
+ newline();
946
+ }
947
+ function showSeedHelp() {
948
+ banner();
949
+ console.log(` ${bold('turbine seed')} — Run seed file`);
950
+ newline();
951
+ console.log(` ${bold('Usage:')}`);
952
+ console.log(` npx turbine seed ${dim('[options]')}`);
953
+ newline();
954
+ console.log(` Runs the seed file specified in ${cyan('turbine.config.ts')}`);
955
+ console.log(` ${dim('(default: ./turbine/seed.ts)')}`);
956
+ newline();
957
+ console.log(` ${bold('Options:')}`);
958
+ console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
959
+ newline();
960
+ }
961
+ function showStatusHelp() {
962
+ banner();
963
+ console.log(` ${bold('turbine status')} — Show database schema summary`);
964
+ newline();
965
+ console.log(` ${bold('Usage:')}`);
966
+ console.log(` npx turbine status ${dim('[options]')}`);
967
+ newline();
968
+ console.log(` Introspects your database and displays tables, columns,`);
969
+ console.log(` types, relations, and indexes.`);
970
+ newline();
971
+ console.log(` ${bold('Options:')}`);
972
+ console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
973
+ console.log(` ${cyan('--schema, -s')} ${dim('<name>')} Postgres schema ${dim('(default: public)')}`);
974
+ newline();
975
+ }
976
+ // ---------------------------------------------------------------------------
785
977
  // Help
786
978
  // ---------------------------------------------------------------------------
787
979
  function showHelp() {
@@ -840,6 +1032,13 @@ async function main() {
840
1032
  showHelp();
841
1033
  return;
842
1034
  }
1035
+ // Subcommand help: e.g. `turbine migrate --help`
1036
+ if (args.help) {
1037
+ if (showSubcommandHelp(args.command))
1038
+ return;
1039
+ showHelp();
1040
+ return;
1041
+ }
843
1042
  if (args.command === 'version' || args.command === '--version' || args.command === '-V') {
844
1043
  showVersion();
845
1044
  return;
@@ -905,7 +1104,7 @@ async function main() {
905
1104
  if (err.message.includes('ECONNREFUSED') || err.message.includes('connection')) {
906
1105
  newline();
907
1106
  error(`Could not connect to database`);
908
- console.log(` ${dim(err.message)}`);
1107
+ console.log(` ${dim(redactUrl(err.message))}`);
909
1108
  newline();
910
1109
  console.log(` ${dim('Check that:')}`);
911
1110
  console.log(` ${dim('1.')} Your database is running`);
@@ -915,25 +1114,25 @@ async function main() {
915
1114
  else if (err.message.includes('authentication')) {
916
1115
  newline();
917
1116
  error(`Authentication failed`);
918
- console.log(` ${dim(err.message)}`);
1117
+ console.log(` ${dim(redactUrl(err.message))}`);
919
1118
  }
920
1119
  else if (err.message.includes('does not exist')) {
921
1120
  newline();
922
1121
  error(`Database or schema not found`);
923
- console.log(` ${dim(err.message)}`);
1122
+ console.log(` ${dim(redactUrl(err.message))}`);
924
1123
  }
925
1124
  else {
926
1125
  newline();
927
- error(err.message);
1126
+ error(redactUrl(err.message));
928
1127
  if (args.verbose && err.stack) {
929
1128
  newline();
930
- console.log(dim(err.stack));
1129
+ console.log(dim(redactUrl(err.stack)));
931
1130
  }
932
1131
  }
933
1132
  }
934
1133
  else {
935
1134
  newline();
936
- error(`Unexpected error: ${String(err)}`);
1135
+ error(`Unexpected error: ${redactUrl(String(err))}`);
937
1136
  }
938
1137
  newline();
939
1138
  process.exit(1);
@@ -73,8 +73,12 @@ export declare function parseMigrationSQL(filePath: string): {
73
73
  };
74
74
  /**
75
75
  * Create a new migration file.
76
+ * If `autoContent` is provided, the UP/DOWN sections are pre-populated with the given SQL.
76
77
  */
77
- export declare function createMigration(migrationsDir: string, name: string): MigrationFile;
78
+ export declare function createMigration(migrationsDir: string, name: string, autoContent?: {
79
+ up: string;
80
+ down: string;
81
+ }): MigrationFile;
78
82
  /**
79
83
  * Apply all pending migrations (UP).
80
84
  *
@@ -86,6 +90,7 @@ export declare function createMigration(migrationsDir: string, name: string): Mi
86
90
  */
87
91
  export declare function migrateUp(connectionString: string, migrationsDir: string, options?: {
88
92
  step?: number;
93
+ force?: boolean;
89
94
  }): Promise<{
90
95
  applied: MigrationFile[];
91
96
  errors: Array<{