@prisma-next/cli 0.14.0 → 0.15.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 (157) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.mjs +33 -14
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-CJzuo5wX.mjs → client-DwOyZz0a.mjs} +141 -123
  5. package/dist/client-DwOyZz0a.mjs.map +1 -0
  6. package/dist/{command-helpers-DGMvGBeX.mjs → command-helpers-CNwURRf9.mjs} +30 -11
  7. package/dist/command-helpers-CNwURRf9.mjs.map +1 -0
  8. package/dist/commands/contract-emit.mjs +1 -1
  9. package/dist/commands/contract-infer.mjs +1 -1
  10. package/dist/commands/db-init.mjs +3 -3
  11. package/dist/commands/db-schema.mjs +3 -3
  12. package/dist/commands/db-sign.mjs +5 -5
  13. package/dist/commands/db-sign.mjs.map +1 -1
  14. package/dist/commands/db-update.mjs +4 -4
  15. package/dist/commands/db-verify.d.mts.map +1 -1
  16. package/dist/commands/db-verify.mjs +1 -1
  17. package/dist/commands/migrate.d.mts +1 -1
  18. package/dist/commands/migrate.d.mts.map +1 -1
  19. package/dist/commands/migrate.mjs +37 -38
  20. package/dist/commands/migrate.mjs.map +1 -1
  21. package/dist/commands/migration-check.d.mts +1 -1
  22. package/dist/commands/migration-check.mjs +1 -1
  23. package/dist/commands/migration-graph.d.mts +1 -1
  24. package/dist/commands/migration-graph.mjs +9 -9
  25. package/dist/commands/migration-graph.mjs.map +1 -1
  26. package/dist/commands/migration-list.d.mts +6 -6
  27. package/dist/commands/migration-list.d.mts.map +1 -1
  28. package/dist/commands/migration-list.mjs +1 -1
  29. package/dist/commands/migration-log.d.mts +1 -1
  30. package/dist/commands/migration-log.mjs +1 -1
  31. package/dist/commands/migration-new.mjs +4 -4
  32. package/dist/commands/migration-new.mjs.map +1 -1
  33. package/dist/commands/migration-plan.d.mts.map +1 -1
  34. package/dist/commands/migration-plan.mjs +1 -1
  35. package/dist/commands/migration-show.mjs +4 -4
  36. package/dist/commands/migration-show.mjs.map +1 -1
  37. package/dist/commands/migration-status.d.mts +1 -1
  38. package/dist/commands/migration-status.mjs +1 -1
  39. package/dist/commands/ref.d.mts +1 -1
  40. package/dist/commands/ref.mjs +3 -3
  41. package/dist/commands/ref.mjs.map +1 -1
  42. package/dist/commands/telemetry/index.mjs +1 -1
  43. package/dist/{contract-at-errors-CFXsstzm.mjs → contract-at-errors-BHbY04pl.mjs} +2 -2
  44. package/dist/{contract-at-errors-CFXsstzm.mjs.map → contract-at-errors-BHbY04pl.mjs.map} +1 -1
  45. package/dist/{contract-emit-C8HmtboH.mjs → contract-emit-BeidkyUC.mjs} +9 -11
  46. package/dist/contract-emit-BeidkyUC.mjs.map +1 -0
  47. package/dist/{contract-emit-B_qriF8B.mjs → contract-emit-IQBYv351.mjs} +6 -6
  48. package/dist/contract-emit-IQBYv351.mjs.map +1 -0
  49. package/dist/{contract-infer-BYT_ra_U.mjs → contract-infer-B3Wq2cu1.mjs} +3 -3
  50. package/dist/{contract-infer-BYT_ra_U.mjs.map → contract-infer-B3Wq2cu1.mjs.map} +1 -1
  51. package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs → contract-space-aggregate-loader-BirzMmW1.mjs} +63 -5
  52. package/dist/contract-space-aggregate-loader-BirzMmW1.mjs.map +1 -0
  53. package/dist/{db-verify-C24FKhb7.mjs → db-verify-DpRrecs7.mjs} +57 -61
  54. package/dist/db-verify-DpRrecs7.mjs.map +1 -0
  55. package/dist/exports/control-api.d.mts +3 -3
  56. package/dist/exports/control-api.d.mts.map +1 -1
  57. package/dist/exports/control-api.mjs +2 -2
  58. package/dist/exports/index.d.mts +5 -1
  59. package/dist/exports/index.d.mts.map +1 -1
  60. package/dist/exports/index.mjs +4 -3
  61. package/dist/exports/index.mjs.map +1 -1
  62. package/dist/format-CaCF63ld.mjs +102 -0
  63. package/dist/format-CaCF63ld.mjs.map +1 -0
  64. package/dist/{framework-components-YVQHhPH7.mjs → framework-components-CnBcJDmF.mjs} +2 -2
  65. package/dist/{framework-components-YVQHhPH7.mjs.map → framework-components-CnBcJDmF.mjs.map} +1 -1
  66. package/dist/{init-0HwB-Vh8.mjs → init-Boa0LzeJ.mjs} +27 -19
  67. package/dist/init-Boa0LzeJ.mjs.map +1 -0
  68. package/dist/{inspect-live-schema-DF6IwcDl.mjs → inspect-live-schema-CldSQjVI.mjs} +4 -4
  69. package/dist/inspect-live-schema-CldSQjVI.mjs.map +1 -0
  70. package/dist/{migration-check-VwM8xCZV.mjs → migration-check-CGtYnR9k.mjs} +10 -11
  71. package/dist/migration-check-CGtYnR9k.mjs.map +1 -0
  72. package/dist/migration-cli.mjs +1 -1
  73. package/dist/migration-cli.mjs.map +1 -1
  74. package/dist/{migration-command-scaffold-DA-Lhx6o.mjs → migration-command-scaffold-B3m-MMAx.mjs} +4 -4
  75. package/dist/migration-command-scaffold-B3m-MMAx.mjs.map +1 -0
  76. package/dist/{migration-graph-command-render-CEez7YUK.mjs → migration-graph-command-render-B83xrnNy.mjs} +24 -24
  77. package/dist/{migration-graph-command-render-CEez7YUK.mjs.map → migration-graph-command-render-B83xrnNy.mjs.map} +1 -1
  78. package/dist/{migration-list-DlJJ_38Z.mjs → migration-list-DBU6Pneg.mjs} +16 -16
  79. package/dist/migration-list-DBU6Pneg.mjs.map +1 -0
  80. package/dist/{migration-log-CG0qQAFm.mjs → migration-log-BYngyvXa.mjs} +5 -5
  81. package/dist/migration-log-BYngyvXa.mjs.map +1 -0
  82. package/dist/{migration-path-target-Ce6OZImp.mjs → migration-path-target-DQa4Caoc.mjs} +2 -2
  83. package/dist/{migration-path-target-Ce6OZImp.mjs.map → migration-path-target-DQa4Caoc.mjs.map} +1 -1
  84. package/dist/{migration-plan-W_E8FQOk.mjs → migration-plan-D6bHpCzE.mjs} +30 -30
  85. package/dist/migration-plan-D6bHpCzE.mjs.map +1 -0
  86. package/dist/{migration-status-CD-LC2Ip.mjs → migration-status-CpQ5UhcU.mjs} +14 -14
  87. package/dist/migration-status-CpQ5UhcU.mjs.map +1 -0
  88. package/dist/{telemetry-BIM4beEO.mjs → telemetry-BQJ_XHa9.mjs} +2 -2
  89. package/dist/{telemetry-BIM4beEO.mjs.map → telemetry-BQJ_XHa9.mjs.map} +1 -1
  90. package/dist/{types-C_tYiJYx.d.mts → types-DFoqNIcQ.d.mts} +27 -21
  91. package/dist/types-DFoqNIcQ.d.mts.map +1 -0
  92. package/dist/{verify-DcOYZ1tH.mjs → verify-CInGyt4U.mjs} +58 -159
  93. package/dist/verify-CInGyt4U.mjs.map +1 -0
  94. package/package.json +28 -30
  95. package/src/cli.ts +7 -0
  96. package/src/commands/contract-emit.ts +1 -1
  97. package/src/commands/db-sign.ts +1 -1
  98. package/src/commands/db-verify.ts +34 -20
  99. package/src/commands/format.ts +75 -0
  100. package/src/commands/init/detect-package-manager.ts +8 -4
  101. package/src/commands/init/hygiene-package-scripts.ts +16 -3
  102. package/src/commands/inspect-live-schema.ts +1 -1
  103. package/src/commands/lsp.ts +32 -0
  104. package/src/commands/migrate.ts +47 -44
  105. package/src/commands/migration-check.ts +6 -6
  106. package/src/commands/migration-graph.ts +5 -5
  107. package/src/commands/migration-list.ts +13 -13
  108. package/src/commands/migration-log.ts +1 -1
  109. package/src/commands/migration-new.ts +1 -1
  110. package/src/commands/migration-plan.ts +15 -4
  111. package/src/commands/migration-show.ts +1 -1
  112. package/src/commands/migration-status.ts +10 -10
  113. package/src/commands/ref.ts +1 -1
  114. package/src/control-api/client.ts +8 -23
  115. package/src/control-api/operations/contract-emit.ts +9 -27
  116. package/src/control-api/operations/db-init.ts +2 -2
  117. package/src/control-api/operations/db-run.ts +18 -17
  118. package/src/control-api/operations/db-verify.ts +52 -44
  119. package/src/control-api/operations/format.ts +98 -0
  120. package/src/control-api/operations/migrate.ts +139 -95
  121. package/src/control-api/operations/run-migration.ts +6 -5
  122. package/src/control-api/types.ts +8 -8
  123. package/src/exports/index.ts +1 -0
  124. package/src/migration-cli.ts +1 -1
  125. package/src/utils/cli-errors.ts +14 -1
  126. package/src/utils/combine-verify-results.ts +94 -0
  127. package/src/utils/extension-pack-inputs.ts +1 -1
  128. package/src/utils/formatters/errors.ts +34 -5
  129. package/src/utils/formatters/verify.ts +89 -227
  130. package/src/utils/migration-command-scaffold.ts +1 -1
  131. package/src/utils/plan-resolution.ts +18 -18
  132. package/dist/client-CJzuo5wX.mjs.map +0 -1
  133. package/dist/command-helpers-DGMvGBeX.mjs.map +0 -1
  134. package/dist/config-loader-p9JMrekQ.mjs +0 -88
  135. package/dist/config-loader-p9JMrekQ.mjs.map +0 -1
  136. package/dist/config-loader.d.mts +0 -16
  137. package/dist/config-loader.d.mts.map +0 -1
  138. package/dist/config-loader.mjs +0 -2
  139. package/dist/contract-emit-B_qriF8B.mjs.map +0 -1
  140. package/dist/contract-emit-C8HmtboH.mjs.map +0 -1
  141. package/dist/contract-space-aggregate-loader-ClI1KN6d.mjs.map +0 -1
  142. package/dist/db-verify-C24FKhb7.mjs.map +0 -1
  143. package/dist/extension-pack-inputs-1ySHqxKG.mjs +0 -62
  144. package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +0 -1
  145. package/dist/init-0HwB-Vh8.mjs.map +0 -1
  146. package/dist/inspect-live-schema-DF6IwcDl.mjs.map +0 -1
  147. package/dist/migration-check-VwM8xCZV.mjs.map +0 -1
  148. package/dist/migration-command-scaffold-DA-Lhx6o.mjs.map +0 -1
  149. package/dist/migration-list-DlJJ_38Z.mjs.map +0 -1
  150. package/dist/migration-log-CG0qQAFm.mjs.map +0 -1
  151. package/dist/migration-plan-W_E8FQOk.mjs.map +0 -1
  152. package/dist/migration-status-CD-LC2Ip.mjs.map +0 -1
  153. package/dist/types-C_tYiJYx.d.mts.map +0 -1
  154. package/dist/verify-DcOYZ1tH.mjs.map +0 -1
  155. package/src/config-loader.ts +0 -92
  156. package/src/config-path-validation.ts +0 -74
  157. package/src/utils/combine-schema-results.ts +0 -84
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Backs the `migrate` command. Strategy: graph-walk-all-members, replay-only (no introspect/synth/planner).
2
+ * Backs the `migrate` command. Resolves the recorded path for every space, replay-only (no introspect/diff/planner).
3
3
  */
4
4
 
5
5
  import type { Contract } from '@prisma-next/contract/types';
@@ -11,13 +11,14 @@ import type {
11
11
  TargetMigrationsCapability,
12
12
  } from '@prisma-next/framework-components/control';
13
13
  import {
14
- buildSynthMigrationEdge,
14
+ type AggregateContractSpace,
15
+ allStorageElementsExternal,
16
+ buildFabricatedMigrationEdge,
15
17
  type ContractMarkerRecordLike,
16
18
  type ContractSpaceAggregate,
17
- type ContractSpaceMember,
18
- graphWalkStrategy,
19
19
  type PerSpacePlan,
20
20
  requireHeadRef,
21
+ resolveRecordedPath,
21
22
  } from '@prisma-next/migration-tools/aggregate';
22
23
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
23
24
  import { errorNoInvariantPath } from '@prisma-next/migration-tools/errors';
@@ -62,8 +63,8 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
62
63
  readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
63
64
  readonly targetId: TTargetId;
64
65
  /**
65
- * Optional app-space ref override. When provided, the app member's
66
- * graph-walk targets this hash instead of `member.headRef.hash`.
66
+ * Optional app-space ref override. When provided, the app space's
67
+ * graph-walk targets this hash instead of `space.headRef.hash`.
67
68
  * Extensions are unaffected — they always walk to their own head.
68
69
  */
69
70
  readonly refHash?: string;
@@ -72,7 +73,7 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
72
73
  * Threaded into the graph-walk's `required` calculation so the
73
74
  * planner picks an invariant-bearing path and surfaces the
74
75
  * required/satisfied set on the success envelope. When `refHash`
75
- * is absent the file's `member.headRef.invariants` are used.
76
+ * is absent the file's `space.headRef.invariants` are used.
76
77
  */
77
78
  readonly refInvariants?: readonly string[];
78
79
  /**
@@ -89,22 +90,26 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
89
90
  /**
90
91
  * Apply pending migrations across every contract space (app +
91
92
  * extensions). Replay-only: graph-walk against the on-disk graph for
92
- * every member; no synth, no introspection.
93
+ * every contract space; no synth, no introspection.
93
94
  *
94
95
  * Pipeline:
95
96
  *
96
97
  * 1. Load aggregate from disk (loader hydrates extension graphs;
97
98
  * caller provides app-space packages).
98
99
  * 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
99
- * 3. Per member: `graphWalkStrategy` plots the path from the live
100
- * marker to `member.headRef.hash` (or `refHash` for the app
101
- * member when provided). Empty-graph members fail loudly a
102
- * "never planned" space is a user-error condition for replay.
100
+ * 3. Per space: `resolveRecordedPath` plots the path from the live
101
+ * marker to `space.headRef.hash` (or `refHash` for the app
102
+ * space when provided). An empty-graph space whose elements are ALL
103
+ * externally managed resolves declaratively (marker to head, zero
104
+ * ops), mirroring the db-init aggregate planner: such a space ships
105
+ * no DDL and has nothing to author. Every other empty-graph space
106
+ * fails loudly — "never planned" is a user-error condition for
107
+ * replay.
103
108
  * 4. Hand off to {@link runMigration} (the runner-driving tail
104
109
  * shared with `db init` / `db update`). Marker advancement is
105
110
  * inside the per-space transaction.
106
111
  *
107
- * Encodes the replay-only contract: every contract space must have an
112
+ * Encodes the replay-only contract: the app contract space must have an
108
113
  * authored migration graph on disk before this operation can advance it.
109
114
  */
110
115
  export async function executeMigrate<TFamilyId extends string, TTargetId extends string>(
@@ -140,41 +145,41 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
140
145
 
141
146
  const markerRows = await familyInstance.readAllMarkers({ driver });
142
147
 
143
- // Plan every member via graph-walk. App member targets `refHash`
148
+ // Plan every space via graph-walk. App space targets `refHash`
144
149
  // when provided, otherwise its own head; extensions always walk
145
150
  // to their own head ref.
146
- const allMembers: ReadonlyArray<ContractSpaceMember> = [aggregate.app, ...aggregate.extensions];
151
+ const allSpaces: ReadonlyArray<AggregateContractSpace> = [aggregate.app, ...aggregate.extensions];
147
152
  const perSpacePlans = new Map<string, PerSpacePlan>();
148
- // Already-at-head empty-graph members (typically extensions whose
153
+ // Already-at-head empty-graph spaces (typically extensions whose
149
154
  // head ref is the empty sentinel, or whose live marker already
150
155
  // matches the target). Kept out of the runner schedule so we don't
151
156
  // write spurious markers for greenfield extensions, but merged back
152
- // into the success envelope so every loaded member is represented.
157
+ // into the success envelope so every loaded space is represented.
153
158
  const atHeadResolutions = new Map<string, PerSpacePlan>();
154
- for (const member of allMembers) {
155
- const isAppMember = member.spaceId === aggregate.app.spaceId;
156
- // The aggregate passed the integrity gate, so every member's head ref
159
+ for (const space of allSpaces) {
160
+ const isAppSpace = space.spaceId === aggregate.app.spaceId;
161
+ // The aggregate passed the integrity gate, so every space's head ref
157
162
  // is resolved (the app's is synthesised from the live contract).
158
- const headRef = requireHeadRef(member);
159
- const memberTargetHash = isAppMember && refHash !== undefined ? refHash : headRef.hash;
160
- const memberRefInvariants = isAppMember && refHash !== undefined ? refInvariants : undefined;
161
- const liveMarker = markerRows.get(member.spaceId) ?? null;
163
+ const headRef = requireHeadRef(space);
164
+ const spaceTargetHash = isAppSpace && refHash !== undefined ? refHash : headRef.hash;
165
+ const spaceRefInvariants = isAppSpace && refHash !== undefined ? refInvariants : undefined;
166
+ const liveMarker = markerRows.get(space.spaceId) ?? null;
162
167
 
163
- const outcome = planMemberPath({
164
- member,
168
+ const outcome = planSpacePath({
169
+ space,
165
170
  aggregate,
166
- targetHash: memberTargetHash,
167
- refInvariants: memberRefInvariants,
171
+ targetHash: spaceTargetHash,
172
+ refInvariants: spaceRefInvariants,
168
173
  liveMarker,
169
- ...(isAppMember ? { refName } : {}),
174
+ ...(isAppSpace ? { refName } : {}),
170
175
  });
171
176
 
172
177
  if (outcome.kind === 'at-head') {
173
- // Empty-graph member whose live marker already matches the target.
178
+ // Empty-graph space whose live marker already matches the target.
174
179
  // Kept out of the runner schedule so we don't write spurious markers
175
180
  // for greenfield extensions, but merged back into the success envelope
176
- // so every loaded member is represented.
177
- atHeadResolutions.set(member.spaceId, outcome.plan);
181
+ // so every loaded space is represented.
182
+ atHeadResolutions.set(space.spaceId, outcome.plan);
178
183
  continue;
179
184
  }
180
185
  if (outcome.kind === 'never-planned') {
@@ -197,9 +202,9 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
197
202
  // is never a graph node, producing an empty `structuralPath` and
198
203
  // a less actionable diagnostic.
199
204
  const structural = findPathWithDecision(
200
- outcome.targetMember.graph(),
205
+ outcome.targetSpace.graph(),
201
206
  outcome.liveHash,
202
- memberTargetHash,
207
+ spaceTargetHash,
203
208
  { required: new Set<string>() },
204
209
  );
205
210
  const structuralPath =
@@ -220,18 +225,23 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
220
225
  });
221
226
  }
222
227
 
223
- perSpacePlans.set(member.spaceId, outcome.plan);
228
+ perSpacePlans.set(space.spaceId, outcome.plan);
224
229
  }
225
230
 
226
231
  const canonicalOrder = [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId];
227
232
  const applyOrder = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId));
228
233
 
229
234
  // Short-circuit: nothing pending across any space (no runner-bound
230
- // plans). Surfaces every loaded member — including at-head empty-
235
+ // plans). Surfaces every loaded space — including at-head empty-
231
236
  // graph extensions — in `perSpace[]` so the result reflects the
232
237
  // full aggregate, not just the spaces the runner would have touched.
233
- const totalPlannedOps = sumPlannedOps(applyOrder, perSpacePlans);
234
- if (totalPlannedOps === 0) {
238
+ // A zero-op plan still counts as pending when it advances a marker
239
+ // (declared-state resolution for an all-external extension space).
240
+ const hasPendingWork = applyOrder.some((spaceId) => {
241
+ const entry = perSpacePlans.get(spaceId);
242
+ return entry !== undefined && planRequiresExecution(entry);
243
+ });
244
+ if (!hasPendingWork) {
235
245
  const ordered = canonicalOrder
236
246
  .filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId))
237
247
  .map((spaceId) => {
@@ -285,7 +295,7 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
285
295
  }
286
296
 
287
297
  // Merge at-head zero-op resolutions back into the canonical order
288
- // so the success envelope surfaces every loaded member, not just
298
+ // so the success envelope surfaces every loaded space, not just
289
299
  // those the runner executed.
290
300
  const orderedAll = canonicalOrder
291
301
  .filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId))
@@ -321,16 +331,16 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
321
331
  }
322
332
 
323
333
  /**
324
- * Outcome variants for one member's path computation.
334
+ * Outcome variants for one space's path computation.
325
335
  *
326
336
  * Callers switch on `kind` and map to their own error representation:
327
337
  * `executeMigrate` throws / returns `notOk`; `executeMigrateShowCommand`
328
338
  * returns a CLI structured error. The shared discriminant guarantees both
329
- * paths feed `graphWalkStrategy` the same inputs.
339
+ * paths feed `resolveRecordedPath` the same inputs.
330
340
  *
331
341
  * @internal Exported for `executeMigrateShowCommand` to call.
332
342
  */
333
- export type MemberPathOutcome =
343
+ export type SpacePathOutcome =
334
344
  | { readonly kind: 'ok'; readonly plan: PerSpacePlan }
335
345
  | { readonly kind: 'at-head'; readonly plan: PerSpacePlan }
336
346
  | { readonly kind: 'never-planned'; readonly spaceId: string; readonly targetHash: string }
@@ -343,16 +353,16 @@ export type MemberPathOutcome =
343
353
  | {
344
354
  readonly kind: 'unsatisfiable';
345
355
  readonly spaceId: string;
346
- readonly isAppMember: boolean;
356
+ readonly isAppSpace: boolean;
347
357
  readonly missing: readonly string[];
348
358
  readonly targetInvariants: readonly string[];
349
- readonly targetMember: ContractSpaceMember;
359
+ readonly targetSpace: AggregateContractSpace;
350
360
  readonly liveHash: string;
351
361
  readonly refName: string | undefined;
352
362
  };
353
363
 
354
364
  /**
355
- * Compute the graph-walk path for one contract-space member.
365
+ * Compute the graph-walk path for one contract space.
356
366
  *
357
367
  * Encapsulates the invariant-correct input assembly that both
358
368
  * `executeMigrate` and `executeMigrateShowCommand` must use:
@@ -361,71 +371,104 @@ export type MemberPathOutcome =
361
371
  * - `targetInvariants` uses the caller-supplied `refInvariants` when a
362
372
  * `--to` ref was resolved (not always the file head ref's invariants).
363
373
  *
364
- * Both callers map the returned `MemberPathOutcome` to their own error
374
+ * Both callers map the returned `SpacePathOutcome` to their own error
365
375
  * representation; the path-compute logic is shared and identical.
366
376
  *
367
377
  * @internal Exported for `executeMigrateShowCommand`.
368
378
  */
369
- export function planMemberPath({
370
- member,
379
+ export function planSpacePath({
380
+ space,
371
381
  aggregate,
372
382
  targetHash,
373
383
  refInvariants,
374
384
  liveMarker,
375
385
  refName,
376
386
  }: {
377
- readonly member: ContractSpaceMember;
387
+ readonly space: AggregateContractSpace;
378
388
  readonly aggregate: Pick<ContractSpaceAggregate, 'targetId' | 'app'>;
379
389
  readonly targetHash: string;
380
390
  readonly refInvariants: readonly string[] | undefined;
381
391
  readonly liveMarker: ContractMarkerRecordLike | null;
382
392
  readonly refName?: string;
383
- }): MemberPathOutcome {
384
- const isAppMember = member.spaceId === aggregate.app.spaceId;
385
- const headRef = requireHeadRef(member);
393
+ }): SpacePathOutcome {
394
+ const isAppSpace = space.spaceId === aggregate.app.spaceId;
395
+ const headRef = requireHeadRef(space);
386
396
 
387
- if (member.graph().nodes.size === 0) {
397
+ if (space.graph().nodes.size === 0) {
388
398
  const liveHash = liveMarker?.storageHash;
389
399
  if (targetHash === liveHash || (liveHash === undefined && targetHash === EMPTY_CONTRACT_HASH)) {
390
400
  return {
391
401
  kind: 'at-head',
392
402
  plan: buildAtHeadResolution({
393
403
  aggregateTargetId: aggregate.targetId,
394
- member,
404
+ space,
395
405
  targetHash,
396
406
  liveMarker,
397
407
  }),
398
408
  };
399
409
  }
400
- return { kind: 'never-planned', spaceId: member.spaceId, targetHash };
410
+ // Empty-graph extension space not yet at head: the space ships no
411
+ // migration packages at all. Advancing the marker without migrations
412
+ // (the db-init aggregate planner's declared-state strategy, mirrored
413
+ // here) is valid exclusively when every element the space declares is
414
+ // externally managed — nothing Prisma Next owns exists in such a space
415
+ // (e.g. Supabase's auth/storage), so its declared state needs no
416
+ // migration to be true, and there is no command that could author an
417
+ // edge for it. A space that declares a managed element but ships no
418
+ // migration graph is an authoring bug: it falls through to the
419
+ // never-planned failure, like an app space with no authored graph.
420
+ if (!isAppSpace && allStorageElementsExternal(space.contract())) {
421
+ if (headRef.invariants.length > 0) {
422
+ return {
423
+ kind: 'unsatisfiable',
424
+ spaceId: space.spaceId,
425
+ isAppSpace,
426
+ missing: [...headRef.invariants].sort(),
427
+ targetInvariants: headRef.invariants,
428
+ targetSpace: space,
429
+ liveHash: liveHash ?? EMPTY_CONTRACT_HASH,
430
+ refName: undefined,
431
+ };
432
+ }
433
+ return {
434
+ kind: 'ok',
435
+ plan: buildAtHeadResolution({
436
+ aggregateTargetId: aggregate.targetId,
437
+ space,
438
+ targetHash,
439
+ liveMarker,
440
+ }),
441
+ };
442
+ }
443
+ return { kind: 'never-planned', spaceId: space.spaceId, targetHash };
401
444
  }
402
445
 
403
446
  const targetInvariants =
404
- isAppMember && refInvariants !== undefined ? refInvariants : headRef.invariants;
405
- const targetMember: ContractSpaceMember =
447
+ isAppSpace && refInvariants !== undefined ? refInvariants : headRef.invariants;
448
+ const targetSpace: AggregateContractSpace =
406
449
  targetHash === headRef.hash && targetInvariants === headRef.invariants
407
- ? member
408
- : { ...member, headRef: { hash: targetHash, invariants: targetInvariants } };
450
+ ? space
451
+ : { ...space, headRef: { hash: targetHash, invariants: targetInvariants } };
409
452
 
410
- const walked = graphWalkStrategy({
453
+ const walked = resolveRecordedPath({
411
454
  aggregateTargetId: aggregate.targetId,
412
- member: targetMember,
455
+ space: targetSpace,
413
456
  currentMarker: liveMarker,
414
- ...(isAppMember && refName !== undefined ? { refName } : {}),
457
+ ...(isAppSpace && refName !== undefined ? { refName } : {}),
415
458
  });
416
459
 
417
460
  if (walked.kind === 'unreachable') {
418
- return { kind: 'unreachable', spaceId: member.spaceId, liveMarker, targetHash };
461
+ return { kind: 'unreachable', spaceId: space.spaceId, liveMarker, targetHash };
419
462
  }
420
463
  if (walked.kind === 'unsatisfiable') {
421
464
  const liveHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
422
465
  return {
423
466
  kind: 'unsatisfiable',
424
- spaceId: member.spaceId,
425
- isAppMember,
467
+ spaceId: space.spaceId,
468
+ isAppSpace,
426
469
  missing: walked.missing,
427
470
  targetInvariants,
428
- targetMember,
471
+ targetSpace,
429
472
  liveHash,
430
473
  refName,
431
474
  };
@@ -434,33 +477,35 @@ export function planMemberPath({
434
477
  }
435
478
 
436
479
  /**
437
- * Build a zero-op {@link PerSpacePlan} for an empty-graph
438
- * member whose live marker already matches the target. Lets the apply
439
- * pipeline thread the member through `perSpacePlans` -> `applyOrder`
440
- * -> the success envelope's `perSpace[]` block so the result reflects
441
- * every loaded space, even when there is nothing to execute.
480
+ * Build a zero-op {@link PerSpacePlan} for an empty-graph space —
481
+ * either one whose live marker already matches the target (at-head), or
482
+ * an all-external extension space whose marker must advance to the head
483
+ * ref with no DDL (declared-state). Lets the apply pipeline thread the
484
+ * space through `perSpacePlans` -> `applyOrder` -> the success
485
+ * envelope's `perSpace[]` block so the result reflects every loaded
486
+ * space, even when there is nothing to execute.
442
487
  */
443
488
  function buildAtHeadResolution(args: {
444
489
  readonly aggregateTargetId: string;
445
- readonly member: ContractSpaceMember;
490
+ readonly space: AggregateContractSpace;
446
491
  readonly targetHash: string;
447
492
  readonly liveMarker: ContractMarkerRecordLike | null;
448
493
  }): PerSpacePlan {
449
- const { aggregateTargetId, member, targetHash, liveMarker } = args;
494
+ const { aggregateTargetId, space, targetHash, liveMarker } = args;
450
495
  return {
451
496
  plan: {
452
497
  targetId: aggregateTargetId,
453
- spaceId: member.spaceId,
498
+ spaceId: space.spaceId,
454
499
  origin: liveMarker === null ? null : { storageHash: liveMarker.storageHash },
455
500
  destination: { storageHash: targetHash },
456
501
  operations: [],
457
502
  providedInvariants: [],
458
503
  },
459
504
  displayOps: [],
460
- destinationContract: member.contract(),
461
- strategy: 'graph-walk',
505
+ destinationContract: space.contract(),
506
+ strategy: 'declared-state',
462
507
  migrationEdges: [
463
- buildSynthMigrationEdge({
508
+ buildFabricatedMigrationEdge({
464
509
  currentMarkerStorageHash: liveMarker?.storageHash,
465
510
  destinationStorageHash: targetHash,
466
511
  operationCount: 0,
@@ -469,17 +514,14 @@ function buildAtHeadResolution(args: {
469
514
  };
470
515
  }
471
516
 
472
- function sumPlannedOps(
473
- applyOrder: readonly string[],
474
- perSpacePlans: ReadonlyMap<string, PerSpacePlan>,
475
- ): number {
476
- let total = 0;
477
- for (const spaceId of applyOrder) {
478
- const entry = perSpacePlans.get(spaceId);
479
- if (!entry) continue;
480
- total += entry.plan.operations.length;
481
- }
482
- return total;
517
+ /**
518
+ * A plan needs the runner when it executes operations or advances the
519
+ * space's marker (a declared-state resolution has zero operations but a
520
+ * destination hash the live marker doesn't carry yet).
521
+ */
522
+ function planRequiresExecution(entry: PerSpacePlan): boolean {
523
+ if (entry.plan.operations.length > 0) return true;
524
+ return entry.plan.origin?.storageHash !== entry.plan.destination.storageHash;
483
525
  }
484
526
 
485
527
  interface BuildSuccessArgs {
@@ -494,7 +536,7 @@ interface BuildSuccessArgs {
494
536
  }
495
537
 
496
538
  function buildSuccess(args: BuildSuccessArgs): MigrateSuccess {
497
- // The marker hash surfaced at the top level is the **app member's**
539
+ // The marker hash surfaced at the top level is the **app space's**
498
540
  // post-migrate marker (the top-level `markerHash` field).
499
541
  // Per-space markers live on `perSpace[].marker.storageHash`.
500
542
  const appResolution = args.orderedResolutions.find(
@@ -552,10 +594,12 @@ function buildSuccess(args: BuildSuccessArgs): MigrateSuccess {
552
594
  }
553
595
 
554
596
  /**
555
- * Build the `neverPlanned` failure raised when a contract space has no on-disk
556
- * migration graph but migrate was asked to reach a target hash. The `why`
557
- * states only the condition; the recovery sequence is composed by
558
- * `errorPathUnreachable`'s `fix`.
597
+ * Build the `neverPlanned` failure raised when a contract space that
598
+ * declares managed storage elements has no on-disk migration graph but
599
+ * migrate was asked to reach a target hash. All-external spaces never reach
600
+ * this: they resolve declaratively (marker advances to the head ref with
601
+ * zero operations). The `why` states only the condition; the recovery
602
+ * sequence is composed by `errorPathUnreachable`'s `fix`.
559
603
  *
560
604
  * @internal Exported for testing only.
561
605
  */
@@ -563,7 +607,7 @@ export function buildNeverPlannedFailure(spaceId: string, targetHash: string): M
563
607
  return {
564
608
  code: 'MIGRATION_PATH_NOT_FOUND',
565
609
  summary: `No on-disk migrations for contract space "${spaceId}"`,
566
- 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}".`,
610
+ why: `migrate is replay-only: a contract space that declares managed storage elements must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}".`,
567
611
  meta: { spaceId, target: targetHash, kind: 'neverPlanned' },
568
612
  };
569
613
  }
@@ -586,7 +630,7 @@ export function buildPathNotFoundFailure(
586
630
  const fromHash = marker?.storageHash ?? '<empty>';
587
631
  // The app-case phrasing names the user-visible condition (a
588
632
  // contract has been emitted that no on-disk migration reaches) so
589
- // the error reads naturally for the app member. Extension spaces
633
+ // the error reads naturally for the app space. Extension spaces
590
634
  // see the same condition expressed against the offending space.
591
635
  const summary =
592
636
  spaceId === 'app'
@@ -45,10 +45,11 @@ export interface RunMigrationInputs<TFamilyId extends string, TTargetId extends
45
45
  readonly aggregate: ContractSpaceAggregate;
46
46
  /**
47
47
  * Per-space plans, keyed by `spaceId`. Produced by either the full
48
- * {@link planMigration} pipeline (`db init` / `db update` — synth
49
- * for the app, graph-walk for extensions) or by direct
50
- * {@link graphWalkStrategy} calls (`migrate` — graph-walk
51
- * for every member). Either way, the runner consumes the same shape.
48
+ * {@link planMigration} pipeline (`db init` / `db update` —
49
+ * `planFromDiff` for the app, `resolveRecordedPath` for extensions) or
50
+ * by direct {@link resolveRecordedPath} calls (`migrate` — resolves
51
+ * the recorded path for every contract space). Either way, the runner
52
+ * consumes the same shape.
52
53
  */
53
54
  readonly perSpacePlans: ReadonlyMap<string, PerSpacePlan>;
54
55
  /**
@@ -229,7 +230,7 @@ export function buildPerSpaceBreakdown(
229
230
 
230
231
  /**
231
232
  * Materialise the `applyOrder` ordering into resolved per-space
232
- * entries. Throws if the planner output is missing a member listed
233
+ * entries. Throws if the planner output is missing a contract space listed
233
234
  * in `applyOrder` — a wiring bug that should never reach runtime.
234
235
  *
235
236
  * Exported so callers building their own success envelopes after a
@@ -545,7 +545,7 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
545
545
  *
546
546
  * The control-api operation is responsible for: loading the
547
547
  * contract-space aggregate, reading per-space marker rows from the
548
- * live database, plotting per-space paths via `graphWalkStrategy`
548
+ * live database, plotting per-space paths via `resolveRecordedPath`
549
549
  * (replay-only — no synth, no introspection), and dispatching
550
550
  * through the shared `runMigration` primitive. The CLI command
551
551
  * just resolves the descriptor surface (config, refs, contract
@@ -557,9 +557,9 @@ export interface MigrateOptions {
557
557
  /** Migrations root directory (`migrations/` under the project). */
558
558
  readonly migrationsDir: string;
559
559
  /**
560
- * Optional app-space ref override. When provided, the app member's
560
+ * Optional app-space ref override. When provided, the app space's
561
561
  * graph-walk targets this hash instead of `contract.storage.storageHash`.
562
- * Extension members always walk to their own `headRef.hash`.
562
+ * Extension spaces always walk to their own `headRef.hash`.
563
563
  */
564
564
  readonly refHash?: string;
565
565
  /**
@@ -631,17 +631,17 @@ export interface MigrateRanEntry {
631
631
 
632
632
  /**
633
633
  * Successful migrate result. Carries both the top-level fields
634
- * (`markerHash` is the **app member's** post-migrate marker) and the
634
+ * (`markerHash` is the **app space's** post-migrate marker) and the
635
635
  * per-space breakdown (`perSpace` — markers / operations in canonical
636
636
  * schedule order).
637
637
  */
638
638
  /**
639
- * Path-decision summary for the **app member** post-migrate. Surfaced
639
+ * Path-decision summary for the **app space** post-migrate. Surfaced
640
640
  * at the top level (and consumed by the cli-journeys suite, which
641
641
  * inspects `requiredInvariants`/`satisfiedInvariants`/
642
642
  * `selectedPath` to validate invariant routing).
643
643
  *
644
- * Per-space path decisions for extension members are not surfaced —
644
+ * Per-space path decisions for extension spaces are not surfaced —
645
645
  * extensions own their own ref/invariant control.
646
646
  */
647
647
  export interface MigratePathDecision {
@@ -673,7 +673,7 @@ export interface MigrateSuccess {
673
673
  */
674
674
  readonly perSpace: ReadonlyArray<PerSpaceExecutionEntry>;
675
675
  /**
676
- * Path-decision data for the app member. Present whenever the
676
+ * Path-decision data for the app space. Present whenever the
677
677
  * graph-walk strategy ran for the app (i.e. always for the
678
678
  * aggregate-walking migrate path). Absent only for the no-op
679
679
  * "Already up to date" early return when the app has no plan.
@@ -864,7 +864,7 @@ export interface ControlClient {
864
864
  * markers, and (unless `skipSchema` is true) per-space schema
865
865
  * verification with pre-projection (closes F23).
866
866
  *
867
- * @returns Result pattern: per-space schema results on success;
867
+ * @returns Result pattern: per-space verify results on success;
868
868
  * structured CLI error on marker / loader failure.
869
869
  * @throws If not connected or infrastructure failure
870
870
  */
@@ -1,4 +1,5 @@
1
1
  // CLI-specific exports
2
2
  export { createContractEmitCommand } from '../commands/contract-emit';
3
+ export { createFormatCommand } from '../commands/format';
3
4
  export type { LoadTsContractOptions } from '../load-ts-contract';
4
5
  export { loadContractFromTs } from '../load-ts-contract';
@@ -45,6 +45,7 @@
45
45
  import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
46
46
  import type { Writable } from 'node:stream';
47
47
  import { fileURLToPath } from 'node:url';
48
+ import { loadConfig } from '@prisma-next/config-loader';
48
49
  import {
49
50
  CliStructuredError,
50
51
  errorMigrationCliInvalidConfigArg,
@@ -57,7 +58,6 @@ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
57
58
  import { buildMigrationArtifacts, type Migration } from '@prisma-next/migration-tools/migration';
58
59
  import { Cli, Command, Option, UsageError } from 'clipanion';
59
60
  import { dirname, join } from 'pathe';
60
- import { loadConfig } from './config-loader';
61
61
 
62
62
  /**
63
63
  * Constructor shape accepted by `MigrationCLI.run`. `Migration` subclasses
@@ -287,7 +287,18 @@ export function errorPathUnreachable(failure: MigrateFailure): CliStructuredErro
287
287
  // Plan-then-apply recovery. The planner destination is the missing edge's
288
288
  // target; `migration plan --to` (built for arbitrary targets) makes this a
289
289
  // real command, so the diagnostic that sends you here is now honest.
290
+ //
291
+ // Never-planned spaces have an EMPTY migration graph, and contract-ref
292
+ // resolution only resolves full hashes against graph nodes — a
293
+ // `--to <hash>` remediation would reject its own input. `migration plan`
294
+ // without `--to` targets the working contract (the same contract the app
295
+ // space's synthesized head ref carries), so the bare form is the one that
296
+ // runs verbatim.
297
+ const neverPlanned = meta['kind'] === 'neverPlanned';
290
298
  const planCommand = (() => {
299
+ if (neverPlanned) {
300
+ return 'prisma-next migration plan --name <slug>';
301
+ }
291
302
  if (planFromHash !== null && targetHash !== null) {
292
303
  return `prisma-next migration plan --from ${planFromHash} --to ${targetHash} --name <slug>`;
293
304
  }
@@ -300,7 +311,9 @@ export function errorPathUnreachable(failure: MigrateFailure): CliStructuredErro
300
311
  return 'prisma-next migration plan';
301
312
  })();
302
313
  const applyCommand =
303
- targetHash !== null ? `prisma-next migrate --to ${targetHash}` : 'prisma-next migrate';
314
+ targetHash !== null && !neverPlanned
315
+ ? `prisma-next migrate --to ${targetHash}`
316
+ : 'prisma-next migrate';
304
317
  return errorRuntime(failure.summary, {
305
318
  why:
306
319
  failure.why ??