@prisma-next/cli 0.11.0 → 0.12.0-dev.10

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 (197) hide show
  1. package/README.md +13 -9
  2. package/dist/cli.mjs +259 -12
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-oXO2WCPD.mjs → client-CDr4o07S.mjs} +88 -63
  5. package/dist/client-CDr4o07S.mjs.map +1 -0
  6. package/dist/{command-helpers-BSb0tRC8.mjs → command-helpers-Bbw1GbwL.mjs} +646 -46
  7. package/dist/command-helpers-Bbw1GbwL.mjs.map +1 -0
  8. package/dist/commands/contract-emit.d.mts.map +1 -1
  9. package/dist/commands/contract-emit.mjs +1 -1
  10. package/dist/commands/contract-infer.d.mts.map +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.d.mts.map +1 -1
  13. package/dist/commands/db-init.mjs +32 -7
  14. package/dist/commands/db-init.mjs.map +1 -1
  15. package/dist/commands/db-schema.d.mts.map +1 -1
  16. package/dist/commands/db-schema.mjs +3 -4
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.d.mts.map +1 -1
  19. package/dist/commands/db-sign.mjs +12 -10
  20. package/dist/commands/db-sign.mjs.map +1 -1
  21. package/dist/commands/db-update.d.mts.map +1 -1
  22. package/dist/commands/db-update.mjs +41 -11
  23. package/dist/commands/db-update.mjs.map +1 -1
  24. package/dist/commands/db-verify.d.mts.map +1 -1
  25. package/dist/commands/db-verify.mjs +1 -1
  26. package/dist/commands/migrate.d.mts +6 -2
  27. package/dist/commands/migrate.d.mts.map +1 -1
  28. package/dist/commands/migrate.mjs +75 -40
  29. package/dist/commands/migrate.mjs.map +1 -1
  30. package/dist/commands/migration-check.d.mts +4 -3
  31. package/dist/commands/migration-check.d.mts.map +1 -1
  32. package/dist/commands/migration-check.mjs +1 -280
  33. package/dist/commands/migration-graph.d.mts +31 -2
  34. package/dist/commands/migration-graph.d.mts.map +1 -1
  35. package/dist/commands/migration-graph.mjs +2 -137
  36. package/dist/commands/migration-list.d.mts +64 -4
  37. package/dist/commands/migration-list.d.mts.map +1 -1
  38. package/dist/commands/migration-list.mjs +143 -56
  39. package/dist/commands/migration-list.mjs.map +1 -1
  40. package/dist/commands/migration-log.d.mts +10 -1
  41. package/dist/commands/migration-log.d.mts.map +1 -1
  42. package/dist/commands/migration-log.mjs +10 -15
  43. package/dist/commands/migration-log.mjs.map +1 -1
  44. package/dist/commands/migration-new.d.mts.map +1 -1
  45. package/dist/commands/migration-new.mjs +32 -38
  46. package/dist/commands/migration-new.mjs.map +1 -1
  47. package/dist/commands/migration-plan.d.mts +3 -2
  48. package/dist/commands/migration-plan.d.mts.map +1 -1
  49. package/dist/commands/migration-plan.mjs +1 -1
  50. package/dist/commands/migration-show.d.mts +4 -55
  51. package/dist/commands/migration-show.d.mts.map +1 -1
  52. package/dist/commands/migration-show.mjs +61 -153
  53. package/dist/commands/migration-show.mjs.map +1 -1
  54. package/dist/commands/migration-status.d.mts +12 -49
  55. package/dist/commands/migration-status.d.mts.map +1 -1
  56. package/dist/commands/migration-status.mjs +86 -82
  57. package/dist/commands/migration-status.mjs.map +1 -1
  58. package/dist/commands/ref.d.mts +1 -1
  59. package/dist/commands/ref.d.mts.map +1 -1
  60. package/dist/commands/ref.mjs +38 -10
  61. package/dist/commands/ref.mjs.map +1 -1
  62. package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
  63. package/dist/config-loader.d.mts.map +1 -1
  64. package/dist/contract-at-errors-BxP-TOMl.mjs +42 -0
  65. package/dist/contract-at-errors-BxP-TOMl.mjs.map +1 -0
  66. package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-D-4jrNve.mjs} +25 -10
  67. package/dist/contract-emit-D-4jrNve.mjs.map +1 -0
  68. package/dist/{contract-emit-r4y8Zhf1.mjs → contract-emit-DxcGl4Uq.mjs} +19 -14
  69. package/dist/contract-emit-DxcGl4Uq.mjs.map +1 -0
  70. package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-a0V5Y_mL.mjs} +4 -25
  71. package/dist/contract-enrichment-a0V5Y_mL.mjs.map +1 -0
  72. package/dist/{contract-infer-BmySmqVT.mjs → contract-infer-C8J1WMvO.mjs} +4 -5
  73. package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-C8J1WMvO.mjs.map} +1 -1
  74. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs +247 -0
  75. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs.map +1 -0
  76. package/dist/{db-verify-BClPs3ph.mjs → db-verify-BeRHwN8M.mjs} +5 -7
  77. package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-BeRHwN8M.mjs.map} +1 -1
  78. package/dist/exports/control-api.d.mts +3 -3
  79. package/dist/exports/control-api.d.mts.map +1 -1
  80. package/dist/exports/control-api.mjs +3 -3
  81. package/dist/exports/index.d.mts.map +1 -1
  82. package/dist/exports/index.mjs +1 -1
  83. package/dist/exports/index.mjs.map +1 -1
  84. package/dist/exports/init-output.d.mts.map +1 -1
  85. package/dist/exports/init-output.mjs +1 -1
  86. package/dist/extension-pack-inputs-IDvjRCi3.mjs +62 -0
  87. package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +1 -0
  88. package/dist/{framework-components-65gOHkHB.mjs → framework-components-fYXjz_in.mjs} +2 -2
  89. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
  90. package/dist/global-flags-DEHjV8_s.d.mts +34 -0
  91. package/dist/global-flags-DEHjV8_s.d.mts.map +1 -0
  92. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-rFAqZujX.mjs} +2 -2
  93. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
  94. package/dist/{init-BCJZPWE1.mjs → init-Cv9UzWL5.mjs} +20 -269
  95. package/dist/init-Cv9UzWL5.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-DSRbFoOL.mjs → inspect-live-schema-BlKR2Zln.mjs} +4 -5
  97. package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-BlKR2Zln.mjs.map} +1 -1
  98. package/dist/migration-check-BiBJoYYW.mjs +341 -0
  99. package/dist/migration-check-BiBJoYYW.mjs.map +1 -0
  100. package/dist/migration-cli.d.mts.map +1 -1
  101. package/dist/migration-cli.mjs +4 -4
  102. package/dist/migration-cli.mjs.map +1 -1
  103. package/dist/{migration-command-scaffold-Bzd9La5c.mjs → migration-command-scaffold-BAGUiGOK.mjs} +4 -5
  104. package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-BAGUiGOK.mjs.map} +1 -1
  105. package/dist/migration-graph-C9WC-7eO.mjs +1478 -0
  106. package/dist/migration-graph-C9WC-7eO.mjs.map +1 -0
  107. package/dist/migration-list-styler-BRwF4-gy.mjs +399 -0
  108. package/dist/migration-list-styler-BRwF4-gy.mjs.map +1 -0
  109. package/dist/{migration-plan-CFwqw3Gk.mjs → migration-plan-9DJ7q7_z.mjs} +372 -133
  110. package/dist/migration-plan-9DJ7q7_z.mjs.map +1 -0
  111. package/dist/{migration-types-BXWvz12q.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
  112. package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
  113. package/dist/{migrations-CwZMa1Ck.mjs → migrations-Cv2jxNNK.mjs} +12 -13
  114. package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
  115. package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
  116. package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  117. package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-C644QK8l.mjs} +1 -1
  118. package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
  119. package/dist/ref-advancement-DUZqsue6.mjs +50 -0
  120. package/dist/ref-advancement-DUZqsue6.mjs.map +1 -0
  121. package/dist/terminal-ui-5Y6mrg93.d.mts +133 -0
  122. package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
  123. package/dist/{types--CqjMdk0.d.mts → types-CeC5ec2Y.d.mts} +35 -29
  124. package/dist/types-CeC5ec2Y.d.mts.map +1 -0
  125. package/dist/{verify-Bom75OYI.mjs → verify-DCA9Sldu.mjs} +2 -2
  126. package/dist/{verify-Bom75OYI.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
  127. package/package.json +35 -24
  128. package/src/commands/contract-emit.ts +19 -7
  129. package/src/commands/contract-infer.ts +1 -1
  130. package/src/commands/db-init.ts +48 -2
  131. package/src/commands/db-sign.ts +9 -5
  132. package/src/commands/db-update.ts +54 -8
  133. package/src/commands/init/hygiene-gitattributes.ts +2 -2
  134. package/src/commands/init/index.ts +2 -1
  135. package/src/commands/init/templates/code-templates.ts +4 -2
  136. package/src/commands/init/templates/env.ts +13 -14
  137. package/src/commands/migrate.ts +125 -44
  138. package/src/commands/migration-check.ts +43 -83
  139. package/src/commands/migration-graph.ts +116 -60
  140. package/src/commands/migration-list.ts +220 -74
  141. package/src/commands/migration-log.ts +8 -14
  142. package/src/commands/migration-new.ts +44 -48
  143. package/src/commands/migration-plan.ts +412 -197
  144. package/src/commands/migration-show.ts +65 -284
  145. package/src/commands/migration-status.ts +128 -125
  146. package/src/commands/ref.ts +53 -8
  147. package/src/control-api/client.ts +11 -2
  148. package/src/control-api/contract-enrichment.ts +6 -42
  149. package/src/control-api/operations/{apply-aggregate.ts → apply.ts} +45 -75
  150. package/src/control-api/operations/contract-emit.ts +14 -6
  151. package/src/control-api/operations/{db-apply-aggregate.ts → db-apply.ts} +19 -19
  152. package/src/control-api/operations/db-init.ts +4 -4
  153. package/src/control-api/operations/db-update.ts +4 -4
  154. package/src/control-api/operations/db-verify.ts +15 -11
  155. package/src/control-api/operations/migration-apply.ts +66 -50
  156. package/src/control-api/types.ts +38 -28
  157. package/src/migration-cli.ts +4 -4
  158. package/src/utils/cli-errors.ts +234 -0
  159. package/src/utils/command-helpers.ts +9 -24
  160. package/src/utils/contract-at-errors.ts +96 -0
  161. package/src/utils/contract-space-aggregate-loader.ts +336 -117
  162. package/src/utils/formatters/migration-graph-lane-colors.ts +31 -0
  163. package/src/utils/formatters/migration-graph-layout.ts +1141 -0
  164. package/src/utils/formatters/migration-graph-rows.ts +336 -0
  165. package/src/utils/formatters/migration-graph-tree-render.ts +768 -0
  166. package/src/utils/formatters/migration-list-data-column.ts +115 -0
  167. package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
  168. package/src/utils/formatters/migration-list-render.ts +191 -0
  169. package/src/utils/formatters/migration-list-styler.ts +63 -0
  170. package/src/utils/formatters/migration-list-types.ts +21 -0
  171. package/src/utils/formatters/migrations.ts +37 -46
  172. package/src/utils/glyph-mode.ts +22 -0
  173. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  174. package/src/utils/plan-resolution.ts +258 -0
  175. package/src/utils/ref-advancement.ts +68 -0
  176. package/src/utils/terminal-ui.ts +42 -1
  177. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  178. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  179. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  180. package/dist/client-oXO2WCPD.mjs.map +0 -1
  181. package/dist/command-helpers-BSb0tRC8.mjs.map +0 -1
  182. package/dist/commands/migration-check.mjs.map +0 -1
  183. package/dist/commands/migration-graph.mjs.map +0 -1
  184. package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
  185. package/dist/contract-emit-r4y8Zhf1.mjs.map +0 -1
  186. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  187. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
  188. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
  189. package/dist/global-flags-CdE7M0d9.d.mts +0 -15
  190. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  191. package/dist/init-BCJZPWE1.mjs.map +0 -1
  192. package/dist/migration-plan-CFwqw3Gk.mjs.map +0 -1
  193. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  194. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  195. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  196. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  197. package/dist/types--CqjMdk0.d.mts.map +0 -1
@@ -7,16 +7,17 @@ import type {
7
7
  TargetMigrationsCapability,
8
8
  } from '@prisma-next/framework-components/control';
9
9
  import {
10
- type AggregatePerSpacePlan,
10
+ buildSynthMigrationEdge,
11
11
  type ContractMarkerRecordLike,
12
12
  type ContractSpaceAggregate,
13
13
  type ContractSpaceMember,
14
14
  graphWalkStrategy,
15
+ type PerSpacePlan,
16
+ requireHeadRef,
15
17
  } from '@prisma-next/migration-tools/aggregate';
16
18
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
17
19
  import { errorNoInvariantPath } from '@prisma-next/migration-tools/errors';
18
20
  import { findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';
19
- import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
20
21
  import { ifDefined } from '@prisma-next/utils/defined';
21
22
  import { notOk, ok } from '@prisma-next/utils/result';
22
23
  import {
@@ -24,14 +25,14 @@ import {
24
25
  buildContractSpaceAggregate,
25
26
  } from '../../utils/contract-space-aggregate-loader';
26
27
  import type {
27
- AggregatePerSpaceExecutionEntry,
28
28
  MigrationApplyFailure,
29
29
  MigrationApplyPathDecision,
30
30
  MigrationApplyResult,
31
31
  MigrationApplySuccess,
32
32
  OnControlProgress,
33
+ PerSpaceExecutionEntry,
33
34
  } from '../types';
34
- import { applyAggregate, buildPerSpaceBreakdown } from './apply-aggregate';
35
+ import { applyMigration, buildPerSpaceBreakdown } from './apply';
35
36
 
36
37
  /**
37
38
  * Inputs for the aggregate-walking `migrate` control-api
@@ -56,21 +57,10 @@ export interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetI
56
57
  readonly migrationsDir: string;
57
58
  readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
58
59
  readonly targetId: TTargetId;
59
- /**
60
- * Already-loaded app-space migration packages. The CLI command
61
- * loads these via `loadMigrationPackages(appMigrationsDir)`; the
62
- * operation hydrates the app member's graph with them. Required
63
- * because the framework-neutral aggregate loader doesn't know how
64
- * to read the user's `migrations/` directory layout (it's family-
65
- * aware: ops.json shape, manifest keys, etc.).
66
- */
67
- readonly appMigrationPackages: ReadonlyArray<OnDiskMigrationPackage>;
68
60
  /**
69
61
  * Optional app-space ref override. When provided, the app member's
70
62
  * graph-walk targets this hash instead of `member.headRef.hash`.
71
63
  * Extensions are unaffected — they always walk to their own head.
72
- *
73
- * Sub-spec § `--ref <hash>` semantics under multi-space.
74
64
  */
75
65
  readonly refHash?: string;
76
66
  /**
@@ -106,7 +96,7 @@ export interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetI
106
96
  * marker to `member.headRef.hash` (or `refHash` for the app
107
97
  * member when provided). Empty-graph members fail loudly — a
108
98
  * "never planned" space is a user-error condition for replay.
109
- * 4. Hand off to {@link applyAggregate} (the runner-driving tail
99
+ * 4. Hand off to {@link applyMigration} (the runner-driving tail
110
100
  * shared with `db init` / `db update`). Marker advancement is
111
101
  * inside the per-space transaction.
112
102
  *
@@ -125,7 +115,6 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
125
115
  migrationsDir,
126
116
  extensionPacks,
127
117
  targetId,
128
- appMigrationPackages,
129
118
  refHash,
130
119
  refInvariants,
131
120
  refName,
@@ -138,7 +127,6 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
138
127
  appContract: contract,
139
128
  extensionPacks,
140
129
  deserializeContract: (json) => familyInstance.deserializeContract(json),
141
- appMigrationPackages,
142
130
  };
143
131
  const loaded = await buildContractSpaceAggregate(loadInputs);
144
132
  if (!loaded.ok) {
@@ -152,21 +140,24 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
152
140
  // when provided, otherwise its own head; extensions always walk
153
141
  // to their own head ref.
154
142
  const allMembers: ReadonlyArray<ContractSpaceMember> = [aggregate.app, ...aggregate.extensions];
155
- const perSpacePlans = new Map<string, AggregatePerSpacePlan>();
143
+ const perSpacePlans = new Map<string, PerSpacePlan>();
156
144
  // Already-at-head empty-graph members (typically extensions whose
157
145
  // head ref is the empty sentinel, or whose live marker already
158
146
  // matches the target). Kept out of the runner schedule so we don't
159
147
  // write spurious markers for greenfield extensions, but merged back
160
148
  // into the success envelope so every loaded member is represented.
161
- const atHeadResolutions = new Map<string, AggregatePerSpacePlan>();
149
+ const atHeadResolutions = new Map<string, PerSpacePlan>();
162
150
  for (const member of allMembers) {
163
151
  const isAppMember = member.spaceId === aggregate.app.spaceId;
164
- const targetHash = isAppMember && refHash !== undefined ? refHash : member.headRef.hash;
152
+ // The aggregate passed the integrity gate, so every member's head ref
153
+ // is resolved (the app's is synthesised from the live contract).
154
+ const headRef = requireHeadRef(member);
155
+ const targetHash = isAppMember && refHash !== undefined ? refHash : headRef.hash;
165
156
  const liveMarker = markerRows.get(member.spaceId) ?? null;
166
157
 
167
158
  // Empty-graph members fail loudly: replay needs an on-disk path
168
159
  // and an empty graph means the user has never planned this space.
169
- if (member.migrations.graph.nodes.size === 0) {
160
+ if (member.graph().nodes.size === 0) {
170
161
  // Edge case: target == EMPTY (greenfield, nothing to do) or
171
162
  // the live marker already matches the target. Loader integrity
172
163
  // allows this for extensions whose head ref is the empty
@@ -197,9 +188,9 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
197
188
  const targetInvariants =
198
189
  isAppMember && refHash !== undefined && refInvariants !== undefined
199
190
  ? refInvariants
200
- : member.headRef.invariants;
191
+ : headRef.invariants;
201
192
  const targetMember: ContractSpaceMember =
202
- targetHash === member.headRef.hash && targetInvariants === member.headRef.invariants
193
+ targetHash === headRef.hash && targetInvariants === headRef.invariants
203
194
  ? member
204
195
  : { ...member, headRef: { hash: targetHash, invariants: targetInvariants } };
205
196
 
@@ -224,7 +215,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
224
215
  // is never a graph node, producing an empty `structuralPath` and
225
216
  // a less actionable diagnostic.
226
217
  const fromHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
227
- const structural = findPathWithDecision(targetMember.migrations.graph, fromHash, targetHash, {
218
+ const structural = findPathWithDecision(targetMember.graph(), fromHash, targetHash, {
228
219
  required: new Set<string>(),
229
220
  });
230
221
  const structuralPath =
@@ -286,7 +277,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
286
277
  );
287
278
  }
288
279
 
289
- const applied = await applyAggregate({
280
+ const applied = await applyMigration({
290
281
  aggregate,
291
282
  perSpacePlans,
292
283
  applyOrder,
@@ -329,7 +320,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
329
320
  includeMarkers: true,
330
321
  });
331
322
  const totalMigrationsApplied = applied.value.orderedResolutions.reduce(
332
- (sum, r) => sum + (r.entry.migrationEdges?.length ?? 0),
323
+ (sum, r) => sum + r.entry.migrationEdges.length,
333
324
  0,
334
325
  );
335
326
  const summary = `Applied ${totalMigrationsApplied} migration(s) (${applied.value.totalOpsExecuted} operation(s)) across ${orderedAll.length} contract space(s)`;
@@ -346,7 +337,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
346
337
  }
347
338
 
348
339
  /**
349
- * Build a zero-op {@link AggregatePerSpacePlan} for an empty-graph
340
+ * Build a zero-op {@link PerSpacePlan} for an empty-graph
350
341
  * member whose live marker already matches the target. Lets the apply
351
342
  * pipeline thread the member through `perSpacePlans` -> `applyOrder`
352
343
  * -> the success envelope's `perSpace[]` block so the result reflects
@@ -357,7 +348,7 @@ function buildAtHeadResolution(args: {
357
348
  readonly member: ContractSpaceMember;
358
349
  readonly targetHash: string;
359
350
  readonly liveMarker: ContractMarkerRecordLike | null;
360
- }): AggregatePerSpacePlan {
351
+ }): PerSpacePlan {
361
352
  const { aggregateTargetId, member, targetHash, liveMarker } = args;
362
353
  return {
363
354
  plan: {
@@ -369,15 +360,21 @@ function buildAtHeadResolution(args: {
369
360
  providedInvariants: [],
370
361
  },
371
362
  displayOps: [],
372
- destinationContract: member.contract,
363
+ destinationContract: member.contract(),
373
364
  strategy: 'graph-walk',
374
- migrationEdges: [],
365
+ migrationEdges: [
366
+ buildSynthMigrationEdge({
367
+ currentMarkerStorageHash: liveMarker?.storageHash,
368
+ destinationStorageHash: targetHash,
369
+ operationCount: 0,
370
+ }),
371
+ ],
375
372
  };
376
373
  }
377
374
 
378
375
  function sumPlannedOps(
379
376
  applyOrder: readonly string[],
380
- perSpacePlans: ReadonlyMap<string, AggregatePerSpacePlan>,
377
+ perSpacePlans: ReadonlyMap<string, PerSpacePlan>,
381
378
  ): number {
382
379
  let total = 0;
383
380
  for (const spaceId of applyOrder) {
@@ -392,30 +389,29 @@ interface BuildSuccessArgs {
392
389
  readonly aggregate: ContractSpaceAggregate;
393
390
  readonly orderedResolutions: ReadonlyArray<{
394
391
  readonly spaceId: string;
395
- readonly entry: AggregatePerSpacePlan;
392
+ readonly entry: PerSpacePlan;
396
393
  }>;
397
- readonly perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
394
+ readonly perSpace: ReadonlyArray<PerSpaceExecutionEntry>;
398
395
  readonly totalOpsExecuted: number;
399
396
  readonly summary: string;
400
397
  }
401
398
 
402
399
  function buildSuccess(args: BuildSuccessArgs): MigrationApplySuccess {
403
400
  // The marker hash surfaced at the top level is the **app member's**
404
- // post-apply marker (today's single-space `markerHash` field).
401
+ // post-apply marker (the top-level `markerHash` field).
405
402
  // Per-space markers live on `perSpace[].marker.storageHash`.
406
403
  const appResolution = args.orderedResolutions.find(
407
404
  (r) => r.spaceId === args.aggregate.app.spaceId,
408
405
  );
409
406
  const appMarkerHash =
410
- appResolution?.entry.plan.destination.storageHash ?? args.aggregate.app.headRef.hash;
407
+ appResolution?.entry.plan.destination.storageHash ?? requireHeadRef(args.aggregate.app).hash;
411
408
 
412
409
  // Per-migration entries (one per authored edge) preserve the
413
- // single-space `migrationsApplied` count semantics for back-compat
414
- // with existing JSON-shape consumers (e.g. `parsed.applied.length`
415
- // in integration tests). The aggregate per-space breakdown lives on
416
- // `perSpace[]`.
410
+ // `migrationsApplied` count semantics for back-compat with existing
411
+ // JSON-shape consumers (e.g. `parsed.applied.length` in integration
412
+ // tests). The aggregate per-space breakdown lives on `perSpace[]`.
417
413
  const applied = args.orderedResolutions.flatMap((r) => {
418
- const edges = r.entry.migrationEdges ?? [];
414
+ const edges = r.entry.migrationEdges;
419
415
  return edges.map((edge) => ({
420
416
  spaceId: r.spaceId,
421
417
  dirName: edge.dirName,
@@ -458,26 +454,46 @@ function buildSuccess(args: BuildSuccessArgs): MigrationApplySuccess {
458
454
  };
459
455
  }
460
456
 
461
- function buildNeverPlannedFailure(spaceId: string, targetHash: string): MigrationApplyFailure {
457
+ /**
458
+ * Build the `neverPlanned` failure raised when a contract space has no on-disk
459
+ * migration graph but migrate was asked to reach a target hash. The `why`
460
+ * states only the condition; the recovery sequence is composed by
461
+ * `errorPathUnreachable`'s `fix`.
462
+ *
463
+ * @internal Exported for testing only.
464
+ */
465
+ export function buildNeverPlannedFailure(
466
+ spaceId: string,
467
+ targetHash: string,
468
+ ): MigrationApplyFailure {
462
469
  return {
463
470
  code: 'MIGRATION_PATH_NOT_FOUND',
464
471
  summary: `No on-disk migrations for contract space "${spaceId}"`,
465
- why: `migrate is replay-only: every contract space must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}". Run \`prisma-next migration plan\` first to materialise the path.`,
472
+ why: `migrate is replay-only: every contract space must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}".`,
466
473
  meta: { spaceId, target: targetHash, kind: 'neverPlanned' },
467
474
  };
468
475
  }
469
476
 
470
- function buildPathNotFoundFailure(
477
+ /**
478
+ * Build the `pathUnreachable` failure raised when an emitted contract has no
479
+ * on-disk migration edge from the current marker to the target. The `why`
480
+ * states only the condition (no edge between the two named states, and migrate
481
+ * replays edges rather than inventing them); the recovery sequence — plan the
482
+ * edge, then re-apply — is composed by `errorPathUnreachable`'s `fix`, so the
483
+ * two read as one non-redundant plan-then-apply story.
484
+ *
485
+ * @internal Exported for testing only.
486
+ */
487
+ export function buildPathNotFoundFailure(
471
488
  spaceId: string,
472
489
  marker: ContractMarkerRecordLike | null,
473
490
  targetHash: string,
474
491
  ): MigrationApplyFailure {
475
492
  const fromHash = marker?.storageHash ?? '<empty>';
476
- // The single-space-degenerate phrasing names the user-visible
477
- // condition (a contract has been emitted that no on-disk
478
- // migration reaches) so the error reads naturally for the
479
- // single-space app case. Multi-space callers see the same
480
- // condition expressed against the offending space.
493
+ // The app-case phrasing names the user-visible condition (a
494
+ // contract has been emitted that no on-disk migration reaches) so
495
+ // the error reads naturally for the app member. Extension spaces
496
+ // see the same condition expressed against the offending space.
481
497
  const summary =
482
498
  spaceId === 'app'
483
499
  ? 'Current contract has no planned migration path'
@@ -485,7 +501,7 @@ function buildPathNotFoundFailure(
485
501
  return {
486
502
  code: 'MIGRATION_PATH_NOT_FOUND',
487
503
  summary,
488
- why: `Cannot reach target "${targetHash}" from current marker "${fromHash}" in space "${spaceId}". The on-disk migration graph for this space does not connect the two states. Run \`prisma-next migration plan\` to materialise the path.`,
504
+ why: `No migration edge connects the current state "${fromHash}" to the target "${targetHash}" in contract space "${spaceId}". The on-disk migration graph does not join the two, and migrate replays existing edges it never invents one.`,
489
505
  meta: { spaceId, fromHash, targetHash, kind: 'pathUnreachable' },
490
506
  };
491
507
  }
@@ -2,7 +2,11 @@ import type {
2
2
  ContractSourceDiagnostics,
3
3
  ContractSourceProvider,
4
4
  } from '@prisma-next/config/config-types';
5
- import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
5
+ import type {
6
+ Contract,
7
+ ContractMarkerRecord,
8
+ LedgerEntryRecord,
9
+ } from '@prisma-next/contract/types';
6
10
  import type {
7
11
  ControlAdapterDescriptor,
8
12
  ControlDriverDescriptor,
@@ -18,7 +22,6 @@ import type {
18
22
  VerifyDatabaseSchemaResult,
19
23
  } from '@prisma-next/framework-components/control';
20
24
  import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
21
- import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
22
25
  import type { Result } from '@prisma-next/utils/result';
23
26
  import type { ExecuteDbVerifyResult } from './operations/db-verify';
24
27
 
@@ -246,7 +249,7 @@ export interface DbUpdateOptions {
246
249
  * Options for the dbVerify operation.
247
250
  *
248
251
  * Drives the loader → aggregate-verifier pipeline. `strict` maps to
249
- * `verifyAggregate({ mode: 'strict' | 'lenient' })`; `skipSchema`
252
+ * `verifyMigration({ mode: 'strict' | 'lenient' })`; `skipSchema`
250
253
  * mirrors the `--marker-only` CLI flag and short-circuits the schema
251
254
  * portion of the verifier.
252
255
  */
@@ -327,7 +330,7 @@ export interface EmitOptions {
327
330
  * to the user instead of being collapsed into a single ambiguous
328
331
  * top-level hash.
329
332
  */
330
- export interface AggregatePerSpaceExecutionEntry {
333
+ export interface PerSpaceExecutionEntry {
331
334
  readonly spaceId: string;
332
335
  /** `'app'` for the application's contract space; `'extension'` for any extension space. */
333
336
  readonly kind: 'app' | 'extension';
@@ -388,9 +391,9 @@ export interface DbInitSuccess {
388
391
  * alphabetically, then app). Present whenever the aggregate flow
389
392
  * produced one — both `mode: 'plan'` and `mode: 'apply'`.
390
393
  *
391
- * See {@link AggregatePerSpaceExecutionEntry}.
394
+ * See {@link PerSpaceExecutionEntry}.
392
395
  */
393
- readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
396
+ readonly perSpace?: ReadonlyArray<PerSpaceExecutionEntry>;
394
397
  readonly summary: string;
395
398
  }
396
399
 
@@ -458,9 +461,9 @@ export interface DbUpdateSuccess {
458
461
  };
459
462
  /**
460
463
  * Per-space breakdown in canonical schedule order (extensions
461
- * alphabetically, then app). See {@link AggregatePerSpaceExecutionEntry}.
464
+ * alphabetically, then app). See {@link PerSpaceExecutionEntry}.
462
465
  */
463
- readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
466
+ readonly perSpace?: ReadonlyArray<PerSpaceExecutionEntry>;
464
467
  readonly summary: string;
465
468
  }
466
469
 
@@ -539,7 +542,7 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
539
542
  * contract-space aggregate, reading per-space marker rows from the
540
543
  * live database, plotting per-space paths via `graphWalkStrategy`
541
544
  * (replay-only — no synth, no introspection), and dispatching
542
- * through the shared `applyAggregate` primitive. The CLI command
545
+ * through the shared `applyMigration` primitive. The CLI command
543
546
  * just resolves the descriptor surface (config, refs, contract
544
547
  * envelope, app-space migration packages) and hands the inputs in.
545
548
  */
@@ -548,12 +551,6 @@ export interface MigrationApplyOptions {
548
551
  readonly contract: unknown;
549
552
  /** Migrations root directory (`migrations/` under the project). */
550
553
  readonly migrationsDir: string;
551
- /**
552
- * Already-loaded app-space migration packages. The CLI loads these
553
- * via `loadMigrationPackages(appMigrationsDir)` before invoking
554
- * `migrationApply`.
555
- */
556
- readonly appMigrationPackages: ReadonlyArray<OnDiskMigrationPackage>;
557
554
  /**
558
555
  * Optional app-space ref override. When provided, the app member's
559
556
  * graph-walk targets this hash instead of `contract.storage.storageHash`.
@@ -584,11 +581,11 @@ export interface MigrationApplyOptions {
584
581
 
585
582
  /**
586
583
  * A single on-disk migration package surfaced to the operation. The
587
- * SQL family already produces this shape via `loadMigrationPackages`;
588
- * the operation hands it through to the framework-neutral aggregate
589
- * loader's `appMigrationPackages` slot.
584
+ * SQL family surfaces this shape from the tolerant contract-space
585
+ * aggregate's app packages; the operation hands it through to the
586
+ * framework-neutral aggregate loader's `appMigrationPackages` slot.
590
587
  *
591
- * (Originally named `MigrationApplyStep` for the legacy single-space
588
+ * (Originally named `MigrationApplyStep` for the earlier app-only
592
589
  * apply path; the name is kept for compatibility with existing CLI
593
590
  * callers and tests.)
594
591
  */
@@ -612,8 +609,8 @@ export interface MigrationApplyStep {
612
609
  */
613
610
  /**
614
611
  * One entry per authored migration package applied. Preserves the
615
- * single-space `migrationsApplied` count semantics (each entry is
616
- * one migration directory) so `applied.length === migrationsApplied`.
612
+ * `migrationsApplied` count semantics (each entry is one migration
613
+ * directory) so `applied.length === migrationsApplied`.
617
614
  *
618
615
  * Per-space aggregate detail (markers, ops grouped by space) lives
619
616
  * on `perSpace[]`; this list is the per-edge view.
@@ -628,16 +625,15 @@ export interface MigrationApplyAppliedEntry {
628
625
  }
629
626
 
630
627
  /**
631
- * Successful migrationApply result. Carries both the legacy
632
- * single-space fields (`markerHash` is the **app member's** post-apply
633
- * marker, surfaced for back-compat with single-space callers) and the
628
+ * Successful migrationApply result. Carries both the top-level fields
629
+ * (`markerHash` is the **app member's** post-apply marker) and the
634
630
  * per-space breakdown (`perSpace` — markers / operations in canonical
635
631
  * schedule order).
636
632
  */
637
633
  /**
638
634
  * Path-decision summary for the **app member** post-apply. Surfaced
639
- * for back-compat with single-space callers (and the cli-journeys
640
- * suite, which inspects `requiredInvariants`/`satisfiedInvariants`/
635
+ * at the top level (and consumed by the cli-journeys suite, which
636
+ * inspects `requiredInvariants`/`satisfiedInvariants`/
641
637
  * `selectedPath` to validate invariant routing).
642
638
  *
643
639
  * Per-space path decisions for extension members are not surfaced —
@@ -667,10 +663,10 @@ export interface MigrationApplySuccess {
667
663
  readonly summary: string;
668
664
  /**
669
665
  * Per-space breakdown in canonical schedule order (extensions
670
- * alphabetically, then app). See {@link AggregatePerSpaceExecutionEntry}.
666
+ * alphabetically, then app). See {@link PerSpaceExecutionEntry}.
671
667
  * Always present for the aggregate-walking operation.
672
668
  */
673
- readonly perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
669
+ readonly perSpace: ReadonlyArray<PerSpaceExecutionEntry>;
674
670
  /**
675
671
  * Path-decision data for the app member. Present whenever the
676
672
  * graph-walk strategy ran for the app (i.e. always for the
@@ -719,6 +715,13 @@ export type MigrationApplyResult = Result<MigrationApplySuccess, MigrationApplyF
719
715
  export interface ContractEmitOptions {
720
716
  /** Path to the prisma-next.config.ts file */
721
717
  readonly configPath: string;
718
+ /**
719
+ * Directory to write contract artifacts into. When set, `contract.json` and
720
+ * `contract.d.ts` are written inside this directory, taking precedence over
721
+ * any output path from the loaded config. The value must be an absolute path;
722
+ * resolution against cwd is the caller's responsibility.
723
+ */
724
+ readonly outputPath?: string;
722
725
  /** Optional AbortSignal for cancelling the in-flight emit */
723
726
  readonly signal?: AbortSignal;
724
727
  /** Optional progress callback for observing source-resolution and emit spans */
@@ -877,6 +880,13 @@ export interface ControlClient {
877
880
  */
878
881
  readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
879
882
 
883
+ /**
884
+ * Reads the per-migration ledger journal for `space` in apply order.
885
+ * Returns an empty array when the ledger store does not yet exist or
886
+ * has no rows for that space.
887
+ */
888
+ readLedger(space?: string): Promise<readonly LedgerEntryRecord[]>;
889
+
880
890
  /**
881
891
  * Applies pre-planned on-disk migrations to the database.
882
892
  * Each migration runs in its own transaction with full execution checks.
@@ -461,8 +461,8 @@ function writeStructuredError(stream: MigrationCliWritable, err: CliStructuredEr
461
461
  * `null` when the file is missing and throwing `MIGRATION.INVALID_JSON`
462
462
  * when the file is present but cannot be parsed as JSON. The CLI feeds
463
463
  * this into `buildMigrationArtifacts` so the pure builder can preserve
464
- * fields owned by `migration plan` (contract bookends, hints, labels,
465
- * `createdAt`) across re-emits.
464
+ * fields owned by `migration plan` (contract bookends, `createdAt`) across
465
+ * re-emits.
466
466
  *
467
467
  * Author-time path: this loader still does not verify the manifest hash
468
468
  * or schema — that is the apply-time loader's job. Hash mismatch is the
@@ -472,8 +472,8 @@ function writeStructuredError(stream: MigrationCliWritable, err: CliStructuredEr
472
472
  * however, is now surfaced rather than swallowed: a malformed
473
473
  * `migration.json` indicates either a hand-edit gone wrong or partial
474
474
  * write, and silently rebuilding from `describe()` would discard the
475
- * user's on-disk content (preserved bookends, hints, labels,
476
- * `createdAt`) without any indication something was wrong on disk.
475
+ * user's on-disk content (preserved bookends, `createdAt`) without any
476
+ * indication something was wrong on disk.
477
477
  * Apply-time consumers always route through the verifying
478
478
  * `readMigrationPackage` in `@prisma-next/migration-tools/io` instead.
479
479
  */