turbine-orm 0.65.0 → 0.66.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 +34 -32
- package/dist/adapters/cockroachdb.js +21 -3
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/yugabytedb.js +20 -3
- package/dist/cjs/adapters/cockroachdb.js +21 -3
- package/dist/cjs/adapters/index.d.ts +15 -0
- package/dist/cjs/adapters/yugabytedb.js +20 -3
- package/dist/cjs/cli/destructive.d.ts +18 -4
- package/dist/cjs/cli/destructive.js +230 -122
- package/dist/cjs/cli/index.d.ts +21 -4
- package/dist/cjs/cli/index.js +119 -22
- package/dist/cjs/cli/mcp.d.ts +28 -8
- package/dist/cjs/cli/mcp.js +170 -127
- package/dist/cjs/cli/migrate.d.ts +134 -13
- package/dist/cjs/cli/migrate.js +349 -241
- package/dist/cjs/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cjs/cli/pii-predicate-guard.js +390 -0
- package/dist/cjs/cli/prisma-resolve.js +75 -4
- package/dist/cjs/cli/prisma-schema.d.ts +17 -1
- package/dist/cjs/cli/prisma-schema.js +83 -17
- package/dist/cjs/cli/sql-statements.d.ts +125 -0
- package/dist/cjs/cli/sql-statements.js +378 -0
- package/dist/cjs/cli/studio.js +49 -118
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +43 -0
- package/dist/cjs/client.js +125 -6
- package/dist/cjs/dialect.d.ts +123 -0
- package/dist/cjs/dialect.js +33 -0
- package/dist/cjs/errors.d.ts +74 -1
- package/dist/cjs/errors.js +239 -25
- package/dist/cjs/index-advisor.d.ts +33 -1
- package/dist/cjs/index-advisor.js +32 -1
- package/dist/cjs/introspect.d.ts +48 -0
- package/dist/cjs/introspect.js +222 -91
- package/dist/cjs/mssql.js +43 -1
- package/dist/cjs/mysql.d.ts +5 -2
- package/dist/cjs/mysql.js +202 -17
- package/dist/cjs/nested-write.js +6 -1
- package/dist/cjs/pipeline-submittable.js +17 -3
- package/dist/cjs/pipeline.js +75 -9
- package/dist/cjs/powdb.d.ts +23 -0
- package/dist/cjs/powdb.js +33 -1
- package/dist/cjs/powql.d.ts +61 -9
- package/dist/cjs/powql.js +186 -49
- package/dist/cjs/prisma-compat.js +160 -41
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +80 -18
- package/dist/cjs/query/batched-loader.d.ts +10 -0
- package/dist/cjs/query/batched-loader.js +268 -7
- package/dist/cjs/query/builder.d.ts +73 -0
- package/dist/cjs/query/builder.js +225 -28
- package/dist/cjs/query/filters.d.ts +162 -0
- package/dist/cjs/query/filters.js +250 -1
- package/dist/cjs/query/relations.d.ts +10 -10
- package/dist/cjs/query/relations.js +93 -12
- package/dist/cjs/query/types.d.ts +14 -1
- package/dist/cjs/query/utils.d.ts +146 -2
- package/dist/cjs/query/utils.js +210 -4
- package/dist/cjs/query/warn-registry.d.ts +10 -0
- package/dist/cjs/query/warn-registry.js +10 -0
- package/dist/cjs/query/where-compile.d.ts +30 -0
- package/dist/cjs/query/where-compile.js +41 -0
- package/dist/cjs/query/where.d.ts +128 -13
- package/dist/cjs/query/where.js +215 -77
- package/dist/cjs/query/writes.d.ts +1 -1
- package/dist/cjs/query/writes.js +39 -15
- package/dist/cjs/schema-builder.d.ts +2 -1
- package/dist/cjs/schema-sql.d.ts +94 -4
- package/dist/cjs/schema-sql.js +506 -30
- package/dist/cjs/schema.d.ts +3 -1
- package/dist/cjs/sqlite.d.ts +6 -0
- package/dist/cjs/sqlite.js +151 -10
- package/dist/cjs/typed-sql.d.ts +29 -1
- package/dist/cjs/typed-sql.js +30 -12
- package/dist/cli/destructive.d.ts +18 -4
- package/dist/cli/destructive.js +229 -121
- package/dist/cli/index.d.ts +21 -4
- package/dist/cli/index.js +120 -24
- package/dist/cli/mcp.d.ts +28 -8
- package/dist/cli/mcp.js +172 -129
- package/dist/cli/migrate.d.ts +134 -13
- package/dist/cli/migrate.js +347 -238
- package/dist/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cli/pii-predicate-guard.js +386 -0
- package/dist/cli/prisma-resolve.js +75 -4
- package/dist/cli/prisma-schema.d.ts +17 -1
- package/dist/cli/prisma-schema.js +83 -17
- package/dist/cli/sql-statements.d.ts +125 -0
- package/dist/cli/sql-statements.js +373 -0
- package/dist/cli/studio.js +49 -118
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +43 -0
- package/dist/client.js +126 -7
- package/dist/dialect.d.ts +123 -0
- package/dist/dialect.js +33 -0
- package/dist/errors.d.ts +74 -1
- package/dist/errors.js +228 -19
- package/dist/index-advisor.d.ts +33 -1
- package/dist/index-advisor.js +31 -1
- package/dist/introspect.d.ts +48 -0
- package/dist/introspect.js +221 -91
- package/dist/mssql.js +44 -2
- package/dist/mysql.d.ts +5 -2
- package/dist/mysql.js +203 -18
- package/dist/nested-write.js +7 -2
- package/dist/pipeline-submittable.js +18 -4
- package/dist/pipeline.js +76 -10
- package/dist/powdb.d.ts +23 -0
- package/dist/powdb.js +33 -2
- package/dist/powql.d.ts +61 -9
- package/dist/powql.js +187 -50
- package/dist/prisma-compat.js +160 -41
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +82 -20
- package/dist/query/batched-loader.d.ts +10 -0
- package/dist/query/batched-loader.js +270 -9
- package/dist/query/builder.d.ts +73 -0
- package/dist/query/builder.js +226 -30
- package/dist/query/filters.d.ts +162 -0
- package/dist/query/filters.js +246 -1
- package/dist/query/relations.d.ts +10 -10
- package/dist/query/relations.js +94 -14
- package/dist/query/types.d.ts +14 -1
- package/dist/query/utils.d.ts +146 -2
- package/dist/query/utils.js +204 -3
- package/dist/query/warn-registry.d.ts +10 -0
- package/dist/query/warn-registry.js +10 -0
- package/dist/query/where-compile.d.ts +30 -0
- package/dist/query/where-compile.js +40 -1
- package/dist/query/where.d.ts +128 -13
- package/dist/query/where.js +216 -80
- package/dist/query/writes.d.ts +1 -1
- package/dist/query/writes.js +40 -16
- package/dist/schema-builder.d.ts +2 -1
- package/dist/schema-sql.d.ts +94 -4
- package/dist/schema-sql.js +505 -30
- package/dist/schema.d.ts +3 -1
- package/dist/sqlite.d.ts +6 -0
- package/dist/sqlite.js +151 -10
- package/dist/typed-sql.d.ts +29 -1
- package/dist/typed-sql.js +30 -12
- package/package.json +6 -4
package/dist/cli/index.js
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* npx turbine init --url postgres://...
|
|
25
25
|
* npx turbine migrate create add_users_table
|
|
26
26
|
*/
|
|
27
|
-
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
|
|
27
|
+
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
28
28
|
import { tmpdir } from 'node:os';
|
|
29
29
|
import { basename, dirname, extname, join, relative, resolve, sep } from 'node:path';
|
|
30
30
|
import { pathToFileURL } from 'node:url';
|
|
31
31
|
import { generate, generatePrismaMap } from '../generate.js';
|
|
32
|
-
import { buildCreateIndexSql, buildDropIndexSql, collectDoctorProbeIndexNames, collectRelationProbeColumns, findMissingRelationIndexes, } from '../index-advisor.js';
|
|
32
|
+
import { buildCreateIndexSql, buildCreateIndexStatements, buildDropIndexSql, collectDoctorProbeIndexNames, collectRelationProbeColumns, findMissingRelationIndexes, } from '../index-advisor.js';
|
|
33
33
|
import { auditDoctorIndexes, collectStatsSnapshot, collectTableHeat, findInvalidIndexes, findRedundantIndexes, findUnusedIndexes, formatBytes, isSnapshotUsable, STATS_THRESHOLDS, scoreMissingIndex, } from '../index-stats.js';
|
|
34
34
|
import { introspect } from '../introspect.js';
|
|
35
35
|
import { collectDivergenceCandidateColumns, collectDivergenceOrderColumns, findPlanDivergence, PLAN_DIVERGENCE_THRESHOLDS, } from '../plan-divergence.js';
|
|
@@ -911,15 +911,21 @@ async function runInitPush(config, url) {
|
|
|
911
911
|
}
|
|
912
912
|
const schemaDef = await loadSchemaFile(config.schemaFile);
|
|
913
913
|
const spinner = new Spinner('Computing schema diff').start();
|
|
914
|
-
const diff = await schemaDiff(schemaDef, url);
|
|
914
|
+
const diff = await schemaDiff(schemaDef, url, { schema: config.schema });
|
|
915
915
|
if (diff.statements.length === 0) {
|
|
916
916
|
spinner.succeed('Database already in sync: nothing to push');
|
|
917
917
|
return;
|
|
918
918
|
}
|
|
919
919
|
spinner.succeed(`Found ${bold(String(diff.statements.length))} change(s) to apply`);
|
|
920
|
+
// Same as `push`: a type change carries a warning naming what it costs the
|
|
921
|
+
// existing rows, and it is worth no less here than on the standalone command.
|
|
922
|
+
if (diff.warnings && diff.warnings.length > 0) {
|
|
923
|
+
for (const w of diff.warnings)
|
|
924
|
+
warn(w);
|
|
925
|
+
}
|
|
920
926
|
const pushSpinner = new Spinner('Applying schema').start();
|
|
921
927
|
try {
|
|
922
|
-
const result = await schemaPush(schemaDef, url, { precomputedDiff: diff });
|
|
928
|
+
const result = await schemaPush(schemaDef, url, { precomputedDiff: diff, schema: config.schema });
|
|
923
929
|
pushSpinner.succeed(`Applied ${bold(String(result.statementsExecuted))} statement(s)`);
|
|
924
930
|
}
|
|
925
931
|
catch (err) {
|
|
@@ -930,7 +936,11 @@ async function runInitPush(config, url) {
|
|
|
930
936
|
warn('Push aborted: no schema changes were applied.');
|
|
931
937
|
return;
|
|
932
938
|
}
|
|
933
|
-
const result = await schemaPush(schemaDef, url, {
|
|
939
|
+
const result = await schemaPush(schemaDef, url, {
|
|
940
|
+
allowDestructive: true,
|
|
941
|
+
precomputedDiff: diff,
|
|
942
|
+
schema: config.schema,
|
|
943
|
+
});
|
|
934
944
|
success(`Applied ${bold(String(result.statementsExecuted))} statement(s)`);
|
|
935
945
|
}
|
|
936
946
|
}
|
|
@@ -1405,19 +1415,57 @@ export function resolveMigrateFromPrismaUrl(configUrl, ast, env) {
|
|
|
1405
1415
|
}
|
|
1406
1416
|
return { source: 'none', missingVariables: lookup.missingVariables };
|
|
1407
1417
|
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Read a Prisma schema from a FILE or from a multi-file schema DIRECTORY
|
|
1420
|
+
* (Prisma >= 5.15, GA). A directory used to reach `readFileSync` unchanged and
|
|
1421
|
+
* die with a raw `EISDIR`, which is a stack trace rather than an answer.
|
|
1422
|
+
*
|
|
1423
|
+
* Prisma treats every `.prisma` file under the directory as one logical schema,
|
|
1424
|
+
* so they are concatenated in sorted order (stable across machines) and parsed
|
|
1425
|
+
* as a single source. Line numbers in any parse error then refer to that
|
|
1426
|
+
* concatenation, so each file is introduced by a comment naming it.
|
|
1427
|
+
*/
|
|
1428
|
+
export function readPrismaSchemaSource(path) {
|
|
1429
|
+
if (!statSync(path).isDirectory())
|
|
1430
|
+
return readFileSync(path, 'utf-8');
|
|
1431
|
+
const files = readdirSync(path)
|
|
1432
|
+
.filter((f) => f.endsWith('.prisma'))
|
|
1433
|
+
.sort();
|
|
1434
|
+
if (files.length === 0) {
|
|
1435
|
+
throw new Error(`No .prisma files found in directory ${path}.`);
|
|
1436
|
+
}
|
|
1437
|
+
return files.map((f) => `// file: ${f}\n${readFileSync(join(path, f), 'utf-8')}`).join('\n');
|
|
1438
|
+
}
|
|
1408
1439
|
async function cmdMigrateFromPrisma(args, config) {
|
|
1409
1440
|
banner();
|
|
1410
|
-
// `--schema` is the Prisma schema file path in this command.
|
|
1411
|
-
|
|
1441
|
+
// `--schema` is the Prisma schema file path in this command. It may also name
|
|
1442
|
+
// a DIRECTORY (Prisma's multi-file schema, GA since 5.15), and the default
|
|
1443
|
+
// `prisma/schema.prisma` falls back to `prisma/schema/` for the same reason.
|
|
1444
|
+
const explicit = args.schema !== undefined;
|
|
1445
|
+
let prismaPath = resolve(args.schema ?? 'prisma/schema.prisma');
|
|
1446
|
+
if (!explicit && !existsSync(prismaPath) && existsSync(resolve('prisma/schema'))) {
|
|
1447
|
+
prismaPath = resolve('prisma/schema');
|
|
1448
|
+
}
|
|
1412
1449
|
if (!existsSync(prismaPath)) {
|
|
1413
|
-
error(`Prisma schema
|
|
1450
|
+
error(`Prisma schema not found: ${cyan(prismaPath)}`);
|
|
1414
1451
|
newline();
|
|
1415
|
-
console.log(` ${dim('Point at it with')} ${cyan('--schema <path
|
|
1452
|
+
console.log(` ${dim('Point at it with')} ${cyan('--schema <path>')} ${dim('(a .prisma file, or a directory of them;')}`);
|
|
1453
|
+
console.log(` ${dim('default: prisma/schema.prisma, then prisma/schema/).')}`);
|
|
1416
1454
|
newline();
|
|
1417
1455
|
process.exit(1);
|
|
1418
1456
|
}
|
|
1419
1457
|
// Parse (fatal only on a construct we must understand).
|
|
1420
|
-
|
|
1458
|
+
let source;
|
|
1459
|
+
try {
|
|
1460
|
+
source = readPrismaSchemaSource(prismaPath);
|
|
1461
|
+
}
|
|
1462
|
+
catch (err) {
|
|
1463
|
+
newline();
|
|
1464
|
+
error(`Could not read ${cyan(prismaPath)}`);
|
|
1465
|
+
console.log(` ${red(err instanceof Error ? err.message : String(err))}`);
|
|
1466
|
+
newline();
|
|
1467
|
+
process.exit(1);
|
|
1468
|
+
}
|
|
1421
1469
|
let ast;
|
|
1422
1470
|
try {
|
|
1423
1471
|
ast = parsePrismaSchema(source);
|
|
@@ -1489,6 +1537,18 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1489
1537
|
console.log(` ${marker} ${line}`);
|
|
1490
1538
|
}
|
|
1491
1539
|
newline();
|
|
1540
|
+
// Parser notes on the CONSOLE, not only in the Markdown report.
|
|
1541
|
+
//
|
|
1542
|
+
// `--no-db` is the first command anyone runs and the one place a parse
|
|
1543
|
+
// problem should surface, and it was the one place that could not: resolution
|
|
1544
|
+
// is skipped, `hasUnresolved` is false by construction, so the run printed a
|
|
1545
|
+
// clean summary and exited 0 no matter what the parser had thrown away.
|
|
1546
|
+
if (result.parseWarnings.length > 0) {
|
|
1547
|
+
header('Parser notes');
|
|
1548
|
+
for (const w of result.parseWarnings)
|
|
1549
|
+
console.log(` ${yellow(symbols.warning)} ${w}`);
|
|
1550
|
+
newline();
|
|
1551
|
+
}
|
|
1492
1552
|
// Write outputs into the generate outDir.
|
|
1493
1553
|
const outDir = resolve(config.out);
|
|
1494
1554
|
const rel = relative(process.cwd(), outDir);
|
|
@@ -1536,6 +1596,15 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1536
1596
|
newline();
|
|
1537
1597
|
process.exit(1);
|
|
1538
1598
|
}
|
|
1599
|
+
// `--no-db` has no resolution to fail on, so parser notes are the ONLY signal
|
|
1600
|
+
// it can carry. Treated like an unresolved item, and cleared by the same
|
|
1601
|
+
// `--allow-partial` opt-in, so "exit 0" means the same thing in both modes.
|
|
1602
|
+
if (args.noDb && result.parseWarnings.length > 0 && !args.allowPartial) {
|
|
1603
|
+
warn(`${result.parseWarnings.length} parser note(s) above (also in the report). ` +
|
|
1604
|
+
'Re-run with --allow-partial to accept them.');
|
|
1605
|
+
newline();
|
|
1606
|
+
process.exit(1);
|
|
1607
|
+
}
|
|
1539
1608
|
if (args.noDb) {
|
|
1540
1609
|
info(`Parse-only report written. Re-run without ${cyan('--no-db')} against your database to resolve names.`);
|
|
1541
1610
|
}
|
|
@@ -1558,7 +1627,7 @@ async function cmdPush(args, config) {
|
|
|
1558
1627
|
info(`Schema defines ${bold(String(tableCount))} tables`);
|
|
1559
1628
|
// Compute diff
|
|
1560
1629
|
const diffSpinner = new Spinner('Computing schema diff').start();
|
|
1561
|
-
const diff = await schemaDiff(schemaDef, url);
|
|
1630
|
+
const diff = await schemaDiff(schemaDef, url, { schema: config.schema });
|
|
1562
1631
|
if (diff.statements.length === 0 && diff.drop.length === 0) {
|
|
1563
1632
|
diffSpinner.succeed('Database is already in sync');
|
|
1564
1633
|
newline();
|
|
@@ -1633,7 +1702,11 @@ async function cmdPush(args, config) {
|
|
|
1633
1702
|
const pushSpinner = new Spinner('Applying changes').start();
|
|
1634
1703
|
let result;
|
|
1635
1704
|
try {
|
|
1636
|
-
result = await schemaPush(schemaDef, url, {
|
|
1705
|
+
result = await schemaPush(schemaDef, url, {
|
|
1706
|
+
allowDestructive: args.allowDestructive,
|
|
1707
|
+
precomputedDiff: diff,
|
|
1708
|
+
schema: config.schema,
|
|
1709
|
+
});
|
|
1637
1710
|
}
|
|
1638
1711
|
catch (err) {
|
|
1639
1712
|
if (!(err instanceof DestructivePushRefusal))
|
|
@@ -1645,7 +1718,11 @@ async function cmdPush(args, config) {
|
|
|
1645
1718
|
process.exit(1);
|
|
1646
1719
|
}
|
|
1647
1720
|
pushSpinner.start();
|
|
1648
|
-
result = await schemaPush(schemaDef, url, {
|
|
1721
|
+
result = await schemaPush(schemaDef, url, {
|
|
1722
|
+
allowDestructive: true,
|
|
1723
|
+
precomputedDiff: diff,
|
|
1724
|
+
schema: config.schema,
|
|
1725
|
+
});
|
|
1649
1726
|
}
|
|
1650
1727
|
pushSpinner.succeed(`Applied ${bold(String(result.statementsExecuted))} statement(s)`);
|
|
1651
1728
|
if (result.tablesCreated.length > 0) {
|
|
@@ -1759,7 +1836,7 @@ async function cmdMigrateCreate(args, config) {
|
|
|
1759
1836
|
newline();
|
|
1760
1837
|
const schemaDef = await loadSchemaFile(config.schemaFile);
|
|
1761
1838
|
const diffSpinner = new Spinner('Computing schema diff').start();
|
|
1762
|
-
const diff = await schemaDiff(schemaDef, url);
|
|
1839
|
+
const diff = await schemaDiff(schemaDef, url, { schema: config.schema });
|
|
1763
1840
|
if (diff.statements.length === 0) {
|
|
1764
1841
|
diffSpinner.succeed('Database is already in sync: nothing to migrate');
|
|
1765
1842
|
newline();
|
|
@@ -1836,7 +1913,7 @@ async function cmdMigrateCreate(args, config) {
|
|
|
1836
1913
|
newline();
|
|
1837
1914
|
const schemaDef = await loadSchemaFile(config.schemaFile);
|
|
1838
1915
|
const diffSpinner = new Spinner('Computing schema diff').start();
|
|
1839
|
-
const diff = await schemaDiff(schemaDef, url);
|
|
1916
|
+
const diff = await schemaDiff(schemaDef, url, { schema: config.schema });
|
|
1840
1917
|
if (diff.statements.length === 0) {
|
|
1841
1918
|
diffSpinner.succeed('Database is already in sync: nothing to migrate');
|
|
1842
1919
|
newline();
|
|
@@ -2496,9 +2573,24 @@ const TIER_LABEL = {
|
|
|
2496
2573
|
'take-deliberately': 'TAKE DELIBERATELY',
|
|
2497
2574
|
scrutinize: 'SCRUTINIZE',
|
|
2498
2575
|
};
|
|
2499
|
-
/**
|
|
2576
|
+
/**
|
|
2577
|
+
* The `CREATE INDEX` for a finding (display / JSON only), honoring the
|
|
2578
|
+
* partial-null suggestion. The migration writer uses
|
|
2579
|
+
* {@link buildCreateIndexStatements} instead, which adds the preceding
|
|
2580
|
+
* `DROP INDEX CONCURRENTLY IF EXISTS` that makes a rerun converge.
|
|
2581
|
+
*/
|
|
2500
2582
|
function doctorCreateSql(f, opts) {
|
|
2501
2583
|
return buildCreateIndexSql(f.missing.table, f.missing.columns, f.missing.indexName, {
|
|
2584
|
+
concurrently: opts.concurrently,
|
|
2585
|
+
// The report line is advice a human may copy into psql, where the
|
|
2586
|
+
// guard is genuinely useful and there is no corpse to skip.
|
|
2587
|
+
ifNotExists: true,
|
|
2588
|
+
partialNotNull: f.score.partialNotNull,
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
/** The statements the fix migration's UP body runs for one finding. */
|
|
2592
|
+
function doctorCreateStatements(f, opts) {
|
|
2593
|
+
return buildCreateIndexStatements(f.missing.table, f.missing.columns, f.missing.indexName, {
|
|
2502
2594
|
concurrently: opts.concurrently,
|
|
2503
2595
|
partialNotNull: f.score.partialNotNull,
|
|
2504
2596
|
});
|
|
@@ -2511,13 +2603,17 @@ const CONCURRENTLY_RECIPE_COMMENT = [
|
|
|
2511
2603
|
'--',
|
|
2512
2604
|
'-- 1. Idempotency is required. This migration is recorded only after ALL',
|
|
2513
2605
|
'-- statements succeed; a mid-file failure leaves earlier indexes built and',
|
|
2514
|
-
'-- the migration unrecorded, so a rerun must be safe.
|
|
2515
|
-
'--
|
|
2516
|
-
'--
|
|
2517
|
-
'--
|
|
2518
|
-
'--
|
|
2519
|
-
'--
|
|
2520
|
-
'--
|
|
2606
|
+
'-- the migration unrecorded, so a rerun must be safe.',
|
|
2607
|
+
'-- 2. The INVALID-index trap, and why each CREATE is preceded by a DROP.',
|
|
2608
|
+
'-- A CREATE INDEX CONCURRENTLY that fails partway leaves an INVALID index',
|
|
2609
|
+
'-- behind, under the name it was asked for. IF NOT EXISTS matches on the',
|
|
2610
|
+
'-- NAME and not on validity, so with it a rerun SKIPS the corpse and the',
|
|
2611
|
+
'-- index is never actually built: the migration records as applied while',
|
|
2612
|
+
'-- the index doctor reported is still missing. The DROP INDEX',
|
|
2613
|
+
'-- CONCURRENTLY IF EXISTS line ahead of each CREATE is a no-op on the',
|
|
2614
|
+
'-- first run (the index does not exist yet, which is why it was',
|
|
2615
|
+
'-- proposed) and clears the corpse on a rerun, so rerunning converges on',
|
|
2616
|
+
'-- a VALID index. Run "turbine doctor" to list invalid indexes.',
|
|
2521
2617
|
'-- 3. Locking. CREATE INDEX CONCURRENTLY waits for every transaction that can',
|
|
2522
2618
|
'-- see the table to finish. A long-running transaction makes it wait and',
|
|
2523
2619
|
'-- makes "migrate up" look hung. For bounded waits, SET lock_timeout /',
|
|
@@ -3231,7 +3327,7 @@ function renderPlanDivergence(divergence, attached) {
|
|
|
3231
3327
|
/** Write the --fix migration (CONCURRENTLY + directive by default; plain with --no-concurrently). */
|
|
3232
3328
|
function renderFixMigration(findings, config, args) {
|
|
3233
3329
|
const concurrently = args.noConcurrently !== true;
|
|
3234
|
-
const up = findings.
|
|
3330
|
+
const up = findings.flatMap((f) => doctorCreateStatements(f, { concurrently })).join('\n');
|
|
3235
3331
|
const down = findings.map((f) => buildDropIndexSql(f.missing.indexName, { concurrently })).join('\n');
|
|
3236
3332
|
let file;
|
|
3237
3333
|
if (concurrently) {
|
package/dist/cli/mcp.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Readable, Writable } from 'node:stream';
|
|
2
2
|
import pg from 'pg';
|
|
3
|
-
import { type ColumnMetadata, type RelationDef } from '../schema.js';
|
|
3
|
+
import { type ColumnMetadata, type IndexMetadata, type RelationDef } from '../schema.js';
|
|
4
4
|
export interface McpServerOptions {
|
|
5
5
|
url: string;
|
|
6
6
|
schema: string;
|
|
@@ -31,6 +31,14 @@ export interface McpServerHandle {
|
|
|
31
31
|
}
|
|
32
32
|
export declare function startMcpServer(options: McpServerOptions, transport?: McpTransport): McpServerHandle;
|
|
33
33
|
interface ForeignKeyRow {
|
|
34
|
+
/**
|
|
35
|
+
* `pg_constraint.oid` as text. THE grouping key: a constraint NAME is unique
|
|
36
|
+
* only per table (conrelid, conname), so two tables in one schema may both
|
|
37
|
+
* have a `shared_fk` and grouping on the name alone merges them, which loses
|
|
38
|
+
* one table's relation entirely and points the other at a column its target
|
|
39
|
+
* does not have. The OID is unique catalog-wide.
|
|
40
|
+
*/
|
|
41
|
+
constraint_oid: string;
|
|
34
42
|
source_table: string;
|
|
35
43
|
source_column: string;
|
|
36
44
|
target_table: string;
|
|
@@ -38,13 +46,25 @@ interface ForeignKeyRow {
|
|
|
38
46
|
constraint_name: string;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
|
-
* Group raw FK rows into constraint-level entries and delegate relation
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
49
|
+
* Group raw FK rows into constraint-level entries and delegate relation naming
|
|
50
|
+
* to `deriveCatalogRelations` in ../introspect.ts, the SAME entry point
|
|
51
|
+
* `turbine generate` goes through. MCP introspects for itself (it cannot assume
|
|
52
|
+
* generated metadata exists), so any divergence here is a divergence a live MCP
|
|
53
|
+
* client trips over: it reads a relation name out of the MCP schema tool, passes
|
|
54
|
+
* it back in a query, and the core builder rejects it.
|
|
55
|
+
*
|
|
56
|
+
* MCP used to assemble the pipeline by hand and drifted twice over, both times
|
|
57
|
+
* by OMITTING an optional argument, which is silently type-correct and changes
|
|
58
|
+
* the answer: without `uniqueSetsByTable` a UNIQUE foreign key came back as
|
|
59
|
+
* `users.profiles` (hasMany) where generate said `users.profile` (hasOne), and
|
|
60
|
+
* without `uniqueIndexColsByTable` every Prisma-style PK-less junction lost its
|
|
61
|
+
* auto-m2m relations. Passing the whole input set to one shared function is what
|
|
62
|
+
* makes those two failures impossible rather than merely fixed.
|
|
63
|
+
*
|
|
64
|
+
* `uniqueByTable` and `indexesByTable` are therefore REQUIRED parameters, not
|
|
65
|
+
* optional ones: an optional catalog input is exactly how the hasOne flip went
|
|
66
|
+
* missing here in the first place. Exported for the parity unit test.
|
|
47
67
|
*/
|
|
48
|
-
export declare function buildRelations(tableNames: string[], columnsByTable: Map<string, ColumnMetadata[]>, pkByTable: Map<string, string[]>, rows: ForeignKeyRow[], enums?: Record<string, string[]>): Map<string, Record<string, RelationDef>>;
|
|
68
|
+
export declare function buildRelations(tableNames: string[], columnsByTable: Map<string, ColumnMetadata[]>, pkByTable: Map<string, string[]>, rows: ForeignKeyRow[], uniqueByTable: Map<string, string[][]>, indexesByTable: Map<string, IndexMetadata[]>, enums?: Record<string, string[]>): Map<string, Record<string, RelationDef>>;
|
|
49
69
|
export declare function runMcpServer(options: McpServerOptions): Promise<void>;
|
|
50
70
|
export {};
|