prisma-next 0.5.0-dev.9 → 0.5.1

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/dist/cli-errors-B9OBbled.d.mts +3 -0
  2. package/dist/cli-errors-D3_sMh2K.mjs +33 -0
  3. package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
  4. package/dist/cli.mjs +16 -78
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/client-BCnP7cHo.mjs +1485 -0
  7. package/dist/client-BCnP7cHo.mjs.map +1 -0
  8. package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
  9. package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
  10. package/dist/commands/contract-emit.d.mts.map +1 -1
  11. package/dist/commands/contract-emit.mjs +2 -4
  12. package/dist/commands/contract-infer.d.mts.map +1 -1
  13. package/dist/commands/contract-infer.mjs +2 -4
  14. package/dist/commands/db-init.d.mts.map +1 -1
  15. package/dist/commands/db-init.mjs +16 -13
  16. package/dist/commands/db-init.mjs.map +1 -1
  17. package/dist/commands/db-schema.d.mts.map +1 -1
  18. package/dist/commands/db-schema.mjs +6 -7
  19. package/dist/commands/db-schema.mjs.map +1 -1
  20. package/dist/commands/db-sign.d.mts.map +1 -1
  21. package/dist/commands/db-sign.mjs +9 -9
  22. package/dist/commands/db-sign.mjs.map +1 -1
  23. package/dist/commands/db-update.d.mts.map +1 -1
  24. package/dist/commands/db-update.mjs +15 -13
  25. package/dist/commands/db-update.mjs.map +1 -1
  26. package/dist/commands/db-verify.d.mts.map +1 -1
  27. package/dist/commands/db-verify.mjs +1 -321
  28. package/dist/commands/migration-apply.d.mts +28 -13
  29. package/dist/commands/migration-apply.d.mts.map +1 -1
  30. package/dist/commands/migration-apply.mjs +55 -151
  31. package/dist/commands/migration-apply.mjs.map +1 -1
  32. package/dist/commands/migration-new.d.mts +0 -1
  33. package/dist/commands/migration-new.d.mts.map +1 -1
  34. package/dist/commands/migration-new.mjs +34 -40
  35. package/dist/commands/migration-new.mjs.map +1 -1
  36. package/dist/commands/migration-plan.d.mts +33 -6
  37. package/dist/commands/migration-plan.d.mts.map +1 -1
  38. package/dist/commands/migration-plan.mjs +2 -348
  39. package/dist/commands/migration-ref.d.mts +1 -1
  40. package/dist/commands/migration-ref.d.mts.map +1 -1
  41. package/dist/commands/migration-ref.mjs +8 -12
  42. package/dist/commands/migration-ref.mjs.map +1 -1
  43. package/dist/commands/migration-show.d.mts +64 -10
  44. package/dist/commands/migration-show.d.mts.map +1 -1
  45. package/dist/commands/migration-show.mjs +166 -60
  46. package/dist/commands/migration-show.mjs.map +1 -1
  47. package/dist/commands/migration-status.d.mts +126 -5
  48. package/dist/commands/migration-status.d.mts.map +1 -1
  49. package/dist/commands/migration-status.mjs +2 -4
  50. package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
  51. package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
  52. package/dist/config-loader.d.mts +0 -1
  53. package/dist/config-loader.d.mts.map +1 -1
  54. package/dist/config-loader.mjs +2 -3
  55. package/dist/contract-emit-9DBda5Ou.mjs +150 -0
  56. package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
  57. package/dist/contract-emit-B77TsJqf.mjs +327 -0
  58. package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
  59. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
  60. package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
  61. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-ByxhPjpW.mjs} +13 -22
  62. package/dist/contract-infer-ByxhPjpW.mjs.map +1 -0
  63. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs +160 -0
  64. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs.map +1 -0
  65. package/dist/db-verify-Czm5T-J4.mjs +404 -0
  66. package/dist/db-verify-Czm5T-J4.mjs.map +1 -0
  67. package/dist/exports/config-types.mjs +1 -2
  68. package/dist/exports/control-api.d.mts +101 -586
  69. package/dist/exports/control-api.d.mts.map +1 -1
  70. package/dist/exports/control-api.mjs +4 -6
  71. package/dist/exports/index.d.mts.map +1 -1
  72. package/dist/exports/index.mjs +28 -30
  73. package/dist/exports/index.mjs.map +1 -1
  74. package/dist/exports/init-output.d.mts +2 -4
  75. package/dist/exports/init-output.d.mts.map +1 -1
  76. package/dist/exports/init-output.mjs +2 -3
  77. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
  78. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
  79. package/dist/global-flags-Icqpxk23.d.mts +12 -0
  80. package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
  81. package/dist/helpers-eqdN8tH6.mjs +25 -0
  82. package/dist/helpers-eqdN8tH6.mjs.map +1 -0
  83. package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
  84. package/dist/init-DETSgw3h.mjs.map +1 -0
  85. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-DxdBd4Er.mjs} +10 -11
  86. package/dist/inspect-live-schema-DxdBd4Er.mjs.map +1 -0
  87. package/dist/migration-cli.d.mts +41 -12
  88. package/dist/migration-cli.d.mts.map +1 -1
  89. package/dist/migration-cli.mjs +309 -86
  90. package/dist/migration-cli.mjs.map +1 -1
  91. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-BdV8JYXV.mjs} +8 -9
  92. package/dist/migration-command-scaffold-BdV8JYXV.mjs.map +1 -0
  93. package/dist/migration-plan-mRu5K81L.mjs +494 -0
  94. package/dist/migration-plan-mRu5K81L.mjs.map +1 -0
  95. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-By9G5p2H.mjs} +270 -65
  96. package/dist/migration-status-By9G5p2H.mjs.map +1 -0
  97. package/dist/migrations-CTsyBXCA.mjs +229 -0
  98. package/dist/migrations-CTsyBXCA.mjs.map +1 -0
  99. package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
  100. package/dist/output-B16Kefzx.mjs.map +1 -0
  101. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
  102. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
  103. package/dist/result-handler-rmPVKIP2.mjs +25 -0
  104. package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
  105. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  106. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
  107. package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
  108. package/dist/types-LItU7E4l.d.mts +856 -0
  109. package/dist/types-LItU7E4l.d.mts.map +1 -0
  110. package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
  111. package/dist/verify-CiwNWM9N.mjs.map +1 -0
  112. package/package.json +19 -17
  113. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  114. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  115. package/dist/client-CrsnY58k.mjs +0 -997
  116. package/dist/client-CrsnY58k.mjs.map +0 -1
  117. package/dist/commands/db-verify.mjs.map +0 -1
  118. package/dist/commands/migration-plan.mjs.map +0 -1
  119. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  120. package/dist/contract-emit--feXyNd7.mjs +0 -4
  121. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  122. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  123. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  124. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  125. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  126. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  127. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  128. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  129. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  130. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  131. package/dist/init-C5220SY9.mjs.map +0 -1
  132. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  133. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  134. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  135. package/dist/migrations-Bo5WtTla.mjs +0 -153
  136. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  137. package/dist/output-BpcQrnnq.mjs.map +0 -1
  138. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  139. package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
  140. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  141. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  142. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
@@ -1,18 +1,21 @@
1
- import { t as loadConfig } from "./config-loader-C25b63rJ.mjs";
2
- import { _ as errorUnexpected, m as errorRuntime } from "./cli-errors-Cd79vmTH.mjs";
3
- import { t as createControlClient } from "./client-CrsnY58k.mjs";
4
- import { t as TerminalUI } from "./terminal-ui-C3ZLwQxK.mjs";
5
- import { _ as formatStyledHeader, a as maskConnectionUrl, c as resolveMigrationPaths, d as setCommandExamples, i as loadAllBundles, m as parseGlobalFlags, n as addGlobalOptions, o as readContractEnvelope, p as toPathDecisionResult, t as handleResult, u as setCommandDescriptions } from "./result-handler-Ba3zWQsI.mjs";
1
+ import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
2
+ import { _ as errorUnexpected, m as errorRuntime, v as mapMigrationToolsError } from "./cli-errors-D3_sMh2K.mjs";
3
+ import { a as loadMigrationPackages, d as setCommandDescriptions, f as setCommandExamples, g as parseGlobalFlags, h as toStructuralEdge, l as resolveMigrationPaths, m as toPathDecisionResult, n as collectDeclaredInvariants, o as maskConnectionUrl, s as readContractEnvelope, t as addGlobalOptions, y as formatStyledHeader } from "./command-helpers-BeZHkxV8.mjs";
4
+ import { t as TerminalUI } from "./terminal-ui-C_hFNbAn.mjs";
5
+ import { t as handleResult } from "./result-handler-rmPVKIP2.mjs";
6
+ import { t as createControlClient } from "./client-BCnP7cHo.mjs";
7
+ import { t as buildContractSpaceAggregate } from "./contract-space-aggregate-loader-BrwKK6Q6.mjs";
6
8
  import { Command } from "commander";
7
- import { notOk, ok } from "@prisma-next/utils/result";
8
9
  import { ifDefined } from "@prisma-next/utils/defined";
9
- import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
10
- import { findPath, findPathWithDecision, findReachableLeaves } from "@prisma-next/migration-tools/dag";
10
+ import { notOk, ok } from "@prisma-next/utils/result";
11
+ import { createControlStack } from "@prisma-next/framework-components/control";
12
+ import { findPath, findPathWithDecision, findReachableLeaves } from "@prisma-next/migration-tools/migration-graph";
11
13
  import { bold, cyan, dim, magenta, yellow } from "colorette";
14
+ import { graphWalkStrategy } from "@prisma-next/migration-tools/aggregate";
15
+ import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
16
+ import { MigrationToolsError, errorNoInvariantPath, errorUnknownInvariant } from "@prisma-next/migration-tools/errors";
12
17
  import { readRefs, resolveRef } from "@prisma-next/migration-tools/refs";
13
- import { MigrationToolsError } from "@prisma-next/migration-tools/types";
14
18
  import dagre from "@dagrejs/dagre";
15
-
16
19
  //#region src/utils/formatters/graph-types.ts
17
20
  /**
18
21
  * Immutable directed graph with adjacency-list indexing.
@@ -52,7 +55,6 @@ var RenderGraph = class {
52
55
  return this.forward.get(nodeId) ?? [];
53
56
  }
54
57
  };
55
-
56
58
  //#endregion
57
59
  //#region src/utils/formatters/graph-migration-mapper.ts
58
60
  /**
@@ -100,7 +102,8 @@ function migrationGraphToRenderInput(input) {
100
102
  for (const [, entries] of graph.forwardChain) for (const entry of entries) {
101
103
  const status = statusByDirName.get(entry.dirName);
102
104
  const icon = status ? STATUS_ICON[status] : "";
103
- const label = `${entry.dirName}${icon}`;
105
+ const invariantsSuffix = entry.invariants.length > 0 ? ` provides [${entry.invariants.map((id) => JSON.stringify(id)).join(", ")}]` : "";
106
+ const label = `${entry.dirName}${icon}${invariantsSuffix}`;
104
107
  edgeList.push({
105
108
  from: toShortId(entry.from),
106
109
  to: toShortId(entry.to),
@@ -171,7 +174,6 @@ function migrationGraphToRenderInput(input) {
171
174
  relevantPaths
172
175
  };
173
176
  }
174
-
175
177
  //#endregion
176
178
  //#region src/utils/formatters/graph-render.ts
177
179
  /**
@@ -1086,9 +1088,24 @@ function isLinearGraph(graph) {
1086
1088
  for (const node of graph.nodes) if (graph.outgoing(node.id).filter((e) => e.style !== "dashed").length > 1) return false;
1087
1089
  return true;
1088
1090
  }
1089
-
1090
1091
  //#endregion
1091
1092
  //#region src/commands/migration-status.ts
1093
+ /**
1094
+ * Sum per-space `pendingCount` into a cross-space total, but only when
1095
+ * every loaded space reports a defined `pendingCount`. Returns
1096
+ * `undefined` if any space is on the marker-unknown / offline path
1097
+ * (where `pendingCount` is intentionally absent), so JSON consumers can
1098
+ * distinguish "no pending" from "unknown".
1099
+ */
1100
+ function computeTotalPendingAcrossSpaces(spaces) {
1101
+ if (spaces.length === 0) return void 0;
1102
+ let total = 0;
1103
+ for (const s of spaces) {
1104
+ if (s.pendingCount === void 0) return void 0;
1105
+ total += s.pendingCount;
1106
+ }
1107
+ return total;
1108
+ }
1092
1109
  function summarizeOps(ops) {
1093
1110
  if (ops.length === 0) return {
1094
1111
  summary: "0 ops",
@@ -1194,7 +1211,7 @@ function buildMigrationEntries(chain, packages, mode, markerHash, edgeStatuses)
1194
1211
  dirName: migration.dirName,
1195
1212
  from: migration.from,
1196
1213
  to: migration.to,
1197
- migrationId: migration.migrationId,
1214
+ migrationHash: migration.migrationHash,
1198
1215
  operationCount: ops.length,
1199
1216
  operationSummary: summary,
1200
1217
  hasDestructive,
@@ -1239,37 +1256,117 @@ function determineLimit(opts) {
1239
1256
  if (Number.isNaN(parsed)) return DEFAULT_LIMIT;
1240
1257
  return parsed;
1241
1258
  }
1259
+ /**
1260
+ * Build the aggregate enumeration of contract spaces for the status
1261
+ * output. Loads the aggregate from disk (lossy on failure — extension
1262
+ * spaces are simply omitted, the existing single-space app behaviour
1263
+ * keeps working), reads per-space marker rows when online, and uses
1264
+ * {@link graphWalkStrategy} to compute each space's pending count.
1265
+ *
1266
+ * Sub-spec § `migration status` semantics — the aggregate-walking
1267
+ * version reports per-space marker + pending state alongside the
1268
+ * cross-space totals.
1269
+ */
1270
+ async function loadAggregateStatusSpaces(args) {
1271
+ const loaded = await buildContractSpaceAggregate({
1272
+ targetId: args.targetId,
1273
+ migrationsDir: args.migrationsDir,
1274
+ appContract: args.validateContract(args.appContractRaw),
1275
+ extensionPacks: args.extensionPacks,
1276
+ validateContract: args.validateContract
1277
+ });
1278
+ if (!loaded.ok) return [];
1279
+ const aggregate = loaded.value;
1280
+ const orderedMembers = [...aggregate.extensions, aggregate.app];
1281
+ const rows = [];
1282
+ for (const member of orderedMembers) {
1283
+ const liveMarker = args.markersBySpace?.get(member.spaceId) ?? null;
1284
+ const isApp = member.spaceId === aggregate.app.spaceId;
1285
+ if (member.migrations.graph.nodes.size === 0) {
1286
+ rows.push({
1287
+ spaceId: member.spaceId,
1288
+ kind: isApp ? "app" : "extension",
1289
+ headHash: member.headRef.hash,
1290
+ ...args.markersBySpace !== null ? {
1291
+ markerHash: liveMarker?.storageHash ?? null,
1292
+ status: member.headRef.hash === EMPTY_CONTRACT_HASH ? "up-to-date" : "never-planned",
1293
+ pendingCount: 0
1294
+ } : {}
1295
+ });
1296
+ continue;
1297
+ }
1298
+ if (args.markersBySpace === null) {
1299
+ rows.push({
1300
+ spaceId: member.spaceId,
1301
+ kind: isApp ? "app" : "extension",
1302
+ headHash: member.headRef.hash
1303
+ });
1304
+ continue;
1305
+ }
1306
+ const walked = graphWalkStrategy({
1307
+ aggregateTargetId: aggregate.targetId,
1308
+ member,
1309
+ currentMarker: liveMarker
1310
+ });
1311
+ let pendingCount = 0;
1312
+ let status;
1313
+ if (walked.kind === "ok") {
1314
+ pendingCount = walked.result.migrationEdges?.length ?? 0;
1315
+ if (liveMarker === null) status = pendingCount === 0 ? "no-marker" : "pending";
1316
+ else status = pendingCount === 0 ? "up-to-date" : "pending";
1317
+ } else status = "unreachable";
1318
+ rows.push({
1319
+ spaceId: member.spaceId,
1320
+ kind: isApp ? "app" : "extension",
1321
+ headHash: member.headRef.hash,
1322
+ markerHash: liveMarker?.storageHash ?? null,
1323
+ pendingCount,
1324
+ ...status ? { status } : {}
1325
+ });
1326
+ }
1327
+ return rows;
1328
+ }
1329
+ /**
1330
+ * Read the raw contract.json bytes from disk for the aggregate
1331
+ * loader. Returns `null` if the file is missing or unparseable —
1332
+ * the existing `readContractEnvelope` path will report the same
1333
+ * problem via a status diagnostic, no need to double-surface.
1334
+ */
1335
+ async function loadContractRawSafely(config) {
1336
+ try {
1337
+ const path = (await import("./command-helpers-BeZHkxV8.mjs").then((n) => n.r)).resolveContractPath(config);
1338
+ const raw = await (await import("node:fs/promises")).readFile(path, "utf-8");
1339
+ return JSON.parse(raw);
1340
+ } catch {
1341
+ return null;
1342
+ }
1343
+ }
1242
1344
  async function executeMigrationStatusCommand(options, flags, ui) {
1243
1345
  const config = await loadConfig(options.config);
1244
- const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(options.config, config);
1346
+ const { configPath, appMigrationsDir, appMigrationsRelative, migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
1245
1347
  const dbConnection = options.db ?? config.db?.connection;
1246
1348
  const hasDriver = !!config.driver;
1247
1349
  let activeRefName;
1248
1350
  let activeRefHash;
1351
+ let activeRefEntry;
1249
1352
  let allRefs = {};
1250
1353
  try {
1251
1354
  allRefs = await readRefs(refsDir);
1252
1355
  } catch (error) {
1253
- if (MigrationToolsError.is(error)) return notOk(errorRuntime(error.message, {
1254
- why: error.why,
1255
- fix: error.fix,
1256
- meta: { code: error.code }
1257
- }));
1356
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
1258
1357
  throw error;
1259
1358
  }
1260
1359
  if (options.ref) {
1261
1360
  activeRefName = options.ref;
1262
1361
  try {
1263
- activeRefHash = resolveRef(allRefs, activeRefName).hash;
1362
+ activeRefEntry = resolveRef(allRefs, activeRefName);
1363
+ activeRefHash = activeRefEntry.hash;
1264
1364
  } catch (error) {
1265
- if (MigrationToolsError.is(error)) return notOk(errorRuntime(error.message, {
1266
- why: error.why,
1267
- fix: error.fix,
1268
- meta: { code: error.code }
1269
- }));
1365
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
1270
1366
  throw error;
1271
1367
  }
1272
1368
  }
1369
+ const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
1273
1370
  const statusRefs = Object.entries(allRefs).map(([name, entry]) => ({
1274
1371
  name,
1275
1372
  hash: entry.hash,
@@ -1281,7 +1378,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1281
1378
  value: configPath
1282
1379
  }, {
1283
1380
  label: "migrations",
1284
- value: migrationsRelative
1381
+ value: appMigrationsRelative
1285
1382
  }];
1286
1383
  if (dbConnection && hasDriver) details.push({
1287
1384
  label: "database",
@@ -1291,6 +1388,10 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1291
1388
  label: "ref",
1292
1389
  value: activeRefName
1293
1390
  });
1391
+ if (activeRefEntry && activeRefEntry.invariants.length > 0) details.push({
1392
+ label: "required",
1393
+ value: formatInvariantList(activeRefEntry.invariants)
1394
+ });
1294
1395
  const header = formatStyledHeader({
1295
1396
  command: "migration status",
1296
1397
  description: "Show migration history and applied status",
@@ -1314,13 +1415,9 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1314
1415
  let bundles;
1315
1416
  let graph;
1316
1417
  try {
1317
- ({bundles, graph} = await loadAllBundles(migrationsDir));
1418
+ ({bundles, graph} = await loadMigrationPackages(appMigrationsDir));
1318
1419
  } catch (error) {
1319
- if (MigrationToolsError.is(error)) return notOk(errorRuntime(error.message, {
1320
- why: error.why,
1321
- fix: error.fix,
1322
- meta: { code: error.code }
1323
- }));
1420
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
1324
1421
  return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}` }));
1325
1422
  }
1326
1423
  if (bundles.length === 0) {
@@ -1337,7 +1434,8 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1337
1434
  targetHash: EMPTY_CONTRACT_HASH,
1338
1435
  contractHash,
1339
1436
  summary: "No migrations found",
1340
- diagnostics
1437
+ diagnostics,
1438
+ requiredInvariants
1341
1439
  });
1342
1440
  }
1343
1441
  let targetHash;
@@ -1354,7 +1452,9 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1354
1452
  });
1355
1453
  }
1356
1454
  let markerHash;
1455
+ let markerInvariants = [];
1357
1456
  let mode = "offline";
1457
+ let allMarkers = null;
1358
1458
  if (dbConnection && hasDriver) {
1359
1459
  const client = createControlClient({
1360
1460
  family: config.family,
@@ -1365,14 +1465,48 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1365
1465
  });
1366
1466
  try {
1367
1467
  await client.connect(dbConnection);
1368
- markerHash = (await client.readMarker())?.storageHash;
1468
+ const marker = await client.readMarker();
1469
+ markerHash = marker?.storageHash;
1470
+ markerInvariants = marker?.invariants ?? [];
1369
1471
  mode = "online";
1472
+ if (typeof client.readAllMarkers === "function") allMarkers = await client.readAllMarkers();
1473
+ else allMarkers = null;
1370
1474
  } catch {
1371
1475
  if (!flags.json && !flags.quiet) ui.warn("Could not connect to database — showing offline status");
1372
1476
  } finally {
1373
1477
  await client.close();
1374
1478
  }
1375
1479
  }
1480
+ const contractRawForAggregate = await loadContractRawSafely(config);
1481
+ let aggregateSpaces = [];
1482
+ if (contractRawForAggregate !== null) {
1483
+ const stack = createControlStack(config);
1484
+ const familyInstance = config.family.create(stack);
1485
+ try {
1486
+ aggregateSpaces = await loadAggregateStatusSpaces({
1487
+ targetId: config.target.targetId,
1488
+ migrationsDir,
1489
+ appContractRaw: contractRawForAggregate,
1490
+ extensionPacks: config.extensionPacks ?? [],
1491
+ validateContract: (json) => familyInstance.validateContract(json),
1492
+ markersBySpace: allMarkers
1493
+ });
1494
+ } catch {
1495
+ aggregateSpaces = [];
1496
+ }
1497
+ }
1498
+ const totalPendingAcrossSpaces = computeTotalPendingAcrossSpaces(aggregateSpaces);
1499
+ if (activeRefEntry && activeRefEntry.invariants.length > 0) {
1500
+ const declared = collectDeclaredInvariants(graph);
1501
+ const known = new Set(declared);
1502
+ if (mode === "online") for (const id of markerInvariants) known.add(id);
1503
+ const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));
1504
+ if (unknown.length > 0) return notOk(mapMigrationToolsError(errorUnknownInvariant({
1505
+ ...ifDefined("refName", activeRefName),
1506
+ unknown,
1507
+ declared: [...declared].sort()
1508
+ })));
1509
+ }
1376
1510
  if (mode === "online" && markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash !== contractHash) {
1377
1511
  const hints = [];
1378
1512
  if (graph.nodes.has(contractHash)) hints.push("Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database", "Run 'prisma-next contract infer' to make your contract match the database", "Run 'prisma-next db verify' to inspect the database state");
@@ -1392,6 +1526,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1392
1526
  summary: `${bundles.length} migration(s) on disk`,
1393
1527
  diagnostics,
1394
1528
  markerHash,
1529
+ requiredInvariants,
1395
1530
  ...statusRefs.length > 0 ? { refs: statusRefs } : {}
1396
1531
  });
1397
1532
  }
@@ -1416,6 +1551,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1416
1551
  summary: `${bundles.length} migration(s) on disk`,
1417
1552
  diagnostics,
1418
1553
  ...ifDefined("markerHash", markerHash),
1554
+ requiredInvariants,
1419
1555
  ...statusRefs.length > 0 ? { refs: statusRefs } : {},
1420
1556
  graph,
1421
1557
  bundles,
@@ -1430,35 +1566,68 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1430
1566
  const entries = buildMigrationEntries(chain, bundles, mode, markerHash, edgeStatuses);
1431
1567
  const pendingCount = edgeStatuses.filter((e) => e.status === "pending").length;
1432
1568
  const appliedCount = edgeStatuses.filter((e) => e.status === "applied").length;
1569
+ let appliedInvariants;
1570
+ let missingInvariants;
1571
+ let effectiveRequired = /* @__PURE__ */ new Set();
1572
+ if (mode === "online") {
1573
+ const markerSet = new Set(markerInvariants);
1574
+ effectiveRequired = new Set(requiredInvariants.filter((id) => !markerSet.has(id)));
1575
+ appliedInvariants = requiredInvariants.filter((id) => markerSet.has(id));
1576
+ missingInvariants = [...effectiveRequired].sort();
1577
+ }
1578
+ const hasInvariantWork = effectiveRequired.size > 0;
1579
+ const missingList = [...effectiveRequired].sort().join(", ");
1433
1580
  let summary;
1434
1581
  if (mode === "online") if (markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash === contractHash) summary = `${bundles.length} migration(s) on disk`;
1435
- else if (activeRefHash && markerHash !== void 0) summary = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName);
1436
- else if (pendingCount === 0) summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? "s" : ""} applied)`;
1582
+ else if (activeRefHash && markerHash !== void 0) {
1583
+ const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName);
1584
+ summary = hasInvariantWork ? `${distance} — missing invariant(s): ${missingList}` : distance;
1585
+ } else if (pendingCount === 0 && !hasInvariantWork) summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? "s" : ""} applied)`;
1586
+ else if (pendingCount === 0 && hasInvariantWork) summary = `Missing invariant(s): ${missingList} — run 'prisma-next migration apply --ref ${activeRefName ?? "<ref>"}' to apply`;
1437
1587
  else if (markerHash === void 0) summary = `${pendingCount} pending migration(s) — database has no marker`;
1438
1588
  else summary = `${pendingCount} pending migration(s) — run 'prisma-next migration apply' to apply`;
1439
1589
  else summary = `${entries.length} migration(s) on disk`;
1440
- if (mode === "online") if (markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash === contractHash) diagnostics.push({
1441
- code: "MIGRATION.MARKER_NOT_IN_HISTORY",
1442
- severity: "warn",
1443
- message: "Database matches the current contract but was updated directly (not via migration apply)",
1444
- hints: ["Run 'prisma-next migration plan' to plan a migration to your current contract"]
1445
- });
1446
- else if (pendingCount > 0) diagnostics.push({
1447
- code: "MIGRATION.DATABASE_BEHIND",
1448
- severity: "info",
1449
- message: `${pendingCount} migration(s) pending`,
1450
- hints: ["Run 'prisma-next migration apply' to apply pending migrations"]
1451
- });
1452
- else diagnostics.push({
1453
- code: "MIGRATION.UP_TO_DATE",
1454
- severity: "info",
1455
- message: "Database is up to date",
1456
- hints: []
1457
- });
1458
1590
  let pathDecision;
1459
- if (mode === "online" && markerHash !== void 0) {
1460
- const decision = findPathWithDecision(graph, markerHash, targetHash, activeRefName);
1461
- if (decision) pathDecision = toPathDecisionResult(decision);
1591
+ let routingUnreachable = false;
1592
+ if (mode === "online") {
1593
+ const outcome = findPathWithDecision(graph, markerHash ?? EMPTY_CONTRACT_HASH, targetHash, {
1594
+ ...ifDefined("refName", activeRefName),
1595
+ required: effectiveRequired
1596
+ });
1597
+ if (outcome.kind === "ok") pathDecision = toPathDecisionResult(outcome.decision);
1598
+ else if (outcome.kind === "unsatisfiable") return notOk(mapMigrationToolsError(errorNoInvariantPath({
1599
+ ...ifDefined("refName", activeRefName),
1600
+ required: [...effectiveRequired].sort(),
1601
+ missing: outcome.missing,
1602
+ structuralPath: outcome.structuralPath.map(toStructuralEdge)
1603
+ })));
1604
+ else routingUnreachable = true;
1605
+ }
1606
+ if (mode === "online") {
1607
+ if (markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash === contractHash) diagnostics.push({
1608
+ code: "MIGRATION.MARKER_NOT_IN_HISTORY",
1609
+ severity: "warn",
1610
+ message: "Database matches the current contract but was updated directly (not via migration apply)",
1611
+ hints: ["Run 'prisma-next migration plan' to plan a migration to your current contract"]
1612
+ });
1613
+ else if (pendingCount > 0) diagnostics.push({
1614
+ code: "MIGRATION.DATABASE_BEHIND",
1615
+ severity: "info",
1616
+ message: `${pendingCount} migration(s) pending`,
1617
+ hints: ["Run 'prisma-next migration apply' to apply pending migrations"]
1618
+ });
1619
+ else if (hasInvariantWork) diagnostics.push({
1620
+ code: "MIGRATION.INVARIANTS_PENDING",
1621
+ severity: "info",
1622
+ message: `Missing required invariant(s): ${missingList}`,
1623
+ hints: [`Run 'prisma-next migration apply --ref ${activeRefName ?? "<ref>"}' to apply a path that covers the required invariants`]
1624
+ });
1625
+ else if (!routingUnreachable) diagnostics.push({
1626
+ code: "MIGRATION.UP_TO_DATE",
1627
+ severity: "info",
1628
+ message: "Database is up to date",
1629
+ hints: []
1630
+ });
1462
1631
  }
1463
1632
  return ok({
1464
1633
  ok: true,
@@ -1469,13 +1638,18 @@ async function executeMigrationStatusCommand(options, flags, ui) {
1469
1638
  summary,
1470
1639
  diagnostics,
1471
1640
  ...ifDefined("markerHash", markerHash),
1641
+ requiredInvariants,
1642
+ ...ifDefined("appliedInvariants", appliedInvariants),
1643
+ ...ifDefined("missingInvariants", missingInvariants),
1472
1644
  ...statusRefs.length > 0 ? { refs: statusRefs } : {},
1473
1645
  ...ifDefined("pathDecision", pathDecision),
1474
1646
  graph,
1475
1647
  bundles,
1476
1648
  edgeStatuses,
1477
1649
  ...ifDefined("activeRefHash", activeRefHash),
1478
- ...ifDefined("activeRefName", activeRefName)
1650
+ ...ifDefined("activeRefName", activeRefName),
1651
+ spaces: aggregateSpaces,
1652
+ ...ifDefined("totalPendingAcrossSpaces", totalPendingAcrossSpaces)
1479
1653
  });
1480
1654
  }
1481
1655
  function createMigrationStatusCommand() {
@@ -1490,7 +1664,7 @@ function createMigrationStatusCommand() {
1490
1664
  });
1491
1665
  const exitCode = handleResult(await executeMigrationStatusCommand(options, flags, ui), flags, ui, (statusResult) => {
1492
1666
  if (flags.json) {
1493
- const { graph: _g, bundles: _b, edgeStatuses: _es, activeRefHash: _arh, activeRefName: _arn, diverged: _d, ...jsonResult } = statusResult;
1667
+ const { graph: _graph, bundles: _bundles, edgeStatuses: _edgeStatuses, activeRefHash: _activeRefHash, activeRefName: _activeRefName, diverged: _diverged, ...jsonResult } = statusResult;
1494
1668
  ui.output(JSON.stringify(jsonResult, null, 2));
1495
1669
  } else if (!flags.quiet) {
1496
1670
  const colorize = flags.color !== false;
@@ -1540,17 +1714,48 @@ function formatStatusSummary(result, colorize) {
1540
1714
  const hasUnknown = result.migrations.some((e) => e.status === "unknown");
1541
1715
  const pendingCount = result.migrations.filter((e) => e.status === "pending").length;
1542
1716
  const hasWarnings = result.diagnostics?.some((d) => d.severity === "warn") ?? false;
1717
+ const hasInvariantPending = result.diagnostics?.some((d) => d.code === "MIGRATION.INVARIANTS_PENDING") ?? false;
1543
1718
  if (result.mode === "online") if (hasUnknown || hasWarnings) lines.push(`${c(yellow, "⚠")} ${result.summary}`);
1544
- else if (pendingCount === 0) lines.push(`${c(cyan, "✔")} ${result.summary}`);
1719
+ else if (pendingCount === 0 && !hasInvariantPending) lines.push(`${c(cyan, "✔")} ${result.summary}`);
1545
1720
  else lines.push(`${c(yellow, "⧗")} ${result.summary}`);
1546
1721
  else lines.push(result.summary);
1722
+ if (result.requiredInvariants.length > 0) if (result.appliedInvariants !== void 0 && result.missingInvariants !== void 0) {
1723
+ lines.push(`${c(dim, "applied ")}${formatInvariantList(result.appliedInvariants)}`);
1724
+ lines.push(`${c(dim, "missing ")}${formatInvariantList(result.missingInvariants)}`);
1725
+ } else lines.push(`${c(dim, "applied ")}(unknown — connect a database to evaluate)`);
1547
1726
  const warnings = result.diagnostics?.filter((d) => d.severity === "warn") ?? [];
1548
1727
  for (const diag of warnings) {
1549
1728
  lines.push(`${c(yellow, "⚠")} ${diag.message}`);
1550
1729
  for (const hint of diag.hints) lines.push(` ${c(dim, hint)}`);
1551
1730
  }
1731
+ if (result.spaces?.some((s) => s.kind === "extension")) {
1732
+ const total = result.totalPendingAcrossSpaces ?? 0;
1733
+ lines.push("");
1734
+ lines.push(c(dim, "spaces"));
1735
+ for (const space of result.spaces) lines.push(formatSpaceLine(space, c));
1736
+ if (total > 0) {
1737
+ lines.push("");
1738
+ lines.push(`${c(yellow, "⧗")} ${total} pending migration(s) across ${result.spaces.length} space(s) — run 'prisma-next migration apply' to apply`);
1739
+ }
1740
+ }
1552
1741
  return lines.join("\n");
1553
1742
  }
1743
+ function formatSpaceLine(space, c) {
1744
+ const glyph = (() => {
1745
+ if (space.status === "up-to-date" || space.status === "no-marker") return c(cyan, "✓");
1746
+ if (space.status === "pending") return c(yellow, "⧗");
1747
+ if (space.status === "unreachable" || space.status === "never-planned") return c(magenta, "✗");
1748
+ return " ";
1749
+ })();
1750
+ const tag = space.kind === "app" ? "[app]" : "[ext]";
1751
+ const head = space.headHash.slice(0, 8);
1752
+ const marker = space.markerHash === void 0 ? "(unknown)" : space.markerHash === null ? "(no marker)" : space.markerHash.slice(0, 8);
1753
+ const pending = space.pendingCount === void 0 ? "" : space.pendingCount === 0 ? c(dim, " (up to date)") : c(yellow, ` (${space.pendingCount} pending)`);
1754
+ return ` ${glyph} ${c(dim, tag)} ${space.spaceId} → head ${c(dim, head)}, marker ${c(dim, marker)}${pending}`;
1755
+ }
1756
+ function formatInvariantList(ids) {
1757
+ return ids.length === 0 ? "(none)" : ids.join(", ");
1758
+ }
1554
1759
  function summarizeRefDistance(graph, markerHash, refHash, refName) {
1555
1760
  if (markerHash === refHash) return `At ref "${refName}" target`;
1556
1761
  const pathToRef = findPath(graph, markerHash, refHash);
@@ -1559,7 +1764,7 @@ function summarizeRefDistance(graph, markerHash, refHash, refName) {
1559
1764
  if (pathFromRef) return `${pathFromRef.length} migration(s) ahead of ref "${refName}"`;
1560
1765
  return `No path between database marker and ref "${refName}" target`;
1561
1766
  }
1562
-
1563
1767
  //#endregion
1564
- export { deriveEdgeStatuses as n, createMigrationStatusCommand as t };
1565
- //# sourceMappingURL=migration-status-DUMiH8_G.mjs.map
1768
+ export { loadAggregateStatusSpaces as a, formatStatusSummary as i, createMigrationStatusCommand as n, deriveEdgeStatuses as r, computeTotalPendingAcrossSpaces as t };
1769
+
1770
+ //# sourceMappingURL=migration-status-By9G5p2H.mjs.map