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.
Files changed (142) hide show
  1. package/README.md +34 -32
  2. package/dist/adapters/cockroachdb.js +21 -3
  3. package/dist/adapters/index.d.ts +15 -0
  4. package/dist/adapters/yugabytedb.js +20 -3
  5. package/dist/cjs/adapters/cockroachdb.js +21 -3
  6. package/dist/cjs/adapters/index.d.ts +15 -0
  7. package/dist/cjs/adapters/yugabytedb.js +20 -3
  8. package/dist/cjs/cli/destructive.d.ts +18 -4
  9. package/dist/cjs/cli/destructive.js +230 -122
  10. package/dist/cjs/cli/index.d.ts +21 -4
  11. package/dist/cjs/cli/index.js +119 -22
  12. package/dist/cjs/cli/mcp.d.ts +28 -8
  13. package/dist/cjs/cli/mcp.js +170 -127
  14. package/dist/cjs/cli/migrate.d.ts +134 -13
  15. package/dist/cjs/cli/migrate.js +349 -241
  16. package/dist/cjs/cli/pii-predicate-guard.d.ts +112 -0
  17. package/dist/cjs/cli/pii-predicate-guard.js +390 -0
  18. package/dist/cjs/cli/prisma-resolve.js +75 -4
  19. package/dist/cjs/cli/prisma-schema.d.ts +17 -1
  20. package/dist/cjs/cli/prisma-schema.js +83 -17
  21. package/dist/cjs/cli/sql-statements.d.ts +125 -0
  22. package/dist/cjs/cli/sql-statements.js +378 -0
  23. package/dist/cjs/cli/studio.js +49 -118
  24. package/dist/cjs/cli/ui.d.ts +1 -1
  25. package/dist/cjs/client.d.ts +43 -0
  26. package/dist/cjs/client.js +125 -6
  27. package/dist/cjs/dialect.d.ts +123 -0
  28. package/dist/cjs/dialect.js +33 -0
  29. package/dist/cjs/errors.d.ts +74 -1
  30. package/dist/cjs/errors.js +239 -25
  31. package/dist/cjs/index-advisor.d.ts +33 -1
  32. package/dist/cjs/index-advisor.js +32 -1
  33. package/dist/cjs/introspect.d.ts +48 -0
  34. package/dist/cjs/introspect.js +222 -91
  35. package/dist/cjs/mssql.js +43 -1
  36. package/dist/cjs/mysql.d.ts +5 -2
  37. package/dist/cjs/mysql.js +202 -17
  38. package/dist/cjs/nested-write.js +6 -1
  39. package/dist/cjs/pipeline-submittable.js +17 -3
  40. package/dist/cjs/pipeline.js +75 -9
  41. package/dist/cjs/powdb.d.ts +23 -0
  42. package/dist/cjs/powdb.js +33 -1
  43. package/dist/cjs/powql.d.ts +61 -9
  44. package/dist/cjs/powql.js +186 -49
  45. package/dist/cjs/prisma-compat.js +160 -41
  46. package/dist/cjs/query/aggregates.d.ts +1 -1
  47. package/dist/cjs/query/aggregates.js +80 -18
  48. package/dist/cjs/query/batched-loader.d.ts +10 -0
  49. package/dist/cjs/query/batched-loader.js +268 -7
  50. package/dist/cjs/query/builder.d.ts +73 -0
  51. package/dist/cjs/query/builder.js +225 -28
  52. package/dist/cjs/query/filters.d.ts +162 -0
  53. package/dist/cjs/query/filters.js +250 -1
  54. package/dist/cjs/query/relations.d.ts +10 -10
  55. package/dist/cjs/query/relations.js +93 -12
  56. package/dist/cjs/query/types.d.ts +14 -1
  57. package/dist/cjs/query/utils.d.ts +146 -2
  58. package/dist/cjs/query/utils.js +210 -4
  59. package/dist/cjs/query/warn-registry.d.ts +10 -0
  60. package/dist/cjs/query/warn-registry.js +10 -0
  61. package/dist/cjs/query/where-compile.d.ts +30 -0
  62. package/dist/cjs/query/where-compile.js +41 -0
  63. package/dist/cjs/query/where.d.ts +128 -13
  64. package/dist/cjs/query/where.js +215 -77
  65. package/dist/cjs/query/writes.d.ts +1 -1
  66. package/dist/cjs/query/writes.js +39 -15
  67. package/dist/cjs/schema-builder.d.ts +2 -1
  68. package/dist/cjs/schema-sql.d.ts +94 -4
  69. package/dist/cjs/schema-sql.js +506 -30
  70. package/dist/cjs/schema.d.ts +3 -1
  71. package/dist/cjs/sqlite.d.ts +6 -0
  72. package/dist/cjs/sqlite.js +151 -10
  73. package/dist/cjs/typed-sql.d.ts +29 -1
  74. package/dist/cjs/typed-sql.js +30 -12
  75. package/dist/cli/destructive.d.ts +18 -4
  76. package/dist/cli/destructive.js +229 -121
  77. package/dist/cli/index.d.ts +21 -4
  78. package/dist/cli/index.js +120 -24
  79. package/dist/cli/mcp.d.ts +28 -8
  80. package/dist/cli/mcp.js +172 -129
  81. package/dist/cli/migrate.d.ts +134 -13
  82. package/dist/cli/migrate.js +347 -238
  83. package/dist/cli/pii-predicate-guard.d.ts +112 -0
  84. package/dist/cli/pii-predicate-guard.js +386 -0
  85. package/dist/cli/prisma-resolve.js +75 -4
  86. package/dist/cli/prisma-schema.d.ts +17 -1
  87. package/dist/cli/prisma-schema.js +83 -17
  88. package/dist/cli/sql-statements.d.ts +125 -0
  89. package/dist/cli/sql-statements.js +373 -0
  90. package/dist/cli/studio.js +49 -118
  91. package/dist/cli/ui.d.ts +1 -1
  92. package/dist/client.d.ts +43 -0
  93. package/dist/client.js +126 -7
  94. package/dist/dialect.d.ts +123 -0
  95. package/dist/dialect.js +33 -0
  96. package/dist/errors.d.ts +74 -1
  97. package/dist/errors.js +228 -19
  98. package/dist/index-advisor.d.ts +33 -1
  99. package/dist/index-advisor.js +31 -1
  100. package/dist/introspect.d.ts +48 -0
  101. package/dist/introspect.js +221 -91
  102. package/dist/mssql.js +44 -2
  103. package/dist/mysql.d.ts +5 -2
  104. package/dist/mysql.js +203 -18
  105. package/dist/nested-write.js +7 -2
  106. package/dist/pipeline-submittable.js +18 -4
  107. package/dist/pipeline.js +76 -10
  108. package/dist/powdb.d.ts +23 -0
  109. package/dist/powdb.js +33 -2
  110. package/dist/powql.d.ts +61 -9
  111. package/dist/powql.js +187 -50
  112. package/dist/prisma-compat.js +160 -41
  113. package/dist/query/aggregates.d.ts +1 -1
  114. package/dist/query/aggregates.js +82 -20
  115. package/dist/query/batched-loader.d.ts +10 -0
  116. package/dist/query/batched-loader.js +270 -9
  117. package/dist/query/builder.d.ts +73 -0
  118. package/dist/query/builder.js +226 -30
  119. package/dist/query/filters.d.ts +162 -0
  120. package/dist/query/filters.js +246 -1
  121. package/dist/query/relations.d.ts +10 -10
  122. package/dist/query/relations.js +94 -14
  123. package/dist/query/types.d.ts +14 -1
  124. package/dist/query/utils.d.ts +146 -2
  125. package/dist/query/utils.js +204 -3
  126. package/dist/query/warn-registry.d.ts +10 -0
  127. package/dist/query/warn-registry.js +10 -0
  128. package/dist/query/where-compile.d.ts +30 -0
  129. package/dist/query/where-compile.js +40 -1
  130. package/dist/query/where.d.ts +128 -13
  131. package/dist/query/where.js +216 -80
  132. package/dist/query/writes.d.ts +1 -1
  133. package/dist/query/writes.js +40 -16
  134. package/dist/schema-builder.d.ts +2 -1
  135. package/dist/schema-sql.d.ts +94 -4
  136. package/dist/schema-sql.js +505 -30
  137. package/dist/schema.d.ts +3 -1
  138. package/dist/sqlite.d.ts +6 -0
  139. package/dist/sqlite.js +151 -10
  140. package/dist/typed-sql.d.ts +29 -1
  141. package/dist/typed-sql.js +30 -12
  142. package/package.json +6 -4
@@ -74,6 +74,7 @@ exports.planEnvScaffold = planEnvScaffold;
74
74
  exports.scaffoldEnvForUrl = scaffoldEnvForUrl;
75
75
  exports.initEnvNotice = initEnvNotice;
76
76
  exports.resolveMigrateFromPrismaUrl = resolveMigrateFromPrismaUrl;
77
+ exports.readPrismaSchemaSource = readPrismaSchemaSource;
77
78
  exports.buildMigrateDeployOptions = buildMigrateDeployOptions;
78
79
  exports.getSeedExecutionPlan = getSeedExecutionPlan;
79
80
  exports.isLoopbackHost = isLoopbackHost;
@@ -965,15 +966,21 @@ async function runInitPush(config, url) {
965
966
  }
966
967
  const schemaDef = await loadSchemaFile(config.schemaFile);
967
968
  const spinner = new ui_js_1.Spinner('Computing schema diff').start();
968
- const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
969
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url, { schema: config.schema });
969
970
  if (diff.statements.length === 0) {
970
971
  spinner.succeed('Database already in sync: nothing to push');
971
972
  return;
972
973
  }
973
974
  spinner.succeed(`Found ${(0, ui_js_1.bold)(String(diff.statements.length))} change(s) to apply`);
975
+ // Same as `push`: a type change carries a warning naming what it costs the
976
+ // existing rows, and it is worth no less here than on the standalone command.
977
+ if (diff.warnings && diff.warnings.length > 0) {
978
+ for (const w of diff.warnings)
979
+ (0, ui_js_1.warn)(w);
980
+ }
974
981
  const pushSpinner = new ui_js_1.Spinner('Applying schema').start();
975
982
  try {
976
- const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { precomputedDiff: diff });
983
+ const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { precomputedDiff: diff, schema: config.schema });
977
984
  pushSpinner.succeed(`Applied ${(0, ui_js_1.bold)(String(result.statementsExecuted))} statement(s)`);
978
985
  }
979
986
  catch (err) {
@@ -984,7 +991,11 @@ async function runInitPush(config, url) {
984
991
  (0, ui_js_1.warn)('Push aborted: no schema changes were applied.');
985
992
  return;
986
993
  }
987
- const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { allowDestructive: true, precomputedDiff: diff });
994
+ const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, {
995
+ allowDestructive: true,
996
+ precomputedDiff: diff,
997
+ schema: config.schema,
998
+ });
988
999
  (0, ui_js_1.success)(`Applied ${(0, ui_js_1.bold)(String(result.statementsExecuted))} statement(s)`);
989
1000
  }
990
1001
  }
@@ -1459,19 +1470,57 @@ function resolveMigrateFromPrismaUrl(configUrl, ast, env) {
1459
1470
  }
1460
1471
  return { source: 'none', missingVariables: lookup.missingVariables };
1461
1472
  }
1473
+ /**
1474
+ * Read a Prisma schema from a FILE or from a multi-file schema DIRECTORY
1475
+ * (Prisma >= 5.15, GA). A directory used to reach `readFileSync` unchanged and
1476
+ * die with a raw `EISDIR`, which is a stack trace rather than an answer.
1477
+ *
1478
+ * Prisma treats every `.prisma` file under the directory as one logical schema,
1479
+ * so they are concatenated in sorted order (stable across machines) and parsed
1480
+ * as a single source. Line numbers in any parse error then refer to that
1481
+ * concatenation, so each file is introduced by a comment naming it.
1482
+ */
1483
+ function readPrismaSchemaSource(path) {
1484
+ if (!(0, node_fs_1.statSync)(path).isDirectory())
1485
+ return (0, node_fs_1.readFileSync)(path, 'utf-8');
1486
+ const files = (0, node_fs_1.readdirSync)(path)
1487
+ .filter((f) => f.endsWith('.prisma'))
1488
+ .sort();
1489
+ if (files.length === 0) {
1490
+ throw new Error(`No .prisma files found in directory ${path}.`);
1491
+ }
1492
+ return files.map((f) => `// file: ${f}\n${(0, node_fs_1.readFileSync)((0, node_path_1.join)(path, f), 'utf-8')}`).join('\n');
1493
+ }
1462
1494
  async function cmdMigrateFromPrisma(args, config) {
1463
1495
  (0, ui_js_1.banner)();
1464
- // `--schema` is the Prisma schema file path in this command.
1465
- const prismaPath = (0, node_path_1.resolve)(args.schema ?? 'prisma/schema.prisma');
1496
+ // `--schema` is the Prisma schema file path in this command. It may also name
1497
+ // a DIRECTORY (Prisma's multi-file schema, GA since 5.15), and the default
1498
+ // `prisma/schema.prisma` falls back to `prisma/schema/` for the same reason.
1499
+ const explicit = args.schema !== undefined;
1500
+ let prismaPath = (0, node_path_1.resolve)(args.schema ?? 'prisma/schema.prisma');
1501
+ if (!explicit && !(0, node_fs_1.existsSync)(prismaPath) && (0, node_fs_1.existsSync)((0, node_path_1.resolve)('prisma/schema'))) {
1502
+ prismaPath = (0, node_path_1.resolve)('prisma/schema');
1503
+ }
1466
1504
  if (!(0, node_fs_1.existsSync)(prismaPath)) {
1467
- (0, ui_js_1.error)(`Prisma schema file not found: ${(0, ui_js_1.cyan)(prismaPath)}`);
1505
+ (0, ui_js_1.error)(`Prisma schema not found: ${(0, ui_js_1.cyan)(prismaPath)}`);
1468
1506
  (0, ui_js_1.newline)();
1469
- console.log(` ${(0, ui_js_1.dim)('Point at it with')} ${(0, ui_js_1.cyan)('--schema <path/to/schema.prisma>')} ${(0, ui_js_1.dim)('(default: prisma/schema.prisma).')}`);
1507
+ console.log(` ${(0, ui_js_1.dim)('Point at it with')} ${(0, ui_js_1.cyan)('--schema <path>')} ${(0, ui_js_1.dim)('(a .prisma file, or a directory of them;')}`);
1508
+ console.log(` ${(0, ui_js_1.dim)('default: prisma/schema.prisma, then prisma/schema/).')}`);
1470
1509
  (0, ui_js_1.newline)();
1471
1510
  process.exit(1);
1472
1511
  }
1473
1512
  // Parse (fatal only on a construct we must understand).
1474
- const source = (0, node_fs_1.readFileSync)(prismaPath, 'utf-8');
1513
+ let source;
1514
+ try {
1515
+ source = readPrismaSchemaSource(prismaPath);
1516
+ }
1517
+ catch (err) {
1518
+ (0, ui_js_1.newline)();
1519
+ (0, ui_js_1.error)(`Could not read ${(0, ui_js_1.cyan)(prismaPath)}`);
1520
+ console.log(` ${(0, ui_js_1.red)(err instanceof Error ? err.message : String(err))}`);
1521
+ (0, ui_js_1.newline)();
1522
+ process.exit(1);
1523
+ }
1475
1524
  let ast;
1476
1525
  try {
1477
1526
  ast = (0, prisma_schema_js_1.parsePrismaSchema)(source);
@@ -1543,6 +1592,18 @@ async function cmdMigrateFromPrisma(args, config) {
1543
1592
  console.log(` ${marker} ${line}`);
1544
1593
  }
1545
1594
  (0, ui_js_1.newline)();
1595
+ // Parser notes on the CONSOLE, not only in the Markdown report.
1596
+ //
1597
+ // `--no-db` is the first command anyone runs and the one place a parse
1598
+ // problem should surface, and it was the one place that could not: resolution
1599
+ // is skipped, `hasUnresolved` is false by construction, so the run printed a
1600
+ // clean summary and exited 0 no matter what the parser had thrown away.
1601
+ if (result.parseWarnings.length > 0) {
1602
+ (0, ui_js_1.header)('Parser notes');
1603
+ for (const w of result.parseWarnings)
1604
+ console.log(` ${(0, ui_js_1.yellow)(ui_js_1.symbols.warning)} ${w}`);
1605
+ (0, ui_js_1.newline)();
1606
+ }
1546
1607
  // Write outputs into the generate outDir.
1547
1608
  const outDir = (0, node_path_1.resolve)(config.out);
1548
1609
  const rel = (0, node_path_1.relative)(process.cwd(), outDir);
@@ -1590,6 +1651,15 @@ async function cmdMigrateFromPrisma(args, config) {
1590
1651
  (0, ui_js_1.newline)();
1591
1652
  process.exit(1);
1592
1653
  }
1654
+ // `--no-db` has no resolution to fail on, so parser notes are the ONLY signal
1655
+ // it can carry. Treated like an unresolved item, and cleared by the same
1656
+ // `--allow-partial` opt-in, so "exit 0" means the same thing in both modes.
1657
+ if (args.noDb && result.parseWarnings.length > 0 && !args.allowPartial) {
1658
+ (0, ui_js_1.warn)(`${result.parseWarnings.length} parser note(s) above (also in the report). ` +
1659
+ 'Re-run with --allow-partial to accept them.');
1660
+ (0, ui_js_1.newline)();
1661
+ process.exit(1);
1662
+ }
1593
1663
  if (args.noDb) {
1594
1664
  (0, ui_js_1.info)(`Parse-only report written. Re-run without ${(0, ui_js_1.cyan)('--no-db')} against your database to resolve names.`);
1595
1665
  }
@@ -1612,7 +1682,7 @@ async function cmdPush(args, config) {
1612
1682
  (0, ui_js_1.info)(`Schema defines ${(0, ui_js_1.bold)(String(tableCount))} tables`);
1613
1683
  // Compute diff
1614
1684
  const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
1615
- const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
1685
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url, { schema: config.schema });
1616
1686
  if (diff.statements.length === 0 && diff.drop.length === 0) {
1617
1687
  diffSpinner.succeed('Database is already in sync');
1618
1688
  (0, ui_js_1.newline)();
@@ -1687,7 +1757,11 @@ async function cmdPush(args, config) {
1687
1757
  const pushSpinner = new ui_js_1.Spinner('Applying changes').start();
1688
1758
  let result;
1689
1759
  try {
1690
- result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { allowDestructive: args.allowDestructive, precomputedDiff: diff });
1760
+ result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, {
1761
+ allowDestructive: args.allowDestructive,
1762
+ precomputedDiff: diff,
1763
+ schema: config.schema,
1764
+ });
1691
1765
  }
1692
1766
  catch (err) {
1693
1767
  if (!(err instanceof schema_sql_js_1.DestructivePushRefusal))
@@ -1699,7 +1773,11 @@ async function cmdPush(args, config) {
1699
1773
  process.exit(1);
1700
1774
  }
1701
1775
  pushSpinner.start();
1702
- result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { allowDestructive: true, precomputedDiff: diff });
1776
+ result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, {
1777
+ allowDestructive: true,
1778
+ precomputedDiff: diff,
1779
+ schema: config.schema,
1780
+ });
1703
1781
  }
1704
1782
  pushSpinner.succeed(`Applied ${(0, ui_js_1.bold)(String(result.statementsExecuted))} statement(s)`);
1705
1783
  if (result.tablesCreated.length > 0) {
@@ -1813,7 +1891,7 @@ async function cmdMigrateCreate(args, config) {
1813
1891
  (0, ui_js_1.newline)();
1814
1892
  const schemaDef = await loadSchemaFile(config.schemaFile);
1815
1893
  const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
1816
- const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
1894
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url, { schema: config.schema });
1817
1895
  if (diff.statements.length === 0) {
1818
1896
  diffSpinner.succeed('Database is already in sync: nothing to migrate');
1819
1897
  (0, ui_js_1.newline)();
@@ -1890,7 +1968,7 @@ async function cmdMigrateCreate(args, config) {
1890
1968
  (0, ui_js_1.newline)();
1891
1969
  const schemaDef = await loadSchemaFile(config.schemaFile);
1892
1970
  const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
1893
- const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
1971
+ const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url, { schema: config.schema });
1894
1972
  if (diff.statements.length === 0) {
1895
1973
  diffSpinner.succeed('Database is already in sync: nothing to migrate');
1896
1974
  (0, ui_js_1.newline)();
@@ -2550,9 +2628,24 @@ const TIER_LABEL = {
2550
2628
  'take-deliberately': 'TAKE DELIBERATELY',
2551
2629
  scrutinize: 'SCRUTINIZE',
2552
2630
  };
2553
- /** Build the `CREATE INDEX` for a finding, honoring the partial-null suggestion. */
2631
+ /**
2632
+ * The `CREATE INDEX` for a finding (display / JSON only), honoring the
2633
+ * partial-null suggestion. The migration writer uses
2634
+ * {@link buildCreateIndexStatements} instead, which adds the preceding
2635
+ * `DROP INDEX CONCURRENTLY IF EXISTS` that makes a rerun converge.
2636
+ */
2554
2637
  function doctorCreateSql(f, opts) {
2555
2638
  return (0, index_advisor_js_1.buildCreateIndexSql)(f.missing.table, f.missing.columns, f.missing.indexName, {
2639
+ concurrently: opts.concurrently,
2640
+ // The report line is advice a human may copy into psql, where the
2641
+ // guard is genuinely useful and there is no corpse to skip.
2642
+ ifNotExists: true,
2643
+ partialNotNull: f.score.partialNotNull,
2644
+ });
2645
+ }
2646
+ /** The statements the fix migration's UP body runs for one finding. */
2647
+ function doctorCreateStatements(f, opts) {
2648
+ return (0, index_advisor_js_1.buildCreateIndexStatements)(f.missing.table, f.missing.columns, f.missing.indexName, {
2556
2649
  concurrently: opts.concurrently,
2557
2650
  partialNotNull: f.score.partialNotNull,
2558
2651
  });
@@ -2565,13 +2658,17 @@ const CONCURRENTLY_RECIPE_COMMENT = [
2565
2658
  '--',
2566
2659
  '-- 1. Idempotency is required. This migration is recorded only after ALL',
2567
2660
  '-- statements succeed; a mid-file failure leaves earlier indexes built and',
2568
- '-- the migration unrecorded, so a rerun must be safe. IF NOT EXISTS keeps',
2569
- '-- each CREATE idempotent.',
2570
- '-- 2. The INVALID-index trap. A CREATE INDEX CONCURRENTLY that fails partway',
2571
- '-- leaves an INVALID index behind. On rerun, IF NOT EXISTS SKIPS that',
2572
- '-- corpse (the name already exists), so the index is never actually built.',
2573
- '-- Fix: DROP INDEX CONCURRENTLY the invalid index, then rerun. Run',
2574
- '-- "turbine doctor" to list invalid indexes.',
2661
+ '-- the migration unrecorded, so a rerun must be safe.',
2662
+ '-- 2. The INVALID-index trap, and why each CREATE is preceded by a DROP.',
2663
+ '-- A CREATE INDEX CONCURRENTLY that fails partway leaves an INVALID index',
2664
+ '-- behind, under the name it was asked for. IF NOT EXISTS matches on the',
2665
+ '-- NAME and not on validity, so with it a rerun SKIPS the corpse and the',
2666
+ '-- index is never actually built: the migration records as applied while',
2667
+ '-- the index doctor reported is still missing. The DROP INDEX',
2668
+ '-- CONCURRENTLY IF EXISTS line ahead of each CREATE is a no-op on the',
2669
+ '-- first run (the index does not exist yet, which is why it was',
2670
+ '-- proposed) and clears the corpse on a rerun, so rerunning converges on',
2671
+ '-- a VALID index. Run "turbine doctor" to list invalid indexes.',
2575
2672
  '-- 3. Locking. CREATE INDEX CONCURRENTLY waits for every transaction that can',
2576
2673
  '-- see the table to finish. A long-running transaction makes it wait and',
2577
2674
  '-- makes "migrate up" look hung. For bounded waits, SET lock_timeout /',
@@ -3285,7 +3382,7 @@ function renderPlanDivergence(divergence, attached) {
3285
3382
  /** Write the --fix migration (CONCURRENTLY + directive by default; plain with --no-concurrently). */
3286
3383
  function renderFixMigration(findings, config, args) {
3287
3384
  const concurrently = args.noConcurrently !== true;
3288
- const up = findings.map((f) => doctorCreateSql(f, { concurrently })).join('\n');
3385
+ const up = findings.flatMap((f) => doctorCreateStatements(f, { concurrently })).join('\n');
3289
3386
  const down = findings.map((f) => (0, index_advisor_js_1.buildDropIndexSql)(f.missing.indexName, { concurrently })).join('\n');
3290
3387
  let file;
3291
3388
  if (concurrently) {
@@ -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
- * naming to the SHARED introspection builder (`buildRelationsFromForeignKeys`
43
- * + `addAutoManyToManyRelations` in ../introspect.ts). MCP previously carried
44
- * a stale copy of a retired naming scheme, so `turbine mcp` and `turbine
45
- * generate` derived DIFFERENT relation names from the same database.
46
- * Exported for the parity unit test.
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 {};