prisma-next 0.5.0-dev.74 → 0.5.0-dev.76

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 (92) hide show
  1. package/dist/cli.mjs +8 -8
  2. package/dist/{client-0ZX24FXF.mjs → client-qVH-rEgd.mjs} +433 -236
  3. package/dist/client-qVH-rEgd.mjs.map +1 -0
  4. package/dist/{result-handler-DWb1rFS-.mjs → command-helpers-BeZHkxV8.mjs} +22 -24
  5. package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
  6. package/dist/commands/contract-emit.mjs +1 -1
  7. package/dist/commands/contract-infer.mjs +1 -1
  8. package/dist/commands/db-init.d.mts.map +1 -1
  9. package/dist/commands/db-init.mjs +7 -5
  10. package/dist/commands/db-init.mjs.map +1 -1
  11. package/dist/commands/db-schema.mjs +5 -4
  12. package/dist/commands/db-schema.mjs.map +1 -1
  13. package/dist/commands/db-sign.mjs +6 -5
  14. package/dist/commands/db-sign.mjs.map +1 -1
  15. package/dist/commands/db-update.d.mts.map +1 -1
  16. package/dist/commands/db-update.mjs +7 -5
  17. package/dist/commands/db-update.mjs.map +1 -1
  18. package/dist/commands/db-verify.mjs +1 -1
  19. package/dist/commands/migration-apply.d.mts +29 -17
  20. package/dist/commands/migration-apply.d.mts.map +1 -1
  21. package/dist/commands/migration-apply.mjs +35 -129
  22. package/dist/commands/migration-apply.mjs.map +1 -1
  23. package/dist/commands/migration-new.mjs +4 -3
  24. package/dist/commands/migration-new.mjs.map +1 -1
  25. package/dist/commands/migration-plan.d.mts +19 -1
  26. package/dist/commands/migration-plan.d.mts.map +1 -1
  27. package/dist/commands/migration-plan.mjs +2 -2
  28. package/dist/commands/migration-ref.d.mts +1 -1
  29. package/dist/commands/migration-ref.mjs +3 -2
  30. package/dist/commands/migration-ref.mjs.map +1 -1
  31. package/dist/commands/migration-show.d.mts +1 -1
  32. package/dist/commands/migration-show.mjs +5 -4
  33. package/dist/commands/migration-show.mjs.map +1 -1
  34. package/dist/commands/migration-status.d.mts +104 -1
  35. package/dist/commands/migration-status.d.mts.map +1 -1
  36. package/dist/commands/migration-status.mjs +2 -2
  37. package/dist/{contract-emit-DkMqO7f2.mjs → contract-emit-9DBda5Ou.mjs} +7 -5
  38. package/dist/{contract-emit-DkMqO7f2.mjs.map → contract-emit-9DBda5Ou.mjs.map} +1 -1
  39. package/dist/{contract-emit-B3ChISB_.mjs → contract-emit-B77TsJqf.mjs} +4 -15
  40. package/dist/{contract-emit-B3ChISB_.mjs.map → contract-emit-B77TsJqf.mjs.map} +1 -1
  41. package/dist/{contract-enrichment-CF6ogEJ_.mjs → contract-enrichment-Dani0mMW.mjs} +1 -1
  42. package/dist/{contract-enrichment-CF6ogEJ_.mjs.map → contract-enrichment-Dani0mMW.mjs.map} +1 -1
  43. package/dist/{contract-infer-BDKAE0B0.mjs → contract-infer-BK9YFGEG.mjs} +5 -4
  44. package/dist/{contract-infer-BDKAE0B0.mjs.map → contract-infer-BK9YFGEG.mjs.map} +1 -1
  45. package/dist/{db-verify-B4TdDKOI.mjs → db-verify-C0y1PCO2.mjs} +7 -6
  46. package/dist/{db-verify-B4TdDKOI.mjs.map → db-verify-C0y1PCO2.mjs.map} +1 -1
  47. package/dist/exports/control-api.d.mts +3 -746
  48. package/dist/exports/control-api.d.mts.map +1 -1
  49. package/dist/exports/control-api.mjs +3 -3
  50. package/dist/exports/index.mjs +1 -1
  51. package/dist/exports/init-output.mjs +1 -1
  52. package/dist/extension-pack-inputs-C7xgE-vv.mjs +74 -0
  53. package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
  54. package/dist/{framework-components-gwAHl7ml.mjs → framework-components-ChqVUxR-.mjs} +1 -1
  55. package/dist/{framework-components-gwAHl7ml.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
  56. package/dist/global-flags-Icqpxk23.d.mts +12 -0
  57. package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
  58. package/dist/helpers-eqdN8tH6.mjs +25 -0
  59. package/dist/helpers-eqdN8tH6.mjs.map +1 -0
  60. package/dist/{init-Deo7U8_U.mjs → init-CoDVPvQ4.mjs} +4 -4
  61. package/dist/{init-Deo7U8_U.mjs.map → init-CoDVPvQ4.mjs.map} +1 -1
  62. package/dist/{inspect-live-schema-BAgQMYpD.mjs → inspect-live-schema-CWYxGKlb.mjs} +4 -4
  63. package/dist/{inspect-live-schema-BAgQMYpD.mjs.map → inspect-live-schema-CWYxGKlb.mjs.map} +1 -1
  64. package/dist/{migration-command-scaffold-B8J702Uh.mjs → migration-command-scaffold-B5dORFEv.mjs} +4 -4
  65. package/dist/{migration-command-scaffold-B8J702Uh.mjs.map → migration-command-scaffold-B5dORFEv.mjs.map} +1 -1
  66. package/dist/{migration-plan-BcKNnTM7.mjs → migration-plan-C6lVaHsO.mjs} +47 -23
  67. package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
  68. package/dist/{migration-status-CjwB2of-.mjs → migration-status-CZ-D5k7k.mjs} +161 -7
  69. package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
  70. package/dist/{migrations-CIK94AJf.mjs → migrations-D_UJnpuW.mjs} +67 -24
  71. package/dist/migrations-D_UJnpuW.mjs.map +1 -0
  72. package/dist/{output-DnjfCC_u.mjs → output-B16Kefzx.mjs} +1 -1
  73. package/dist/{output-DnjfCC_u.mjs.map → output-B16Kefzx.mjs.map} +1 -1
  74. package/dist/{progress-adapter-xASh41wr.mjs → progress-adapter-DFfvZcYL.mjs} +1 -1
  75. package/dist/{progress-adapter-xASh41wr.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
  76. package/dist/result-handler-rmPVKIP2.mjs +25 -0
  77. package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
  78. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  79. package/dist/{terminal-ui-zaRDhJnP.mjs → terminal-ui-C_hFNbAn.mjs} +3 -23
  80. package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
  81. package/dist/types-D7x-IFLO.d.mts +858 -0
  82. package/dist/types-D7x-IFLO.d.mts.map +1 -0
  83. package/dist/{verify-BEIa9638.mjs → verify-CiwNWM9N.mjs} +2 -2
  84. package/dist/{verify-BEIa9638.mjs.map → verify-CiwNWM9N.mjs.map} +1 -1
  85. package/package.json +10 -10
  86. package/dist/client-0ZX24FXF.mjs.map +0 -1
  87. package/dist/migration-plan-BcKNnTM7.mjs.map +0 -1
  88. package/dist/migration-status-CjwB2of-.mjs.map +0 -1
  89. package/dist/migrations-CIK94AJf.mjs.map +0 -1
  90. package/dist/result-handler-DWb1rFS-.mjs.map +0 -1
  91. package/dist/terminal-ui-zaRDhJnP.mjs.map +0 -1
  92. /package/dist/{cli-errors-QH8kf-C2.d.mts → cli-errors-B9OBbled.d.mts} +0 -0
@@ -1,12 +1,15 @@
1
1
  import { p as errorRunnerFailed, t as CliStructuredError } from "./cli-errors-D3_sMh2K.mjs";
2
- import { t as assertFrameworkComponentsCompatible } from "./framework-components-gwAHl7ml.mjs";
3
- import { t as enrichContract } from "./contract-enrichment-CF6ogEJ_.mjs";
2
+ import { t as assertFrameworkComponentsCompatible } from "./framework-components-ChqVUxR-.mjs";
3
+ import { t as enrichContract } from "./contract-enrichment-Dani0mMW.mjs";
4
+ import { n as toExtensionInputs, t as toDeclaredExtensions } from "./extension-pack-inputs-C7xgE-vv.mjs";
4
5
  import { emit } from "@prisma-next/emitter";
5
6
  import { ifDefined } from "@prisma-next/utils/defined";
6
7
  import { notOk, ok } from "@prisma-next/utils/result";
7
8
  import { APP_SPACE_ID, createControlStack, hasMigrations, hasMultiSpaceRunner, hasOperationPreview, hasPslContractInfer, hasSchemaView } from "@prisma-next/framework-components/control";
8
- import { loadContractSpaceAggregate, planAggregate, verifyAggregate } from "@prisma-next/migration-tools/aggregate";
9
+ import { findPathWithDecision } from "@prisma-next/migration-tools/migration-graph";
10
+ import { graphWalkStrategy, loadContractSpaceAggregate, planAggregate, verifyAggregate } from "@prisma-next/migration-tools/aggregate";
9
11
  import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
12
+ import { errorNoInvariantPath } from "@prisma-next/migration-tools/errors";
10
13
  //#region src/control-api/errors.ts
11
14
  var ContractValidationError = class extends Error {
12
15
  cause;
@@ -19,38 +22,6 @@ var ContractValidationError = class extends Error {
19
22
  //#endregion
20
23
  //#region src/utils/contract-space-aggregate-loader.ts
21
24
  /**
22
- * Convert the CLI's `Config.extensionPacks` array into the loader's
23
- * `DeclaredExtensionEntry[]` shape.
24
- *
25
- * The loader hashes `contractSpace.contractJson` to compare against the
26
- * on-disk `refs/head.json.hash` (drift detection). Rather than re-running
27
- * the canonical-JSON + SHA-256 pipeline at the CLI surface, we look up
28
- * the descriptor's pre-computed `headRef.hash` via reference identity
29
- * on the contract JSON value — the loader passes the same
30
- * `entry.contractSpace.contractJson` reference through to the hasher,
31
- * so identity-keyed lookup is safe.
32
- */
33
- function toDeclaredExtensions(extensionPacks) {
34
- const entries = [];
35
- const hashByContractJson = /* @__PURE__ */ new Map();
36
- for (const pack of extensionPacks) {
37
- const entry = pack.contractSpace ? {
38
- id: pack.id,
39
- targetId: pack.targetId,
40
- contractSpace: { contractJson: pack.contractSpace.contractJson }
41
- } : {
42
- id: pack.id,
43
- targetId: pack.targetId
44
- };
45
- entries.push(entry);
46
- if (pack.contractSpace) hashByContractJson.set(pack.contractSpace.contractJson, pack.contractSpace.headRef.hash);
47
- }
48
- return {
49
- entries,
50
- hashByContractJson
51
- };
52
- }
53
- /**
54
25
  * Render a {@link LoadAggregateError} into a CLI structured-error
55
26
  * envelope. Preserves error codes `5001` (layout) and `5002` (marker /
56
27
  * drift / disjointness / etc.) so existing integration tests and
@@ -132,15 +103,18 @@ function mapLoadAggregateError(error) {
132
103
  * Run the aggregate loader at the CLI surface, mapping any
133
104
  * {@link LoadAggregateError} into a {@link CliStructuredError} envelope.
134
105
  *
135
- * App-side migration packages are intentionally not threaded through:
136
- * `db init` / `db update` go through the planner's `synth` strategy for
137
- * the app member (driven by `callerPolicy.ignoreGraphFor`), so the
138
- * app's authored `migrations/` graph is not walked.
106
+ * App-side migration packages flow through `inputs.appMigrationPackages`
107
+ * (defaulting to `[]`). `db init` / `db update` leave it empty: the
108
+ * planner's `synth` strategy is used for the app member (driven by
109
+ * `callerPolicy.ignoreGraphFor`), so the app's authored `migrations/`
110
+ * graph does not need to be walked. `migration apply` threads the
111
+ * already-loaded app-space packages through so the graph-walk strategy
112
+ * can plot a path through them — replay forbids synth.
139
113
  *
140
114
  * @see specs/contract-space-aggregate-spec.md § Loader.
141
115
  */
142
116
  async function buildContractSpaceAggregate(inputs) {
143
- const { entries, hashByContractJson } = toDeclaredExtensions(inputs.extensionPacks);
117
+ const { entries, hashByContractJson } = toDeclaredExtensions(toExtensionInputs(inputs.extensionPacks));
144
118
  const result = await loadContractSpaceAggregate({
145
119
  targetId: inputs.targetId,
146
120
  migrationsDir: inputs.migrationsDir,
@@ -152,12 +126,157 @@ async function buildContractSpaceAggregate(inputs) {
152
126
  if (precomputed === void 0) throw new Error("CLI aggregate loader: encountered an extension contract without a pre-computed descriptor hash. This is a wiring bug.");
153
127
  return precomputed;
154
128
  },
155
- appMigrationPackages: []
129
+ appMigrationPackages: inputs.appMigrationPackages ?? []
156
130
  });
157
131
  if (!result.ok) return notOk(mapLoadAggregateError(result.failure));
158
132
  return ok(result.value.aggregate);
159
133
  }
160
134
  //#endregion
135
+ //#region src/control-api/operations/apply-aggregate.ts
136
+ /**
137
+ * Span id emitted via `onProgress` for the apply phase. Stable
138
+ * identifier consumed by the structured-output renderer and by tests.
139
+ */
140
+ const APPLY_SPAN_ID = "apply";
141
+ /**
142
+ * Runner-driving tail shared by every aggregate apply caller — `db init`,
143
+ * `db update`, and `migration apply`. Consumes already-resolved per-space
144
+ * plans (the planner-vs-replay distinction is owned by the caller) and
145
+ * dispatches them to the multi-space runner in canonical order.
146
+ *
147
+ * Marker advancement is part of the runner's per-space transaction
148
+ * (the SQL family runner writes the marker as the last step of each
149
+ * space's transaction), so this primitive does not advance markers
150
+ * separately — by the time `executeAcrossSpaces` returns ok, every
151
+ * space's marker has been advanced to its plan's destination.
152
+ *
153
+ * Span emission (`spanStart 'apply'` / `spanEnd 'apply'`) is owned here
154
+ * so callers don't have to duplicate it; the `action` field on each
155
+ * progress event is taken from the caller's `action` argument.
156
+ */
157
+ async function applyAggregate(inputs) {
158
+ const { aggregate, perSpacePlans, applyOrder, driver, familyInstance, migrations, frameworkComponents, policy, action, onProgress } = inputs;
159
+ const orderedResolutions = collectOrdered(applyOrder, perSpacePlans);
160
+ const runner = migrations.createRunner(familyInstance);
161
+ if (!hasMultiSpaceRunner(runner)) throw errorRunnerFailed(`Runner for target "${aggregate.targetId}" does not implement \`executeAcrossSpaces\``, { why: `${labelForAction(action)} requires multi-space-capable runners (today: every SQL family runner).` });
162
+ onProgress?.({
163
+ action,
164
+ kind: "spanStart",
165
+ spanId: APPLY_SPAN_ID,
166
+ label: progressLabelForAction(action)
167
+ });
168
+ const perSpaceOptions = orderedResolutions.map((r) => ({
169
+ space: r.spaceId,
170
+ plan: r.entry.plan,
171
+ driver,
172
+ destinationContract: r.entry.destinationContract,
173
+ policy,
174
+ frameworkComponents,
175
+ strictVerification: false
176
+ }));
177
+ const runnerResult = await runner.executeAcrossSpaces({
178
+ driver,
179
+ perSpaceOptions
180
+ });
181
+ if (!runnerResult.ok) {
182
+ onProgress?.({
183
+ action,
184
+ kind: "spanEnd",
185
+ spanId: APPLY_SPAN_ID,
186
+ outcome: "error"
187
+ });
188
+ return notOk({
189
+ summary: runnerResult.failure.summary,
190
+ ...ifDefined("why", runnerResult.failure.why),
191
+ meta: {
192
+ ...runnerResult.failure.meta ?? {},
193
+ failingSpace: runnerResult.failure.failingSpace
194
+ }
195
+ });
196
+ }
197
+ onProgress?.({
198
+ action,
199
+ kind: "spanEnd",
200
+ spanId: APPLY_SPAN_ID,
201
+ outcome: "ok"
202
+ });
203
+ return ok({
204
+ orderedResolutions,
205
+ totalOpsPlanned: runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsPlanned, 0),
206
+ totalOpsExecuted: runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsExecuted, 0),
207
+ perSpace: buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, { includeMarkers: true })
208
+ });
209
+ }
210
+ /**
211
+ * Project the planner's per-space resolutions into the
212
+ * `AggregatePerSpaceExecutionEntry[]` shape the CLI surfaces.
213
+ *
214
+ * `includeMarkers` is `true` for apply-mode (each space's marker is
215
+ * the `destination.storageHash` of its plan, which the runner
216
+ * advances as the last step of each space's transaction) and `false`
217
+ * for plan-mode (no marker has been written yet).
218
+ *
219
+ * Exported alongside {@link applyAggregate} so plan-mode callers can
220
+ * assemble the same per-space block without going through the runner.
221
+ */
222
+ function buildPerSpaceBreakdown(orderedResolutions, appSpaceId, options) {
223
+ return orderedResolutions.map((r) => {
224
+ const operations = r.entry.displayOps.map((op) => ({
225
+ id: op.id,
226
+ label: op.label,
227
+ operationClass: op.operationClass
228
+ }));
229
+ const base = {
230
+ spaceId: r.spaceId,
231
+ kind: r.spaceId === appSpaceId ? "app" : "extension",
232
+ operations
233
+ };
234
+ if (!options.includeMarkers) return base;
235
+ return {
236
+ ...base,
237
+ marker: { storageHash: r.entry.plan.destination.storageHash }
238
+ };
239
+ });
240
+ }
241
+ /**
242
+ * Materialise the `applyOrder` ordering into resolved per-space
243
+ * entries. Throws if the planner output is missing a member listed
244
+ * in `applyOrder` — a wiring bug that should never reach runtime.
245
+ *
246
+ * Exported so callers building their own success envelopes after a
247
+ * plan-mode dispatch can replay the same ordering.
248
+ */
249
+ function collectOrdered(applyOrder, perSpace) {
250
+ return applyOrder.map((spaceId) => {
251
+ const entry = perSpace.get(spaceId);
252
+ if (!entry) throw new Error(`Aggregate planner output missing per-space plan for "${spaceId}"`);
253
+ return {
254
+ spaceId,
255
+ entry
256
+ };
257
+ });
258
+ }
259
+ /**
260
+ * Action-appropriate label for the `spanStart` event the apply
261
+ * primitive emits. `applyAggregate` is shared by `db init`, `db update`,
262
+ * and `migration apply`; the span label tracks the user-visible action
263
+ * so structured-progress output reads naturally for each surface.
264
+ */
265
+ function progressLabelForAction(action) {
266
+ switch (action) {
267
+ case "dbInit": return "Initialising database across spaces";
268
+ case "dbUpdate": return "Updating database across spaces";
269
+ case "migrationApply": return "Applying migration plan across spaces";
270
+ }
271
+ }
272
+ function labelForAction(action) {
273
+ switch (action) {
274
+ case "dbInit": return "db init";
275
+ case "dbUpdate": return "db update";
276
+ case "migrationApply": return "migration apply";
277
+ }
278
+ }
279
+ //#endregion
161
280
  //#region src/control-api/operations/migration-helpers.ts
162
281
  /**
163
282
  * Strips operation objects to their public shape (id, label, operationClass).
@@ -175,12 +294,13 @@ function stripOperations(operations) {
175
294
  /**
176
295
  * Span IDs emitted via `onProgress` during the aggregate apply flow.
177
296
  * Stable identifiers consumed by the structured-output renderer and by
178
- * tests asserting on span ids.
297
+ * tests asserting on span ids. The `apply` span itself is owned by
298
+ * the {@link applyAggregate} primitive — only the introspect / plan
299
+ * spans are emitted directly here.
179
300
  */
180
301
  const SPAN_IDS$1 = {
181
302
  introspect: "introspect",
182
- plan: "plan",
183
- apply: "apply"
303
+ plan: "plan"
184
304
  };
185
305
  /**
186
306
  * Loader → planner → runner pipeline shared by `db init` and `db update`.
@@ -268,71 +388,41 @@ async function executeAggregateApply(options) {
268
388
  if (mode === "plan") {
269
389
  const aggregateOps = orderedResolutions.flatMap((r) => r.entry.displayOps);
270
390
  const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(aggregateOps) : void 0;
391
+ const perSpace = buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, { includeMarkers: false });
271
392
  const summary = `Planned ${aggregateOps.length} operation(s) across ${orderedResolutions.length} space(s)`;
272
393
  return wrapPlanResult({
273
394
  operations: aggregateOps,
274
395
  destination: appPlan.destination,
275
396
  preview,
397
+ perSpace,
276
398
  summary
277
399
  });
278
400
  }
279
- const runner = migrations.createRunner(familyInstance);
280
- if (!hasMultiSpaceRunner(runner)) throw errorRunnerFailed(`Runner for target "${aggregate.targetId}" does not implement \`executeAcrossSpaces\``, { why: `${action === "dbInit" ? "db init" : "db update"} requires multi-space-capable runners (today: every SQL family runner).` });
281
- onProgress?.({
282
- action,
283
- kind: "spanStart",
284
- spanId: SPAN_IDS$1.apply,
285
- label: "Applying migration plan across spaces"
286
- });
287
- const perSpaceOptions = orderedResolutions.map((r) => ({
288
- space: r.spaceId,
289
- plan: r.entry.plan,
401
+ const applied = await applyAggregate({
402
+ aggregate,
403
+ perSpacePlans: planResult.value.perSpace,
404
+ applyOrder: planResult.value.applyOrder,
290
405
  driver,
291
- destinationContract: r.entry.destinationContract,
292
- policy,
293
- executionChecks: {
294
- prechecks: false,
295
- postchecks: false,
296
- idempotencyChecks: false
297
- },
406
+ familyInstance,
407
+ migrations,
298
408
  frameworkComponents,
299
- strictVerification: false
300
- }));
301
- const runnerResult = await runner.executeAcrossSpaces({
302
- driver,
303
- perSpaceOptions
304
- });
305
- if (!runnerResult.ok) {
306
- onProgress?.({
307
- action,
308
- kind: "spanEnd",
309
- spanId: SPAN_IDS$1.apply,
310
- outcome: "error"
311
- });
312
- return buildRunnerFailure({
313
- summary: runnerResult.failure.summary,
314
- ...ifDefined("why", runnerResult.failure.why),
315
- meta: {
316
- ...runnerResult.failure.meta ?? {},
317
- failingSpace: runnerResult.failure.failingSpace
318
- }
319
- });
320
- }
321
- onProgress?.({
409
+ policy,
322
410
  action,
323
- kind: "spanEnd",
324
- spanId: SPAN_IDS$1.apply,
325
- outcome: "ok"
411
+ ...ifDefined("onProgress", onProgress)
412
+ });
413
+ if (!applied.ok) return buildRunnerFailure({
414
+ summary: applied.failure.summary,
415
+ ...ifDefined("why", applied.failure.why),
416
+ meta: applied.failure.meta
326
417
  });
327
- const totalOpsPlanned = runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsPlanned, 0);
328
- const totalOpsExecuted = runnerResult.value.perSpaceResults.reduce((sum, r) => sum + r.value.operationsExecuted, 0);
329
- const aggregateOps = orderedResolutions.flatMap((r) => r.entry.displayOps);
330
- const summary = action === "dbInit" ? `Applied ${totalOpsExecuted} operation(s) across ${orderedResolutions.length} space(s), database signed` : totalOpsExecuted === 0 ? `Database already matches contract across ${orderedResolutions.length} space(s), signature updated` : `Applied ${totalOpsExecuted} operation(s) across ${orderedResolutions.length} space(s), signature updated`;
418
+ const aggregateOps = applied.value.orderedResolutions.flatMap((r) => r.entry.displayOps);
419
+ const summary = action === "dbInit" ? `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), database signed` : applied.value.totalOpsExecuted === 0 ? `Database already matches contract across ${applied.value.orderedResolutions.length} space(s), signature updated` : `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), signature updated`;
331
420
  return wrapApplyResult({
332
421
  operations: aggregateOps,
333
422
  destination: appPlan.destination,
334
- operationsPlanned: totalOpsPlanned,
335
- operationsExecuted: totalOpsExecuted,
423
+ operationsPlanned: applied.value.totalOpsPlanned,
424
+ operationsExecuted: applied.value.totalOpsExecuted,
425
+ perSpace: applied.value.perSpace,
336
426
  summary
337
427
  });
338
428
  }
@@ -367,16 +457,6 @@ function detectOrphanMarkers(aggregate, markerRows) {
367
457
  })) }
368
458
  });
369
459
  }
370
- function collectOrdered(applyOrder, perSpace) {
371
- return applyOrder.map((spaceId) => {
372
- const entry = perSpace.get(spaceId);
373
- if (!entry) throw new Error(`Aggregate planner output missing per-space plan for "${spaceId}"`);
374
- return {
375
- spaceId,
376
- entry
377
- };
378
- });
379
- }
380
460
  function mapPlannerError(error) {
381
461
  if (error.kind === "appSynthFailure") return notOk({
382
462
  code: "PLANNING_FAILED",
@@ -418,6 +498,7 @@ function wrapPlanResult(args) {
418
498
  storageHash: args.destination.storageHash,
419
499
  ...ifDefined("profileHash", args.destination.profileHash)
420
500
  },
501
+ perSpace: args.perSpace,
421
502
  summary: args.summary
422
503
  });
423
504
  }
@@ -437,6 +518,7 @@ function wrapApplyResult(args) {
437
518
  storageHash: args.destination.storageHash,
438
519
  profileHash: args.destination.profileHash
439
520
  } : { storageHash: args.destination.storageHash },
521
+ perSpace: args.perSpace,
440
522
  summary: args.summary
441
523
  });
442
524
  }
@@ -764,150 +846,255 @@ function mapMarkerCheckFailures(appSpaceId, section) {
764
846
  //#endregion
765
847
  //#region src/control-api/operations/migration-apply.ts
766
848
  /**
767
- * Apply a sequence of migration packages against the configured driver.
849
+ * Apply pending migrations across every contract space (app +
850
+ * extensions). Replay-only: graph-walk against the on-disk graph for
851
+ * every member; no synth, no introspection.
852
+ *
853
+ * Pipeline:
768
854
  *
769
- * Validates the path's continuity (origin ... → destination, no gaps),
770
- * then drives the family/target's migration runner over each package's
771
- * operations in order, surfacing per-migration progress through `onProgress`.
855
+ * 1. Load aggregate from disk (loader hydrates extension graphs;
856
+ * caller provides app-space packages).
857
+ * 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
858
+ * 3. Per member: `graphWalkStrategy` plots the path from the live
859
+ * marker to `member.headRef.hash` (or `refHash` for the app
860
+ * member when provided). Empty-graph members fail loudly — a
861
+ * "never planned" space is a user-error condition for replay.
862
+ * 4. Hand off to {@link applyAggregate} (the runner-driving tail
863
+ * shared with `db init` / `db update`). Marker advancement is
864
+ * inside the per-space transaction.
772
865
  *
773
- * The `pendingMigrations` parameter is trusted input. Callers are responsible
774
- * for upstream verification of the originating migration packages — typically
775
- * by loading them via `readMigrationPackage` from
776
- * `@prisma-next/migration-tools/io`, which performs hash-integrity checks at
777
- * the load boundary. This operation does not re-verify the packages and
778
- * assumes the `(metadata, ops)` pairs on disk have not been tampered with
779
- * since emit.
866
+ * Sub-spec § `migration apply` semantics + § Required changes 1.
780
867
  */
781
868
  async function executeMigrationApply(options) {
782
- const { driver, familyInstance, originHash, destinationHash, pendingMigrations, migrations, frameworkComponents, targetId, onProgress } = options;
783
- if (pendingMigrations.length === 0) {
784
- if (originHash !== destinationHash) return notOk({
785
- code: "MIGRATION_PATH_NOT_FOUND",
786
- summary: "No migrations provided for requested origin and destination",
787
- why: `Requested ${originHash} -> ${destinationHash} but pendingMigrations is empty`,
788
- meta: {
789
- originHash,
790
- destinationHash
869
+ const { driver, familyInstance, contract, migrations, frameworkComponents, migrationsDir, extensionPacks, targetId, appMigrationPackages, refHash, refInvariants, refName, onProgress } = options;
870
+ const loaded = await buildContractSpaceAggregate({
871
+ targetId,
872
+ migrationsDir,
873
+ appContract: contract,
874
+ extensionPacks,
875
+ validateContract: (json) => familyInstance.validateContract(json),
876
+ appMigrationPackages
877
+ });
878
+ if (!loaded.ok) throw loaded.failure;
879
+ const aggregate = loaded.value;
880
+ const markerRows = await familyInstance.readAllMarkers({ driver });
881
+ const allMembers = [aggregate.app, ...aggregate.extensions];
882
+ const perSpacePlans = /* @__PURE__ */ new Map();
883
+ const atHeadResolutions = /* @__PURE__ */ new Map();
884
+ for (const member of allMembers) {
885
+ const isAppMember = member.spaceId === aggregate.app.spaceId;
886
+ const targetHash = isAppMember && refHash !== void 0 ? refHash : member.headRef.hash;
887
+ const liveMarker = markerRows.get(member.spaceId) ?? null;
888
+ if (member.migrations.graph.nodes.size === 0) {
889
+ const liveHash = liveMarker?.storageHash;
890
+ if (targetHash === liveHash || liveHash === void 0 && targetHash === EMPTY_CONTRACT_HASH) {
891
+ atHeadResolutions.set(member.spaceId, buildAtHeadResolution({
892
+ aggregateTargetId: aggregate.targetId,
893
+ member,
894
+ targetHash,
895
+ liveMarker
896
+ }));
897
+ continue;
791
898
  }
792
- });
793
- return ok({
794
- migrationsApplied: 0,
795
- markerHash: originHash,
796
- applied: [],
797
- summary: "Already up to date"
798
- });
799
- }
800
- const firstMigration = pendingMigrations[0];
801
- const lastMigration = pendingMigrations[pendingMigrations.length - 1];
802
- const firstFromMarker = firstMigration.from ?? EMPTY_CONTRACT_HASH;
803
- if (firstFromMarker !== originHash || lastMigration.to !== destinationHash) return notOk({
804
- code: "MIGRATION_PATH_NOT_FOUND",
805
- summary: "Migration apply path does not match requested origin and destination",
806
- why: `Path resolved as ${firstFromMarker} -> ${lastMigration.to}, but requested ${originHash} -> ${destinationHash}`,
807
- meta: {
808
- originHash,
809
- destinationHash,
810
- pathOrigin: firstFromMarker,
811
- pathDestination: lastMigration.to
899
+ return notOk(buildNeverPlannedFailure(member.spaceId, targetHash));
812
900
  }
813
- });
814
- for (let i = 1; i < pendingMigrations.length; i++) {
815
- const previous = pendingMigrations[i - 1];
816
- const current = pendingMigrations[i];
817
- const currentFromMarker = current.from ?? EMPTY_CONTRACT_HASH;
818
- if (previous.to !== currentFromMarker) return notOk({
819
- code: "MIGRATION_PATH_NOT_FOUND",
820
- summary: "Migration apply path contains a discontinuity between adjacent migrations",
821
- why: `Migration "${previous.dirName}" ends at ${previous.to}, but next migration "${current.dirName}" starts at ${currentFromMarker}`,
822
- meta: {
823
- originHash,
824
- destinationHash,
825
- previousDirName: previous.dirName,
826
- previousTo: previous.to,
827
- currentDirName: current.dirName,
828
- currentFrom: currentFromMarker,
829
- discontinuityIndex: i
901
+ const targetInvariants = isAppMember && refHash !== void 0 && refInvariants !== void 0 ? refInvariants : member.headRef.invariants;
902
+ const targetMember = targetHash === member.headRef.hash && targetInvariants === member.headRef.invariants ? member : {
903
+ ...member,
904
+ headRef: {
905
+ hash: targetHash,
906
+ invariants: targetInvariants
830
907
  }
908
+ };
909
+ const walked = graphWalkStrategy({
910
+ aggregateTargetId: aggregate.targetId,
911
+ member: targetMember,
912
+ currentMarker: liveMarker,
913
+ ...isAppMember && refName !== void 0 ? { refName } : {}
831
914
  });
915
+ if (walked.kind === "unreachable") return notOk(buildPathNotFoundFailure(member.spaceId, liveMarker, targetHash));
916
+ if (walked.kind === "unsatisfiable") {
917
+ const fromHash = liveMarker?.storageHash ?? "";
918
+ const structural = findPathWithDecision(targetMember.migrations.graph, fromHash, targetHash, { required: /* @__PURE__ */ new Set() });
919
+ const structuralPath = structural.kind === "ok" ? structural.decision.selectedPath.map((edge) => ({
920
+ dirName: edge.dirName,
921
+ migrationHash: edge.migrationHash,
922
+ from: edge.from,
923
+ to: edge.to,
924
+ invariants: edge.invariants
925
+ })) : [];
926
+ throw errorNoInvariantPath({
927
+ ...isAppMember && refName !== void 0 ? { refName } : {},
928
+ required: targetInvariants,
929
+ missing: walked.missing,
930
+ structuralPath
931
+ });
932
+ }
933
+ perSpacePlans.set(member.spaceId, walked.result);
832
934
  }
833
- const runner = migrations.createRunner(familyInstance);
834
- const applied = [];
835
- for (const migration of pendingMigrations) {
836
- const migrationSpanId = `migration:${migration.dirName}`;
837
- onProgress?.({
838
- action: "migrationApply",
839
- kind: "spanStart",
840
- spanId: migrationSpanId,
841
- label: `Applying ${migration.dirName}`
935
+ const canonicalOrder = [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId];
936
+ const applyOrder = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId));
937
+ if (sumPlannedOps(applyOrder, perSpacePlans) === 0) {
938
+ const ordered = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId)).map((spaceId) => {
939
+ const entry = perSpacePlans.get(spaceId) ?? atHeadResolutions.get(spaceId);
940
+ if (entry === void 0) throw new Error(`Unreachable: missing per-space plan for "${spaceId}"`);
941
+ return {
942
+ spaceId,
943
+ entry
944
+ };
842
945
  });
843
- const { operations } = migration;
844
- const policy = { allowedOperationClasses: [
946
+ const perSpace = buildPerSpaceBreakdown(ordered, aggregate.app.spaceId, { includeMarkers: true });
947
+ const totalSpaces = ordered.length;
948
+ return ok(buildSuccess({
949
+ aggregate,
950
+ orderedResolutions: ordered,
951
+ perSpace,
952
+ totalOpsExecuted: 0,
953
+ summary: totalSpaces === 0 ? "Already up to date — no contract spaces are loaded" : totalSpaces === 1 ? "Already up to date" : `Already up to date across ${totalSpaces} space(s)`
954
+ }));
955
+ }
956
+ const applied = await applyAggregate({
957
+ aggregate,
958
+ perSpacePlans,
959
+ applyOrder,
960
+ driver,
961
+ familyInstance,
962
+ migrations,
963
+ frameworkComponents,
964
+ policy: { allowedOperationClasses: [
845
965
  "additive",
846
966
  "widening",
847
967
  "destructive",
848
968
  "data"
849
- ] };
850
- const plan = {
851
- targetId,
852
- spaceId: APP_SPACE_ID,
853
- origin: migration.from === null ? null : { storageHash: migration.from },
854
- destination: { storageHash: migration.to },
855
- operations,
856
- providedInvariants: migration.providedInvariants
857
- };
858
- const destinationContract = familyInstance.validateContract(migration.toContract);
859
- const runnerResult = await runner.execute({
860
- plan,
861
- driver,
862
- destinationContract,
863
- policy,
864
- executionChecks: {
865
- prechecks: true,
866
- postchecks: true,
867
- idempotencyChecks: true
868
- },
869
- frameworkComponents
870
- });
871
- if (!runnerResult.ok) {
872
- onProgress?.({
873
- action: "migrationApply",
874
- kind: "spanEnd",
875
- spanId: migrationSpanId,
876
- outcome: "error"
877
- });
878
- return notOk({
879
- code: "RUNNER_FAILED",
880
- summary: runnerResult.failure.summary,
881
- why: runnerResult.failure.why,
882
- meta: {
883
- migration: migration.dirName,
884
- from: migration.from,
885
- to: migration.to,
886
- ...runnerResult.failure.meta ?? {}
887
- }
888
- });
969
+ ] },
970
+ action: "migrationApply",
971
+ ...ifDefined("onProgress", onProgress)
972
+ });
973
+ if (!applied.ok) return notOk({
974
+ code: "RUNNER_FAILED",
975
+ summary: applied.failure.summary,
976
+ why: applied.failure.why,
977
+ meta: applied.failure.meta
978
+ });
979
+ const orderedAll = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId)).map((spaceId) => {
980
+ if (perSpacePlans.has(spaceId)) {
981
+ const fromRunner = applied.value.orderedResolutions.find((r) => r.spaceId === spaceId);
982
+ if (fromRunner !== void 0) return fromRunner;
889
983
  }
890
- onProgress?.({
891
- action: "migrationApply",
892
- kind: "spanEnd",
893
- spanId: migrationSpanId,
894
- outcome: "ok"
895
- });
896
- applied.push({
897
- dirName: migration.dirName,
898
- from: migration.from,
899
- to: migration.to,
900
- operationsExecuted: runnerResult.value.operationsExecuted
901
- });
984
+ const entry = atHeadResolutions.get(spaceId);
985
+ if (entry === void 0) throw new Error(`Unreachable: missing per-space plan for "${spaceId}"`);
986
+ return {
987
+ spaceId,
988
+ entry
989
+ };
990
+ });
991
+ const perSpaceAll = buildPerSpaceBreakdown(orderedAll, aggregate.app.spaceId, { includeMarkers: true });
992
+ const summary = `Applied ${applied.value.orderedResolutions.reduce((sum, r) => sum + (r.entry.migrationEdges?.length ?? 0), 0)} migration(s) (${applied.value.totalOpsExecuted} operation(s)) across ${orderedAll.length} contract space(s)`;
993
+ return ok(buildSuccess({
994
+ aggregate,
995
+ orderedResolutions: orderedAll,
996
+ perSpace: perSpaceAll,
997
+ totalOpsExecuted: applied.value.totalOpsExecuted,
998
+ summary
999
+ }));
1000
+ }
1001
+ /**
1002
+ * Build a zero-op {@link AggregatePerSpacePlan} for an empty-graph
1003
+ * member whose live marker already matches the target. Lets the apply
1004
+ * pipeline thread the member through `perSpacePlans` -> `applyOrder`
1005
+ * -> the success envelope's `perSpace[]` block so the result reflects
1006
+ * every loaded space, even when there is nothing to execute.
1007
+ */
1008
+ function buildAtHeadResolution(args) {
1009
+ const { aggregateTargetId, member, targetHash, liveMarker } = args;
1010
+ return {
1011
+ plan: {
1012
+ targetId: aggregateTargetId,
1013
+ spaceId: member.spaceId,
1014
+ origin: liveMarker === null ? null : { storageHash: liveMarker.storageHash },
1015
+ destination: { storageHash: targetHash },
1016
+ operations: [],
1017
+ providedInvariants: []
1018
+ },
1019
+ displayOps: [],
1020
+ destinationContract: member.contract,
1021
+ strategy: "graph-walk",
1022
+ migrationEdges: []
1023
+ };
1024
+ }
1025
+ function sumPlannedOps(applyOrder, perSpacePlans) {
1026
+ let total = 0;
1027
+ for (const spaceId of applyOrder) {
1028
+ const entry = perSpacePlans.get(spaceId);
1029
+ if (!entry) continue;
1030
+ total += entry.plan.operations.length;
902
1031
  }
903
- const finalHash = pendingMigrations[pendingMigrations.length - 1].to;
904
- const totalOps = applied.reduce((sum, a) => sum + a.operationsExecuted, 0);
905
- return ok({
1032
+ return total;
1033
+ }
1034
+ function buildSuccess(args) {
1035
+ const appResolution = args.orderedResolutions.find((r) => r.spaceId === args.aggregate.app.spaceId);
1036
+ const appMarkerHash = appResolution?.entry.plan.destination.storageHash ?? args.aggregate.app.headRef.hash;
1037
+ const applied = args.orderedResolutions.flatMap((r) => {
1038
+ return (r.entry.migrationEdges ?? []).map((edge) => ({
1039
+ spaceId: r.spaceId,
1040
+ dirName: edge.dirName,
1041
+ migrationHash: edge.migrationHash,
1042
+ from: edge.from,
1043
+ to: edge.to,
1044
+ operationsExecuted: edge.operationCount
1045
+ }));
1046
+ });
1047
+ const appPlan = appResolution?.entry;
1048
+ const pathDecision = appPlan?.pathDecision ? {
1049
+ fromHash: appPlan.pathDecision.fromHash,
1050
+ toHash: appPlan.pathDecision.toHash,
1051
+ alternativeCount: appPlan.pathDecision.alternativeCount,
1052
+ tieBreakReasons: appPlan.pathDecision.tieBreakReasons,
1053
+ ...appPlan.pathDecision.refName !== void 0 ? { refName: appPlan.pathDecision.refName } : {},
1054
+ requiredInvariants: appPlan.pathDecision.requiredInvariants ?? [],
1055
+ satisfiedInvariants: appPlan.pathDecision.satisfiedInvariants ?? [],
1056
+ selectedPath: appPlan.pathDecision.selectedPath.map((entry) => ({
1057
+ dirName: entry.dirName,
1058
+ migrationHash: entry.migrationHash,
1059
+ from: entry.from,
1060
+ to: entry.to,
1061
+ invariants: entry.invariants
1062
+ }))
1063
+ } : void 0;
1064
+ return {
906
1065
  migrationsApplied: applied.length,
907
- markerHash: finalHash,
1066
+ markerHash: appMarkerHash,
908
1067
  applied,
909
- summary: `Applied ${applied.length} migration(s) (${totalOps} operation(s)), marker at ${finalHash}`
910
- });
1068
+ summary: args.summary,
1069
+ perSpace: args.perSpace,
1070
+ ...pathDecision !== void 0 ? { pathDecision } : {}
1071
+ };
1072
+ }
1073
+ function buildNeverPlannedFailure(spaceId, targetHash) {
1074
+ return {
1075
+ code: "MIGRATION_PATH_NOT_FOUND",
1076
+ summary: `No on-disk migrations for contract space "${spaceId}"`,
1077
+ why: `migration apply 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.`,
1078
+ meta: {
1079
+ spaceId,
1080
+ target: targetHash,
1081
+ kind: "neverPlanned"
1082
+ }
1083
+ };
1084
+ }
1085
+ function buildPathNotFoundFailure(spaceId, marker, targetHash) {
1086
+ const fromHash = marker?.storageHash ?? "<empty>";
1087
+ return {
1088
+ code: "MIGRATION_PATH_NOT_FOUND",
1089
+ summary: spaceId === "app" ? "Current contract has no planned migration path" : `Current contract has no planned migration path for contract space "${spaceId}"`,
1090
+ 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.`,
1091
+ meta: {
1092
+ spaceId,
1093
+ fromHash,
1094
+ targetHash,
1095
+ kind: "pathUnreachable"
1096
+ }
1097
+ };
911
1098
  }
912
1099
  //#endregion
913
1100
  //#region src/control-api/client.ts
@@ -1218,16 +1405,26 @@ var ControlClientImpl = class {
1218
1405
  await this.connectWithProgress(options.connection, "migrationApply", onProgress);
1219
1406
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
1220
1407
  if (!hasMigrations(this.options.target)) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
1408
+ let contract;
1409
+ try {
1410
+ contract = familyInstance.validateContract(options.contract);
1411
+ } catch (error) {
1412
+ throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
1413
+ }
1221
1414
  return executeMigrationApply({
1222
1415
  driver,
1223
1416
  familyInstance,
1224
- originHash: options.originHash,
1225
- destinationHash: options.destinationHash,
1226
- pendingMigrations: options.pendingMigrations,
1417
+ contract,
1227
1418
  migrations: this.options.target.migrations,
1228
1419
  frameworkComponents,
1420
+ migrationsDir: options.migrationsDir,
1421
+ extensionPacks: this.options.extensionPacks ?? [],
1229
1422
  targetId: this.options.target.targetId,
1230
- ...onProgress ? { onProgress } : {}
1423
+ appMigrationPackages: options.appMigrationPackages,
1424
+ ...ifDefined("refHash", options.refHash),
1425
+ ...ifDefined("refInvariants", options.refInvariants),
1426
+ ...ifDefined("refName", options.refName),
1427
+ ...ifDefined("onProgress", onProgress)
1231
1428
  });
1232
1429
  }
1233
1430
  async introspect(options) {
@@ -1393,6 +1590,6 @@ var ControlClientImpl = class {
1393
1590
  }
1394
1591
  };
1395
1592
  //#endregion
1396
- export { ContractValidationError as a, executeDbInit as i, executeDbVerify as n, executeDbUpdate as r, createControlClient as t };
1593
+ export { buildContractSpaceAggregate as a, executeDbInit as i, executeDbVerify as n, ContractValidationError as o, executeDbUpdate as r, createControlClient as t };
1397
1594
 
1398
- //# sourceMappingURL=client-0ZX24FXF.mjs.map
1595
+ //# sourceMappingURL=client-qVH-rEgd.mjs.map