@prisma-next/cli 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 (105) 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 +14 -14
  86. package/src/commands/db-init.ts +1 -0
  87. package/src/commands/db-update.ts +1 -0
  88. package/src/commands/migration-apply.ts +94 -213
  89. package/src/commands/migration-plan.ts +89 -32
  90. package/src/commands/migration-status.ts +288 -5
  91. package/src/control-api/client.ts +16 -4
  92. package/src/control-api/operations/apply-aggregate.ts +290 -0
  93. package/src/control-api/operations/db-apply-aggregate.ts +42 -91
  94. package/src/control-api/operations/migration-apply.ts +420 -155
  95. package/src/control-api/types.ts +165 -32
  96. package/src/utils/contract-space-aggregate-loader.ts +24 -56
  97. package/src/utils/extension-pack-inputs.ts +170 -0
  98. package/src/utils/formatters/migrations.ts +135 -35
  99. package/dist/client-0ZX24FXF.mjs.map +0 -1
  100. package/dist/migration-plan-BcKNnTM7.mjs.map +0 -1
  101. package/dist/migration-status-CjwB2of-.mjs.map +0 -1
  102. package/dist/migrations-CIK94AJf.mjs.map +0 -1
  103. package/dist/result-handler-DWb1rFS-.mjs.map +0 -1
  104. package/dist/terminal-ui-zaRDhJnP.mjs.map +0 -1
  105. /package/dist/{cli-errors-QH8kf-C2.d.mts → cli-errors-B9OBbled.d.mts} +0 -0
@@ -18,6 +18,7 @@ import type {
18
18
  VerifyDatabaseSchemaResult,
19
19
  } from '@prisma-next/framework-components/control';
20
20
  import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
21
+ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
21
22
  import type { Result } from '@prisma-next/utils/result';
22
23
  import type { ExecuteDbVerifyResult } from './operations/db-verify';
23
24
 
@@ -308,6 +309,42 @@ export interface EmitOptions {
308
309
  // Result Types
309
310
  // ============================================================================
310
311
 
312
+ /**
313
+ * Per-space breakdown of an aggregate plan / apply.
314
+ *
315
+ * Surfaces the canonical schedule shape — extensions alphabetically,
316
+ * then app — together with the operations attributed to each space and,
317
+ * when the run was applied, the resulting per-space marker hash.
318
+ *
319
+ * M6 sub-spec § Output shape contract — every space involved in a run
320
+ * is observable in the success summary, including its post-apply
321
+ * marker, so the per-space invariant is visible to the user (closing
322
+ * F4 / F7 from `e2e-verification.md`).
323
+ */
324
+ export interface AggregatePerSpaceExecutionEntry {
325
+ readonly spaceId: string;
326
+ /** `'app'` for the application's contract space; `'extension'` for any extension space. */
327
+ readonly kind: 'app' | 'extension';
328
+ /**
329
+ * Operations attributed to this space (display ops). In `mode: 'plan'`
330
+ * this is the planned set; in `mode: 'apply'` it is the same set
331
+ * (every op was executed in order, the runner does not skip).
332
+ */
333
+ readonly operations: ReadonlyArray<{
334
+ readonly id: string;
335
+ readonly label: string;
336
+ readonly operationClass: string;
337
+ }>;
338
+ /**
339
+ * Post-apply marker hash for this space. Present only when the run
340
+ * was applied (i.e. `mode: 'apply'` and the runner returned ok).
341
+ * Equals the per-space plan's `destination.storageHash`.
342
+ */
343
+ readonly marker?: {
344
+ readonly storageHash: string;
345
+ };
346
+ }
347
+
311
348
  /**
312
349
  * Successful dbInit result.
313
350
  */
@@ -340,6 +377,14 @@ export interface DbInitSuccess {
340
377
  readonly storageHash: string;
341
378
  readonly profileHash?: string;
342
379
  };
380
+ /**
381
+ * Per-space breakdown in canonical schedule order (extensions
382
+ * alphabetically, then app). Present whenever the aggregate flow
383
+ * produced one — both `mode: 'plan'` and `mode: 'apply'`.
384
+ *
385
+ * See {@link AggregatePerSpaceExecutionEntry}.
386
+ */
387
+ readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
343
388
  readonly summary: string;
344
389
  }
345
390
 
@@ -405,6 +450,11 @@ export interface DbUpdateSuccess {
405
450
  readonly storageHash: string;
406
451
  readonly profileHash?: string;
407
452
  };
453
+ /**
454
+ * Per-space breakdown in canonical schedule order (extensions
455
+ * alphabetically, then app). See {@link AggregatePerSpaceExecutionEntry}.
456
+ */
457
+ readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
408
458
  readonly summary: string;
409
459
  }
410
460
 
@@ -477,44 +527,48 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
477
527
  // ============================================================================
478
528
 
479
529
  /**
480
- * A pre-planned migration step ready for execution.
481
- * Contains the manifest metadata and the serialized operations from ops.json.
530
+ * Options for the aggregate-walking `migrationApply` operation.
531
+ *
532
+ * The control-api operation is responsible for: loading the
533
+ * contract-space aggregate, reading per-space marker rows from the
534
+ * live database, plotting per-space paths via `graphWalkStrategy`
535
+ * (replay-only — no synth, no introspection), and dispatching
536
+ * through the shared `applyAggregate` primitive. The CLI command
537
+ * just resolves the descriptor surface (config, refs, contract
538
+ * envelope, app-space migration packages) and hands the inputs in.
539
+ *
540
+ * Sub-spec § `migration apply` semantics + § Required changes 1.
482
541
  */
483
- export interface MigrationApplyStep {
484
- readonly dirName: string;
485
- readonly from: string | null;
486
- readonly to: string;
487
- readonly toContract: Contract;
488
- readonly operations: readonly MigrationPlanOperation[];
542
+ export interface MigrationApplyOptions {
543
+ /** Already-validated app contract (the canonical "where we are heading" hash). */
544
+ readonly contract: unknown;
545
+ /** Migrations root directory (`migrations/` under the project). */
546
+ readonly migrationsDir: string;
489
547
  /**
490
- * Sorted, deduplicated invariant ids from `migration.json.providedInvariants`.
491
- * Verified at load time by `readMigrationPackage` (manifest copy must equal
492
- * the value derived from `ops.json`). The control-api passes this through
493
- * to the runner via `MigrationPlan.providedInvariants` so target runners
494
- * read the canonical set instead of re-deriving from `operations`.
548
+ * Already-loaded app-space migration packages. The CLI loads these
549
+ * via `loadMigrationPackages(appMigrationsDir)` before invoking
550
+ * `migrationApply`.
495
551
  */
496
- readonly providedInvariants: readonly string[];
497
- }
498
-
499
- /**
500
- * Options for the migrationApply operation.
501
- */
502
- export interface MigrationApplyOptions {
552
+ readonly appMigrationPackages: ReadonlyArray<OnDiskMigrationPackage>;
503
553
  /**
504
- * Hash of the database state this apply path starts from.
505
- * This is resolved by the caller (typically the CLI orchestration layer).
554
+ * Optional app-space ref override. When provided, the app member's
555
+ * graph-walk targets this hash instead of `contract.storage.storageHash`.
556
+ * Extension members always walk to their own `headRef.hash`.
506
557
  */
507
- readonly originHash: string;
558
+ readonly refHash?: string;
508
559
  /**
509
- * Hash of the target contract this apply path must reach.
510
- * This is resolved by the caller (typically the CLI orchestration layer).
560
+ * Required invariants on the user-supplied app-space ref. Threaded
561
+ * into the graph-walk's `required` calculation so the planner picks
562
+ * an invariant-bearing path. Ignored when `refHash` is absent.
511
563
  */
512
- readonly destinationHash: string;
564
+ readonly refInvariants?: readonly string[];
513
565
  /**
514
- * Ordered list of migrations to execute from originHash to destinationHash.
515
- * The execution layer does not choose defaults; it only executes this explicit path.
566
+ * Resolved name of the user-supplied app-space ref (the literal the
567
+ * user passed to `--ref`). Decorates `pathDecision.refName` and any
568
+ * `MIGRATION.NO_INVARIANT_PATH` envelope raised during graph-walk.
569
+ * Ignored when `refHash` is absent.
516
570
  */
517
- readonly pendingMigrations: readonly MigrationApplyStep[];
571
+ readonly refName?: string;
518
572
  /**
519
573
  * Database connection. If provided, migrationApply will connect before executing.
520
574
  * If omitted, the client must already be connected.
@@ -525,23 +579,102 @@ export interface MigrationApplyOptions {
525
579
  }
526
580
 
527
581
  /**
528
- * Record of a successfully applied migration.
582
+ * A single on-disk migration package surfaced to the operation. The
583
+ * SQL family already produces this shape via `loadMigrationPackages`;
584
+ * the operation hands it through to the framework-neutral aggregate
585
+ * loader's `appMigrationPackages` slot.
586
+ *
587
+ * (Originally named `MigrationApplyStep` for the legacy single-space
588
+ * apply path; the name is kept for compatibility with existing CLI
589
+ * callers and tests.)
529
590
  */
530
- export interface MigrationApplyAppliedEntry {
591
+ export interface MigrationApplyStep {
531
592
  readonly dirName: string;
532
593
  readonly from: string | null;
533
594
  readonly to: string;
595
+ readonly toContract: Contract;
596
+ readonly operations: readonly MigrationPlanOperation[];
597
+ /**
598
+ * Sorted, deduplicated invariant ids from `migration.json.providedInvariants`.
599
+ * Verified at load time by `readMigrationPackage` (manifest copy must equal
600
+ * the value derived from `ops.json`).
601
+ */
602
+ readonly providedInvariants: readonly string[];
603
+ }
604
+
605
+ /**
606
+ * Record of a successfully applied per-space migration. One entry per
607
+ * contract space that had pending migrations — empty `applied` means
608
+ * every space was already at its head.
609
+ */
610
+ /**
611
+ * One entry per authored migration package applied. Preserves the
612
+ * single-space `migrationsApplied` count semantics (each entry is
613
+ * one migration directory) so `applied.length === migrationsApplied`.
614
+ *
615
+ * Per-space aggregate detail (markers, ops grouped by space) lives
616
+ * on `perSpace[]`; this list is the per-edge view.
617
+ */
618
+ export interface MigrationApplyAppliedEntry {
619
+ readonly spaceId: string;
620
+ readonly dirName: string;
621
+ readonly migrationHash: string;
622
+ readonly from: string;
623
+ readonly to: string;
534
624
  readonly operationsExecuted: number;
535
625
  }
536
626
 
537
627
  /**
538
- * Successful migrationApply result.
628
+ * Successful migrationApply result. Carries both the legacy
629
+ * single-space fields (`markerHash` is the **app member's** post-apply
630
+ * marker, surfaced for back-compat with single-space callers) and the
631
+ * per-space breakdown (`perSpace` — markers / operations / canonical
632
+ * order, per M6 sub-spec § Output shape).
539
633
  */
634
+ /**
635
+ * Path-decision summary for the **app member** post-apply. Surfaced
636
+ * for back-compat with single-space callers (and the cli-journeys
637
+ * suite, which inspects `requiredInvariants`/`satisfiedInvariants`/
638
+ * `selectedPath` to validate invariant routing).
639
+ *
640
+ * Per-space path decisions for extension members are not surfaced —
641
+ * extensions own their own ref/invariant control.
642
+ */
643
+ export interface MigrationApplyPathDecision {
644
+ readonly fromHash: string;
645
+ readonly toHash: string;
646
+ readonly alternativeCount: number;
647
+ readonly tieBreakReasons: readonly string[];
648
+ readonly refName?: string;
649
+ readonly requiredInvariants: readonly string[];
650
+ readonly satisfiedInvariants: readonly string[];
651
+ readonly selectedPath: readonly {
652
+ readonly dirName: string;
653
+ readonly migrationHash: string;
654
+ readonly from: string;
655
+ readonly to: string;
656
+ readonly invariants: readonly string[];
657
+ }[];
658
+ }
659
+
540
660
  export interface MigrationApplySuccess {
541
661
  readonly migrationsApplied: number;
542
662
  readonly markerHash: string;
543
663
  readonly applied: readonly MigrationApplyAppliedEntry[];
544
664
  readonly summary: string;
665
+ /**
666
+ * Per-space breakdown in canonical schedule order (extensions
667
+ * alphabetically, then app). See {@link AggregatePerSpaceExecutionEntry}.
668
+ * Always present for the aggregate-walking operation.
669
+ */
670
+ readonly perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
671
+ /**
672
+ * Path-decision data for the app member. Present whenever the
673
+ * graph-walk strategy ran for the app (i.e. always for the
674
+ * aggregate-walking apply path). Absent only for the no-op
675
+ * "Already up to date" early return when the app has no plan.
676
+ */
677
+ readonly pathDecision?: MigrationApplyPathDecision;
545
678
  }
546
679
 
547
680
  /**
@@ -2,63 +2,15 @@ import type { Contract } from '@prisma-next/contract/types';
2
2
  import type { ControlExtensionDescriptor } from '@prisma-next/framework-components/control';
3
3
  import type {
4
4
  ContractSpaceAggregate,
5
- DeclaredExtensionEntry,
6
5
  LoadAggregateError,
7
6
  LoadAggregateInput,
8
7
  LoadAggregateOutput,
9
8
  } from '@prisma-next/migration-tools/aggregate';
10
9
  import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
10
+ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
11
11
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
12
12
  import { CliStructuredError } from './cli-errors';
13
-
14
- /**
15
- * Structural shape the aggregate loader needs from each declared
16
- * `Config.extensionPacks` entry. Mirrors the SQL family's
17
- * `SqlControlExtensionDescriptor.contractSpace` shape but kept
18
- * structural so the loader doesn't depend on the SQL family.
19
- */
20
- type ExtensionPackForAggregate = {
21
- readonly id: string;
22
- readonly targetId: string;
23
- readonly contractSpace?: {
24
- readonly contractJson: unknown;
25
- readonly headRef: { readonly hash: string; readonly invariants: readonly string[] };
26
- };
27
- };
28
-
29
- /**
30
- * Convert the CLI's `Config.extensionPacks` array into the loader's
31
- * `DeclaredExtensionEntry[]` shape.
32
- *
33
- * The loader hashes `contractSpace.contractJson` to compare against the
34
- * on-disk `refs/head.json.hash` (drift detection). Rather than re-running
35
- * the canonical-JSON + SHA-256 pipeline at the CLI surface, we look up
36
- * the descriptor's pre-computed `headRef.hash` via reference identity
37
- * on the contract JSON value — the loader passes the same
38
- * `entry.contractSpace.contractJson` reference through to the hasher,
39
- * so identity-keyed lookup is safe.
40
- */
41
- function toDeclaredExtensions(extensionPacks: ReadonlyArray<ExtensionPackForAggregate>): {
42
- readonly entries: ReadonlyArray<DeclaredExtensionEntry>;
43
- readonly hashByContractJson: Map<unknown, string>;
44
- } {
45
- const entries: DeclaredExtensionEntry[] = [];
46
- const hashByContractJson = new Map<unknown, string>();
47
- for (const pack of extensionPacks) {
48
- const entry: DeclaredExtensionEntry = pack.contractSpace
49
- ? {
50
- id: pack.id,
51
- targetId: pack.targetId,
52
- contractSpace: { contractJson: pack.contractSpace.contractJson },
53
- }
54
- : { id: pack.id, targetId: pack.targetId };
55
- entries.push(entry);
56
- if (pack.contractSpace) {
57
- hashByContractJson.set(pack.contractSpace.contractJson, pack.contractSpace.headRef.hash);
58
- }
59
- }
60
- return { entries, hashByContractJson };
61
- }
13
+ import { toDeclaredExtensions, toExtensionInputs } from './extension-pack-inputs';
62
14
 
63
15
  /**
64
16
  * Render a {@link LoadAggregateError} into a CLI structured-error
@@ -187,16 +139,32 @@ export interface BuildAggregateInputs<TFamilyId extends string, TTargetId extend
187
139
  readonly appContract: Contract;
188
140
  readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
189
141
  readonly validateContract: (contractJson: unknown) => Contract;
142
+ /**
143
+ * App-space migration packages to hydrate the app member's
144
+ * migration graph with. Defaults to `[]` (matches the `db init` /
145
+ * `db update` daily-driver behaviour, where the app's authored
146
+ * `migrations/` graph is not walked — the planner uses the synth
147
+ * strategy for the app member instead).
148
+ *
149
+ * `migration apply` callers thread the user's authored app-space
150
+ * packages (loaded via `loadMigrationPackages(appMigrationsDir)`)
151
+ * through here so the graph-walk strategy can plot a path through
152
+ * them — the prod-time replay path explicitly forbids synth.
153
+ */
154
+ readonly appMigrationPackages?: ReadonlyArray<OnDiskMigrationPackage>;
190
155
  }
191
156
 
192
157
  /**
193
158
  * Run the aggregate loader at the CLI surface, mapping any
194
159
  * {@link LoadAggregateError} into a {@link CliStructuredError} envelope.
195
160
  *
196
- * App-side migration packages are intentionally not threaded through:
197
- * `db init` / `db update` go through the planner's `synth` strategy for
198
- * the app member (driven by `callerPolicy.ignoreGraphFor`), so the
199
- * app's authored `migrations/` graph is not walked.
161
+ * App-side migration packages flow through `inputs.appMigrationPackages`
162
+ * (defaulting to `[]`). `db init` / `db update` leave it empty: the
163
+ * planner's `synth` strategy is used for the app member (driven by
164
+ * `callerPolicy.ignoreGraphFor`), so the app's authored `migrations/`
165
+ * graph does not need to be walked. `migration apply` threads the
166
+ * already-loaded app-space packages through so the graph-walk strategy
167
+ * can plot a path through them — replay forbids synth.
200
168
  *
201
169
  * @see specs/contract-space-aggregate-spec.md § Loader.
202
170
  */
@@ -207,7 +175,7 @@ export async function buildContractSpaceAggregate<
207
175
  inputs: BuildAggregateInputs<TFamilyId, TTargetId>,
208
176
  ): Promise<Result<ContractSpaceAggregate, CliStructuredError>> {
209
177
  const { entries, hashByContractJson } = toDeclaredExtensions(
210
- inputs.extensionPacks as ReadonlyArray<ExtensionPackForAggregate>,
178
+ toExtensionInputs(inputs.extensionPacks),
211
179
  );
212
180
 
213
181
  const loadInput: LoadAggregateInput = {
@@ -225,7 +193,7 @@ export async function buildContractSpaceAggregate<
225
193
  }
226
194
  return precomputed;
227
195
  },
228
- appMigrationPackages: [],
196
+ appMigrationPackages: inputs.appMigrationPackages ?? [],
229
197
  };
230
198
 
231
199
  const result: LoadAggregateOutput = await loadContractSpaceAggregate(loadInput);
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Single descriptor-import boundary for CLI consumers of `Config.extensionPacks`.
3
+ *
4
+ * Every CLI command / utility that reads an extension descriptor's
5
+ * `contractSpace` projection (loader, migrate-pass, extension-migrations
6
+ * pass, migration commands) goes through {@link toExtensionInputs}. The
7
+ * structural cast `pack as { contractSpace?: ... }` lives **only** here —
8
+ * downstream code consumes the canonical shape and maps it to its own
9
+ * narrower shape via the per-consumer adapters below.
10
+ *
11
+ * The CLI receives extension descriptors typed against the SQL family
12
+ * (or any other family in the future); this helper only depends on the
13
+ * structural shape of `contractSpace`. SQL-family callers pass the same
14
+ * `contractJson` / `headRef.hash` value through unchanged.
15
+ */
16
+ import type { DeclaredExtensionEntry } from '@prisma-next/migration-tools/aggregate';
17
+ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
18
+ import type { MigrationOps } from '@prisma-next/migration-tools/package';
19
+ import type { ExtensionMigrationsExtensionInput } from './contract-space-extension-migrations-pass';
20
+ import type { MigrateExtensionInput } from './contract-space-migrate-pass';
21
+
22
+ /**
23
+ * In-memory authored migration package shipped by an extension descriptor.
24
+ * Mirrors the `MigrationPackage` shape from
25
+ * `@prisma-next/framework-components/control` minus `dirPath`; redeclared
26
+ * structurally here so the helper does not couple to the SQL family's
27
+ * `ExtensionMigrationPackage` type.
28
+ */
29
+ export interface DescriptorMigrationPackage {
30
+ readonly dirName: string;
31
+ readonly metadata: MigrationMetadata;
32
+ readonly ops: MigrationOps;
33
+ }
34
+
35
+ /**
36
+ * The most-general projection of a single declared extension pack
37
+ * needed by the CLI's descriptor-import boundary.
38
+ *
39
+ * - `id` / `targetId` are always present.
40
+ * - `contractSpace` is present only when the extension declares one.
41
+ * When present, it carries the canonical inputs every downstream
42
+ * consumer needs — `contractJson`, `headRef`, and the descriptor's
43
+ * pre-built migration packages.
44
+ */
45
+ export interface ExtensionPackInput {
46
+ readonly id: string;
47
+ readonly targetId: string;
48
+ readonly contractSpace?: {
49
+ readonly contractJson: unknown;
50
+ readonly headRef: {
51
+ readonly hash: string;
52
+ readonly invariants: readonly string[];
53
+ };
54
+ readonly migrations: readonly DescriptorMigrationPackage[];
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Structural shape we read off each `Config.extensionPacks` entry.
60
+ *
61
+ * The CLI is the descriptor-import boundary; `extensionPacks` is the only
62
+ * surface where the SQL-family-typed `ControlExtensionDescriptor` flows
63
+ * into framework-neutral helpers. The structural cast lives here, and
64
+ * here alone — every other CLI consumer reads the canonical
65
+ * {@link ExtensionPackInput} shape produced by {@link toExtensionInputs}.
66
+ */
67
+ type ExtensionPackLike = {
68
+ readonly id: string;
69
+ readonly targetId: string;
70
+ readonly contractSpace?: {
71
+ readonly contractJson: unknown;
72
+ readonly headRef: {
73
+ readonly hash: string;
74
+ readonly invariants: readonly string[];
75
+ };
76
+ readonly migrations?: readonly DescriptorMigrationPackage[];
77
+ };
78
+ };
79
+
80
+ /**
81
+ * Project the CLI's `Config.extensionPacks` array into the canonical
82
+ * {@link ExtensionPackInput} shape. The single `as ExtensionPackLike`
83
+ * structural cast in the CLI lives inside this function.
84
+ */
85
+ export function toExtensionInputs(
86
+ extensionPacks: ReadonlyArray<unknown>,
87
+ ): readonly ExtensionPackInput[] {
88
+ return extensionPacks.map((raw) => {
89
+ const pack = raw as ExtensionPackLike;
90
+ if (pack.contractSpace === undefined) {
91
+ return { id: pack.id, targetId: pack.targetId };
92
+ }
93
+ return {
94
+ id: pack.id,
95
+ targetId: pack.targetId,
96
+ contractSpace: {
97
+ contractJson: pack.contractSpace.contractJson,
98
+ headRef: pack.contractSpace.headRef,
99
+ migrations: pack.contractSpace.migrations ?? [],
100
+ },
101
+ };
102
+ });
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Per-consumer adapters: take the canonical `ExtensionPackInput[]` and
107
+ // project to whatever narrower shape the downstream primitive needs.
108
+ // ---------------------------------------------------------------------------
109
+
110
+ /**
111
+ * Aggregate-loader projection: surfaces `targetId` + `contractSpace.contractJson`
112
+ * to {@link import('./contract-space-aggregate-loader').buildContractSpaceAggregate}
113
+ * and a `hashByContractJson` map keyed by the same `contractJson` reference
114
+ * the loader hands to its hash callback.
115
+ */
116
+ export function toDeclaredExtensions(inputs: ReadonlyArray<ExtensionPackInput>): {
117
+ readonly entries: ReadonlyArray<DeclaredExtensionEntry>;
118
+ readonly hashByContractJson: Map<unknown, string>;
119
+ } {
120
+ const entries: DeclaredExtensionEntry[] = [];
121
+ const hashByContractJson = new Map<unknown, string>();
122
+ for (const pack of inputs) {
123
+ if (pack.contractSpace) {
124
+ entries.push({
125
+ id: pack.id,
126
+ targetId: pack.targetId,
127
+ contractSpace: { contractJson: pack.contractSpace.contractJson },
128
+ });
129
+ hashByContractJson.set(pack.contractSpace.contractJson, pack.contractSpace.headRef.hash);
130
+ } else {
131
+ entries.push({ id: pack.id, targetId: pack.targetId });
132
+ }
133
+ }
134
+ return { entries, hashByContractJson };
135
+ }
136
+
137
+ /** Migrate-time per-space pass projection. */
138
+ export function toMigratePassInputs(
139
+ inputs: ReadonlyArray<ExtensionPackInput>,
140
+ ): readonly MigrateExtensionInput[] {
141
+ return inputs.map((pack) =>
142
+ pack.contractSpace
143
+ ? {
144
+ id: pack.id,
145
+ contractSpace: {
146
+ contractJson: pack.contractSpace.contractJson,
147
+ headRef: pack.contractSpace.headRef,
148
+ },
149
+ }
150
+ : { id: pack.id },
151
+ );
152
+ }
153
+
154
+ /** Extension-migrations materialisation pass projection. */
155
+ export function toExtensionMigrationsInputs(
156
+ inputs: ReadonlyArray<ExtensionPackInput>,
157
+ ): readonly ExtensionMigrationsExtensionInput[] {
158
+ return inputs.map((pack) =>
159
+ pack.contractSpace
160
+ ? {
161
+ id: pack.id,
162
+ contractSpace: {
163
+ contractJson: pack.contractSpace.contractJson,
164
+ headRef: pack.contractSpace.headRef,
165
+ migrations: pack.contractSpace.migrations,
166
+ },
167
+ }
168
+ : { id: pack.id },
169
+ );
170
+ }