@prisma-next/cli 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 (185) hide show
  1. package/README.md +61 -26
  2. package/dist/cli-errors-B9OBbled.d.mts +3 -0
  3. package/dist/cli-errors-D3_sMh2K.mjs +33 -0
  4. package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
  5. package/dist/cli.mjs +16 -78
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/client-BCnP7cHo.mjs +1485 -0
  8. package/dist/client-BCnP7cHo.mjs.map +1 -0
  9. package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
  10. package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -1
  12. package/dist/commands/contract-emit.mjs +2 -4
  13. package/dist/commands/contract-infer.d.mts.map +1 -1
  14. package/dist/commands/contract-infer.mjs +2 -4
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +16 -13
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.d.mts.map +1 -1
  19. package/dist/commands/db-schema.mjs +6 -7
  20. package/dist/commands/db-schema.mjs.map +1 -1
  21. package/dist/commands/db-sign.d.mts.map +1 -1
  22. package/dist/commands/db-sign.mjs +9 -9
  23. package/dist/commands/db-sign.mjs.map +1 -1
  24. package/dist/commands/db-update.d.mts.map +1 -1
  25. package/dist/commands/db-update.mjs +15 -13
  26. package/dist/commands/db-update.mjs.map +1 -1
  27. package/dist/commands/db-verify.d.mts.map +1 -1
  28. package/dist/commands/db-verify.mjs +1 -321
  29. package/dist/commands/migration-apply.d.mts +28 -13
  30. package/dist/commands/migration-apply.d.mts.map +1 -1
  31. package/dist/commands/migration-apply.mjs +55 -151
  32. package/dist/commands/migration-apply.mjs.map +1 -1
  33. package/dist/commands/migration-new.d.mts +0 -1
  34. package/dist/commands/migration-new.d.mts.map +1 -1
  35. package/dist/commands/migration-new.mjs +34 -40
  36. package/dist/commands/migration-new.mjs.map +1 -1
  37. package/dist/commands/migration-plan.d.mts +33 -6
  38. package/dist/commands/migration-plan.d.mts.map +1 -1
  39. package/dist/commands/migration-plan.mjs +2 -348
  40. package/dist/commands/migration-ref.d.mts +1 -1
  41. package/dist/commands/migration-ref.d.mts.map +1 -1
  42. package/dist/commands/migration-ref.mjs +8 -12
  43. package/dist/commands/migration-ref.mjs.map +1 -1
  44. package/dist/commands/migration-show.d.mts +64 -10
  45. package/dist/commands/migration-show.d.mts.map +1 -1
  46. package/dist/commands/migration-show.mjs +166 -60
  47. package/dist/commands/migration-show.mjs.map +1 -1
  48. package/dist/commands/migration-status.d.mts +126 -5
  49. package/dist/commands/migration-status.d.mts.map +1 -1
  50. package/dist/commands/migration-status.mjs +2 -4
  51. package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
  52. package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
  53. package/dist/config-loader.d.mts +0 -1
  54. package/dist/config-loader.d.mts.map +1 -1
  55. package/dist/config-loader.mjs +2 -3
  56. package/dist/contract-emit-9DBda5Ou.mjs +150 -0
  57. package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
  58. package/dist/contract-emit-B77TsJqf.mjs +327 -0
  59. package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
  60. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
  61. package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
  62. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-ByxhPjpW.mjs} +13 -22
  63. package/dist/contract-infer-ByxhPjpW.mjs.map +1 -0
  64. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs +160 -0
  65. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs.map +1 -0
  66. package/dist/db-verify-Czm5T-J4.mjs +404 -0
  67. package/dist/db-verify-Czm5T-J4.mjs.map +1 -0
  68. package/dist/exports/config-types.mjs +1 -2
  69. package/dist/exports/control-api.d.mts +101 -586
  70. package/dist/exports/control-api.d.mts.map +1 -1
  71. package/dist/exports/control-api.mjs +4 -6
  72. package/dist/exports/index.d.mts.map +1 -1
  73. package/dist/exports/index.mjs +28 -30
  74. package/dist/exports/index.mjs.map +1 -1
  75. package/dist/exports/init-output.d.mts +2 -4
  76. package/dist/exports/init-output.d.mts.map +1 -1
  77. package/dist/exports/init-output.mjs +2 -3
  78. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
  79. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
  80. package/dist/global-flags-Icqpxk23.d.mts +12 -0
  81. package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
  82. package/dist/helpers-eqdN8tH6.mjs +25 -0
  83. package/dist/helpers-eqdN8tH6.mjs.map +1 -0
  84. package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
  85. package/dist/init-DETSgw3h.mjs.map +1 -0
  86. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-DxdBd4Er.mjs} +10 -11
  87. package/dist/inspect-live-schema-DxdBd4Er.mjs.map +1 -0
  88. package/dist/migration-cli.d.mts +41 -12
  89. package/dist/migration-cli.d.mts.map +1 -1
  90. package/dist/migration-cli.mjs +309 -86
  91. package/dist/migration-cli.mjs.map +1 -1
  92. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-BdV8JYXV.mjs} +8 -9
  93. package/dist/migration-command-scaffold-BdV8JYXV.mjs.map +1 -0
  94. package/dist/migration-plan-mRu5K81L.mjs +494 -0
  95. package/dist/migration-plan-mRu5K81L.mjs.map +1 -0
  96. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-By9G5p2H.mjs} +270 -65
  97. package/dist/migration-status-By9G5p2H.mjs.map +1 -0
  98. package/dist/migrations-CTsyBXCA.mjs +229 -0
  99. package/dist/migrations-CTsyBXCA.mjs.map +1 -0
  100. package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
  101. package/dist/output-B16Kefzx.mjs.map +1 -0
  102. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
  103. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
  104. package/dist/result-handler-rmPVKIP2.mjs +25 -0
  105. package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
  106. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  107. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
  108. package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
  109. package/dist/types-LItU7E4l.d.mts +856 -0
  110. package/dist/types-LItU7E4l.d.mts.map +1 -0
  111. package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
  112. package/dist/verify-CiwNWM9N.mjs.map +1 -0
  113. package/package.json +28 -26
  114. package/src/cli.ts +32 -6
  115. package/src/commands/contract-emit.ts +67 -163
  116. package/src/commands/contract-infer.ts +7 -20
  117. package/src/commands/db-init.ts +15 -3
  118. package/src/commands/db-update.ts +9 -4
  119. package/src/commands/db-verify.ts +47 -15
  120. package/src/commands/init/index.ts +1 -1
  121. package/src/commands/init/init.ts +2 -2
  122. package/src/commands/init/templates/code-templates.ts +26 -18
  123. package/src/commands/inspect-live-schema.ts +10 -5
  124. package/src/commands/migration-apply.ts +114 -212
  125. package/src/commands/migration-new.ts +42 -45
  126. package/src/commands/migration-plan.ts +213 -75
  127. package/src/commands/migration-ref.ts +8 -7
  128. package/src/commands/migration-show.ts +274 -70
  129. package/src/commands/migration-status.ts +491 -64
  130. package/src/config-path-validation.ts +0 -1
  131. package/src/control-api/client.ts +85 -5
  132. package/src/control-api/contract-enrichment.ts +6 -4
  133. package/src/control-api/operations/apply-aggregate.ts +290 -0
  134. package/src/control-api/operations/contract-emit.ts +198 -115
  135. package/src/control-api/operations/db-apply-aggregate.ts +399 -0
  136. package/src/control-api/operations/db-init.ts +51 -253
  137. package/src/control-api/operations/db-update.ts +66 -183
  138. package/src/control-api/operations/db-verify.ts +342 -0
  139. package/src/control-api/operations/migration-apply.ts +430 -131
  140. package/src/control-api/types.ts +278 -29
  141. package/src/exports/control-api.ts +15 -3
  142. package/src/load-ts-contract.ts +28 -26
  143. package/src/migration-cli.ts +445 -122
  144. package/src/utils/cli-errors.ts +49 -2
  145. package/src/utils/combine-schema-results.ts +84 -0
  146. package/src/utils/command-helpers.ts +69 -25
  147. package/src/utils/contract-space-aggregate-loader.ts +177 -0
  148. package/src/utils/contract-space-seed-phase.ts +201 -0
  149. package/src/utils/emit-queue.ts +26 -0
  150. package/src/utils/extension-pack-inputs.ts +162 -0
  151. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  152. package/src/utils/formatters/migrations.ts +255 -77
  153. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  154. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  155. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  156. package/dist/client-CrsnY58k.mjs +0 -997
  157. package/dist/client-CrsnY58k.mjs.map +0 -1
  158. package/dist/commands/db-verify.mjs.map +0 -1
  159. package/dist/commands/migration-plan.mjs.map +0 -1
  160. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  161. package/dist/contract-emit--feXyNd7.mjs +0 -4
  162. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  163. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  164. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  165. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  166. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  167. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  168. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  169. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  170. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  171. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  172. package/dist/init-C5220SY9.mjs.map +0 -1
  173. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  174. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  175. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  176. package/dist/migrations-Bo5WtTla.mjs +0 -153
  177. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  178. package/dist/output-BpcQrnnq.mjs.map +0 -1
  179. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  180. package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
  181. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  182. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  183. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
  184. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  185. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Re-export all domain error factories from @prisma-next/errors for convenience.
3
- * CLI-specific errors (e.g., Commander.js argument validation) can be added here if needed.
3
+ * CLI-specific errors (e.g., Commander argument validation in the main CLI, or
4
+ * clipanion parse errors in the migration-file CLI) can be added here if needed.
4
5
  */
5
6
  export type { CliErrorConflict, CliErrorEnvelope } from '@prisma-next/errors/control';
6
- export {
7
+
8
+ import {
7
9
  CliStructuredError,
8
10
  errorConfigFileNotFound,
9
11
  errorConfigValidation,
@@ -15,11 +17,33 @@ export {
15
17
  errorFamilyReadMarkerSqlRequired,
16
18
  errorFileNotFound,
17
19
  errorMigrationCliInvalidConfigArg,
20
+ errorMigrationCliUnknownFlag,
18
21
  errorMigrationPlanningFailed,
19
22
  errorQueryRunnerFactoryRequired,
20
23
  errorTargetMigrationNotSupported,
21
24
  errorUnexpected,
22
25
  } from '@prisma-next/errors/control';
26
+ import { errorRuntime } from '@prisma-next/errors/execution';
27
+ import type { MigrationToolsError } from '@prisma-next/migration-tools/errors';
28
+
29
+ export {
30
+ CliStructuredError,
31
+ errorConfigFileNotFound,
32
+ errorConfigValidation,
33
+ errorContractConfigMissing,
34
+ errorContractMissingExtensionPacks,
35
+ errorContractValidationFailed,
36
+ errorDatabaseConnectionRequired,
37
+ errorDriverRequired,
38
+ errorFamilyReadMarkerSqlRequired,
39
+ errorFileNotFound,
40
+ errorMigrationCliInvalidConfigArg,
41
+ errorMigrationCliUnknownFlag,
42
+ errorMigrationPlanningFailed,
43
+ errorQueryRunnerFactoryRequired,
44
+ errorTargetMigrationNotSupported,
45
+ errorUnexpected,
46
+ };
23
47
  export {
24
48
  ERROR_CODE_DESTRUCTIVE_CHANGES,
25
49
  errorDestructiveChanges,
@@ -38,3 +62,26 @@ export {
38
62
  errorUnfilledPlaceholder,
39
63
  placeholder,
40
64
  } from '@prisma-next/errors/migration';
65
+
66
+ /**
67
+ * Maps a `MigrationToolsError` raised by the migration-tools loader/graph
68
+ * surface (`readMigrationPackage`, `readMigrationsDir`, `readRefs`,
69
+ * `resolveRef`, `reconstructGraph`, ...) into a CLI `errorRuntime` envelope.
70
+ *
71
+ * The full `error.details` payload is forwarded into `meta` so machine
72
+ * consumers (`--json`) see structural fields like `dir`, `storedHash`,
73
+ * `computedHash` (for `MIGRATION.HASH_MISMATCH`) alongside the stable
74
+ * `code`. The user-visible `summary`/`why`/`fix` text is unchanged.
75
+ *
76
+ * Callers are expected to gate on `MigrationToolsError.is(error)` first
77
+ * (mirroring the original inline pattern); non-`MigrationToolsError`
78
+ * values are caller-classified (rethrow, wrap with command-specific
79
+ * `errorUnexpected`, etc.).
80
+ */
81
+ export function mapMigrationToolsError(error: MigrationToolsError): CliStructuredError {
82
+ return errorRuntime(error.message, {
83
+ why: error.why,
84
+ fix: error.fix,
85
+ meta: { code: error.code, ...(error.details ?? {}) },
86
+ });
87
+ }
@@ -0,0 +1,84 @@
1
+ import type { VerifyDatabaseSchemaResult } from '@prisma-next/framework-components/control';
2
+
3
+ /**
4
+ * Collapse the aggregate verifier's per-space schema results into a
5
+ * single {@link VerifyDatabaseSchemaResult} for the existing CLI
6
+ * display surface. Concatenates issues across members; sums counts;
7
+ * uses the app member's result as the structural envelope (storage
8
+ * hash, target).
9
+ *
10
+ * **Summary policy.** Preserve the per-family phrasing whenever the
11
+ * combined `ok` flag agrees with the app member's `ok` flag — this is
12
+ * the common case (single-family deployments, single-app deployments)
13
+ * and the family's "satisfies / does not satisfy contract" phrasing
14
+ * stays user-visible. When the app passes but an extension fails (or
15
+ * vice versa) the app's summary contradicts the envelope, so fall back
16
+ * to the first failing member's summary. This keeps family phrasing
17
+ * intact and the envelope internally consistent (`ok: false` ↔ failure
18
+ * summary).
19
+ */
20
+ export function combineSchemaResults(
21
+ perSpace: ReadonlyMap<string, VerifyDatabaseSchemaResult>,
22
+ appSpaceId: string,
23
+ strict: boolean,
24
+ ): VerifyDatabaseSchemaResult {
25
+ const appResult = perSpace.get(appSpaceId) ?? perSpace.values().next().value;
26
+ if (appResult === undefined) {
27
+ throw new Error('Aggregate verifier returned no schema results — this is a wiring bug.');
28
+ }
29
+
30
+ let okAll = true;
31
+ let firstFailure: VerifyDatabaseSchemaResult | undefined;
32
+ let issues: VerifyDatabaseSchemaResult['schema']['issues'] = [];
33
+ const counts = { pass: 0, warn: 0, fail: 0, totalNodes: 0 };
34
+ const childRoots: Array<VerifyDatabaseSchemaResult['schema']['root']> = [];
35
+ for (const [, result] of perSpace) {
36
+ if (!result.ok) {
37
+ okAll = false;
38
+ if (firstFailure === undefined) firstFailure = result;
39
+ }
40
+ issues = [...issues, ...result.schema.issues];
41
+ counts.pass += result.schema.counts.pass;
42
+ counts.warn += result.schema.counts.warn;
43
+ counts.fail += result.schema.counts.fail;
44
+ counts.totalNodes += result.schema.counts.totalNodes;
45
+ childRoots.push(result.schema.root);
46
+ }
47
+
48
+ // When `okAll !== appResult.ok`, exactly one shape is reachable: app passes
49
+ // (`appResult.ok === true`) and at least one other member failed
50
+ // (`okAll === false`). In that shape the failure was assigned to
51
+ // `firstFailure` during iteration, so non-null assertion is safe. The mirror
52
+ // shape (app fails while every member passes) is impossible because
53
+ // `appResult` either *is* a member of `perSpace` or is the first iterator
54
+ // value; either way its `ok` flag participates in `okAll`.
55
+ const summary =
56
+ okAll === appResult.ok
57
+ ? appResult.summary
58
+ : (firstFailure as VerifyDatabaseSchemaResult).summary;
59
+
60
+ return {
61
+ ok: okAll,
62
+ ...(okAll ? {} : { code: appResult.code ?? 'PN-RUN-3010' }),
63
+ summary,
64
+ contract: appResult.contract,
65
+ target: appResult.target,
66
+ schema: {
67
+ issues,
68
+ root: {
69
+ status: okAll ? 'pass' : 'fail',
70
+ kind: 'aggregate',
71
+ name: 'aggregate',
72
+ contractPath: '',
73
+ code: 'AGGREGATE',
74
+ message: okAll ? 'Aggregate schema matches' : 'Aggregate schema mismatch',
75
+ expected: undefined,
76
+ actual: undefined,
77
+ children: childRoots,
78
+ },
79
+ counts,
80
+ },
81
+ meta: { strict },
82
+ timings: { total: 0 },
83
+ };
84
+ }
@@ -1,10 +1,13 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import type { ControlTargetDescriptor } from '@prisma-next/framework-components/control';
3
3
  import { hasMigrations } from '@prisma-next/framework-components/control';
4
- import type { PathDecision } from '@prisma-next/migration-tools/dag';
5
- import { reconstructGraph } from '@prisma-next/migration-tools/dag';
4
+ import type { NoInvariantPathStructuralEdge } from '@prisma-next/migration-tools/errors';
5
+ import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
6
6
  import { readMigrationsDir } from '@prisma-next/migration-tools/io';
7
- import type { MigrationBundle, MigrationGraph } from '@prisma-next/migration-tools/types';
7
+ import type { PathDecision } from '@prisma-next/migration-tools/migration-graph';
8
+ import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
9
+ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
10
+ import { APP_SPACE_ID, spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
8
11
  import { ifDefined } from '@prisma-next/utils/defined';
9
12
  import type { Command } from 'commander';
10
13
  import { relative, resolve } from 'pathe';
@@ -77,6 +80,16 @@ export function resolveContractPath(config: { contract?: { output?: string } }):
77
80
  /**
78
81
  * Resolves the migrations directory and config path from CLI options.
79
82
  * Shared by migration-apply, migration-plan, and migration-status.
83
+ *
84
+ * - `migrationsDir` is the project's top-level `migrations/` directory
85
+ * (the root that the aggregate loader walks for every contract space).
86
+ * - `appMigrationsDir` is the app subspace directory under it
87
+ * (`<migrationsDir>/<APP_SPACE_ID>/`). Every per-app reader / writer
88
+ * (`migration new`, `migration plan`, `migration apply`,
89
+ * `migration status`, `migration show`, `migration ref`) operates on
90
+ * this directory. Extensions own their own `migrations/<spaceId>/`.
91
+ * - `refsDir` is the app's refs directory (`<appMigrationsDir>/refs/`).
92
+ * The framework does not maintain refs at the migrations root.
80
93
  */
81
94
  export function resolveMigrationPaths(
82
95
  configOption: string | undefined,
@@ -85,6 +98,8 @@ export function resolveMigrationPaths(
85
98
  configPath: string;
86
99
  migrationsDir: string;
87
100
  migrationsRelative: string;
101
+ appMigrationsDir: string;
102
+ appMigrationsRelative: string;
88
103
  refsDir: string;
89
104
  } {
90
105
  const configPath = configOption
@@ -95,8 +110,17 @@ export function resolveMigrationPaths(
95
110
  config.migrations?.dir ?? 'migrations',
96
111
  );
97
112
  const migrationsRelative = relative(process.cwd(), migrationsDir);
98
- const refsDir = resolve(migrationsDir, 'refs');
99
- return { configPath, migrationsDir, migrationsRelative, refsDir };
113
+ const appMigrationsDir = spaceMigrationDirectory(migrationsDir, APP_SPACE_ID);
114
+ const appMigrationsRelative = relative(process.cwd(), appMigrationsDir);
115
+ const refsDir = resolve(appMigrationsDir, 'refs');
116
+ return {
117
+ configPath,
118
+ migrationsDir,
119
+ migrationsRelative,
120
+ appMigrationsDir,
121
+ appMigrationsRelative,
122
+ refsDir,
123
+ };
100
124
  }
101
125
 
102
126
  /**
@@ -109,14 +133,45 @@ export interface PathDecisionResult {
109
133
  readonly alternativeCount: number;
110
134
  readonly tieBreakReasons: readonly string[];
111
135
  readonly refName?: string;
136
+ readonly requiredInvariants: readonly string[];
137
+ readonly satisfiedInvariants: readonly string[];
112
138
  readonly selectedPath: readonly {
113
139
  readonly dirName: string;
114
- readonly migrationId: string;
140
+ readonly migrationHash: string;
115
141
  readonly from: string;
116
142
  readonly to: string;
143
+ readonly invariants: readonly string[];
117
144
  }[];
118
145
  }
119
146
 
147
+ export function collectDeclaredInvariants(graph: MigrationGraph): ReadonlySet<string> {
148
+ const declared = new Set<string>();
149
+ for (const edges of graph.forwardChain.values()) {
150
+ for (const edge of edges) {
151
+ for (const inv of edge.invariants) {
152
+ declared.add(inv);
153
+ }
154
+ }
155
+ }
156
+ return declared;
157
+ }
158
+
159
+ /**
160
+ * Maps a `MigrationEdge` to the structural-edge shape used in the
161
+ * `MIGRATION.NO_INVARIANT_PATH` error envelope. Shared between
162
+ * `migration apply` and `migration status` so both commands surface
163
+ * the same JSON wire shape when an invariant-aware route is unsatisfiable.
164
+ */
165
+ export function toStructuralEdge(edge: MigrationEdge): NoInvariantPathStructuralEdge {
166
+ return {
167
+ dirName: edge.dirName,
168
+ migrationHash: edge.migrationHash,
169
+ from: edge.from,
170
+ to: edge.to,
171
+ invariants: edge.invariants,
172
+ };
173
+ }
174
+
120
175
  /**
121
176
  * Maps a PathDecision to the slim CLI output representation.
122
177
  */
@@ -126,12 +181,15 @@ export function toPathDecisionResult(decision: PathDecision): PathDecisionResult
126
181
  toHash: decision.toHash,
127
182
  alternativeCount: decision.alternativeCount,
128
183
  tieBreakReasons: decision.tieBreakReasons,
184
+ requiredInvariants: decision.requiredInvariants ?? [],
185
+ satisfiedInvariants: decision.satisfiedInvariants ?? [],
129
186
  ...ifDefined('refName', decision.refName),
130
187
  selectedPath: decision.selectedPath.map((entry) => ({
131
188
  dirName: entry.dirName,
132
- migrationId: entry.migrationId,
189
+ migrationHash: entry.migrationHash,
133
190
  from: entry.from,
134
191
  to: entry.to,
192
+ invariants: entry.invariants,
135
193
  })),
136
194
  };
137
195
  }
@@ -146,13 +204,13 @@ export function getTargetMigrations(target: ControlTargetDescriptor<string, stri
146
204
 
147
205
  /**
148
206
  * Reads the migrations directory and builds the migration graph from all
149
- * bundles. Throws on I/O or graph errors — callers handle error mapping.
207
+ * packages. Throws on I/O or graph errors — callers handle error mapping.
150
208
  *
151
- * Every on-disk bundle is content-addressed (`migrationId` is always a
209
+ * Every on-disk package is content-addressed (`migrationHash` is always a
152
210
  * string); there is no draft state to filter out.
153
211
  */
154
- export async function loadMigrationBundles(migrationsDir: string): Promise<{
155
- bundles: readonly MigrationBundle[];
212
+ export async function loadMigrationPackages(migrationsDir: string): Promise<{
213
+ bundles: readonly OnDiskMigrationPackage[];
156
214
  graph: MigrationGraph;
157
215
  }> {
158
216
  const bundles = await readMigrationsDir(migrationsDir);
@@ -160,20 +218,6 @@ export async function loadMigrationBundles(migrationsDir: string): Promise<{
160
218
  return { bundles, graph };
161
219
  }
162
220
 
163
- export interface MigrationBundleSet {
164
- readonly bundles: readonly MigrationBundle[];
165
- readonly graph: MigrationGraph;
166
- }
167
-
168
- /**
169
- * Alias of `loadMigrationBundles` retained for naming-clarity in commands
170
- * that previously needed both attested and draft splits. With the
171
- * collapse of the draft state, both helpers do the same thing.
172
- */
173
- export async function loadAllBundles(migrationsDir: string): Promise<MigrationBundleSet> {
174
- return loadMigrationBundles(migrationsDir);
175
- }
176
-
177
221
  /**
178
222
  * The subset of the emitted contract.json that the framework layer can
179
223
  * safely type. The emitter adds these fields on top of the family-specific
@@ -0,0 +1,177 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import type { ControlExtensionDescriptor } from '@prisma-next/framework-components/control';
3
+ import type {
4
+ ContractSpaceAggregate,
5
+ LoadAggregateError,
6
+ LoadAggregateInput,
7
+ LoadAggregateOutput,
8
+ } from '@prisma-next/migration-tools/aggregate';
9
+ import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
10
+ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
11
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
12
+ import { CliStructuredError } from './cli-errors';
13
+ import { toDeclaredExtensionsFromRaw } from './extension-pack-inputs';
14
+
15
+ /**
16
+ * Render a {@link LoadAggregateError} into a CLI structured-error
17
+ * envelope. Preserves error codes `5001` (layout) and `5002` (marker /
18
+ * disjointness / etc.) so existing integration tests and downstream
19
+ * tooling continue to assert on the same `meta.violations[]` shape
20
+ * they did under the old precheck/marker-check helpers.
21
+ */
22
+ export function mapLoadAggregateError(error: LoadAggregateError): CliStructuredError {
23
+ if (error.kind === 'layoutViolation') {
24
+ const lines = error.violations.map((v) => `- [${v.kind}] ${v.spaceId}`);
25
+ const summary =
26
+ error.violations.length === 1
27
+ ? 'Contract-space layout violation detected'
28
+ : `Contract-space layout violations detected (${error.violations.length})`;
29
+ return new CliStructuredError('5001', summary, {
30
+ domain: 'MIG',
31
+ why: `The on-disk \`migrations/\` directory and your \`extensionPacks\` declaration are not in agreement.\n${lines.join('\n')}`,
32
+ fix: 'Run `prisma-next migrate` to materialise on-disk artefacts for declared extensions, or remove the orphan directory.',
33
+ docsUrl: 'https://pris.ly/contract-spaces',
34
+ meta: {
35
+ violations: error.violations.map((v) => ({
36
+ kind: v.kind,
37
+ spaceId: v.spaceId,
38
+ })),
39
+ },
40
+ });
41
+ }
42
+ if (error.kind === 'disjointnessViolation') {
43
+ return new CliStructuredError(
44
+ '5002',
45
+ `Contract-space disjointness violation: storage element "${error.element}" claimed by multiple spaces`,
46
+ {
47
+ domain: 'MIG',
48
+ why: `Spaces ${error.claimedBy.map((s) => `"${s}"`).join(', ')} all claim the storage element "${error.element}". Each storage element must be owned by exactly one contract space.`,
49
+ fix: 'Update the conflicting contracts so each storage element is claimed by exactly one space.',
50
+ docsUrl: 'https://pris.ly/contract-spaces',
51
+ meta: {
52
+ violations: [
53
+ {
54
+ kind: 'disjointness',
55
+ spaceId: error.claimedBy.join(','),
56
+ element: error.element,
57
+ claimedBy: error.claimedBy,
58
+ },
59
+ ],
60
+ },
61
+ },
62
+ );
63
+ }
64
+ if (error.kind === 'integrityFailure') {
65
+ return new CliStructuredError(
66
+ '5002',
67
+ `Contract-space integrity failure for "${error.spaceId}"`,
68
+ {
69
+ domain: 'MIG',
70
+ why: error.detail,
71
+ fix: 'Run `prisma-next migrate` to refresh on-disk artefacts, or restore the on-disk `migrations/` directory from version control.',
72
+ docsUrl: 'https://pris.ly/contract-spaces',
73
+ meta: {
74
+ violations: [{ kind: 'integrity', spaceId: error.spaceId, detail: error.detail }],
75
+ },
76
+ },
77
+ );
78
+ }
79
+ if (error.kind === 'validationFailure') {
80
+ return new CliStructuredError(
81
+ '5002',
82
+ `Contract-space contract validation failed for "${error.spaceId}"`,
83
+ {
84
+ domain: 'MIG',
85
+ why: error.detail,
86
+ fix: 'Run `prisma-next migrate` to refresh on-disk artefacts, or fix the extension descriptor producing the invalid contract.',
87
+ meta: {
88
+ violations: [{ kind: 'validation', spaceId: error.spaceId, detail: error.detail }],
89
+ },
90
+ },
91
+ );
92
+ }
93
+ // targetMismatch
94
+ return new CliStructuredError('5002', `Contract-space target mismatch for "${error.spaceId}"`, {
95
+ domain: 'MIG',
96
+ why: `Space "${error.spaceId}" targets "${error.actual}" but the project's adapter targets "${error.expected}".`,
97
+ fix: 'Update the extension descriptor to target the configured database, or change the project adapter.',
98
+ meta: {
99
+ violations: [
100
+ {
101
+ kind: 'targetMismatch',
102
+ spaceId: error.spaceId,
103
+ expected: error.expected,
104
+ actual: error.actual,
105
+ },
106
+ ],
107
+ },
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Inputs needed to compose the aggregate loader at the CLI surface.
113
+ *
114
+ * Keeps the loader framework-neutral (no `Config` import) by accepting
115
+ * already-resolved structural inputs: validated app contract, target
116
+ * id, migrations root directory, and the set of extension descriptors.
117
+ */
118
+ export interface BuildAggregateInputs<TFamilyId extends string, TTargetId extends string> {
119
+ readonly targetId: TTargetId;
120
+ readonly migrationsDir: string;
121
+ readonly appContract: Contract;
122
+ readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
123
+ readonly validateContract: (contractJson: unknown) => Contract;
124
+ /**
125
+ * App-space migration packages to hydrate the app member's
126
+ * migration graph with. Defaults to `[]` (matches the `db init` /
127
+ * `db update` daily-driver behaviour, where the app's authored
128
+ * `migrations/` graph is not walked — the planner uses the synth
129
+ * strategy for the app member instead).
130
+ *
131
+ * `migration apply` callers thread the user's authored app-space
132
+ * packages (loaded via `loadMigrationPackages(appMigrationsDir)`)
133
+ * through here so the graph-walk strategy can plot a path through
134
+ * them — the prod-time replay path explicitly forbids synth.
135
+ */
136
+ readonly appMigrationPackages?: ReadonlyArray<OnDiskMigrationPackage>;
137
+ }
138
+
139
+ /**
140
+ * Run the aggregate loader at the CLI surface, mapping any
141
+ * {@link LoadAggregateError} into a {@link CliStructuredError} envelope.
142
+ *
143
+ * App-side migration packages flow through `inputs.appMigrationPackages`
144
+ * (defaulting to `[]`). `db init` / `db update` leave it empty: the
145
+ * planner's `synth` strategy is used for the app member (driven by
146
+ * `callerPolicy.ignoreGraphFor`), so the app's authored `migrations/`
147
+ * graph does not need to be walked. `migration apply` threads the
148
+ * already-loaded app-space packages through so the graph-walk strategy
149
+ * can plot a path through them — replay forbids synth.
150
+ *
151
+ * @see specs/contract-space-aggregate-spec.md § Loader.
152
+ */
153
+ export async function buildContractSpaceAggregate<
154
+ TFamilyId extends string,
155
+ TTargetId extends string,
156
+ >(
157
+ inputs: BuildAggregateInputs<TFamilyId, TTargetId>,
158
+ ): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {
159
+ const declaredExtensions = toDeclaredExtensionsFromRaw(
160
+ inputs.extensionPacks as ReadonlyArray<unknown>,
161
+ );
162
+
163
+ const loadInput: LoadAggregateInput = {
164
+ targetId: inputs.targetId,
165
+ migrationsDir: inputs.migrationsDir,
166
+ appContract: inputs.appContract,
167
+ declaredExtensions,
168
+ validateContract: inputs.validateContract,
169
+ appMigrationPackages: inputs.appMigrationPackages ?? [],
170
+ };
171
+
172
+ const result: LoadAggregateOutput = await loadContractSpaceAggregate(loadInput);
173
+ if (!result.ok) {
174
+ return notOk(mapLoadAggregateError(result.failure));
175
+ }
176
+ return ok(result.value.aggregate);
177
+ }