@prisma-next/cli 0.5.0-dev.9 → 0.5.1

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 (185) hide show
  1. package/README.md +61 -26
  2. package/dist/cli-errors-B9OBbled.d.mts +3 -0
  3. package/dist/cli-errors-D3_sMh2K.mjs +33 -0
  4. package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
  5. package/dist/cli.mjs +16 -78
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/client-BCnP7cHo.mjs +1485 -0
  8. package/dist/client-BCnP7cHo.mjs.map +1 -0
  9. package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
  10. package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -1
  12. package/dist/commands/contract-emit.mjs +2 -4
  13. package/dist/commands/contract-infer.d.mts.map +1 -1
  14. package/dist/commands/contract-infer.mjs +2 -4
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +16 -13
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.d.mts.map +1 -1
  19. package/dist/commands/db-schema.mjs +6 -7
  20. package/dist/commands/db-schema.mjs.map +1 -1
  21. package/dist/commands/db-sign.d.mts.map +1 -1
  22. package/dist/commands/db-sign.mjs +9 -9
  23. package/dist/commands/db-sign.mjs.map +1 -1
  24. package/dist/commands/db-update.d.mts.map +1 -1
  25. package/dist/commands/db-update.mjs +15 -13
  26. package/dist/commands/db-update.mjs.map +1 -1
  27. package/dist/commands/db-verify.d.mts.map +1 -1
  28. package/dist/commands/db-verify.mjs +1 -321
  29. package/dist/commands/migration-apply.d.mts +28 -13
  30. package/dist/commands/migration-apply.d.mts.map +1 -1
  31. package/dist/commands/migration-apply.mjs +55 -151
  32. package/dist/commands/migration-apply.mjs.map +1 -1
  33. package/dist/commands/migration-new.d.mts +0 -1
  34. package/dist/commands/migration-new.d.mts.map +1 -1
  35. package/dist/commands/migration-new.mjs +34 -40
  36. package/dist/commands/migration-new.mjs.map +1 -1
  37. package/dist/commands/migration-plan.d.mts +33 -6
  38. package/dist/commands/migration-plan.d.mts.map +1 -1
  39. package/dist/commands/migration-plan.mjs +2 -348
  40. package/dist/commands/migration-ref.d.mts +1 -1
  41. package/dist/commands/migration-ref.d.mts.map +1 -1
  42. package/dist/commands/migration-ref.mjs +8 -12
  43. package/dist/commands/migration-ref.mjs.map +1 -1
  44. package/dist/commands/migration-show.d.mts +64 -10
  45. package/dist/commands/migration-show.d.mts.map +1 -1
  46. package/dist/commands/migration-show.mjs +166 -60
  47. package/dist/commands/migration-show.mjs.map +1 -1
  48. package/dist/commands/migration-status.d.mts +126 -5
  49. package/dist/commands/migration-status.d.mts.map +1 -1
  50. package/dist/commands/migration-status.mjs +2 -4
  51. package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
  52. package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
  53. package/dist/config-loader.d.mts +0 -1
  54. package/dist/config-loader.d.mts.map +1 -1
  55. package/dist/config-loader.mjs +2 -3
  56. package/dist/contract-emit-9DBda5Ou.mjs +150 -0
  57. package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
  58. package/dist/contract-emit-B77TsJqf.mjs +327 -0
  59. package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
  60. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
  61. package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
  62. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-ByxhPjpW.mjs} +13 -22
  63. package/dist/contract-infer-ByxhPjpW.mjs.map +1 -0
  64. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs +160 -0
  65. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs.map +1 -0
  66. package/dist/db-verify-Czm5T-J4.mjs +404 -0
  67. package/dist/db-verify-Czm5T-J4.mjs.map +1 -0
  68. package/dist/exports/config-types.mjs +1 -2
  69. package/dist/exports/control-api.d.mts +101 -586
  70. package/dist/exports/control-api.d.mts.map +1 -1
  71. package/dist/exports/control-api.mjs +4 -6
  72. package/dist/exports/index.d.mts.map +1 -1
  73. package/dist/exports/index.mjs +28 -30
  74. package/dist/exports/index.mjs.map +1 -1
  75. package/dist/exports/init-output.d.mts +2 -4
  76. package/dist/exports/init-output.d.mts.map +1 -1
  77. package/dist/exports/init-output.mjs +2 -3
  78. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
  79. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
  80. package/dist/global-flags-Icqpxk23.d.mts +12 -0
  81. package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
  82. package/dist/helpers-eqdN8tH6.mjs +25 -0
  83. package/dist/helpers-eqdN8tH6.mjs.map +1 -0
  84. package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
  85. package/dist/init-DETSgw3h.mjs.map +1 -0
  86. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-DxdBd4Er.mjs} +10 -11
  87. package/dist/inspect-live-schema-DxdBd4Er.mjs.map +1 -0
  88. package/dist/migration-cli.d.mts +41 -12
  89. package/dist/migration-cli.d.mts.map +1 -1
  90. package/dist/migration-cli.mjs +309 -86
  91. package/dist/migration-cli.mjs.map +1 -1
  92. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-BdV8JYXV.mjs} +8 -9
  93. package/dist/migration-command-scaffold-BdV8JYXV.mjs.map +1 -0
  94. package/dist/migration-plan-mRu5K81L.mjs +494 -0
  95. package/dist/migration-plan-mRu5K81L.mjs.map +1 -0
  96. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-By9G5p2H.mjs} +270 -65
  97. package/dist/migration-status-By9G5p2H.mjs.map +1 -0
  98. package/dist/migrations-CTsyBXCA.mjs +229 -0
  99. package/dist/migrations-CTsyBXCA.mjs.map +1 -0
  100. package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
  101. package/dist/output-B16Kefzx.mjs.map +1 -0
  102. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
  103. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
  104. package/dist/result-handler-rmPVKIP2.mjs +25 -0
  105. package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
  106. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  107. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
  108. package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
  109. package/dist/types-LItU7E4l.d.mts +856 -0
  110. package/dist/types-LItU7E4l.d.mts.map +1 -0
  111. package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
  112. package/dist/verify-CiwNWM9N.mjs.map +1 -0
  113. package/package.json +28 -26
  114. package/src/cli.ts +32 -6
  115. package/src/commands/contract-emit.ts +67 -163
  116. package/src/commands/contract-infer.ts +7 -20
  117. package/src/commands/db-init.ts +15 -3
  118. package/src/commands/db-update.ts +9 -4
  119. package/src/commands/db-verify.ts +47 -15
  120. package/src/commands/init/index.ts +1 -1
  121. package/src/commands/init/init.ts +2 -2
  122. package/src/commands/init/templates/code-templates.ts +26 -18
  123. package/src/commands/inspect-live-schema.ts +10 -5
  124. package/src/commands/migration-apply.ts +114 -212
  125. package/src/commands/migration-new.ts +42 -45
  126. package/src/commands/migration-plan.ts +213 -75
  127. package/src/commands/migration-ref.ts +8 -7
  128. package/src/commands/migration-show.ts +274 -70
  129. package/src/commands/migration-status.ts +491 -64
  130. package/src/config-path-validation.ts +0 -1
  131. package/src/control-api/client.ts +85 -5
  132. package/src/control-api/contract-enrichment.ts +6 -4
  133. package/src/control-api/operations/apply-aggregate.ts +290 -0
  134. package/src/control-api/operations/contract-emit.ts +198 -115
  135. package/src/control-api/operations/db-apply-aggregate.ts +399 -0
  136. package/src/control-api/operations/db-init.ts +51 -253
  137. package/src/control-api/operations/db-update.ts +66 -183
  138. package/src/control-api/operations/db-verify.ts +342 -0
  139. package/src/control-api/operations/migration-apply.ts +430 -131
  140. package/src/control-api/types.ts +278 -29
  141. package/src/exports/control-api.ts +15 -3
  142. package/src/load-ts-contract.ts +28 -26
  143. package/src/migration-cli.ts +445 -122
  144. package/src/utils/cli-errors.ts +49 -2
  145. package/src/utils/combine-schema-results.ts +84 -0
  146. package/src/utils/command-helpers.ts +69 -25
  147. package/src/utils/contract-space-aggregate-loader.ts +177 -0
  148. package/src/utils/contract-space-seed-phase.ts +201 -0
  149. package/src/utils/emit-queue.ts +26 -0
  150. package/src/utils/extension-pack-inputs.ts +162 -0
  151. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  152. package/src/utils/formatters/migrations.ts +255 -77
  153. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  154. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  155. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  156. package/dist/client-CrsnY58k.mjs +0 -997
  157. package/dist/client-CrsnY58k.mjs.map +0 -1
  158. package/dist/commands/db-verify.mjs.map +0 -1
  159. package/dist/commands/migration-plan.mjs.map +0 -1
  160. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  161. package/dist/contract-emit--feXyNd7.mjs +0 -4
  162. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  163. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  164. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  165. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  166. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  167. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  168. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  169. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  170. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  171. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  172. package/dist/init-C5220SY9.mjs.map +0 -1
  173. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  174. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  175. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  176. package/dist/migrations-Bo5WtTla.mjs +0 -153
  177. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  178. package/dist/output-BpcQrnnq.mjs.map +0 -1
  179. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  180. package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
  181. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  182. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  183. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
  184. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  185. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -12,11 +12,15 @@ import type {
12
12
  CoreSchemaView,
13
13
  MigrationPlannerConflict,
14
14
  MigrationPlanOperation,
15
+ OperationPreview,
15
16
  SignDatabaseResult,
16
17
  VerifyDatabaseResult,
17
18
  VerifyDatabaseSchemaResult,
18
19
  } from '@prisma-next/framework-components/control';
20
+ import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
21
+ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
19
22
  import type { Result } from '@prisma-next/utils/result';
23
+ import type { ExecuteDbVerifyResult } from './operations/db-verify';
20
24
 
21
25
  // ============================================================================
22
26
  // Client Options
@@ -63,6 +67,7 @@ export interface ControlClientOptions {
63
67
  export type ControlActionName =
64
68
  | 'dbInit'
65
69
  | 'dbUpdate'
70
+ | 'dbVerify'
66
71
  | 'migrationApply'
67
72
  | 'verify'
68
73
  | 'schemaVerify'
@@ -189,6 +194,13 @@ export interface DbInitOptions {
189
194
  * The type is driver-specific (e.g., string URL for Postgres).
190
195
  */
191
196
  readonly connection?: unknown;
197
+ /**
198
+ * On-disk migrations directory. Always required — every `db init`
199
+ * routes through the per-space flow, which reads on-disk
200
+ * `refs/head.json` and extension destination contracts from this
201
+ * root.
202
+ */
203
+ readonly migrationsDir: string;
192
204
  /** Optional progress callback for observing operation progress */
193
205
  readonly onProgress?: OnControlProgress;
194
206
  }
@@ -219,10 +231,35 @@ export interface DbUpdateOptions {
219
231
  * or re-run with -y/--yes.
220
232
  */
221
233
  readonly acceptDataLoss?: boolean;
234
+ /**
235
+ * On-disk migrations directory. Always required — every `db update`
236
+ * routes through the per-space flow, which reads on-disk
237
+ * `refs/head.json` and extension destination contracts from this
238
+ * root.
239
+ */
240
+ readonly migrationsDir: string;
222
241
  /** Optional progress callback for observing operation progress */
223
242
  readonly onProgress?: OnControlProgress;
224
243
  }
225
244
 
245
+ /**
246
+ * Options for the dbVerify operation.
247
+ *
248
+ * Drives the loader → aggregate-verifier pipeline. `strict` maps to
249
+ * `verifyAggregate({ mode: 'strict' | 'lenient' })`; `skipSchema`
250
+ * mirrors the `--marker-only` CLI flag and short-circuits the schema
251
+ * portion of the verifier.
252
+ */
253
+ export interface DbVerifyOptions {
254
+ readonly contract: unknown;
255
+ readonly migrationsDir: string;
256
+ readonly strict: boolean;
257
+ readonly skipSchema: boolean;
258
+ readonly skipMarker: boolean;
259
+ readonly connection?: unknown;
260
+ readonly onProgress?: OnControlProgress;
261
+ }
262
+
226
263
  /**
227
264
  * Options for the introspect operation.
228
265
  */
@@ -272,6 +309,42 @@ export interface EmitOptions {
272
309
  // Result Types
273
310
  // ============================================================================
274
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
+ * Every space involved in a run is observable in the success summary,
320
+ * including its post-apply marker — the per-space marker is visible
321
+ * to the user instead of being collapsed into a single ambiguous
322
+ * top-level hash.
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
+
275
348
  /**
276
349
  * Successful dbInit result.
277
350
  */
@@ -283,7 +356,14 @@ export interface DbInitSuccess {
283
356
  readonly label: string;
284
357
  readonly operationClass: string;
285
358
  }>;
286
- readonly sql?: ReadonlyArray<string>;
359
+ /**
360
+ * Family-agnostic textual preview of the planned operations, e.g.
361
+ * `language: 'sql'` for SQL families and `language: 'mongodb-shell'`
362
+ * for the Mongo family. Replaces the previous `sql?: readonly string[]`
363
+ * field; consumers that previously read `plan.sql` should read
364
+ * `plan.preview?.statements.map((s) => s.text)`.
365
+ */
366
+ readonly preview?: OperationPreview;
287
367
  };
288
368
  readonly destination: {
289
369
  readonly storageHash: string;
@@ -297,6 +377,14 @@ export interface DbInitSuccess {
297
377
  readonly storageHash: string;
298
378
  readonly profileHash?: string;
299
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>;
300
388
  readonly summary: string;
301
389
  }
302
390
 
@@ -341,7 +429,14 @@ export interface DbUpdateSuccess {
341
429
  readonly label: string;
342
430
  readonly operationClass: string;
343
431
  }>;
344
- readonly sql?: ReadonlyArray<string>;
432
+ /**
433
+ * Family-agnostic textual preview of the planned operations, e.g.
434
+ * `language: 'sql'` for SQL families and `language: 'mongodb-shell'`
435
+ * for the Mongo family. Replaces the previous `sql?: readonly string[]`
436
+ * field; consumers that previously read `plan.sql` should read
437
+ * `plan.preview?.statements.map((s) => s.text)`.
438
+ */
439
+ readonly preview?: OperationPreview;
345
440
  };
346
441
  readonly destination: {
347
442
  readonly storageHash: string;
@@ -355,6 +450,11 @@ export interface DbUpdateSuccess {
355
450
  readonly storageHash: string;
356
451
  readonly profileHash?: string;
357
452
  };
453
+ /**
454
+ * Per-space breakdown in canonical schedule order (extensions
455
+ * alphabetically, then app). See {@link AggregatePerSpaceExecutionEntry}.
456
+ */
457
+ readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
358
458
  readonly summary: string;
359
459
  }
360
460
 
@@ -427,36 +527,46 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
427
527
  // ============================================================================
428
528
 
429
529
  /**
430
- * A pre-planned migration step ready for execution.
431
- * Contains the manifest metadata and the serialized operations from ops.json.
432
- */
433
- export interface MigrationApplyStep {
434
- readonly dirName: string;
435
- readonly from: string;
436
- readonly to: string;
437
- readonly toContract: Contract;
438
- readonly operations: readonly MigrationPlanOperation[];
439
- }
440
-
441
- /**
442
- * Options for the migrationApply operation.
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.
443
539
  */
444
540
  export interface MigrationApplyOptions {
541
+ /** Already-validated app contract (the canonical "where we are heading" hash). */
542
+ readonly contract: unknown;
543
+ /** Migrations root directory (`migrations/` under the project). */
544
+ readonly migrationsDir: string;
445
545
  /**
446
- * Hash of the database state this apply path starts from.
447
- * This is resolved by the caller (typically the CLI orchestration layer).
546
+ * Already-loaded app-space migration packages. The CLI loads these
547
+ * via `loadMigrationPackages(appMigrationsDir)` before invoking
548
+ * `migrationApply`.
448
549
  */
449
- readonly originHash: string;
550
+ readonly appMigrationPackages: ReadonlyArray<OnDiskMigrationPackage>;
450
551
  /**
451
- * Hash of the target contract this apply path must reach.
452
- * This is resolved by the caller (typically the CLI orchestration layer).
552
+ * Optional app-space ref override. When provided, the app member's
553
+ * graph-walk targets this hash instead of `contract.storage.storageHash`.
554
+ * Extension members always walk to their own `headRef.hash`.
453
555
  */
454
- readonly destinationHash: string;
556
+ readonly refHash?: string;
455
557
  /**
456
- * Ordered list of migrations to execute from originHash to destinationHash.
457
- * The execution layer does not choose defaults; it only executes this explicit path.
558
+ * Required invariants on the user-supplied app-space ref. Threaded
559
+ * into the graph-walk's `required` calculation so the planner picks
560
+ * an invariant-bearing path. Ignored when `refHash` is absent.
458
561
  */
459
- readonly pendingMigrations: readonly MigrationApplyStep[];
562
+ readonly refInvariants?: readonly string[];
563
+ /**
564
+ * Resolved name of the user-supplied app-space ref (the literal the
565
+ * user passed to `--ref`). Decorates `pathDecision.refName` and any
566
+ * `MIGRATION.NO_INVARIANT_PATH` envelope raised during graph-walk.
567
+ * Ignored when `refHash` is absent.
568
+ */
569
+ readonly refName?: string;
460
570
  /**
461
571
  * Database connection. If provided, migrationApply will connect before executing.
462
572
  * If omitted, the client must already be connected.
@@ -467,23 +577,102 @@ export interface MigrationApplyOptions {
467
577
  }
468
578
 
469
579
  /**
470
- * Record of a successfully applied migration.
580
+ * A single on-disk migration package surfaced to the operation. The
581
+ * SQL family already produces this shape via `loadMigrationPackages`;
582
+ * the operation hands it through to the framework-neutral aggregate
583
+ * loader's `appMigrationPackages` slot.
584
+ *
585
+ * (Originally named `MigrationApplyStep` for the legacy single-space
586
+ * apply path; the name is kept for compatibility with existing CLI
587
+ * callers and tests.)
588
+ */
589
+ export interface MigrationApplyStep {
590
+ readonly dirName: string;
591
+ readonly from: string | null;
592
+ readonly to: string;
593
+ readonly toContract: Contract;
594
+ readonly operations: readonly MigrationPlanOperation[];
595
+ /**
596
+ * Sorted, deduplicated invariant ids from `migration.json.providedInvariants`.
597
+ * Verified at load time by `readMigrationPackage` (manifest copy must equal
598
+ * the value derived from `ops.json`).
599
+ */
600
+ readonly providedInvariants: readonly string[];
601
+ }
602
+
603
+ /**
604
+ * Record of a successfully applied per-space migration. One entry per
605
+ * contract space that had pending migrations — empty `applied` means
606
+ * every space was already at its head.
607
+ */
608
+ /**
609
+ * One entry per authored migration package applied. Preserves the
610
+ * single-space `migrationsApplied` count semantics (each entry is
611
+ * one migration directory) so `applied.length === migrationsApplied`.
612
+ *
613
+ * Per-space aggregate detail (markers, ops grouped by space) lives
614
+ * on `perSpace[]`; this list is the per-edge view.
471
615
  */
472
616
  export interface MigrationApplyAppliedEntry {
617
+ readonly spaceId: string;
473
618
  readonly dirName: string;
619
+ readonly migrationHash: string;
474
620
  readonly from: string;
475
621
  readonly to: string;
476
622
  readonly operationsExecuted: number;
477
623
  }
478
624
 
479
625
  /**
480
- * Successful migrationApply result.
626
+ * Successful migrationApply result. Carries both the legacy
627
+ * single-space fields (`markerHash` is the **app member's** post-apply
628
+ * marker, surfaced for back-compat with single-space callers) and the
629
+ * per-space breakdown (`perSpace` — markers / operations in canonical
630
+ * schedule order).
481
631
  */
632
+ /**
633
+ * Path-decision summary for the **app member** post-apply. Surfaced
634
+ * for back-compat with single-space callers (and the cli-journeys
635
+ * suite, which inspects `requiredInvariants`/`satisfiedInvariants`/
636
+ * `selectedPath` to validate invariant routing).
637
+ *
638
+ * Per-space path decisions for extension members are not surfaced —
639
+ * extensions own their own ref/invariant control.
640
+ */
641
+ export interface MigrationApplyPathDecision {
642
+ readonly fromHash: string;
643
+ readonly toHash: string;
644
+ readonly alternativeCount: number;
645
+ readonly tieBreakReasons: readonly string[];
646
+ readonly refName?: string;
647
+ readonly requiredInvariants: readonly string[];
648
+ readonly satisfiedInvariants: readonly string[];
649
+ readonly selectedPath: readonly {
650
+ readonly dirName: string;
651
+ readonly migrationHash: string;
652
+ readonly from: string;
653
+ readonly to: string;
654
+ readonly invariants: readonly string[];
655
+ }[];
656
+ }
657
+
482
658
  export interface MigrationApplySuccess {
483
659
  readonly migrationsApplied: number;
484
660
  readonly markerHash: string;
485
661
  readonly applied: readonly MigrationApplyAppliedEntry[];
486
662
  readonly summary: string;
663
+ /**
664
+ * Per-space breakdown in canonical schedule order (extensions
665
+ * alphabetically, then app). See {@link AggregatePerSpaceExecutionEntry}.
666
+ * Always present for the aggregate-walking operation.
667
+ */
668
+ readonly perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
669
+ /**
670
+ * Path-decision data for the app member. Present whenever the
671
+ * graph-walk strategy ran for the app (i.e. always for the
672
+ * aggregate-walking apply path). Absent only for the no-op
673
+ * "Already up to date" early return when the app has no plan.
674
+ */
675
+ readonly pathDecision?: MigrationApplyPathDecision;
487
676
  }
488
677
 
489
678
  /**
@@ -512,18 +701,31 @@ export type MigrationApplyResult = Result<MigrationApplySuccess, MigrationApplyF
512
701
 
513
702
  /**
514
703
  * Options for the standalone executeContractEmit function.
515
- * Used by tooling (e.g., Vite plugin) that needs to emit contracts
516
- * without the full ControlClient infrastructure.
704
+ *
705
+ * `executeContractEmit` is the canonical publication path for both the
706
+ * `prisma-next contract emit` CLI command and the `@prisma-next/vite-plugin-contract-emit`
707
+ * Vite plugin. Do not duplicate the load → emit → publish dance elsewhere; if a
708
+ * caller needs additional behavior, extend this options shape and update the
709
+ * single implementation rather than building a parallel publication path.
710
+ *
711
+ * Concurrent calls for the same output JSON path are serialized per-output via
712
+ * a FIFO queue; concurrent calls for distinct outputs run in parallel.
517
713
  */
518
714
  export interface ContractEmitOptions {
519
715
  /** Path to the prisma-next.config.ts file */
520
716
  readonly configPath: string;
521
- /** Optional AbortSignal for cancellation support */
717
+ /** Optional AbortSignal for cancelling the in-flight emit */
522
718
  readonly signal?: AbortSignal;
719
+ /** Optional progress callback for observing source-resolution and emit spans */
720
+ readonly onProgress?: OnControlProgress;
523
721
  }
524
722
 
525
723
  /**
526
724
  * Result from the standalone executeContractEmit function.
725
+ *
726
+ * Always describes the bytes that were just published to disk. Failures throw
727
+ * (config / source-resolution / emit / publish) — callers do not need to
728
+ * branch on a result discriminator.
527
729
  */
528
730
  export interface ContractEmitResult {
529
731
  /** Hash of the storage contract (schema-level) */
@@ -539,6 +741,12 @@ export interface ContractEmitResult {
539
741
  /** Path to the emitted contract.d.ts file */
540
742
  readonly dts: string;
541
743
  };
744
+ /**
745
+ * Warning surfaced by `validateContractDeps` after a successful publication.
746
+ * Callers (CLI, Vite plugin) decide how to render this; the operation does
747
+ * not write to stderr itself. Undefined when no warning was raised.
748
+ */
749
+ readonly validationWarning?: string;
542
750
  }
543
751
 
544
752
  // ============================================================================
@@ -634,6 +842,21 @@ export interface ControlClient {
634
842
  */
635
843
  dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult>;
636
844
 
845
+ /**
846
+ * Verifies the database against every contract space (app + extensions).
847
+ *
848
+ * Loader → aggregate-verifier pipeline:
849
+ * - The loader catches layout / drift / disjointness violations.
850
+ * - The aggregate verifier surfaces marker-vs-on-disk drift and orphan
851
+ * markers, and (unless `skipSchema` is true) per-space schema
852
+ * verification with pre-projection (closes F23).
853
+ *
854
+ * @returns Result pattern: per-space schema results on success;
855
+ * structured CLI error on marker / loader failure.
856
+ * @throws If not connected or infrastructure failure
857
+ */
858
+ dbVerify(options: DbVerifyOptions): Promise<ExecuteDbVerifyResult>;
859
+
637
860
  /**
638
861
  * Reads the contract marker from the database.
639
862
  * Returns null if no marker exists (fresh database).
@@ -642,6 +865,13 @@ export interface ControlClient {
642
865
  */
643
866
  readMarker(): Promise<ContractMarkerRecord | null>;
644
867
 
868
+ /**
869
+ * Reads every marker row (one per contract space). Used by the
870
+ * per-space verifier to detect orphan marker rows and marker-vs-on-disk
871
+ * drift after a database connection has been established.
872
+ */
873
+ readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
874
+
645
875
  /**
646
876
  * Applies pre-planned on-disk migrations to the database.
647
877
  * Each migration runs in its own transaction with full execution checks.
@@ -672,6 +902,25 @@ export interface ControlClient {
672
902
  */
673
903
  toSchemaView(schemaIR: unknown): CoreSchemaView | undefined;
674
904
 
905
+ /**
906
+ * Infers a PSL contract AST from an introspected schema IR.
907
+ * Delegates to the family instance's inferPslContract method.
908
+ *
909
+ * @param schemaIR - The schema IR from introspect()
910
+ * @returns PslDocumentAst if the family supports the capability, undefined otherwise
911
+ */
912
+ inferPslContract(schemaIR: unknown): PslDocumentAst | undefined;
913
+
914
+ /**
915
+ * Renders a textual preview of a migration plan's operations for the CLI's
916
+ * "DDL preview" output. Delegates to the family instance's
917
+ * `toOperationPreview` method.
918
+ *
919
+ * @param operations - The migration plan operations to render
920
+ * @returns OperationPreview if the family supports the capability, undefined otherwise
921
+ */
922
+ toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview | undefined;
923
+
675
924
  /**
676
925
  * Emits the contract to JSON and TypeScript declarations.
677
926
  * This is an offline operation that does NOT require a database connection.
@@ -20,10 +20,20 @@ export { createControlClient } from '../control-api/client';
20
20
 
21
21
  // Contract enrichment (merges framework-derived capabilities and extension pack metadata)
22
22
  export { enrichContract } from '../control-api/contract-enrichment';
23
-
24
- // Standalone operations (for tooling that doesn't need full client)
25
23
  export { executeContractEmit } from '../control-api/operations/contract-emit';
26
-
24
+ // Standalone operations (for tooling that doesn't need full client).
25
+ // These drive the aggregate-pipeline `db init` / `db update` / `db verify`
26
+ // flow against a loaded contract-space aggregate.
27
+ export { type ExecuteDbInitOptions, executeDbInit } from '../control-api/operations/db-init';
28
+ export {
29
+ type ExecuteDbUpdateOptions,
30
+ executeDbUpdate,
31
+ } from '../control-api/operations/db-update';
32
+ export {
33
+ type ExecuteDbVerifyOptions,
34
+ type ExecuteDbVerifyResult,
35
+ executeDbVerify,
36
+ } from '../control-api/operations/db-verify';
27
37
  // CLI-specific types
28
38
  export type {
29
39
  ContractEmitOptions,
@@ -54,3 +64,5 @@ export type {
54
64
  SignOptions,
55
65
  VerifyOptions,
56
66
  } from '../control-api/types';
67
+ // Lifecycle helpers for hosts that publish to many output paths
68
+ export { disposeEmitQueue } from '../utils/emit-queue';
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url';
4
4
  import type { Contract } from '@prisma-next/contract/types';
5
5
  import type { Plugin } from 'esbuild';
6
6
  import { build } from 'esbuild';
7
- import { join } from 'pathe';
7
+ import { join, resolve as resolvePath } from 'pathe';
8
8
 
9
9
  export interface LoadTsContractOptions {
10
10
  readonly allowlist?: ReadonlyArray<string>;
@@ -78,7 +78,21 @@ function validatePurity(value: unknown): void {
78
78
  }
79
79
  }
80
80
 
81
- function createImportAllowlistPlugin(allowlist: ReadonlyArray<string>, entryPath: string): Plugin {
81
+ function createImportAllowlistPlugin(
82
+ allowlist: ReadonlyArray<string>,
83
+ entryPath: string,
84
+ collected: Set<string>,
85
+ ): Plugin {
86
+ // Match against several path forms that esbuild may use as the importer:
87
+ // the absolute resolved entry, the value the caller passed (which may be
88
+ // relative), and the conventional `<stdin>` placeholder. This is more
89
+ // forgiving than `===` against a single form, which broke when esbuild
90
+ // resolved the entry to an absolute path while the caller passed a
91
+ // relative one (or vice versa).
92
+ const entryAbs = resolvePath(entryPath);
93
+ function isFromEntry(importer: string): boolean {
94
+ return importer === entryAbs || importer === entryPath || importer === '<stdin>';
95
+ }
82
96
  return {
83
97
  name: 'import-allowlist',
84
98
  setup(build) {
@@ -89,8 +103,8 @@ function createImportAllowlistPlugin(allowlist: ReadonlyArray<string>, entryPath
89
103
  if (args.path.startsWith('.') || args.path.startsWith('/')) {
90
104
  return undefined;
91
105
  }
92
- const isFromEntryPoint = args.importer === entryPath || args.importer === '<stdin>';
93
- if (isFromEntryPoint && !isAllowedImport(args.path, allowlist)) {
106
+ if (isFromEntry(args.importer) && !isAllowedImport(args.path, allowlist)) {
107
+ collected.add(args.path);
94
108
  return {
95
109
  path: args.path,
96
110
  external: true,
@@ -132,6 +146,13 @@ export async function loadContractFromTs(
132
146
  `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`,
133
147
  );
134
148
 
149
+ // Disallowed imports are collected by the allowlist resolver plugin itself,
150
+ // which has the `importer` context to distinguish entry-direct imports from
151
+ // transitive imports made inside allowlisted (`@prisma-next/*`) dependencies.
152
+ // The metafile is intentionally not re-walked: it would surface internal
153
+ // `node:*` imports inside framework code as false positives.
154
+ const disallowedFromEntry = new Set<string>();
155
+
135
156
  try {
136
157
  const result = await build({
137
158
  entryPoints: [entryPath],
@@ -142,7 +163,7 @@ export async function loadContractFromTs(
142
163
  outfile: tempFile,
143
164
  write: false,
144
165
  metafile: true,
145
- plugins: [createImportAllowlistPlugin(allowlist, entryPath)],
166
+ plugins: [createImportAllowlistPlugin(allowlist, entryPath, disallowedFromEntry)],
146
167
  logLevel: 'error',
147
168
  });
148
169
 
@@ -155,28 +176,9 @@ export async function loadContractFromTs(
155
176
  throw new Error('No output files generated from bundling');
156
177
  }
157
178
 
158
- const disallowedImports: string[] = [];
159
- if (result.metafile) {
160
- const inputs = result.metafile.inputs;
161
- for (const [, inputData] of Object.entries(inputs)) {
162
- const imports =
163
- (inputData as { imports?: Array<{ path: string; external?: boolean }> }).imports || [];
164
- for (const imp of imports) {
165
- if (
166
- imp.external &&
167
- !imp.path.startsWith('.') &&
168
- !imp.path.startsWith('/') &&
169
- !isAllowedImport(imp.path, allowlist)
170
- ) {
171
- disallowedImports.push(imp.path);
172
- }
173
- }
174
- }
175
- }
176
-
177
- if (disallowedImports.length > 0) {
179
+ if (disallowedFromEntry.size > 0) {
178
180
  throw new Error(
179
- `Disallowed imports detected. Only imports matching the allowlist are permitted:\n Allowlist: ${allowlist.join(', ')}\n Disallowed imports: ${disallowedImports.join(', ')}\n\nOnly @prisma-next/* packages are allowed in contract files.`,
181
+ `Disallowed imports detected. Only imports matching the allowlist are permitted:\n Allowlist: ${allowlist.join(', ')}\n Disallowed imports: ${[...disallowedFromEntry].join(', ')}`,
180
182
  );
181
183
  }
182
184