@prisma-next/cli 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) 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-KgJorIvG.mjs} +72 -60
  5. package/dist/client-KgJorIvG.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 +13 -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 +85 -81
  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-D8uEbJuu.mjs} +4 -5
  73. package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-D8uEbJuu.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-v_vUKXTU.mjs} +5 -7
  77. package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-v_vUKXTU.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-C6ohV_oQ.mjs} +4 -5
  97. package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-C6ohV_oQ.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-CjvwO6at.mjs} +4 -5
  104. package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-CjvwO6at.mjs.map} +1 -1
  105. package/dist/migration-graph-D7DVUElV.mjs +1232 -0
  106. package/dist/migration-graph-D7DVUElV.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-Dt_SfqFm.d.mts} +28 -28
  124. package/dist/types-Dt_SfqFm.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 +75 -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 +127 -124
  146. package/src/commands/ref.ts +53 -8
  147. package/src/control-api/client.ts +0 -1
  148. package/src/control-api/contract-enrichment.ts +6 -42
  149. package/src/control-api/operations/{apply-aggregate.ts → apply.ts} +44 -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 +56 -47
  156. package/src/control-api/types.ts +26 -27
  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-layout.ts +1119 -0
  163. package/src/utils/formatters/migration-graph-rows.ts +336 -0
  164. package/src/utils/formatters/migration-graph-tree-render.ts +459 -0
  165. package/src/utils/formatters/migration-list-data-column.ts +115 -0
  166. package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
  167. package/src/utils/formatters/migration-list-render.ts +191 -0
  168. package/src/utils/formatters/migration-list-styler.ts +63 -0
  169. package/src/utils/formatters/migration-list-types.ts +21 -0
  170. package/src/utils/formatters/migrations.ts +37 -46
  171. package/src/utils/glyph-mode.ts +22 -0
  172. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  173. package/src/utils/plan-resolution.ts +258 -0
  174. package/src/utils/ref-advancement.ts +68 -0
  175. package/src/utils/terminal-ui.ts +42 -1
  176. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  177. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  178. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  179. package/dist/client-oXO2WCPD.mjs.map +0 -1
  180. package/dist/command-helpers-BSb0tRC8.mjs.map +0 -1
  181. package/dist/commands/migration-check.mjs.map +0 -1
  182. package/dist/commands/migration-graph.mjs.map +0 -1
  183. package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
  184. package/dist/contract-emit-r4y8Zhf1.mjs.map +0 -1
  185. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  186. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
  187. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
  188. package/dist/global-flags-CdE7M0d9.d.mts +0 -15
  189. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  190. package/dist/init-BCJZPWE1.mjs.map +0 -1
  191. package/dist/migration-plan-CFwqw3Gk.mjs.map +0 -1
  192. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  193. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  194. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  195. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  196. package/dist/types--CqjMdk0.d.mts.map +0 -1
@@ -3,19 +3,12 @@ import type {
3
3
  ControlDriverInstance,
4
4
  ControlFamilyInstance,
5
5
  MigrationOperationPolicy,
6
- MultiSpaceCapableRunner,
7
- MultiSpaceRunnerPerSpaceOptions,
8
6
  TargetMigrationsCapability,
9
7
  } from '@prisma-next/framework-components/control';
10
- import { hasMultiSpaceRunner } from '@prisma-next/framework-components/control';
11
- import type {
12
- AggregatePerSpacePlan,
13
- ContractSpaceAggregate,
14
- } from '@prisma-next/migration-tools/aggregate';
8
+ import type { ContractSpaceAggregate, PerSpacePlan } from '@prisma-next/migration-tools/aggregate';
15
9
  import { ifDefined } from '@prisma-next/utils/defined';
16
10
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
17
- import { errorRunnerFailed } from '../../utils/cli-errors';
18
- import type { AggregatePerSpaceExecutionEntry, OnControlProgress } from '../types';
11
+ import type { OnControlProgress, PerSpaceExecutionEntry } from '../types';
19
12
 
20
13
  /**
21
14
  * Span id emitted via `onProgress` for the apply phase. Stable
@@ -28,35 +21,35 @@ const APPLY_SPAN_ID = 'apply' as const;
28
21
  * events so the parent CLI command can attribute the span correctly,
29
22
  * and used to compose action-specific summary phrasing.
30
23
  */
31
- export type AggregateApplyAction = 'dbInit' | 'dbUpdate' | 'migrationApply';
24
+ export type ApplyAction = 'dbInit' | 'dbUpdate' | 'migrationApply';
32
25
 
33
26
  /**
34
- * Failure variant emitted by {@link applyAggregate} when the multi-space
35
- * runner itself rejects the apply. Mirrors the failure shape callers
27
+ * Failure variant emitted by {@link applyMigration} when the runner
28
+ * itself rejects the apply. Mirrors the failure shape callers
36
29
  * already wrap into their own action-specific failure envelopes
37
30
  * (`DbInitFailure`, `DbUpdateFailure`, `MigrationApplyFailure`) so each
38
31
  * caller keeps owning its own discriminated failure code.
39
32
  */
40
- export interface AggregateApplyRunnerFailure {
33
+ export interface ApplyRunnerFailure {
41
34
  readonly summary: string;
42
35
  readonly why?: string;
43
36
  readonly meta: Record<string, unknown>;
44
37
  }
45
38
 
46
- export interface ApplyAggregateInputs<TFamilyId extends string, TTargetId extends string> {
39
+ export interface ApplyMigrationInputs<TFamilyId extends string, TTargetId extends string> {
47
40
  readonly aggregate: ContractSpaceAggregate;
48
41
  /**
49
42
  * Per-space plans, keyed by `spaceId`. Produced by either the full
50
- * {@link planAggregate} pipeline (`db init` / `db update` — synth
43
+ * {@link planMigration} pipeline (`db init` / `db update` — synth
51
44
  * for the app, graph-walk for extensions) or by direct
52
45
  * {@link graphWalkStrategy} calls (`migrate` — graph-walk
53
46
  * for every member). Either way, the runner consumes the same shape.
54
47
  */
55
- readonly perSpacePlans: ReadonlyMap<string, AggregatePerSpacePlan>;
48
+ readonly perSpacePlans: ReadonlyMap<string, PerSpacePlan>;
56
49
  /**
57
50
  * Canonical schedule order — extensions alphabetically by `spaceId`,
58
51
  * then app. Mirrors {@link import('@prisma-next/migration-tools/concatenate-space-apply-inputs').concatenateSpaceApplyInputs}'s
59
- * convention so `MultiSpaceRunnerFailure.failingSpace` attribution
52
+ * convention so `MigrationRunnerFailure.failingSpace` attribution
60
53
  * stays byte-for-byte stable across callers.
61
54
  */
62
55
  readonly applyOrder: readonly string[];
@@ -69,22 +62,22 @@ export interface ApplyAggregateInputs<TFamilyId extends string, TTargetId extend
69
62
  >;
70
63
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
71
64
  readonly policy: MigrationOperationPolicy;
72
- readonly action: AggregateApplyAction;
65
+ readonly action: ApplyAction;
73
66
  readonly onProgress?: OnControlProgress;
74
67
  }
75
68
 
76
69
  /**
77
70
  * Resolved per-space plan in canonical schedule order. Surfaced from
78
- * {@link applyAggregate} to callers so each one can build its own
71
+ * {@link applyMigration} to callers so each one can build its own
79
72
  * action-specific success envelope (e.g. `DbInitSuccess` vs
80
73
  * `MigrationApplySuccess`) without re-deriving the ordering.
81
74
  */
82
75
  export interface OrderedResolution {
83
76
  readonly spaceId: string;
84
- readonly entry: AggregatePerSpacePlan;
77
+ readonly entry: PerSpacePlan;
85
78
  }
86
79
 
87
- export interface ApplyAggregateValue {
80
+ export interface ApplyMigrationValue {
88
81
  readonly orderedResolutions: readonly OrderedResolution[];
89
82
  readonly totalOpsPlanned: number;
90
83
  readonly totalOpsExecuted: number;
@@ -93,30 +86,30 @@ export interface ApplyAggregateValue {
93
86
  * envelopes. Each entry carries the post-apply marker (live storage hash
94
87
  * plus invariants) so callers can render it directly without re-reading.
95
88
  */
96
- readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
89
+ readonly perSpace: readonly PerSpaceExecutionEntry[];
97
90
  }
98
91
 
99
- export type ApplyAggregateResult = Result<ApplyAggregateValue, AggregateApplyRunnerFailure>;
92
+ export type ApplyMigrationResult = Result<ApplyMigrationValue, ApplyRunnerFailure>;
100
93
 
101
94
  /**
102
- * Runner-driving tail shared by every aggregate apply caller — `db init`,
95
+ * Runner-driving tail shared by every apply caller — `db init`,
103
96
  * `db update`, and `migrate`. Consumes already-resolved per-space
104
97
  * plans (the planner-vs-replay distinction is owned by the caller) and
105
- * dispatches them to the multi-space runner in canonical order.
98
+ * dispatches them to the runner in canonical order.
106
99
  *
107
100
  * Marker advancement is part of the runner's per-space transaction
108
101
  * (the SQL family runner writes the marker as the last step of each
109
102
  * space's transaction), so this primitive does not advance markers
110
- * separately — by the time `executeAcrossSpaces` returns ok, every
103
+ * separately — by the time `execute` returns ok, every
111
104
  * space's marker has been advanced to its plan's destination.
112
105
  *
113
106
  * Span emission (`spanStart 'apply'` / `spanEnd 'apply'`) is owned here
114
107
  * so callers don't have to duplicate it; the `action` field on each
115
108
  * progress event is taken from the caller's `action` argument.
116
109
  */
117
- export async function applyAggregate<TFamilyId extends string, TTargetId extends string>(
118
- inputs: ApplyAggregateInputs<TFamilyId, TTargetId>,
119
- ): Promise<ApplyAggregateResult> {
110
+ export async function applyMigration<TFamilyId extends string, TTargetId extends string>(
111
+ inputs: ApplyMigrationInputs<TFamilyId, TTargetId>,
112
+ ): Promise<ApplyMigrationResult> {
120
113
  const {
121
114
  aggregate,
122
115
  perSpacePlans,
@@ -133,14 +126,6 @@ export async function applyAggregate<TFamilyId extends string, TTargetId extends
133
126
  const orderedResolutions = collectOrdered(applyOrder, perSpacePlans);
134
127
 
135
128
  const runner = migrations.createRunner(familyInstance);
136
- if (!hasMultiSpaceRunner(runner)) {
137
- throw errorRunnerFailed(
138
- `Runner for target "${aggregate.targetId}" does not implement \`executeAcrossSpaces\``,
139
- {
140
- why: `${labelForAction(action)} requires multi-space-capable runners (today: every SQL family runner).`,
141
- },
142
- );
143
- }
144
129
 
145
130
  onProgress?.({
146
131
  action,
@@ -149,26 +134,21 @@ export async function applyAggregate<TFamilyId extends string, TTargetId extends
149
134
  label: progressLabelForAction(action),
150
135
  });
151
136
 
152
- const perSpaceOptions: MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>[] =
153
- orderedResolutions.map((r) => ({
154
- space: r.spaceId,
155
- plan: r.entry.plan,
156
- driver,
157
- destinationContract: r.entry.destinationContract,
158
- policy,
159
- frameworkComponents,
160
- // Per-space post-apply schema verification is non-strict: each
161
- // space's `destinationContract` describes only its own slice; a
162
- // strict verifier would treat every other space's tables as
163
- // `extras`. Tolerant mode still catches missing tables / columns.
164
- // SQL family runners read `strictVerification` via structural
165
- // typing.
166
- strictVerification: false,
167
- })) as MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>[];
137
+ const perSpaceOptions = orderedResolutions.map((r) => ({
138
+ space: r.spaceId,
139
+ plan: r.entry.plan,
140
+ driver,
141
+ destinationContract: r.entry.destinationContract,
142
+ policy,
143
+ frameworkComponents,
144
+ // Per-space post-apply schema verification is non-strict: each
145
+ // space's `destinationContract` describes only its own slice; a
146
+ // strict verifier would treat every other space's tables as
147
+ // `extras`. Tolerant mode still catches missing tables / columns.
148
+ strictVerification: false,
149
+ }));
168
150
 
169
- const runnerResult = await (
170
- runner as MultiSpaceCapableRunner<TFamilyId, TTargetId>
171
- ).executeAcrossSpaces({ driver, perSpaceOptions });
151
+ const runnerResult = await runner.execute({ driver, perSpaceOptions });
172
152
 
173
153
  if (!runnerResult.ok) {
174
154
  onProgress?.({ action, kind: 'spanEnd', spanId: APPLY_SPAN_ID, outcome: 'error' });
@@ -207,28 +187,28 @@ export async function applyAggregate<TFamilyId extends string, TTargetId extends
207
187
 
208
188
  /**
209
189
  * Project the planner's per-space resolutions into the
210
- * `AggregatePerSpaceExecutionEntry[]` shape the CLI surfaces.
190
+ * `PerSpaceExecutionEntry[]` shape the CLI surfaces.
211
191
  *
212
192
  * `includeMarkers` is `true` for apply-mode (each space's marker is
213
193
  * the `destination.storageHash` of its plan, which the runner
214
194
  * advances as the last step of each space's transaction) and `false`
215
195
  * for plan-mode (no marker has been written yet).
216
196
  *
217
- * Exported alongside {@link applyAggregate} so plan-mode callers can
197
+ * Exported alongside {@link applyMigration} so plan-mode callers can
218
198
  * assemble the same per-space block without going through the runner.
219
199
  */
220
200
  export function buildPerSpaceBreakdown(
221
201
  orderedResolutions: readonly OrderedResolution[],
222
202
  appSpaceId: string,
223
203
  options: { readonly includeMarkers: boolean },
224
- ): readonly AggregatePerSpaceExecutionEntry[] {
204
+ ): readonly PerSpaceExecutionEntry[] {
225
205
  return orderedResolutions.map((r) => {
226
206
  const operations = r.entry.displayOps.map((op) => ({
227
207
  id: op.id,
228
208
  label: op.label,
229
209
  operationClass: op.operationClass,
230
210
  }));
231
- const base: AggregatePerSpaceExecutionEntry = {
211
+ const base: PerSpaceExecutionEntry = {
232
212
  spaceId: r.spaceId,
233
213
  kind: r.spaceId === appSpaceId ? 'app' : 'extension',
234
214
  operations,
@@ -251,12 +231,12 @@ export function buildPerSpaceBreakdown(
251
231
  */
252
232
  export function collectOrdered(
253
233
  applyOrder: readonly string[],
254
- perSpace: ReadonlyMap<string, AggregatePerSpacePlan>,
234
+ perSpace: ReadonlyMap<string, PerSpacePlan>,
255
235
  ): readonly OrderedResolution[] {
256
236
  return applyOrder.map((spaceId) => {
257
237
  const entry = perSpace.get(spaceId);
258
238
  if (!entry) {
259
- throw new Error(`Aggregate planner output missing per-space plan for "${spaceId}"`);
239
+ throw new Error(`planner output missing per-space plan for "${spaceId}"`);
260
240
  }
261
241
  return { spaceId, entry };
262
242
  });
@@ -264,11 +244,11 @@ export function collectOrdered(
264
244
 
265
245
  /**
266
246
  * Action-appropriate label for the `spanStart` event the apply
267
- * primitive emits. `applyAggregate` is shared by `db init`, `db update`,
247
+ * primitive emits. `applyMigration` is shared by `db init`, `db update`,
268
248
  * and `migrate`; the span label tracks the user-visible action
269
249
  * so structured-progress output reads naturally for each surface.
270
250
  */
271
- export function progressLabelForAction(action: AggregateApplyAction): string {
251
+ export function progressLabelForAction(action: ApplyAction): string {
272
252
  switch (action) {
273
253
  case 'dbInit':
274
254
  return 'Initialising database across spaces';
@@ -278,14 +258,3 @@ export function progressLabelForAction(action: AggregateApplyAction): string {
278
258
  return 'Applying migration plan across spaces';
279
259
  }
280
260
  }
281
-
282
- function labelForAction(action: AggregateApplyAction): string {
283
- switch (action) {
284
- case 'dbInit':
285
- return 'db init';
286
- case 'dbUpdate':
287
- return 'db update';
288
- case 'migrationApply':
289
- return 'migrate';
290
- }
291
- }
@@ -5,7 +5,7 @@ import { createControlStack } from '@prisma-next/framework-components/control';
5
5
  import { abortable } from '@prisma-next/utils/abortable';
6
6
  import { ifDefined } from '@prisma-next/utils/defined';
7
7
  import type { JsonObject } from '@prisma-next/utils/json';
8
- import { dirname } from 'pathe';
8
+ import { dirname, join } from 'pathe';
9
9
  import { loadConfig } from '../../config-loader';
10
10
  import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
11
11
  import { queueEmitByOutput } from '../../utils/emit-queue';
@@ -153,7 +153,7 @@ function validateProviderResult(providerResult: unknown): ValidatedProviderResul
153
153
  export async function executeContractEmit(
154
154
  options: ContractEmitOptions,
155
155
  ): Promise<ContractEmitResult> {
156
- const { configPath, signal = new AbortController().signal, onProgress } = options;
156
+ const { configPath, outputPath, signal = new AbortController().signal, onProgress } = options;
157
157
  const unlessAborted = abortable(signal);
158
158
 
159
159
  const config = await unlessAborted(loadConfig(configPath));
@@ -166,7 +166,10 @@ export async function executeContractEmit(
166
166
 
167
167
  const contractConfig = config.contract;
168
168
 
169
- if (!contractConfig.output) {
169
+ const effectiveOutput =
170
+ outputPath !== undefined ? join(outputPath, 'contract.json') : contractConfig.output;
171
+
172
+ if (!effectiveOutput) {
170
173
  throw errorContractConfigMissing({
171
174
  why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
172
175
  });
@@ -180,7 +183,7 @@ export async function executeContractEmit(
180
183
 
181
184
  let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;
182
185
  try {
183
- outputPaths = getEmittedArtifactPaths(contractConfig.output);
186
+ outputPaths = getEmittedArtifactPaths(effectiveOutput);
184
187
  } catch (error) {
185
188
  throw errorContractConfigMissing({
186
189
  why: error instanceof Error ? error.message : String(error),
@@ -250,13 +253,18 @@ export async function executeContractEmit(
250
253
  // on-disk JSON envelope is constructed by target-owned code
251
254
  // rather than by walking the in-memory contract with
252
255
  // `Object.entries` (which would leak runtime-only class API
253
- // fields into the persisted shape).
256
+ // fields into the persisted shape). The optional `shouldPreserveEmpty`
257
+ // and `sortStorage` hooks let the family contribute storage-specific
258
+ // canonicalization rules without the framework importing family code.
259
+ const { contractSerializer } = config.target;
254
260
  const serializeContract = (c: Contract): JsonObject =>
255
- config.target.contractSerializer.serializeContract(c);
261
+ contractSerializer.serializeContract(c);
256
262
  emitResult = await unlessAborted(
257
263
  emit(enrichedIR, stack, config.family.emission, {
258
264
  outputJsonPath,
259
265
  serializeContract,
266
+ ...ifDefined('shouldPreserveEmpty', contractSerializer.shouldPreserveEmpty),
267
+ ...ifDefined('sortStorage', contractSerializer.sortStorage),
260
268
  }),
261
269
  );
262
270
  } catch (error) {
@@ -11,9 +11,9 @@ import type {
11
11
  } from '@prisma-next/framework-components/control';
12
12
  import { hasOperationPreview } from '@prisma-next/framework-components/control';
13
13
  import {
14
- type AggregatePlannerError,
15
14
  type ContractSpaceAggregate,
16
- planAggregate,
15
+ type PlannerError,
16
+ planMigration,
17
17
  } from '@prisma-next/migration-tools/aggregate';
18
18
  import { ifDefined } from '@prisma-next/utils/defined';
19
19
  import { notOk, ok } from '@prisma-next/utils/result';
@@ -23,7 +23,6 @@ import {
23
23
  buildContractSpaceAggregate,
24
24
  } from '../../utils/contract-space-aggregate-loader';
25
25
  import type {
26
- AggregatePerSpaceExecutionEntry,
27
26
  DbInitFailure,
28
27
  DbInitResult,
29
28
  DbInitSuccess,
@@ -31,15 +30,16 @@ import type {
31
30
  DbUpdateResult,
32
31
  DbUpdateSuccess,
33
32
  OnControlProgress,
33
+ PerSpaceExecutionEntry,
34
34
  } from '../types';
35
- import { applyAggregate, buildPerSpaceBreakdown, collectOrdered } from './apply-aggregate';
35
+ import { applyMigration, buildPerSpaceBreakdown, collectOrdered } from './apply';
36
36
  import { stripOperations } from './migration-helpers';
37
37
 
38
38
  /**
39
- * Span IDs emitted via `onProgress` during the aggregate apply flow.
39
+ * Span IDs emitted via `onProgress` during the apply flow.
40
40
  * Stable identifiers consumed by the structured-output renderer and by
41
41
  * tests asserting on span ids. The `apply` span itself is owned by
42
- * the {@link applyAggregate} primitive — only the introspect / plan
42
+ * the {@link applyMigration} primitive — only the introspect / plan
43
43
  * spans are emitted directly here.
44
44
  */
45
45
  const SPAN_IDS = {
@@ -48,13 +48,13 @@ const SPAN_IDS = {
48
48
  } as const;
49
49
 
50
50
  /**
51
- * Inputs shared by `db init` and `db update` aggregate apply flows.
51
+ * Inputs shared by `db init` and `db update` apply flows.
52
52
  *
53
53
  * Accepts the already-validated app contract + descriptor list — the
54
54
  * loader gathers the rest from disk + descriptors. The CLI is the
55
55
  * descriptor-import boundary; everything downstream is descriptor-free.
56
56
  */
57
- export interface ExecuteAggregateApplyOptions<TFamilyId extends string, TTargetId extends string> {
57
+ export interface ExecuteApplyOptions<TFamilyId extends string, TTargetId extends string> {
58
58
  readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
59
59
  readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
60
60
  readonly contract: Contract;
@@ -83,17 +83,17 @@ export interface ExecuteAggregateApplyOptions<TFamilyId extends string, TTargetI
83
83
  * integrity violation short-circuits with a structured error.
84
84
  * 2. **Read DB state**: marker rows (`familyInstance.readAllMarkers`)
85
85
  * + introspected schema (`familyInstance.introspect`).
86
- * 3. **Plan**: {@link planAggregate} chooses graph-walk vs synth per
86
+ * 3. **Plan**: {@link planMigration} chooses graph-walk vs synth per
87
87
  * member according to `callerPolicy.ignoreGraphFor`. The app member
88
88
  * is forced through synth (today's daily-driver behaviour); every
89
89
  * extension member walks its on-disk graph.
90
90
  * 4. **Apply** (when `mode === 'apply'`): every per-space `MigrationPlan`
91
- * feeds into the runner's `executeAcrossSpaces` — one outer
91
+ * feeds into the runner's `execute` — one outer
92
92
  * transaction across every space; failure on any space rolls back
93
93
  * every space's writes.
94
94
  */
95
- export async function executeAggregateApply<TFamilyId extends string, TTargetId extends string>(
96
- options: ExecuteAggregateApplyOptions<TFamilyId, TTargetId>,
95
+ export async function executeApply<TFamilyId extends string, TTargetId extends string>(
96
+ options: ExecuteApplyOptions<TFamilyId, TTargetId>,
97
97
  ): Promise<DbInitResult | DbUpdateResult> {
98
98
  const {
99
99
  driver,
@@ -159,7 +159,7 @@ export async function executeAggregateApply<TFamilyId extends string, TTargetId
159
159
  spanId: SPAN_IDS.plan,
160
160
  label: 'Planning migration',
161
161
  });
162
- const planResult = await planAggregate<TFamilyId, TTargetId>({
162
+ const planResult = await planMigration<TFamilyId, TTargetId>({
163
163
  aggregate,
164
164
  currentDBState: { markersBySpaceId: markerRows, schemaIntrospection: schemaIR },
165
165
  familyInstance,
@@ -205,13 +205,13 @@ export async function executeAggregateApply<TFamilyId extends string, TTargetId
205
205
  });
206
206
  }
207
207
 
208
- // 5. Apply mode: hand off to the shared `applyAggregate` primitive.
208
+ // 5. Apply mode: hand off to the shared `applyMigration` primitive.
209
209
  // The runner-driving tail is identical for `db init` / `db update` /
210
210
  // `migrate` — only how each caller produces `perSpacePlans`
211
- // differs (synth + graph-walk via planAggregate here; graph-walk
211
+ // differs (synth + graph-walk via planMigration here; graph-walk
212
212
  // only for migrate). Each caller produces perSpacePlans differently;
213
213
  // this helper handles the shared apply tail.
214
- const applied = await applyAggregate({
214
+ const applied = await applyMigration({
215
215
  aggregate,
216
216
  perSpacePlans: planResult.value.perSpace,
217
217
  applyOrder: planResult.value.applyOrder,
@@ -298,7 +298,7 @@ function detectOrphanMarkers(
298
298
  });
299
299
  }
300
300
 
301
- function mapPlannerError(error: AggregatePlannerError): DbInitResult | DbUpdateResult {
301
+ function mapPlannerError(error: PlannerError): DbInitResult | DbUpdateResult {
302
302
  if (error.kind === 'appSynthFailure') {
303
303
  const failure: DbInitFailure | DbUpdateFailure = {
304
304
  code: 'PLANNING_FAILED',
@@ -337,7 +337,7 @@ function wrapPlanResult(args: {
337
337
  readonly operations: readonly MigrationPlanOperation[];
338
338
  readonly destination: { readonly storageHash: string; readonly profileHash?: string };
339
339
  readonly preview: OperationPreview | undefined;
340
- readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
340
+ readonly perSpace: readonly PerSpaceExecutionEntry[];
341
341
  readonly summary: string;
342
342
  }): DbInitResult | DbUpdateResult {
343
343
  const success: DbInitSuccess | DbUpdateSuccess = {
@@ -361,7 +361,7 @@ function wrapApplyResult(args: {
361
361
  readonly destination: { readonly storageHash: string; readonly profileHash?: string };
362
362
  readonly operationsPlanned: number;
363
363
  readonly operationsExecuted: number;
364
- readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
364
+ readonly perSpace: readonly PerSpaceExecutionEntry[];
365
365
  readonly summary: string;
366
366
  }): DbInitResult | DbUpdateResult {
367
367
  const success: DbInitSuccess | DbUpdateSuccess = {
@@ -8,20 +8,20 @@ import type {
8
8
  } from '@prisma-next/framework-components/control';
9
9
  import { ifDefined } from '@prisma-next/utils/defined';
10
10
  import type { DbInitResult, OnControlProgress } from '../types';
11
- import { executeAggregateApply } from './db-apply-aggregate';
11
+ import { executeApply } from './db-apply';
12
12
 
13
13
  /**
14
14
  * Options for executing the `db init` operation.
15
15
  *
16
16
  * `db init` runs the loader → planner → runner pipeline:
17
17
  *
18
- * 1. {@link executeAggregateApply} loads a `ContractSpaceAggregate` via
18
+ * 1. {@link executeApply} loads a `ContractSpaceAggregate` via
19
19
  * {@link import('@prisma-next/migration-tools/aggregate').loadContractSpaceAggregate}
20
20
  * from the supplied descriptor set + on-disk on-disk artefacts.
21
21
  * 2. The aggregate planner runs with `callerPolicy.ignoreGraphFor`
22
22
  * locked to the app member — synth strategy for the app, graph-walk
23
23
  * for every extension.
24
- * 3. The runner's `executeAcrossSpaces` applies the per-space plans
24
+ * 3. The runner's `execute` applies the per-space plans
25
25
  * inside one outer transaction.
26
26
  *
27
27
  * `extensionPacks` mirrors `Config.extensionPacks` (descriptor list).
@@ -68,7 +68,7 @@ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extend
68
68
  export async function executeDbInit<TFamilyId extends string, TTargetId extends string>(
69
69
  options: ExecuteDbInitOptions<TFamilyId, TTargetId>,
70
70
  ): Promise<DbInitResult> {
71
- const result = await executeAggregateApply<TFamilyId, TTargetId>({
71
+ const result = await executeApply<TFamilyId, TTargetId>({
72
72
  driver: options.driver,
73
73
  familyInstance: options.familyInstance,
74
74
  contract: options.contract,
@@ -9,7 +9,7 @@ import type {
9
9
  import { ifDefined } from '@prisma-next/utils/defined';
10
10
  import { notOk } from '@prisma-next/utils/result';
11
11
  import type { DbUpdateResult, OnControlProgress } from '../types';
12
- import { executeAggregateApply } from './db-apply-aggregate';
12
+ import { executeApply } from './db-apply';
13
13
 
14
14
  const DB_UPDATE_POLICY = {
15
15
  allowedOperationClasses: ['additive', 'widening', 'destructive'] as const,
@@ -71,7 +71,7 @@ export async function executeDbUpdate<TFamilyId extends string, TTargetId extend
71
71
  const gate = await guardDestructiveChanges<TFamilyId, TTargetId>(sharedInputs);
72
72
  if (gate !== null) return gate;
73
73
  }
74
- return (await executeAggregateApply<TFamilyId, TTargetId>({
74
+ return (await executeApply<TFamilyId, TTargetId>({
75
75
  ...sharedInputs,
76
76
  mode: options.mode,
77
77
  })) as DbUpdateResult;
@@ -85,9 +85,9 @@ export async function executeDbUpdate<TFamilyId extends string, TTargetId extend
85
85
  * run.
86
86
  */
87
87
  async function guardDestructiveChanges<TFamilyId extends string, TTargetId extends string>(
88
- sharedInputs: Omit<Parameters<typeof executeAggregateApply<TFamilyId, TTargetId>>[0], 'mode'>,
88
+ sharedInputs: Omit<Parameters<typeof executeApply<TFamilyId, TTargetId>>[0], 'mode'>,
89
89
  ): Promise<DbUpdateResult | null> {
90
- const planResult = (await executeAggregateApply<TFamilyId, TTargetId>({
90
+ const planResult = (await executeApply<TFamilyId, TTargetId>({
91
91
  ...sharedInputs,
92
92
  mode: 'plan',
93
93
  })) as DbUpdateResult;
@@ -7,10 +7,12 @@ import type {
7
7
  VerifyDatabaseSchemaResult,
8
8
  } from '@prisma-next/framework-components/control';
9
9
  import {
10
- type AggregateVerifierOutput,
11
10
  type ContractSpaceMember,
12
- verifyAggregate,
11
+ requireHeadRef,
12
+ type VerifierOutput,
13
+ verifyMigration,
13
14
  } from '@prisma-next/migration-tools/aggregate';
15
+ import { castAs } from '@prisma-next/utils/casts';
14
16
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
15
17
  import { CliStructuredError } from '../../utils/cli-errors';
16
18
  import {
@@ -82,7 +84,7 @@ export type ExecuteDbVerifyResult = Result<ExecuteDbVerifySuccess, CliStructured
82
84
  * structured CLI error.
83
85
  * 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
84
86
  * schema introspection.
85
- * 3. **Verify**: {@link verifyAggregate} returns per-space
87
+ * 3. **Verify**: {@link verifyMigration} returns per-space
86
88
  * `markerCheck` + per-space pre-projected `schemaCheck` (closes F23).
87
89
  * Marker mismatches map to `CliStructuredError` (code `5002`) so
88
90
  * callers (CLI command) can render and exit. Schema results are
@@ -102,7 +104,7 @@ export async function executeDbVerify<TFamilyId extends string, TTargetId extend
102
104
  : await runIntrospection({ driver, familyInstance, onProgress });
103
105
 
104
106
  emitVerifySpan(onProgress, 'spanStart');
105
- const verifyResult = verifyAggregate({
107
+ const verifyResult = verifyMigration({
106
108
  aggregate,
107
109
  markersBySpaceId,
108
110
  schemaIntrospection,
@@ -162,7 +164,7 @@ async function runIntrospection<TFamilyId extends string, TTargetId extends stri
162
164
  * `ok` result so the verifier still runs the (cheap) schemaCheck loop
163
165
  * without invoking the family's verification path.
164
166
  */
165
- function createPerMemberVerifier<TFamilyId extends string, TTargetId extends string>(
167
+ export function createPerMemberVerifier<TFamilyId extends string, TTargetId extends string>(
166
168
  options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
167
169
  ): (
168
170
  projectedSchema: unknown,
@@ -173,7 +175,7 @@ function createPerMemberVerifier<TFamilyId extends string, TTargetId extends str
173
175
  return (projectedSchema, member, verifyMode) => {
174
176
  if (skipSchema) return buildSkippedSchemaResult(member);
175
177
  return familyInstance.verifySchema({
176
- contract: member.contract,
178
+ contract: member.contract(),
177
179
  // The family's `TSchemaIR` is opaque to migration-tools; the
178
180
  // aggregate verifier passes through whatever we hand it. The
179
181
  // family expects its own IR shape on the way back.
@@ -206,12 +208,12 @@ function emitVerifySpan(
206
208
  }
207
209
 
208
210
  /**
209
- * Map an {@link AggregateVerifierOutput} to the operation's
211
+ * Map an {@link VerifierOutput} to the operation's
210
212
  * {@link ExecuteDbVerifyResult}, applying the `skipMarker` policy used
211
213
  * by the CLI's `--schema-only` mode.
212
214
  */
213
215
  function finaliseVerifyResult(args: {
214
- verifyResult: AggregateVerifierOutput<VerifyDatabaseSchemaResult>;
216
+ verifyResult: VerifierOutput<VerifyDatabaseSchemaResult>;
215
217
  aggregate: {
216
218
  readonly app: { readonly spaceId: string };
217
219
  readonly extensions: ReadonlyArray<{ readonly spaceId: string }>;
@@ -247,15 +249,17 @@ function finaliseVerifyResult(args: {
247
249
  }
248
250
 
249
251
  function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSchemaResult {
250
- const profileHash = (member.contract as { profileHash?: string }).profileHash;
252
+ const contract = member.contract();
253
+ const headRef = requireHeadRef(member);
254
+ const profileHash = castAs<{ profileHash?: string }>(contract).profileHash;
251
255
  return {
252
256
  ok: true,
253
257
  summary: 'Schema verification skipped',
254
258
  contract: {
255
- storageHash: member.headRef.hash,
259
+ storageHash: headRef.hash,
256
260
  ...(profileHash ? { profileHash } : {}),
257
261
  },
258
- target: { expected: member.contract.target },
262
+ target: { expected: contract.target },
259
263
  schema: {
260
264
  issues: [],
261
265
  root: {