turbine-orm 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -26
- package/dist/cjs/cli/config.js +151 -0
- package/dist/cjs/cli/index.js +1176 -0
- package/dist/cjs/cli/migrate.js +446 -0
- package/dist/cjs/cli/ui.js +233 -0
- package/dist/cjs/client.js +512 -0
- package/dist/cjs/errors.js +293 -0
- package/dist/cjs/generate.js +321 -0
- package/dist/cjs/index.js +94 -0
- package/dist/cjs/introspect.js +287 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pipeline.js +78 -0
- package/dist/cjs/query.js +1891 -0
- package/dist/cjs/schema-builder.js +238 -0
- package/dist/cjs/schema-sql.js +509 -0
- package/dist/cjs/schema.js +140 -0
- package/dist/cjs/serverless.js +110 -0
- package/dist/cli/config.js +6 -16
- package/dist/cli/index.js +256 -49
- package/dist/cli/migrate.d.ts +35 -6
- package/dist/cli/migrate.js +124 -76
- package/dist/cli/ui.js +5 -9
- package/dist/client.d.ts +87 -3
- package/dist/client.js +122 -46
- package/dist/errors.d.ts +138 -0
- package/dist/errors.js +278 -0
- package/dist/generate.js +37 -11
- package/dist/index.d.ts +10 -8
- package/dist/index.js +15 -11
- package/dist/introspect.js +3 -5
- package/dist/pipeline.js +8 -1
- package/dist/query.d.ts +310 -45
- package/dist/query.js +565 -237
- package/dist/schema-builder.js +91 -23
- package/dist/schema-sql.d.ts +6 -2
- package/dist/schema-sql.js +180 -26
- package/dist/schema.js +4 -1
- package/dist/serverless.d.ts +91 -139
- package/dist/serverless.js +86 -173
- package/package.json +44 -21
- package/dist/cli/config.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/migrate.d.ts.map +0 -1
- package/dist/cli/ui.d.ts.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/generate.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/introspect.d.ts.map +0 -1
- package/dist/pipeline.d.ts.map +0 -1
- package/dist/query.d.ts.map +0 -1
- package/dist/schema-builder.d.ts.map +0 -1
- package/dist/schema-sql.d.ts.map +0 -1
- package/dist/schema.d.ts.map +0 -1
- package/dist/serverless.d.ts.map +0 -1
- package/dist/types.d.ts +0 -93
- package/dist/types.d.ts.map +0 -1
- 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,
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
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 {
|
|
28
|
-
import { createMigration,
|
|
29
|
-
import {
|
|
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
|
|
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://
|
|
241
|
+
* @see https://turbineorm.dev
|
|
235
242
|
*/
|
|
236
243
|
|
|
237
244
|
import { defineSchema } from 'turbine-orm';
|
|
@@ -248,13 +255,20 @@ export default defineSchema({
|
|
|
248
255
|
`, 'utf-8');
|
|
249
256
|
success(`Created ${cyan(config.schemaFile)}`);
|
|
250
257
|
}
|
|
251
|
-
// Add .gitignore
|
|
258
|
+
// Add .gitignore entries for generated output and config (may contain connection strings)
|
|
252
259
|
const gitignorePath = '.gitignore';
|
|
253
260
|
if (existsSync(gitignorePath)) {
|
|
254
261
|
const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
|
|
262
|
+
const additions = [];
|
|
255
263
|
if (!gitignoreContent.includes('generated/turbine')) {
|
|
256
|
-
|
|
257
|
-
|
|
264
|
+
additions.push('generated/turbine/');
|
|
265
|
+
}
|
|
266
|
+
if (!gitignoreContent.includes('turbine.config.ts')) {
|
|
267
|
+
additions.push('turbine.config.ts');
|
|
268
|
+
}
|
|
269
|
+
if (additions.length > 0) {
|
|
270
|
+
appendFileSync(gitignorePath, `\n# Turbine generated client & config\n${additions.join('\n')}\n`);
|
|
271
|
+
success(`Added ${cyan(additions.join(', '))} to ${cyan('.gitignore')}`);
|
|
258
272
|
}
|
|
259
273
|
}
|
|
260
274
|
// If we have a URL, run initial generate
|
|
@@ -275,15 +289,15 @@ export default defineSchema({
|
|
|
275
289
|
spinner.succeed(`Found ${bold(String(tableCount))} tables`);
|
|
276
290
|
const genSpinner = new Spinner('Generating TypeScript client').start();
|
|
277
291
|
const result = generate({ schema, outDir: config.out, connectionString: url });
|
|
278
|
-
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}/`)}`);
|
|
279
293
|
}
|
|
280
294
|
catch (err) {
|
|
281
295
|
spinner.fail('Could not connect to database');
|
|
282
296
|
if (err instanceof Error) {
|
|
283
|
-
console.log(` ${dim(err.message)}`);
|
|
297
|
+
console.log(` ${dim(redactUrl(err.message))}`);
|
|
284
298
|
}
|
|
285
299
|
newline();
|
|
286
|
-
info(
|
|
300
|
+
info(`You can run generation later with: ${cyan('npx turbine generate')}`);
|
|
287
301
|
}
|
|
288
302
|
}
|
|
289
303
|
// Next steps
|
|
@@ -359,7 +373,7 @@ async function cmdGenerate(args, config) {
|
|
|
359
373
|
genSpinner.succeed(`Generated ${bold(String(result.files.length))} files in ${elapsed(startTime)}`);
|
|
360
374
|
// List files
|
|
361
375
|
for (const file of result.files) {
|
|
362
|
-
console.log(` ${dim(symbols.teeEnd)} ${cyan(result.outDir
|
|
376
|
+
console.log(` ${dim(symbols.teeEnd)} ${cyan(`${result.outDir}/${file}`)}`);
|
|
363
377
|
}
|
|
364
378
|
// Usage hint
|
|
365
379
|
newline();
|
|
@@ -407,9 +421,11 @@ async function cmdPush(args, config) {
|
|
|
407
421
|
for (const a of diff.alter) {
|
|
408
422
|
console.log(` ${yellow(symbols.arrowRight)} ${a.table}`);
|
|
409
423
|
for (const col of a.columns) {
|
|
410
|
-
const actionLabel = col.action === 'add'
|
|
411
|
-
|
|
412
|
-
|
|
424
|
+
const actionLabel = col.action === 'add'
|
|
425
|
+
? green('+ add')
|
|
426
|
+
: col.action === 'drop'
|
|
427
|
+
? red('- drop')
|
|
428
|
+
: yellow(`~ ${col.action.replace('_', ' ')}`);
|
|
413
429
|
console.log(` ${actionLabel} ${col.column}`);
|
|
414
430
|
}
|
|
415
431
|
}
|
|
@@ -463,17 +479,20 @@ async function cmdMigrate(args, config) {
|
|
|
463
479
|
console.log(` ${bold('turbine migrate')} ${dim('— SQL-first migration system')}`);
|
|
464
480
|
newline();
|
|
465
481
|
console.log(` ${bold('Commands:')}`);
|
|
466
|
-
console.log(` ${cyan('create <name>')}
|
|
467
|
-
console.log(` ${cyan('
|
|
468
|
-
console.log(` ${cyan('
|
|
469
|
-
console.log(` ${cyan('
|
|
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`);
|
|
470
487
|
newline();
|
|
471
488
|
console.log(` ${bold('Options:')}`);
|
|
489
|
+
console.log(` ${cyan('--auto')} Auto-generate UP/DOWN SQL from schema diff`);
|
|
472
490
|
console.log(` ${cyan('--step, -n')} Number of migrations to apply/rollback`);
|
|
473
491
|
console.log(` ${cyan('--dry-run')} Show SQL without executing`);
|
|
474
492
|
newline();
|
|
475
493
|
console.log(` ${bold('Examples:')}`);
|
|
476
494
|
console.log(` ${dim('npx turbine migrate create add_users_table')}`);
|
|
495
|
+
console.log(` ${dim('npx turbine migrate create add_email_index --auto')}`);
|
|
477
496
|
console.log(` ${dim('npx turbine migrate up')}`);
|
|
478
497
|
console.log(` ${dim('npx turbine migrate down --step 2')}`);
|
|
479
498
|
newline();
|
|
@@ -507,9 +526,61 @@ async function cmdMigrateCreate(args, config) {
|
|
|
507
526
|
newline();
|
|
508
527
|
console.log(` ${dim('Usage:')} ${cyan('npx turbine migrate create <name>')}`);
|
|
509
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')}`);
|
|
510
530
|
newline();
|
|
511
531
|
process.exit(1);
|
|
512
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
|
+
}
|
|
513
584
|
const file = createMigration(config.migrationsDir, name);
|
|
514
585
|
const relPath = relative(process.cwd(), file.path);
|
|
515
586
|
success(`Created migration: ${bold(file.filename)}`);
|
|
@@ -591,7 +662,7 @@ async function cmdMigrateDown(args, config) {
|
|
|
591
662
|
}
|
|
592
663
|
newline();
|
|
593
664
|
}
|
|
594
|
-
async function cmdMigrateStatus(
|
|
665
|
+
async function cmdMigrateStatus(_args, config) {
|
|
595
666
|
banner();
|
|
596
667
|
const url = requireUrl(config);
|
|
597
668
|
label('Database', redactUrl(url));
|
|
@@ -621,19 +692,22 @@ async function cmdMigrateStatus(args, config) {
|
|
|
621
692
|
const rows = statuses.map((s) => {
|
|
622
693
|
let status;
|
|
623
694
|
if (s.applied && s.checksumValid === false) {
|
|
624
|
-
status = red(symbols.warning
|
|
695
|
+
status = red(`${symbols.warning} Drifted`);
|
|
625
696
|
}
|
|
626
697
|
else if (s.applied) {
|
|
627
|
-
status = green(symbols.check
|
|
698
|
+
status = green(`${symbols.check} Applied`);
|
|
628
699
|
}
|
|
629
700
|
else {
|
|
630
|
-
status = yellow(symbols.dot
|
|
701
|
+
status = yellow(`${symbols.dot} Pending`);
|
|
631
702
|
}
|
|
632
703
|
return [
|
|
633
704
|
status,
|
|
634
705
|
s.file.filename,
|
|
635
706
|
s.appliedAt
|
|
636
|
-
? dim(s.appliedAt
|
|
707
|
+
? dim(s.appliedAt
|
|
708
|
+
.toISOString()
|
|
709
|
+
.replace('T', ' ')
|
|
710
|
+
.replace(/\.\d+Z$/, ' UTC'))
|
|
637
711
|
: dim('—'),
|
|
638
712
|
];
|
|
639
713
|
});
|
|
@@ -647,7 +721,7 @@ async function cmdMigrateStatus(args, config) {
|
|
|
647
721
|
// ---------------------------------------------------------------------------
|
|
648
722
|
// Command: seed
|
|
649
723
|
// ---------------------------------------------------------------------------
|
|
650
|
-
async function cmdSeed(
|
|
724
|
+
async function cmdSeed(_args, config) {
|
|
651
725
|
banner();
|
|
652
726
|
const seedFile = resolve(config.seedFile);
|
|
653
727
|
label('Seed file', config.seedFile);
|
|
@@ -663,20 +737,20 @@ async function cmdSeed(args, config) {
|
|
|
663
737
|
const spinner = new Spinner('Running seed file').start();
|
|
664
738
|
try {
|
|
665
739
|
// Use child_process to run the seed file via tsx or node
|
|
666
|
-
const {
|
|
740
|
+
const { execFileSync } = await import('node:child_process');
|
|
667
741
|
// Try tsx first (most compatible with .ts files), fall back to node --experimental-strip-types
|
|
668
742
|
const runners = [
|
|
669
|
-
{ cmd: 'npx tsx', name: 'tsx' },
|
|
670
|
-
{ 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' },
|
|
671
745
|
];
|
|
672
746
|
let ran = false;
|
|
673
747
|
for (const runner of runners) {
|
|
674
748
|
try {
|
|
675
|
-
|
|
749
|
+
execFileSync(runner.cmd, runner.args, {
|
|
676
750
|
stdio: 'inherit',
|
|
677
751
|
env: {
|
|
678
752
|
...process.env,
|
|
679
|
-
DATABASE_URL: config.url || process.env
|
|
753
|
+
DATABASE_URL: config.url || process.env.DATABASE_URL,
|
|
680
754
|
},
|
|
681
755
|
});
|
|
682
756
|
ran = true;
|
|
@@ -698,7 +772,7 @@ async function cmdSeed(args, config) {
|
|
|
698
772
|
catch (err) {
|
|
699
773
|
spinner.fail('Seed failed');
|
|
700
774
|
if (err instanceof Error) {
|
|
701
|
-
console.log(` ${dim(err.message)}`);
|
|
775
|
+
console.log(` ${dim(redactUrl(err.message))}`);
|
|
702
776
|
}
|
|
703
777
|
newline();
|
|
704
778
|
process.exit(1);
|
|
@@ -708,7 +782,7 @@ async function cmdSeed(args, config) {
|
|
|
708
782
|
// ---------------------------------------------------------------------------
|
|
709
783
|
// Command: status
|
|
710
784
|
// ---------------------------------------------------------------------------
|
|
711
|
-
async function cmdStatus(
|
|
785
|
+
async function cmdStatus(_args, config) {
|
|
712
786
|
banner();
|
|
713
787
|
const url = requireUrl(config);
|
|
714
788
|
label('Database', redactUrl(url));
|
|
@@ -726,7 +800,7 @@ async function cmdStatus(args, config) {
|
|
|
726
800
|
newline();
|
|
727
801
|
for (const tbl of Object.values(schema.tables)) {
|
|
728
802
|
const relCount = Object.keys(tbl.relations).length;
|
|
729
|
-
const
|
|
803
|
+
const _pk = tbl.primaryKey.join(', ') || dim('(none)');
|
|
730
804
|
console.log(` ${bold(cyan(tbl.name))}`);
|
|
731
805
|
for (let i = 0; i < tbl.columns.length; i++) {
|
|
732
806
|
const col = tbl.columns[i];
|
|
@@ -735,7 +809,7 @@ async function cmdStatus(args, config) {
|
|
|
735
809
|
const nullable = col.nullable ? dim('?') : '';
|
|
736
810
|
const def = col.hasDefault ? dim(' (default)') : '';
|
|
737
811
|
const pkLabel = tbl.primaryKey.includes(col.name) ? ` ${magenta('PK')}` : '';
|
|
738
|
-
console.log(` ${dim(prefix)} ${col.field}${nullable}: ${green(col.tsType)}${pkLabel}${def} ${gray(symbols.arrow
|
|
812
|
+
console.log(` ${dim(prefix)} ${col.field}${nullable}: ${green(col.tsType)}${pkLabel}${def} ${gray(`${symbols.arrow} ${col.pgType}`)}`);
|
|
739
813
|
}
|
|
740
814
|
const rels = Object.entries(tbl.relations);
|
|
741
815
|
if (rels.length > 0) {
|
|
@@ -768,11 +842,138 @@ async function cmdStudio(_args, _config) {
|
|
|
768
842
|
'A local web UI for browsing your database,',
|
|
769
843
|
'exploring relations, and managing data.',
|
|
770
844
|
'',
|
|
771
|
-
`Follow ${cyan('@
|
|
845
|
+
`Follow ${cyan('@turbineorm')} for updates.`,
|
|
772
846
|
].join('\n'), { title: bold(cyan('Studio')), padding: 2 }));
|
|
773
847
|
newline();
|
|
774
848
|
}
|
|
775
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
|
+
// ---------------------------------------------------------------------------
|
|
776
977
|
// Help
|
|
777
978
|
// ---------------------------------------------------------------------------
|
|
778
979
|
function showHelp() {
|
|
@@ -819,8 +1020,7 @@ function showHelp() {
|
|
|
819
1020
|
// Version
|
|
820
1021
|
// ---------------------------------------------------------------------------
|
|
821
1022
|
function showVersion() {
|
|
822
|
-
|
|
823
|
-
console.log(`turbine-orm v0.3.0`);
|
|
1023
|
+
console.log(`turbine-orm v0.5.0`);
|
|
824
1024
|
}
|
|
825
1025
|
// ---------------------------------------------------------------------------
|
|
826
1026
|
// Main
|
|
@@ -832,6 +1032,13 @@ async function main() {
|
|
|
832
1032
|
showHelp();
|
|
833
1033
|
return;
|
|
834
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
|
+
}
|
|
835
1042
|
if (args.command === 'version' || args.command === '--version' || args.command === '-V') {
|
|
836
1043
|
showVersion();
|
|
837
1044
|
return;
|
|
@@ -897,7 +1104,7 @@ async function main() {
|
|
|
897
1104
|
if (err.message.includes('ECONNREFUSED') || err.message.includes('connection')) {
|
|
898
1105
|
newline();
|
|
899
1106
|
error(`Could not connect to database`);
|
|
900
|
-
console.log(` ${dim(err.message)}`);
|
|
1107
|
+
console.log(` ${dim(redactUrl(err.message))}`);
|
|
901
1108
|
newline();
|
|
902
1109
|
console.log(` ${dim('Check that:')}`);
|
|
903
1110
|
console.log(` ${dim('1.')} Your database is running`);
|
|
@@ -907,25 +1114,25 @@ async function main() {
|
|
|
907
1114
|
else if (err.message.includes('authentication')) {
|
|
908
1115
|
newline();
|
|
909
1116
|
error(`Authentication failed`);
|
|
910
|
-
console.log(` ${dim(err.message)}`);
|
|
1117
|
+
console.log(` ${dim(redactUrl(err.message))}`);
|
|
911
1118
|
}
|
|
912
1119
|
else if (err.message.includes('does not exist')) {
|
|
913
1120
|
newline();
|
|
914
1121
|
error(`Database or schema not found`);
|
|
915
|
-
console.log(` ${dim(err.message)}`);
|
|
1122
|
+
console.log(` ${dim(redactUrl(err.message))}`);
|
|
916
1123
|
}
|
|
917
1124
|
else {
|
|
918
1125
|
newline();
|
|
919
|
-
error(err.message);
|
|
1126
|
+
error(redactUrl(err.message));
|
|
920
1127
|
if (args.verbose && err.stack) {
|
|
921
1128
|
newline();
|
|
922
|
-
console.log(dim(err.stack));
|
|
1129
|
+
console.log(dim(redactUrl(err.stack)));
|
|
923
1130
|
}
|
|
924
1131
|
}
|
|
925
1132
|
}
|
|
926
1133
|
else {
|
|
927
1134
|
newline();
|
|
928
|
-
error(`Unexpected error: ${String(err)}`);
|
|
1135
|
+
error(`Unexpected error: ${redactUrl(String(err))}`);
|
|
929
1136
|
}
|
|
930
1137
|
newline();
|
|
931
1138
|
process.exit(1);
|
package/dist/cli/migrate.d.ts
CHANGED
|
@@ -12,16 +12,14 @@
|
|
|
12
12
|
* DROP TABLE users;
|
|
13
13
|
*/
|
|
14
14
|
export interface MigrationFile {
|
|
15
|
-
/** Full filename (e.g. "
|
|
15
|
+
/** Full filename (e.g. "20260325120000_create_users.sql") */
|
|
16
16
|
filename: string;
|
|
17
17
|
/** Absolute path to the file */
|
|
18
18
|
path: string;
|
|
19
|
-
/** Extracted name portion (e.g. "
|
|
19
|
+
/** Extracted name portion (e.g. "20260325120000_create_users") */
|
|
20
20
|
name: string;
|
|
21
|
-
/** Timestamp prefix (e.g. "
|
|
21
|
+
/** Timestamp prefix (e.g. "20260325120000") — YYYYMMDDHHMMSS */
|
|
22
22
|
timestamp: string;
|
|
23
|
-
/** Sequence number (e.g. "001") */
|
|
24
|
-
sequence: string;
|
|
25
23
|
}
|
|
26
24
|
export interface AppliedMigration {
|
|
27
25
|
id: number;
|
|
@@ -36,10 +34,36 @@ export interface MigrationStatus {
|
|
|
36
34
|
/** True if the file checksum matches the stored checksum (only set for applied migrations) */
|
|
37
35
|
checksumValid?: boolean;
|
|
38
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse a migration filename into its components.
|
|
39
|
+
* Expected format: YYYYMMDDHHMMSS_description.sql
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseMigrationFilename(filename: string): MigrationFile | null;
|
|
42
|
+
/**
|
|
43
|
+
* Sanitize a migration name: lowercase, replace non-alnum with _, collapse duplicates, trim.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sanitizeName(name: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Generate a YYYYMMDDHHMMSS timestamp string from a Date.
|
|
48
|
+
*/
|
|
49
|
+
export declare function formatTimestamp(date: Date): string;
|
|
50
|
+
/**
|
|
51
|
+
* Get pending migration files — those not yet applied.
|
|
52
|
+
* Returns files sorted by timestamp (ascending).
|
|
53
|
+
*/
|
|
54
|
+
export declare function getPendingMigrations(migrationsDir: string, applied: string[]): MigrationFile[];
|
|
39
55
|
/**
|
|
40
56
|
* List all migration files in the migrations directory, sorted by name.
|
|
41
57
|
*/
|
|
42
58
|
export declare function listMigrationFiles(migrationsDir: string): MigrationFile[];
|
|
59
|
+
/**
|
|
60
|
+
* Parse migration content string into UP and DOWN sections.
|
|
61
|
+
* Exported for unit testing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseMigrationContent(content: string): {
|
|
64
|
+
up: string;
|
|
65
|
+
down: string;
|
|
66
|
+
};
|
|
43
67
|
/**
|
|
44
68
|
* Parse a migration file into UP and DOWN sections.
|
|
45
69
|
*/
|
|
@@ -49,8 +73,12 @@ export declare function parseMigrationSQL(filePath: string): {
|
|
|
49
73
|
};
|
|
50
74
|
/**
|
|
51
75
|
* Create a new migration file.
|
|
76
|
+
* If `autoContent` is provided, the UP/DOWN sections are pre-populated with the given SQL.
|
|
52
77
|
*/
|
|
53
|
-
export declare function createMigration(migrationsDir: string, name: string
|
|
78
|
+
export declare function createMigration(migrationsDir: string, name: string, autoContent?: {
|
|
79
|
+
up: string;
|
|
80
|
+
down: string;
|
|
81
|
+
}): MigrationFile;
|
|
54
82
|
/**
|
|
55
83
|
* Apply all pending migrations (UP).
|
|
56
84
|
*
|
|
@@ -62,6 +90,7 @@ export declare function createMigration(migrationsDir: string, name: string): Mi
|
|
|
62
90
|
*/
|
|
63
91
|
export declare function migrateUp(connectionString: string, migrationsDir: string, options?: {
|
|
64
92
|
step?: number;
|
|
93
|
+
force?: boolean;
|
|
65
94
|
}): Promise<{
|
|
66
95
|
applied: MigrationFile[];
|
|
67
96
|
errors: Array<{
|