turbine-orm 0.50.0 → 0.51.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 +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
* turbine-orm CLI
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* turbine init
|
|
7
|
-
* turbine generate | pull
|
|
6
|
+
* turbine init , Initialize a Turbine project
|
|
7
|
+
* turbine generate | pull , Introspect database and generate TypeScript types
|
|
8
8
|
* turbine migrate-from-prisma - Parse a schema.prisma and emit a Prisma->Turbine name map + report
|
|
9
9
|
* turbine push - Apply schema-builder definitions to database (destructive ops gated)
|
|
10
10
|
* turbine migrate create <name> - Create a new SQL migration file (--auto | --from-diff | --recipe <name>)
|
|
11
|
-
* turbine migrate up
|
|
12
|
-
* turbine migrate deploy
|
|
13
|
-
* turbine migrate down
|
|
14
|
-
* turbine migrate status
|
|
15
|
-
* turbine seed
|
|
16
|
-
* turbine status
|
|
11
|
+
* turbine migrate up , Apply pending migrations
|
|
12
|
+
* turbine migrate deploy , Apply pending migrations without prompts
|
|
13
|
+
* turbine migrate down , Rollback last migration
|
|
14
|
+
* turbine migrate status , Show migration status
|
|
15
|
+
* turbine seed , Run seed file
|
|
16
|
+
* turbine status , Show schema summary
|
|
17
17
|
* turbine doctor - Cost-aware missing-FK-index triage (--fix, --json, --no-concurrently, --unused, --audit)
|
|
18
18
|
* turbine studio : Launch local read-only web UI (--demo for a seeded sample DB)
|
|
19
|
-
* turbine mcp
|
|
20
|
-
* turbine observe
|
|
19
|
+
* turbine mcp , Start read-only MCP server over JSON-RPC stdio
|
|
20
|
+
* turbine observe , Launch metrics dashboard (requires TURBINE_OBSERVE_URL)
|
|
21
21
|
*
|
|
22
22
|
* Usage:
|
|
23
23
|
* DATABASE_URL=postgres://... npx turbine generate
|
|
@@ -228,7 +228,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
228
228
|
return result;
|
|
229
229
|
}
|
|
230
230
|
// ---------------------------------------------------------------------------
|
|
231
|
-
// TypeScript loader
|
|
231
|
+
// TypeScript loader, user-facing error helper
|
|
232
232
|
// ---------------------------------------------------------------------------
|
|
233
233
|
/**
|
|
234
234
|
* Print a friendly error explaining how to install tsx, then exit.
|
|
@@ -244,7 +244,7 @@ function failMissingTsLoader(filePath, reason) {
|
|
|
244
244
|
}
|
|
245
245
|
else if (reason === 'failed') {
|
|
246
246
|
// tsx IS installed but registering its loader threw. Report the real
|
|
247
|
-
// cause
|
|
247
|
+
// cause, telling the user to install tsx here would be a misdiagnosis.
|
|
248
248
|
console.log(` ${dim('tsx is installed, but registering its TypeScript loader failed:')}`);
|
|
249
249
|
newline();
|
|
250
250
|
console.log(` ${getTsLoaderError() ?? '(unknown error)'}`);
|
|
@@ -758,6 +758,7 @@ async function runInitGenerate(config, url) {
|
|
|
758
758
|
schema: config.schema,
|
|
759
759
|
include: config.include.length ? config.include : undefined,
|
|
760
760
|
exclude: config.exclude.length ? config.exclude : undefined,
|
|
761
|
+
relationNames: config.relationNames,
|
|
761
762
|
});
|
|
762
763
|
spinner.succeed(`Found ${bold(String(Object.keys(schema.tables).length))} tables`);
|
|
763
764
|
const genSpinner = new Spinner('Generating TypeScript client').start();
|
|
@@ -1065,7 +1066,7 @@ async function cmdGenerate(args, config) {
|
|
|
1065
1066
|
const url = requireUrl(config);
|
|
1066
1067
|
const startTime = performance.now();
|
|
1067
1068
|
// Guard: `schema` is the Postgres NAMESPACE to introspect (default `public`),
|
|
1068
|
-
// NOT the path to your schema-builder file
|
|
1069
|
+
// NOT the path to your schema-builder file, that goes in `schemaFile`. If the
|
|
1069
1070
|
// configured `schema` looks like a file path, introspection would silently
|
|
1070
1071
|
// match zero tables and emit an empty client. Fail loudly instead.
|
|
1071
1072
|
if (!args.allowEmpty && looksLikeSchemaFilePath(config.schema)) {
|
|
@@ -1075,7 +1076,7 @@ async function cmdGenerate(args, config) {
|
|
|
1075
1076
|
console.log(` ${dim('The path to your defineSchema() file belongs in')} ${cyan('schemaFile')}${dim('.')}`);
|
|
1076
1077
|
newline();
|
|
1077
1078
|
console.log(` ${dim('Fix your')} ${cyan('turbine.config.ts')}${dim(':')}`);
|
|
1078
|
-
console.log(` ${green('schema:')} ${cyan("'public'")}${dim(", // or omit
|
|
1079
|
+
console.log(` ${green('schema:')} ${cyan("'public'")}${dim(", // or omit, introspects the 'public' schema")}`);
|
|
1079
1080
|
console.log(` ${green('schemaFile:')} ${cyan(`'${config.schema}'`)}${dim(', // your defineSchema() file (used by `turbine push`)')}`);
|
|
1080
1081
|
newline();
|
|
1081
1082
|
console.log(` ${dim('Re-run with')} ${cyan('--allow-empty')} ${dim('to introspect this literal schema name anyway.')}`);
|
|
@@ -1094,6 +1095,7 @@ async function cmdGenerate(args, config) {
|
|
|
1094
1095
|
schema: config.schema,
|
|
1095
1096
|
include: config.include.length ? config.include : undefined,
|
|
1096
1097
|
exclude: config.exclude.length ? config.exclude : undefined,
|
|
1098
|
+
relationNames: config.relationNames,
|
|
1097
1099
|
includeViews: args.includeViews,
|
|
1098
1100
|
legacyToManyUniques: config.legacyToManyUniques,
|
|
1099
1101
|
onDefaultTableExclusion: (tables) => skippedInternalTables.push(...tables),
|
|
@@ -1113,13 +1115,13 @@ async function cmdGenerate(args, config) {
|
|
|
1113
1115
|
// instead of silently emitting an empty typed client.
|
|
1114
1116
|
if (tableNames.length === 0 && !args.allowEmpty) {
|
|
1115
1117
|
newline();
|
|
1116
|
-
error(`Introspection matched 0 tables in schema ${cyan(config.schema)}
|
|
1118
|
+
error(`Introspection matched 0 tables in schema ${cyan(config.schema)}, refusing to generate an empty client.`);
|
|
1117
1119
|
newline();
|
|
1118
1120
|
console.log(` ${dim('Common causes:')}`);
|
|
1119
1121
|
console.log(` ${dim('•')} ${cyan('schema')} ${dim('points at the wrong Postgres namespace')} ${dim('(it is the schema NAME, default')} ${cyan('public')}${dim(').')}`);
|
|
1120
1122
|
console.log(` ${dim('•')} ${dim('You meant to set')} ${cyan('schemaFile')} ${dim('(your defineSchema() file), not')} ${cyan('schema')}${dim('.')}`);
|
|
1121
1123
|
console.log(` ${dim('•')} ${cyan('include')}/${cyan('exclude')} ${dim('filtered out every table.')}`);
|
|
1122
|
-
console.log(` ${dim('•')} ${dim('The database has no tables yet
|
|
1124
|
+
console.log(` ${dim('•')} ${dim('The database has no tables yet, run')} ${cyan('turbine push')} ${dim('or a migration first.')}`);
|
|
1123
1125
|
newline();
|
|
1124
1126
|
console.log(` ${dim('If an empty client is genuinely what you want, re-run with')} ${cyan('--allow-empty')}${dim('.')}`);
|
|
1125
1127
|
newline();
|
|
@@ -1246,6 +1248,7 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1246
1248
|
includeViews: true,
|
|
1247
1249
|
// Inherit the shared bookkeeping-table exclusions (Turbine + Prisma).
|
|
1248
1250
|
exclude: [...new Set([...config.exclude, ...DEFAULT_EXCLUDED_TABLES])],
|
|
1251
|
+
relationNames: config.relationNames,
|
|
1249
1252
|
});
|
|
1250
1253
|
spinner.succeed(`Introspected ${bold(String(Object.keys(schemaMeta.tables).length))} tables`);
|
|
1251
1254
|
}
|
|
@@ -1381,7 +1384,7 @@ async function cmdPush(args, config) {
|
|
|
1381
1384
|
newline();
|
|
1382
1385
|
}
|
|
1383
1386
|
if (args.dryRun) {
|
|
1384
|
-
info('Dry run
|
|
1387
|
+
info('Dry run, no changes applied.');
|
|
1385
1388
|
newline();
|
|
1386
1389
|
return;
|
|
1387
1390
|
}
|
|
@@ -1430,7 +1433,7 @@ async function cmdMigrate(args, config) {
|
|
|
1430
1433
|
const sub = args.subcommand;
|
|
1431
1434
|
if (!sub || sub === 'help') {
|
|
1432
1435
|
banner();
|
|
1433
|
-
console.log(` ${bold('turbine migrate')} ${dim('
|
|
1436
|
+
console.log(` ${bold('turbine migrate')} ${dim(', SQL-first migration system')}`);
|
|
1434
1437
|
newline();
|
|
1435
1438
|
console.log(` ${bold('Commands:')}`);
|
|
1436
1439
|
console.log(` ${cyan('create <name>')} Create a new migration file`);
|
|
@@ -1709,16 +1712,16 @@ async function cmdMigrateUp(args, config) {
|
|
|
1709
1712
|
newline();
|
|
1710
1713
|
return;
|
|
1711
1714
|
}
|
|
1712
|
-
// Big, loud warning when bypassing drift detection
|
|
1715
|
+
// Big, loud warning when bypassing drift detection, this is a deliberately
|
|
1713
1716
|
// dangerous operation and the user should see it on every invocation.
|
|
1714
1717
|
if (args.allowDrift) {
|
|
1715
|
-
warn('--allow-drift is set
|
|
1718
|
+
warn('--allow-drift is set, checksum validation is DISABLED for this run.');
|
|
1716
1719
|
console.log(` ${dim('Applied migrations may have been modified or deleted on disk.')}`);
|
|
1717
1720
|
console.log(` ${dim('Proceed only if you are intentionally rewriting migration history.')}`);
|
|
1718
1721
|
newline();
|
|
1719
1722
|
}
|
|
1720
1723
|
if (args.allowDestructive) {
|
|
1721
|
-
warn('--allow-destructive is set
|
|
1724
|
+
warn('--allow-destructive is set, data-destroying statements in migrations WILL run.');
|
|
1722
1725
|
newline();
|
|
1723
1726
|
}
|
|
1724
1727
|
const spinner = new Spinner('Applying migrations').start();
|
|
@@ -1747,7 +1750,7 @@ async function cmdMigrateUp(args, config) {
|
|
|
1747
1750
|
throw err;
|
|
1748
1751
|
spinner.stop();
|
|
1749
1752
|
if (!(await confirmDestructive(err.message))) {
|
|
1750
|
-
error('Aborted
|
|
1753
|
+
error('Aborted, no migrations were applied and no data was touched.');
|
|
1751
1754
|
newline();
|
|
1752
1755
|
process.exit(1);
|
|
1753
1756
|
}
|
|
@@ -1895,7 +1898,7 @@ function isDestructiveRefusal(err) {
|
|
|
1895
1898
|
* 1. show the full itemized report (statement kinds + targets),
|
|
1896
1899
|
* 2. require typing the literal phrase `destroy my data`,
|
|
1897
1900
|
* 3. require a final explicit `yes`.
|
|
1898
|
-
* Non-interactive shells (CI, pipes) can never pass this
|
|
1901
|
+
* Non-interactive shells (CI, pipes) can never pass this, they must use the
|
|
1899
1902
|
* explicit `--allow-destructive` flag instead. Anything but exact answers aborts.
|
|
1900
1903
|
*/
|
|
1901
1904
|
async function confirmDestructive(report) {
|
|
@@ -1917,7 +1920,7 @@ async function confirmDestructive(report) {
|
|
|
1917
1920
|
const phrase = await rl.question(` Type ${bold('destroy my data')} to continue, anything else to abort: `);
|
|
1918
1921
|
if (phrase.trim() !== 'destroy my data')
|
|
1919
1922
|
return false;
|
|
1920
|
-
const finalAnswer = await rl.question(` Final confirmation
|
|
1923
|
+
const finalAnswer = await rl.question(` Final confirmation, apply the destructive statements above? Type ${bold('yes')}: `);
|
|
1921
1924
|
return finalAnswer.trim() === 'yes';
|
|
1922
1925
|
}
|
|
1923
1926
|
finally {
|
|
@@ -1943,7 +1946,7 @@ async function cmdMigrateDown(args, config) {
|
|
|
1943
1946
|
throw err;
|
|
1944
1947
|
spinner.stop();
|
|
1945
1948
|
if (!(await confirmDestructive(err.message))) {
|
|
1946
|
-
error('Aborted
|
|
1949
|
+
error('Aborted, nothing was rolled back and no data was touched.');
|
|
1947
1950
|
newline();
|
|
1948
1951
|
process.exit(1);
|
|
1949
1952
|
}
|
|
@@ -2033,7 +2036,7 @@ async function cmdMigrateStatus(_args, config) {
|
|
|
2033
2036
|
.toISOString()
|
|
2034
2037
|
.replace('T', ' ')
|
|
2035
2038
|
.replace(/\.\d+Z$/, ' UTC'))
|
|
2036
|
-
: dim('
|
|
2039
|
+
: dim('-'),
|
|
2037
2040
|
];
|
|
2038
2041
|
});
|
|
2039
2042
|
console.log(formatTable(headers, rows));
|
|
@@ -2063,7 +2066,7 @@ async function runSeedPlan(plan, config) {
|
|
|
2063
2066
|
try {
|
|
2064
2067
|
if (plan.kind === 'tsx') {
|
|
2065
2068
|
if (!canResolveTsx()) {
|
|
2066
|
-
throw new Error('TypeScript seed files require tsx
|
|
2069
|
+
throw new Error('TypeScript seed files require tsx, install tsx or use seed.js/seed.sql.');
|
|
2067
2070
|
}
|
|
2068
2071
|
// The seed runs in a child process, so we cannot observe its callback
|
|
2069
2072
|
// directly. Hand it a sentinel path: `defineSeed`'s runner writes the file
|
|
@@ -2164,6 +2167,7 @@ async function cmdStatus(_args, config) {
|
|
|
2164
2167
|
schema: config.schema,
|
|
2165
2168
|
include: config.include.length ? config.include : undefined,
|
|
2166
2169
|
exclude: config.exclude.length ? config.exclude : undefined,
|
|
2170
|
+
relationNames: config.relationNames,
|
|
2167
2171
|
});
|
|
2168
2172
|
const tableNames = Object.keys(schema.tables);
|
|
2169
2173
|
spinner.succeed(`Found ${bold(String(tableNames.length))} tables`);
|
|
@@ -2627,7 +2631,7 @@ export function isLoopbackHost(host) {
|
|
|
2627
2631
|
return h === '127.0.0.1' || h === 'localhost' || h === '::1' || h === '[::1]';
|
|
2628
2632
|
}
|
|
2629
2633
|
// ---------------------------------------------------------------------------
|
|
2630
|
-
// Command: studio
|
|
2634
|
+
// Command: studio, local read-only web UI
|
|
2631
2635
|
// ---------------------------------------------------------------------------
|
|
2632
2636
|
async function cmdStudio(args, config) {
|
|
2633
2637
|
banner();
|
|
@@ -2643,7 +2647,7 @@ async function cmdStudio(args, config) {
|
|
|
2643
2647
|
process.exit(1);
|
|
2644
2648
|
}
|
|
2645
2649
|
// Non-loopback binds require an explicit --allow-remote opt-in. Studio has
|
|
2646
|
-
// only a random session token
|
|
2650
|
+
// only a random session token, exposing it on a LAN interface is foot-gun
|
|
2647
2651
|
// territory, so we refuse rather than warn-and-proceed.
|
|
2648
2652
|
if (!isLoopbackHost(host)) {
|
|
2649
2653
|
if (!args.allowRemote) {
|
|
@@ -2654,7 +2658,7 @@ async function cmdStudio(args, config) {
|
|
|
2654
2658
|
newline();
|
|
2655
2659
|
process.exit(1);
|
|
2656
2660
|
}
|
|
2657
|
-
console.log(warn(`Studio is binding to ${yellow(host)}
|
|
2661
|
+
console.log(warn(`Studio is binding to ${yellow(host)}, this is NOT loopback. ` +
|
|
2658
2662
|
`Anyone on your network who can reach this port + guess the session token can read your database.`));
|
|
2659
2663
|
}
|
|
2660
2664
|
const spinner = new Spinner(demo ? 'Seeding demo dataset' : 'Introspecting database').start();
|
|
@@ -2754,7 +2758,7 @@ async function cmdStudio(args, config) {
|
|
|
2754
2758
|
});
|
|
2755
2759
|
}
|
|
2756
2760
|
// ---------------------------------------------------------------------------
|
|
2757
|
-
// Command: mcp
|
|
2761
|
+
// Command: mcp, read-only JSON-RPC stdio server
|
|
2758
2762
|
// ---------------------------------------------------------------------------
|
|
2759
2763
|
async function cmdMcp(_args, config) {
|
|
2760
2764
|
const url = requireUrl(config);
|
|
@@ -2799,7 +2803,7 @@ async function cmdObserve(args) {
|
|
|
2799
2803
|
newline();
|
|
2800
2804
|
process.exit(1);
|
|
2801
2805
|
}
|
|
2802
|
-
console.log(warn(`Observe is binding to ${yellow(host)}
|
|
2806
|
+
console.log(warn(`Observe is binding to ${yellow(host)}, this is NOT loopback. ` +
|
|
2803
2807
|
`Anyone on your network who can reach this port + guess the session token can read your metrics.`));
|
|
2804
2808
|
}
|
|
2805
2809
|
const spinner = new Spinner('Connecting to metrics database').start();
|
|
@@ -2814,7 +2818,7 @@ async function cmdObserve(args) {
|
|
|
2814
2818
|
}
|
|
2815
2819
|
newline();
|
|
2816
2820
|
console.log(box([
|
|
2817
|
-
`${bold('Turbine Observe')} ${dim('
|
|
2821
|
+
`${bold('Turbine Observe')} ${dim(', query metrics dashboard')}`,
|
|
2818
2822
|
'',
|
|
2819
2823
|
` ${cyan('URL:')} ${bold(handle.url)}`,
|
|
2820
2824
|
'',
|
|
@@ -2861,7 +2865,7 @@ function showSubcommandHelp(command) {
|
|
|
2861
2865
|
}
|
|
2862
2866
|
function showInitHelp() {
|
|
2863
2867
|
banner();
|
|
2864
|
-
console.log(` ${bold('turbine init')}
|
|
2868
|
+
console.log(` ${bold('turbine init')}, Initialize a Turbine project`);
|
|
2865
2869
|
newline();
|
|
2866
2870
|
console.log(` ${bold('Usage:')}`);
|
|
2867
2871
|
console.log(` npx turbine init ${dim('[options]')}`);
|
|
@@ -2886,16 +2890,16 @@ function showInitHelp() {
|
|
|
2886
2890
|
}
|
|
2887
2891
|
function showGenerateHelp() {
|
|
2888
2892
|
banner();
|
|
2889
|
-
console.log(` ${bold('turbine generate')}
|
|
2893
|
+
console.log(` ${bold('turbine generate')}, Introspect database and generate TypeScript types`);
|
|
2890
2894
|
newline();
|
|
2891
2895
|
console.log(` ${bold('Usage:')}`);
|
|
2892
2896
|
console.log(` npx turbine generate ${dim('[options]')}`);
|
|
2893
2897
|
newline();
|
|
2894
2898
|
console.log(` Connects to your database, reads the schema, and generates:`);
|
|
2895
|
-
console.log(` ${dim('•')} ${cyan('types.ts')}
|
|
2896
|
-
console.log(` ${dim('•')} ${cyan('metadata.ts')}
|
|
2897
|
-
console.log(` ${dim('•')} ${cyan('index.ts')}
|
|
2898
|
-
console.log(` ${dim('•')} ${cyan('zod.ts')}
|
|
2899
|
+
console.log(` ${dim('•')} ${cyan('types.ts')} , Entity interfaces, Create/Update input types`);
|
|
2900
|
+
console.log(` ${dim('•')} ${cyan('metadata.ts')}, Runtime schema metadata`);
|
|
2901
|
+
console.log(` ${dim('•')} ${cyan('index.ts')} , Configured client with typed table accessors`);
|
|
2902
|
+
console.log(` ${dim('•')} ${cyan('zod.ts')} , Zod schemas ${dim('(with --zod)')}`);
|
|
2899
2903
|
newline();
|
|
2900
2904
|
console.log(` ${bold('Options:')}`);
|
|
2901
2905
|
console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
|
|
@@ -2943,7 +2947,7 @@ function showMigrateFromPrismaHelp() {
|
|
|
2943
2947
|
}
|
|
2944
2948
|
function showPushHelp() {
|
|
2945
2949
|
banner();
|
|
2946
|
-
console.log(` ${bold('turbine push')}
|
|
2950
|
+
console.log(` ${bold('turbine push')}, Apply schema-builder definitions to database`);
|
|
2947
2951
|
newline();
|
|
2948
2952
|
console.log(` ${bold('Usage:')}`);
|
|
2949
2953
|
console.log(` npx turbine push ${dim('[options]')}`);
|
|
@@ -2960,7 +2964,7 @@ function showPushHelp() {
|
|
|
2960
2964
|
}
|
|
2961
2965
|
function showMigrateHelp() {
|
|
2962
2966
|
banner();
|
|
2963
|
-
console.log(` ${bold('turbine migrate')}
|
|
2967
|
+
console.log(` ${bold('turbine migrate')}, SQL migration management`);
|
|
2964
2968
|
newline();
|
|
2965
2969
|
console.log(` ${bold('Usage:')}`);
|
|
2966
2970
|
console.log(` npx turbine migrate ${cyan('<subcommand>')} ${dim('[options]')}`);
|
|
@@ -2979,7 +2983,7 @@ function showMigrateHelp() {
|
|
|
2979
2983
|
console.log(` ${cyan('--recipe')} ${dim('<name>')} Scaffold a sanctioned migration pattern ${dim('(create only, e.g. backfill)')}`);
|
|
2980
2984
|
console.log(` ${cyan('--step, -n')} ${dim('<N>')} Number of migrations to apply/rollback`);
|
|
2981
2985
|
console.log(` ${cyan('--dry-run')} Show SQL without executing`);
|
|
2982
|
-
console.log(` ${cyan('--allow-drift')} Bypass checksum validation ${dim('(migrate up only
|
|
2986
|
+
console.log(` ${cyan('--allow-drift')} Bypass checksum validation ${dim('(migrate up only, advanced)')}`);
|
|
2983
2987
|
console.log(` ${cyan('--allow-destructive')} Run data-destroying migration statements without the interactive confirm`);
|
|
2984
2988
|
console.log(` ${cyan('--verbose, -v')} Show detailed output`);
|
|
2985
2989
|
newline();
|
|
@@ -2996,7 +3000,7 @@ function showMigrateHelp() {
|
|
|
2996
3000
|
}
|
|
2997
3001
|
function showSeedHelp() {
|
|
2998
3002
|
banner();
|
|
2999
|
-
console.log(` ${bold('turbine seed')}
|
|
3003
|
+
console.log(` ${bold('turbine seed')}, Run seed file`);
|
|
3000
3004
|
newline();
|
|
3001
3005
|
console.log(` ${bold('Usage:')}`);
|
|
3002
3006
|
console.log(` npx turbine seed ${dim('[options]')}`);
|
|
@@ -3012,7 +3016,7 @@ function showSeedHelp() {
|
|
|
3012
3016
|
}
|
|
3013
3017
|
function showStatusHelp() {
|
|
3014
3018
|
banner();
|
|
3015
|
-
console.log(` ${bold('turbine status')}
|
|
3019
|
+
console.log(` ${bold('turbine status')}, Show database schema summary`);
|
|
3016
3020
|
newline();
|
|
3017
3021
|
console.log(` ${bold('Usage:')}`);
|
|
3018
3022
|
console.log(` npx turbine status ${dim('[options]')}`);
|
|
@@ -3027,7 +3031,7 @@ function showStatusHelp() {
|
|
|
3027
3031
|
}
|
|
3028
3032
|
function showMcpHelp() {
|
|
3029
3033
|
banner();
|
|
3030
|
-
console.log(` ${bold('turbine mcp')}
|
|
3034
|
+
console.log(` ${bold('turbine mcp')}, Start read-only MCP server over stdio`);
|
|
3031
3035
|
newline();
|
|
3032
3036
|
console.log(` ${bold('Usage:')}`);
|
|
3033
3037
|
console.log(` npx turbine mcp ${dim('[options]')}`);
|
package/dist/cli/loader.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm CLI
|
|
2
|
+
* turbine-orm CLI, TypeScript loader registration
|
|
3
3
|
*
|
|
4
4
|
* The CLI loads user-supplied config and schema files via dynamic `import()`.
|
|
5
5
|
* Plain Node has no built-in `.ts` loader, so importing `turbine.config.ts`
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
* 2. Prefer tsx's supported programmatic API, `tsx/esm/api`'s `register()`.
|
|
13
13
|
* Calling Node's `module.register('tsx/esm', ...)` directly throws
|
|
14
14
|
* "tsx must be loaded with --import instead of --loader" on every Node
|
|
15
|
-
* version that has `module.register()` (>= 20.6)
|
|
15
|
+
* version that has `module.register()` (>= 20.6), tsx's hook file
|
|
16
16
|
* guards against being loaded that way. The `tsx/esm/api` entry point
|
|
17
17
|
* is the documented path and works everywhere `module.register()` does.
|
|
18
18
|
* 3. Fall back to `module.register('tsx/esm', ...)` only for very old tsx
|
|
19
19
|
* versions (< 4.0) that predate `tsx/esm/api`.
|
|
20
20
|
* 4. If tsx isn't installed, or registration genuinely fails, surface an
|
|
21
|
-
* actionable error
|
|
21
|
+
* actionable error, including the REAL underlying error message, never
|
|
22
22
|
* a misdiagnosed "tsx is not installed".
|
|
23
23
|
*
|
|
24
|
-
* `tsx` is intentionally NOT a runtime dependency
|
|
24
|
+
* `tsx` is intentionally NOT a runtime dependency, many projects already
|
|
25
25
|
* have it, and adding a heavy dev tool to a 1-dependency ORM would be silly.
|
|
26
26
|
*/
|
|
27
27
|
/**
|
|
@@ -45,7 +45,7 @@ export type TsLoaderStatus = 'registered' | 'already' | 'unsupported' | 'missing
|
|
|
45
45
|
export declare function getTsLoaderError(): string | null;
|
|
46
46
|
/**
|
|
47
47
|
* Register the tsx ESM loader so subsequent dynamic imports of `.ts` files
|
|
48
|
-
* work. Safe to call multiple times
|
|
48
|
+
* work. Safe to call multiple times, internal flag prevents double registration.
|
|
49
49
|
*
|
|
50
50
|
* Returns:
|
|
51
51
|
* - 'registered' loader was successfully registered this call
|
|
@@ -53,9 +53,9 @@ export declare function getTsLoaderError(): string | null;
|
|
|
53
53
|
* - 'unsupported' Node lacks `module.register()` (Node < 20.6) and tsx has
|
|
54
54
|
* no programmatic API to fall back to
|
|
55
55
|
* - 'missing' `tsx` is not installed in the user's project
|
|
56
|
-
* - 'failed' tsx IS installed but registration threw
|
|
56
|
+
* - 'failed' tsx IS installed but registration threw, see
|
|
57
57
|
* {@link getTsLoaderError} for the underlying message
|
|
58
58
|
*/
|
|
59
59
|
export declare function registerTsLoader(): Promise<TsLoaderStatus>;
|
|
60
|
-
/** Reset the loader state
|
|
60
|
+
/** Reset the loader state, used by unit tests only. */
|
|
61
61
|
export declare function _resetTsLoaderStateForTests(): void;
|
package/dist/cli/loader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm CLI
|
|
2
|
+
* turbine-orm CLI, TypeScript loader registration
|
|
3
3
|
*
|
|
4
4
|
* The CLI loads user-supplied config and schema files via dynamic `import()`.
|
|
5
5
|
* Plain Node has no built-in `.ts` loader, so importing `turbine.config.ts`
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
* 2. Prefer tsx's supported programmatic API, `tsx/esm/api`'s `register()`.
|
|
13
13
|
* Calling Node's `module.register('tsx/esm', ...)` directly throws
|
|
14
14
|
* "tsx must be loaded with --import instead of --loader" on every Node
|
|
15
|
-
* version that has `module.register()` (>= 20.6)
|
|
15
|
+
* version that has `module.register()` (>= 20.6), tsx's hook file
|
|
16
16
|
* guards against being loaded that way. The `tsx/esm/api` entry point
|
|
17
17
|
* is the documented path and works everywhere `module.register()` does.
|
|
18
18
|
* 3. Fall back to `module.register('tsx/esm', ...)` only for very old tsx
|
|
19
19
|
* versions (< 4.0) that predate `tsx/esm/api`.
|
|
20
20
|
* 4. If tsx isn't installed, or registration genuinely fails, surface an
|
|
21
|
-
* actionable error
|
|
21
|
+
* actionable error, including the REAL underlying error message, never
|
|
22
22
|
* a misdiagnosed "tsx is not installed".
|
|
23
23
|
*
|
|
24
|
-
* `tsx` is intentionally NOT a runtime dependency
|
|
24
|
+
* `tsx` is intentionally NOT a runtime dependency, many projects already
|
|
25
25
|
* have it, and adding a heavy dev tool to a 1-dependency ORM would be silly.
|
|
26
26
|
*/
|
|
27
27
|
import { createRequire } from 'node:module';
|
|
@@ -69,7 +69,7 @@ export function getTsLoaderError() {
|
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Register the tsx ESM loader so subsequent dynamic imports of `.ts` files
|
|
72
|
-
* work. Safe to call multiple times
|
|
72
|
+
* work. Safe to call multiple times, internal flag prevents double registration.
|
|
73
73
|
*
|
|
74
74
|
* Returns:
|
|
75
75
|
* - 'registered' loader was successfully registered this call
|
|
@@ -77,7 +77,7 @@ export function getTsLoaderError() {
|
|
|
77
77
|
* - 'unsupported' Node lacks `module.register()` (Node < 20.6) and tsx has
|
|
78
78
|
* no programmatic API to fall back to
|
|
79
79
|
* - 'missing' `tsx` is not installed in the user's project
|
|
80
|
-
* - 'failed' tsx IS installed but registration threw
|
|
80
|
+
* - 'failed' tsx IS installed but registration threw, see
|
|
81
81
|
* {@link getTsLoaderError} for the underlying message
|
|
82
82
|
*/
|
|
83
83
|
export async function registerTsLoader() {
|
|
@@ -110,14 +110,14 @@ export async function registerTsLoader() {
|
|
|
110
110
|
return 'failed';
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
// tsx/esm/api not resolvable
|
|
113
|
+
// tsx/esm/api not resolvable, is tsx installed at all?
|
|
114
114
|
if (!canResolveTsx()) {
|
|
115
115
|
tsLoaderState = 'missing';
|
|
116
116
|
return 'missing';
|
|
117
117
|
}
|
|
118
118
|
// Legacy fallback for tsx < 4.0 (no tsx/esm/api): Node's module.register.
|
|
119
119
|
// On tsx >= 4.19 this path throws ("tsx must be loaded with --import
|
|
120
|
-
// instead of --loader")
|
|
120
|
+
// instead of --loader"), but those versions all ship tsx/esm/api, so we
|
|
121
121
|
// only land here for genuinely old installs.
|
|
122
122
|
try {
|
|
123
123
|
const mod = await import('node:module');
|
|
@@ -137,7 +137,7 @@ export async function registerTsLoader() {
|
|
|
137
137
|
return 'failed';
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
/** Reset the loader state
|
|
140
|
+
/** Reset the loader state, used by unit tests only. */
|
|
141
141
|
export function _resetTsLoaderStateForTests() {
|
|
142
142
|
tsLoaderState = null;
|
|
143
143
|
tsLoaderError = null;
|
package/dist/cli/mcp.js
CHANGED
|
@@ -72,7 +72,7 @@ const TOOLS = [
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
name: 'explain_query',
|
|
75
|
-
description: 'Run EXPLAIN (FORMAT JSON) for a schema-validated findMany query. Pass table + optional where/orderBy/limit/select
|
|
75
|
+
description: 'Run EXPLAIN (FORMAT JSON) for a schema-validated findMany query. Pass table + optional where/orderBy/limit/select, free-form SQL is rejected.',
|
|
76
76
|
inputSchema: {
|
|
77
77
|
type: 'object',
|
|
78
78
|
properties: {
|
|
@@ -322,7 +322,7 @@ async function doctorReport(ctx) {
|
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
324
|
/**
|
|
325
|
-
* EXPLAIN a schema-validated findMany query. Free-form SQL is never accepted
|
|
325
|
+
* EXPLAIN a schema-validated findMany query. Free-form SQL is never accepted -
|
|
326
326
|
* table/field identifiers are checked against introspected metadata and the
|
|
327
327
|
* SELECT is compiled by QueryInterface (same stance as Studio `/api/builder`).
|
|
328
328
|
*/
|
|
@@ -364,7 +364,7 @@ async function explainQuery(ctx, args) {
|
|
|
364
364
|
}
|
|
365
365
|
/**
|
|
366
366
|
* Extract the allowed findMany subset for explain_query (no `with` / raw SQL).
|
|
367
|
-
* Returns a plain object cast at the buildFindMany call site
|
|
367
|
+
* Returns a plain object cast at the buildFindMany call site, same pattern as Studio.
|
|
368
368
|
*/
|
|
369
369
|
function parseExplainFindManyArgs(args) {
|
|
370
370
|
const findManyArgs = {};
|
|
@@ -642,7 +642,7 @@ export function buildRelations(tableNames, columnsByTable, pkByTable, rows, enum
|
|
|
642
642
|
for (const [tbl, cols] of columnsByTable) {
|
|
643
643
|
columnFieldsByTable.set(tbl, new Set(cols.map((c) => c.field)));
|
|
644
644
|
// Enum-typed columns also report tsType 'unknown', but the generated type
|
|
645
|
-
// layer gives them a concrete union
|
|
645
|
+
// layer gives them a concrete union, only json/jsonb qualify as shadows.
|
|
646
646
|
unknownTypedFieldsByTable.set(tbl, new Set(cols.filter((c) => isUnknownTsType(c.tsType) && !Object.hasOwn(enums, c.pgType)).map((c) => c.field)));
|
|
647
647
|
}
|
|
648
648
|
const relations = buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, undefined, unknownTypedFieldsByTable);
|
package/dist/cli/migrate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm CLI
|
|
2
|
+
* turbine-orm CLI, Migration system
|
|
3
3
|
*
|
|
4
4
|
* SQL-first migrations with UP/DOWN sections, tracked in _turbine_migrations.
|
|
5
5
|
* Migration files are timestamp-prefixed .sql files.
|
|
@@ -22,7 +22,7 @@ export interface MigrationFile {
|
|
|
22
22
|
path: string;
|
|
23
23
|
/** Extracted name portion (e.g. "20260325120000_create_users") */
|
|
24
24
|
name: string;
|
|
25
|
-
/** Timestamp prefix (e.g. "20260325120000")
|
|
25
|
+
/** Timestamp prefix (e.g. "20260325120000"), YYYYMMDDHHMMSS */
|
|
26
26
|
timestamp: string;
|
|
27
27
|
}
|
|
28
28
|
export interface AppliedMigration {
|
|
@@ -95,7 +95,7 @@ export declare function sanitizeName(name: string): string;
|
|
|
95
95
|
*/
|
|
96
96
|
export declare function formatTimestamp(date: Date): string;
|
|
97
97
|
/**
|
|
98
|
-
* Get pending migration files
|
|
98
|
+
* Get pending migration files, those not yet applied.
|
|
99
99
|
* Returns files sorted by timestamp (ascending).
|
|
100
100
|
*/
|
|
101
101
|
export declare function getPendingMigrations(migrationsDir: string, applied: string[]): MigrationFile[];
|
|
@@ -227,7 +227,7 @@ export declare function createMigration(migrationsDir: string, name: string, aut
|
|
|
227
227
|
/**
|
|
228
228
|
* Derive a Postgres advisory lock ID (positive int4) from the database name.
|
|
229
229
|
*
|
|
230
|
-
* Uses FNV-1a 32-bit hash
|
|
230
|
+
* Uses FNV-1a 32-bit hash, a well-known, stable, non-cryptographic hash with
|
|
231
231
|
* excellent distribution over short strings (database names are typically <64
|
|
232
232
|
* chars). Chosen over alternatives because it's:
|
|
233
233
|
* - deterministic (same input → same output, across processes/machines)
|
|
@@ -297,7 +297,7 @@ export declare function inspectMigrationDeploy(connectionString: string, migrati
|
|
|
297
297
|
* Features:
|
|
298
298
|
* - Idempotent: running twice is safe (already-applied migrations are skipped)
|
|
299
299
|
* - Advisory lock: prevents concurrent migration runs
|
|
300
|
-
* - Checksum validation: detects modified migration files (BLOCKING
|
|
300
|
+
* - Checksum validation: detects modified migration files (BLOCKING, use
|
|
301
301
|
* `allowDrift: true` to bypass when intentionally rewriting history)
|
|
302
302
|
* - Each migration runs in its own transaction
|
|
303
303
|
*
|
package/dist/cli/migrate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm CLI
|
|
2
|
+
* turbine-orm CLI, Migration system
|
|
3
3
|
*
|
|
4
4
|
* SQL-first migrations with UP/DOWN sections, tracked in _turbine_migrations.
|
|
5
5
|
* Migration files are timestamp-prefixed .sql files.
|
|
@@ -108,7 +108,7 @@ export function formatTimestamp(date) {
|
|
|
108
108
|
].join('');
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
|
-
* Get pending migration files
|
|
111
|
+
* Get pending migration files, those not yet applied.
|
|
112
112
|
* Returns files sorted by timestamp (ascending).
|
|
113
113
|
*/
|
|
114
114
|
export function getPendingMigrations(migrationsDir, applied) {
|
|
@@ -612,7 +612,7 @@ ${autoContent.down}
|
|
|
612
612
|
/**
|
|
613
613
|
* Derive a Postgres advisory lock ID (positive int4) from the database name.
|
|
614
614
|
*
|
|
615
|
-
* Uses FNV-1a 32-bit hash
|
|
615
|
+
* Uses FNV-1a 32-bit hash, a well-known, stable, non-cryptographic hash with
|
|
616
616
|
* excellent distribution over short strings (database names are typically <64
|
|
617
617
|
* chars). Chosen over alternatives because it's:
|
|
618
618
|
* - deterministic (same input → same output, across processes/machines)
|
|
@@ -729,7 +729,7 @@ export function formatChecksumMismatchError(mismatches) {
|
|
|
729
729
|
const modified = mismatches.filter((m) => m.type === 'modified');
|
|
730
730
|
const missing = mismatches.filter((m) => m.type === 'missing');
|
|
731
731
|
const lines = [
|
|
732
|
-
'[turbine] Migration drift detected
|
|
732
|
+
'[turbine] Migration drift detected, refusing to apply pending migrations.',
|
|
733
733
|
'',
|
|
734
734
|
'Applied migrations should be immutable. The following files no longer match their applied state:',
|
|
735
735
|
'',
|
|
@@ -751,7 +751,7 @@ export function formatChecksumMismatchError(mismatches) {
|
|
|
751
751
|
if (missing.length > 0) {
|
|
752
752
|
lines.push(' (deleted files cannot be rolled back: restore the file, then run `migrate down` if needed), OR');
|
|
753
753
|
}
|
|
754
|
-
lines.push(' 3. Pass `--allow-drift` to bypass this check (advanced
|
|
754
|
+
lines.push(' 3. Pass `--allow-drift` to bypass this check (advanced, make sure you know what you are doing).');
|
|
755
755
|
return lines.join('\n');
|
|
756
756
|
}
|
|
757
757
|
/**
|
|
@@ -820,7 +820,7 @@ export async function inspectMigrationDeploy(connectionString, migrationsDir) {
|
|
|
820
820
|
* Features:
|
|
821
821
|
* - Idempotent: running twice is safe (already-applied migrations are skipped)
|
|
822
822
|
* - Advisory lock: prevents concurrent migration runs
|
|
823
|
-
* - Checksum validation: detects modified migration files (BLOCKING
|
|
823
|
+
* - Checksum validation: detects modified migration files (BLOCKING, use
|
|
824
824
|
* `allowDrift: true` to bypass when intentionally rewriting history)
|
|
825
825
|
* - Each migration runs in its own transaction
|
|
826
826
|
*
|
|
@@ -844,7 +844,7 @@ export async function migrateUp(connectionString, migrationsDir, options) {
|
|
|
844
844
|
const adapter = options?.adapter;
|
|
845
845
|
const gotLock = await acquireLock(client, lockId, adapter);
|
|
846
846
|
if (!gotLock) {
|
|
847
|
-
throw new MigrationError('[turbine] Could not acquire migration lock
|
|
847
|
+
throw new MigrationError('[turbine] Could not acquire migration lock, another migration is already running');
|
|
848
848
|
}
|
|
849
849
|
try {
|
|
850
850
|
await ensureTrackingTable(client, dialect);
|
|
@@ -881,7 +881,7 @@ export async function migrateUp(connectionString, migrationsDir, options) {
|
|
|
881
881
|
// Data-loss gate: refuse to run pending migrations containing destructive
|
|
882
882
|
// statements unless the caller has EXPLICITLY opted in. The CLI layers an
|
|
883
883
|
// interactive typed confirmation on top of this; programmatic callers must
|
|
884
|
-
// pass `allowDestructive: true`. Safe-by-default is the whole point
|
|
884
|
+
// pass `allowDestructive: true`. Safe-by-default is the whole point, a
|
|
885
885
|
// DROP TABLE should never run just because a file exists.
|
|
886
886
|
if (!options?.allowDestructive && destructive.length > 0) {
|
|
887
887
|
const lines = ['[turbine] Refusing to apply migrations containing DESTRUCTIVE statements:', ''];
|
|
@@ -995,7 +995,7 @@ export async function migrateDown(connectionString, migrationsDir, options) {
|
|
|
995
995
|
const adapter = options?.adapter;
|
|
996
996
|
const gotLock = await acquireLock(client, lockId, adapter);
|
|
997
997
|
if (!gotLock) {
|
|
998
|
-
throw new MigrationError('[turbine] Could not acquire migration lock
|
|
998
|
+
throw new MigrationError('[turbine] Could not acquire migration lock, another migration is already running');
|
|
999
999
|
}
|
|
1000
1000
|
try {
|
|
1001
1001
|
await ensureTrackingTable(client, dialect);
|
|
@@ -1005,9 +1005,9 @@ export async function migrateDown(connectionString, migrationsDir, options) {
|
|
|
1005
1005
|
}
|
|
1006
1006
|
const allFiles = listMigrationFiles(migrationsDir);
|
|
1007
1007
|
const fileMap = new Map(allFiles.map((f) => [f.name, f]));
|
|
1008
|
-
// Reverse order
|
|
1008
|
+
// Reverse order, rollback most recent first
|
|
1009
1009
|
const toRollback = applied.reverse().slice(0, options?.step ?? 1);
|
|
1010
|
-
// Same data-loss gate as migrateUp
|
|
1010
|
+
// Same data-loss gate as migrateUp, DOWN sections routinely contain
|
|
1011
1011
|
// DROP TABLE (the legitimate reverse of a CREATE), which still destroys
|
|
1012
1012
|
// every row written since the migration ran. Explicit opt-in required.
|
|
1013
1013
|
if (!options?.allowDestructive) {
|