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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +60 -25
  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-qVH-rEgd.mjs +1595 -0
  8. package/dist/client-qVH-rEgd.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 +13 -7
  45. package/dist/commands/migration-show.d.mts.map +1 -1
  46. package/dist/commands/migration-show.mjs +35 -36
  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-BK9YFGEG.mjs} +13 -22
  63. package/dist/contract-infer-BK9YFGEG.mjs.map +1 -0
  64. package/dist/db-verify-C0y1PCO2.mjs +404 -0
  65. package/dist/db-verify-C0y1PCO2.mjs.map +1 -0
  66. package/dist/exports/config-types.mjs +1 -2
  67. package/dist/exports/control-api.d.mts +101 -586
  68. package/dist/exports/control-api.d.mts.map +1 -1
  69. package/dist/exports/control-api.mjs +4 -6
  70. package/dist/exports/index.d.mts.map +1 -1
  71. package/dist/exports/index.mjs +28 -30
  72. package/dist/exports/index.mjs.map +1 -1
  73. package/dist/exports/init-output.d.mts +2 -4
  74. package/dist/exports/init-output.d.mts.map +1 -1
  75. package/dist/exports/init-output.mjs +2 -3
  76. package/dist/extension-pack-inputs-C7xgE-vv.mjs +74 -0
  77. package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
  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-CWYxGKlb.mjs} +10 -11
  87. package/dist/inspect-live-schema-CWYxGKlb.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-B5dORFEv.mjs} +8 -9
  93. package/dist/migration-command-scaffold-B5dORFEv.mjs.map +1 -0
  94. package/dist/migration-plan-C6lVaHsO.mjs +554 -0
  95. package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
  96. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CZ-D5k7k.mjs} +272 -65
  97. package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
  98. package/dist/migrations-D_UJnpuW.mjs +216 -0
  99. package/dist/migrations-D_UJnpuW.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-D7x-IFLO.d.mts +858 -0
  110. package/dist/types-D7x-IFLO.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 +212 -72
  127. package/src/commands/migration-ref.ts +8 -7
  128. package/src/commands/migration-show.ts +60 -41
  129. package/src/commands/migration-status.ts +483 -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 +397 -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 +424 -131
  140. package/src/control-api/types.ts +280 -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 +204 -0
  148. package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
  149. package/src/utils/contract-space-migrate-pass.ts +156 -0
  150. package/src/utils/emit-queue.ts +26 -0
  151. package/src/utils/extension-pack-inputs.ts +170 -0
  152. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  153. package/src/utils/formatters/migrations.ts +197 -61
  154. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  155. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  156. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  157. package/dist/client-CrsnY58k.mjs +0 -997
  158. package/dist/client-CrsnY58k.mjs.map +0 -1
  159. package/dist/commands/db-verify.mjs.map +0 -1
  160. package/dist/commands/migration-plan.mjs.map +0 -1
  161. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  162. package/dist/contract-emit--feXyNd7.mjs +0 -4
  163. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  164. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  165. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  166. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  167. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  168. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  169. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  170. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  171. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  172. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  173. package/dist/init-C5220SY9.mjs.map +0 -1
  174. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  175. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  176. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  177. package/dist/migrations-Bo5WtTla.mjs +0 -153
  178. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  179. package/dist/output-BpcQrnnq.mjs.map +0 -1
  180. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  181. package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
  182. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  183. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  184. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
  185. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  186. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -57,7 +57,6 @@ export function finalizeConfig(config: PrismaNextConfig, configDir: string): Pri
57
57
  if (!config.contract) {
58
58
  return config;
59
59
  }
60
-
61
60
  const contract = normalizeContractConfig(config.contract);
62
61
  const source = finalizeContractSource(contract.source, configDir);
63
62
  const output = resolve(configDir, contract.output);
@@ -6,15 +6,21 @@ import type {
6
6
  ControlFamilyInstance,
7
7
  ControlStack,
8
8
  CoreSchemaView,
9
+ MigrationPlanOperation,
10
+ OperationPreview,
9
11
  SignDatabaseResult,
10
12
  VerifyDatabaseResult,
11
13
  VerifyDatabaseSchemaResult,
12
14
  } from '@prisma-next/framework-components/control';
13
15
  import {
16
+ APP_SPACE_ID,
14
17
  createControlStack,
15
18
  hasMigrations,
19
+ hasOperationPreview,
20
+ hasPslContractInfer,
16
21
  hasSchemaView,
17
22
  } from '@prisma-next/framework-components/control';
23
+ import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
18
24
  import { ifDefined } from '@prisma-next/utils/defined';
19
25
  import { notOk, ok } from '@prisma-next/utils/result';
20
26
  import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
@@ -22,7 +28,9 @@ import { enrichContract } from './contract-enrichment';
22
28
  import { ContractValidationError } from './errors';
23
29
  import { executeDbInit } from './operations/db-init';
24
30
  import { executeDbUpdate } from './operations/db-update';
31
+ import { type ExecuteDbVerifyResult, executeDbVerify } from './operations/db-verify';
25
32
  import { executeMigrationApply } from './operations/migration-apply';
33
+
26
34
  import type {
27
35
  ControlActionName,
28
36
  ControlClient,
@@ -31,6 +39,7 @@ import type {
31
39
  DbInitResult,
32
40
  DbUpdateOptions,
33
41
  DbUpdateResult,
42
+ DbVerifyOptions,
34
43
  EmitOptions,
35
44
  EmitResult,
36
45
  IntrospectOptions,
@@ -362,6 +371,9 @@ class ControlClientImpl implements ControlClient {
362
371
  mode: options.mode,
363
372
  migrations: this.options.target.migrations,
364
373
  frameworkComponents,
374
+ migrationsDir: options.migrationsDir,
375
+ targetId: this.options.target.targetId,
376
+ extensionPacks: this.options.extensionPacks ?? [],
365
377
  ...ifDefined('onProgress', onProgress),
366
378
  });
367
379
  }
@@ -390,14 +402,54 @@ class ControlClientImpl implements ControlClient {
390
402
  mode: options.mode,
391
403
  migrations: this.options.target.migrations,
392
404
  frameworkComponents,
405
+ migrationsDir: options.migrationsDir,
406
+ targetId: this.options.target.targetId,
407
+ extensionPacks: this.options.extensionPacks ?? [],
393
408
  ...ifDefined('acceptDataLoss', options.acceptDataLoss),
394
409
  ...ifDefined('onProgress', onProgress),
395
410
  });
396
411
  }
397
412
 
413
+ async dbVerify(options: DbVerifyOptions): Promise<ExecuteDbVerifyResult> {
414
+ const { onProgress } = options;
415
+ await this.connectWithProgress(options.connection, 'dbVerify', onProgress);
416
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
417
+
418
+ let contract: Contract;
419
+ try {
420
+ contract = familyInstance.validateContract(options.contract);
421
+ } catch (error) {
422
+ const message = error instanceof Error ? error.message : String(error);
423
+ throw new ContractValidationError(message, error);
424
+ }
425
+
426
+ return executeDbVerify({
427
+ driver,
428
+ familyInstance,
429
+ contract,
430
+ migrationsDir: options.migrationsDir,
431
+ targetId: this.options.target.targetId,
432
+ extensionPacks: this.options.extensionPacks ?? [],
433
+ frameworkComponents,
434
+ mode: options.strict ? 'strict' : 'lenient',
435
+ skipSchema: options.skipSchema,
436
+ skipMarker: options.skipMarker,
437
+ ...ifDefined('onProgress', onProgress),
438
+ });
439
+ }
440
+
398
441
  async readMarker(): Promise<ContractMarkerRecord | null> {
399
442
  const { driver, familyInstance } = await this.ensureConnected();
400
- return familyInstance.readMarker({ driver });
443
+ // The CLI client's readMarker reads the app's marker. Per-extension
444
+ // readers go through the orchestrator's per-space planner / runner
445
+ // boundary, which threads the extension's space id through the
446
+ // family interface explicitly.
447
+ return familyInstance.readMarker({ driver, space: APP_SPACE_ID });
448
+ }
449
+
450
+ async readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
451
+ const { driver, familyInstance } = await this.ensureConnected();
452
+ return familyInstance.readAllMarkers({ driver });
401
453
  }
402
454
 
403
455
  async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {
@@ -409,16 +461,28 @@ class ControlClientImpl implements ControlClient {
409
461
  throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
410
462
  }
411
463
 
464
+ let contract: Contract;
465
+ try {
466
+ contract = familyInstance.validateContract(options.contract);
467
+ } catch (error) {
468
+ const message = error instanceof Error ? error.message : String(error);
469
+ throw new ContractValidationError(message, error);
470
+ }
471
+
412
472
  return executeMigrationApply({
413
473
  driver,
414
474
  familyInstance,
415
- originHash: options.originHash,
416
- destinationHash: options.destinationHash,
417
- pendingMigrations: options.pendingMigrations,
475
+ contract,
418
476
  migrations: this.options.target.migrations,
419
477
  frameworkComponents,
478
+ migrationsDir: options.migrationsDir,
479
+ extensionPacks: this.options.extensionPacks ?? [],
420
480
  targetId: this.options.target.targetId,
421
- ...(onProgress ? { onProgress } : {}),
481
+ appMigrationPackages: options.appMigrationPackages,
482
+ ...ifDefined('refHash', options.refHash),
483
+ ...ifDefined('refInvariants', options.refInvariants),
484
+ ...ifDefined('refName', options.refName),
485
+ ...ifDefined('onProgress', onProgress),
422
486
  });
423
487
  }
424
488
 
@@ -469,6 +533,22 @@ class ControlClientImpl implements ControlClient {
469
533
  return undefined;
470
534
  }
471
535
 
536
+ inferPslContract(schemaIR: unknown): PslDocumentAst | undefined {
537
+ this.init();
538
+ if (this.familyInstance && hasPslContractInfer(this.familyInstance)) {
539
+ return this.familyInstance.inferPslContract(schemaIR);
540
+ }
541
+ return undefined;
542
+ }
543
+
544
+ toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview | undefined {
545
+ this.init();
546
+ if (this.familyInstance && hasOperationPreview(this.familyInstance)) {
547
+ return this.familyInstance.toOperationPreview(operations);
548
+ }
549
+ return undefined;
550
+ }
551
+
472
552
  async emit(options: EmitOptions): Promise<EmitResult> {
473
553
  const { onProgress, contractConfig } = options;
474
554
 
@@ -73,7 +73,11 @@ function extractExtensionPackMeta(
73
73
  }
74
74
  if (types) {
75
75
  if (types.codecTypes) {
76
- const { controlPlaneHooks: _, codecInstances: _ci, ...cleanedCodecTypes } = types.codecTypes;
76
+ const {
77
+ controlPlaneHooks: _,
78
+ codecDescriptors: _cd,
79
+ ...cleanedCodecTypes
80
+ } = types.codecTypes;
77
81
  base['types'] = { ...types, codecTypes: cleanedCodecTypes };
78
82
  } else {
79
83
  base['types'] = types;
@@ -83,9 +87,7 @@ function extractExtensionPackMeta(
83
87
  }
84
88
 
85
89
  /**
86
- * Enriches a raw contract with framework-derived metadata:
87
- * capabilities from all component descriptors and extension pack metadata
88
- * from extension descriptors. Produces deterministically sorted output.
90
+ * Enriches a raw contract with framework-derived metadata: capabilities from all component descriptors and extension pack metadata from extension descriptors. Produces deterministically sorted output.
89
91
  */
90
92
  export function enrichContract(
91
93
  ir: Contract,
@@ -0,0 +1,290 @@
1
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
2
+ import type {
3
+ ControlDriverInstance,
4
+ ControlFamilyInstance,
5
+ MigrationOperationPolicy,
6
+ MultiSpaceCapableRunner,
7
+ MultiSpaceRunnerPerSpaceOptions,
8
+ TargetMigrationsCapability,
9
+ } from '@prisma-next/framework-components/control';
10
+ import { hasMultiSpaceRunner } from '@prisma-next/framework-components/control';
11
+ import type {
12
+ AggregatePerSpacePlan,
13
+ ContractSpaceAggregate,
14
+ } from '@prisma-next/migration-tools/aggregate';
15
+ import { ifDefined } from '@prisma-next/utils/defined';
16
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
17
+ import { errorRunnerFailed } from '../../utils/cli-errors';
18
+ import type { AggregatePerSpaceExecutionEntry, OnControlProgress } from '../types';
19
+
20
+ /**
21
+ * Span id emitted via `onProgress` for the apply phase. Stable
22
+ * identifier consumed by the structured-output renderer and by tests.
23
+ */
24
+ const APPLY_SPAN_ID = 'apply' as const;
25
+
26
+ /**
27
+ * Action that originated this apply call. Threaded into `OnControlProgress`
28
+ * events so the parent CLI command can attribute the span correctly,
29
+ * and used to compose action-specific summary phrasing.
30
+ */
31
+ export type AggregateApplyAction = 'dbInit' | 'dbUpdate' | 'migrationApply';
32
+
33
+ /**
34
+ * Failure variant emitted by {@link applyAggregate} when the multi-space
35
+ * runner itself rejects the apply. Mirrors the failure shape callers
36
+ * already wrap into their own action-specific failure envelopes
37
+ * (`DbInitFailure`, `DbUpdateFailure`, `MigrationApplyFailure`) so each
38
+ * caller keeps owning its own discriminated failure code.
39
+ */
40
+ export interface AggregateApplyRunnerFailure {
41
+ readonly summary: string;
42
+ readonly why?: string;
43
+ readonly meta: Record<string, unknown>;
44
+ }
45
+
46
+ export interface ApplyAggregateInputs<TFamilyId extends string, TTargetId extends string> {
47
+ readonly aggregate: ContractSpaceAggregate;
48
+ /**
49
+ * Per-space plans, keyed by `spaceId`. Produced by either the full
50
+ * {@link planAggregate} pipeline (`db init` / `db update` — synth
51
+ * for the app, graph-walk for extensions) or by direct
52
+ * {@link graphWalkStrategy} calls (`migration apply` — graph-walk
53
+ * for every member). Either way, the runner consumes the same shape.
54
+ */
55
+ readonly perSpacePlans: ReadonlyMap<string, AggregatePerSpacePlan>;
56
+ /**
57
+ * Canonical schedule order — extensions alphabetically by `spaceId`,
58
+ * then app. Mirrors {@link import('@prisma-next/migration-tools/concatenate-space-apply-inputs').concatenateSpaceApplyInputs}'s
59
+ * convention so `MultiSpaceRunnerFailure.failingSpace` attribution
60
+ * stays byte-for-byte stable across callers.
61
+ */
62
+ readonly applyOrder: readonly string[];
63
+ readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
64
+ readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
65
+ readonly migrations: TargetMigrationsCapability<
66
+ TFamilyId,
67
+ TTargetId,
68
+ ControlFamilyInstance<TFamilyId, unknown>
69
+ >;
70
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
71
+ readonly policy: MigrationOperationPolicy;
72
+ readonly action: AggregateApplyAction;
73
+ readonly onProgress?: OnControlProgress;
74
+ }
75
+
76
+ /**
77
+ * Resolved per-space plan in canonical schedule order. Surfaced from
78
+ * {@link applyAggregate} to callers so each one can build its own
79
+ * action-specific success envelope (e.g. `DbInitSuccess` vs
80
+ * `MigrationApplySuccess`) without re-deriving the ordering.
81
+ */
82
+ export interface OrderedResolution {
83
+ readonly spaceId: string;
84
+ readonly entry: AggregatePerSpacePlan;
85
+ }
86
+
87
+ export interface ApplyAggregateValue {
88
+ readonly orderedResolutions: readonly OrderedResolution[];
89
+ readonly totalOpsPlanned: number;
90
+ readonly totalOpsExecuted: number;
91
+ /**
92
+ * Per-space breakdown ready to thread into action-specific success
93
+ * envelopes. Each entry carries the post-apply marker (live storage hash
94
+ * plus invariants) so callers can render it directly without re-reading.
95
+ */
96
+ readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
97
+ }
98
+
99
+ export type ApplyAggregateResult = Result<ApplyAggregateValue, AggregateApplyRunnerFailure>;
100
+
101
+ /**
102
+ * Runner-driving tail shared by every aggregate apply caller — `db init`,
103
+ * `db update`, and `migration apply`. Consumes already-resolved per-space
104
+ * plans (the planner-vs-replay distinction is owned by the caller) and
105
+ * dispatches them to the multi-space runner in canonical order.
106
+ *
107
+ * Marker advancement is part of the runner's per-space transaction
108
+ * (the SQL family runner writes the marker as the last step of each
109
+ * space's transaction), so this primitive does not advance markers
110
+ * separately — by the time `executeAcrossSpaces` returns ok, every
111
+ * space's marker has been advanced to its plan's destination.
112
+ *
113
+ * Span emission (`spanStart 'apply'` / `spanEnd 'apply'`) is owned here
114
+ * so callers don't have to duplicate it; the `action` field on each
115
+ * progress event is taken from the caller's `action` argument.
116
+ */
117
+ export async function applyAggregate<TFamilyId extends string, TTargetId extends string>(
118
+ inputs: ApplyAggregateInputs<TFamilyId, TTargetId>,
119
+ ): Promise<ApplyAggregateResult> {
120
+ const {
121
+ aggregate,
122
+ perSpacePlans,
123
+ applyOrder,
124
+ driver,
125
+ familyInstance,
126
+ migrations,
127
+ frameworkComponents,
128
+ policy,
129
+ action,
130
+ onProgress,
131
+ } = inputs;
132
+
133
+ const orderedResolutions = collectOrdered(applyOrder, perSpacePlans);
134
+
135
+ const runner = migrations.createRunner(familyInstance);
136
+ if (!hasMultiSpaceRunner(runner)) {
137
+ throw errorRunnerFailed(
138
+ `Runner for target "${aggregate.targetId}" does not implement \`executeAcrossSpaces\``,
139
+ {
140
+ why: `${labelForAction(action)} requires multi-space-capable runners (today: every SQL family runner).`,
141
+ },
142
+ );
143
+ }
144
+
145
+ onProgress?.({
146
+ action,
147
+ kind: 'spanStart',
148
+ spanId: APPLY_SPAN_ID,
149
+ label: progressLabelForAction(action),
150
+ });
151
+
152
+ const perSpaceOptions: MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>[] =
153
+ orderedResolutions.map((r) => ({
154
+ space: r.spaceId,
155
+ plan: r.entry.plan,
156
+ driver,
157
+ destinationContract: r.entry.destinationContract,
158
+ policy,
159
+ frameworkComponents,
160
+ // Per-space post-apply schema verification is non-strict: each
161
+ // space's `destinationContract` describes only its own slice; a
162
+ // strict verifier would treat every other space's tables as
163
+ // `extras`. Tolerant mode still catches missing tables / columns.
164
+ // SQL family runners read `strictVerification` via structural
165
+ // typing.
166
+ strictVerification: false,
167
+ })) as MultiSpaceRunnerPerSpaceOptions<TFamilyId, TTargetId>[];
168
+
169
+ const runnerResult = await (
170
+ runner as MultiSpaceCapableRunner<TFamilyId, TTargetId>
171
+ ).executeAcrossSpaces({ driver, perSpaceOptions });
172
+
173
+ if (!runnerResult.ok) {
174
+ onProgress?.({ action, kind: 'spanEnd', spanId: APPLY_SPAN_ID, outcome: 'error' });
175
+ return notOk({
176
+ summary: runnerResult.failure.summary,
177
+ ...ifDefined('why', runnerResult.failure.why),
178
+ meta: {
179
+ ...(runnerResult.failure.meta ?? {}),
180
+ failingSpace: runnerResult.failure.failingSpace,
181
+ },
182
+ });
183
+ }
184
+ onProgress?.({ action, kind: 'spanEnd', spanId: APPLY_SPAN_ID, outcome: 'ok' });
185
+
186
+ const totalOpsPlanned = runnerResult.value.perSpaceResults.reduce(
187
+ (sum, r) => sum + r.value.operationsPlanned,
188
+ 0,
189
+ );
190
+ const totalOpsExecuted = runnerResult.value.perSpaceResults.reduce(
191
+ (sum, r) => sum + r.value.operationsExecuted,
192
+ 0,
193
+ );
194
+
195
+ const perSpace = buildPerSpaceBreakdown(orderedResolutions, aggregate.app.spaceId, {
196
+ includeMarkers: true,
197
+ });
198
+
199
+ return ok({
200
+ orderedResolutions,
201
+ totalOpsPlanned,
202
+ totalOpsExecuted,
203
+ perSpace,
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Project the planner's per-space resolutions into the
209
+ * `AggregatePerSpaceExecutionEntry[]` shape the CLI surfaces.
210
+ *
211
+ * `includeMarkers` is `true` for apply-mode (each space's marker is
212
+ * the `destination.storageHash` of its plan, which the runner
213
+ * advances as the last step of each space's transaction) and `false`
214
+ * for plan-mode (no marker has been written yet).
215
+ *
216
+ * Exported alongside {@link applyAggregate} so plan-mode callers can
217
+ * assemble the same per-space block without going through the runner.
218
+ */
219
+ export function buildPerSpaceBreakdown(
220
+ orderedResolutions: readonly OrderedResolution[],
221
+ appSpaceId: string,
222
+ options: { readonly includeMarkers: boolean },
223
+ ): readonly AggregatePerSpaceExecutionEntry[] {
224
+ return orderedResolutions.map((r) => {
225
+ const operations = r.entry.displayOps.map((op) => ({
226
+ id: op.id,
227
+ label: op.label,
228
+ operationClass: op.operationClass,
229
+ }));
230
+ const base: AggregatePerSpaceExecutionEntry = {
231
+ spaceId: r.spaceId,
232
+ kind: r.spaceId === appSpaceId ? 'app' : 'extension',
233
+ operations,
234
+ };
235
+ if (!options.includeMarkers) return base;
236
+ return {
237
+ ...base,
238
+ marker: { storageHash: r.entry.plan.destination.storageHash },
239
+ };
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Materialise the `applyOrder` ordering into resolved per-space
245
+ * entries. Throws if the planner output is missing a member listed
246
+ * in `applyOrder` — a wiring bug that should never reach runtime.
247
+ *
248
+ * Exported so callers building their own success envelopes after a
249
+ * plan-mode dispatch can replay the same ordering.
250
+ */
251
+ export function collectOrdered(
252
+ applyOrder: readonly string[],
253
+ perSpace: ReadonlyMap<string, AggregatePerSpacePlan>,
254
+ ): readonly OrderedResolution[] {
255
+ return applyOrder.map((spaceId) => {
256
+ const entry = perSpace.get(spaceId);
257
+ if (!entry) {
258
+ throw new Error(`Aggregate planner output missing per-space plan for "${spaceId}"`);
259
+ }
260
+ return { spaceId, entry };
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Action-appropriate label for the `spanStart` event the apply
266
+ * primitive emits. `applyAggregate` is shared by `db init`, `db update`,
267
+ * and `migration apply`; the span label tracks the user-visible action
268
+ * so structured-progress output reads naturally for each surface.
269
+ */
270
+ export function progressLabelForAction(action: AggregateApplyAction): string {
271
+ switch (action) {
272
+ case 'dbInit':
273
+ return 'Initialising database across spaces';
274
+ case 'dbUpdate':
275
+ return 'Updating database across spaces';
276
+ case 'migrationApply':
277
+ return 'Applying migration plan across spaces';
278
+ }
279
+ }
280
+
281
+ function labelForAction(action: AggregateApplyAction): string {
282
+ switch (action) {
283
+ case 'dbInit':
284
+ return 'db init';
285
+ case 'dbUpdate':
286
+ return 'db update';
287
+ case 'migrationApply':
288
+ return 'migration apply';
289
+ }
290
+ }