@prisma-next/cli 0.5.0-dev.7 → 0.5.0-dev.71

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 (171) hide show
  1. package/README.md +56 -21
  2. package/dist/cli-errors-D3_sMh2K.mjs +33 -0
  3. package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
  4. package/dist/cli-errors-QH8kf-C2.d.mts +3 -0
  5. package/dist/cli.mjs +16 -78
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/client-0ZX24FXF.mjs +1398 -0
  8. package/dist/client-0ZX24FXF.mjs.map +1 -0
  9. package/dist/commands/contract-emit.d.mts.map +1 -1
  10. package/dist/commands/contract-emit.mjs +2 -4
  11. package/dist/commands/contract-infer.d.mts.map +1 -1
  12. package/dist/commands/contract-infer.mjs +2 -4
  13. package/dist/commands/db-init.d.mts.map +1 -1
  14. package/dist/commands/db-init.mjs +14 -13
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.d.mts.map +1 -1
  17. package/dist/commands/db-schema.mjs +5 -7
  18. package/dist/commands/db-schema.mjs.map +1 -1
  19. package/dist/commands/db-sign.d.mts.map +1 -1
  20. package/dist/commands/db-sign.mjs +8 -9
  21. package/dist/commands/db-sign.mjs.map +1 -1
  22. package/dist/commands/db-update.d.mts.map +1 -1
  23. package/dist/commands/db-update.mjs +13 -13
  24. package/dist/commands/db-update.mjs.map +1 -1
  25. package/dist/commands/db-verify.d.mts.map +1 -1
  26. package/dist/commands/db-verify.mjs +1 -321
  27. package/dist/commands/migration-apply.d.mts +5 -2
  28. package/dist/commands/migration-apply.d.mts.map +1 -1
  29. package/dist/commands/migration-apply.mjs +64 -66
  30. package/dist/commands/migration-apply.mjs.map +1 -1
  31. package/dist/commands/migration-new.d.mts +0 -1
  32. package/dist/commands/migration-new.d.mts.map +1 -1
  33. package/dist/commands/migration-new.mjs +33 -40
  34. package/dist/commands/migration-new.mjs.map +1 -1
  35. package/dist/commands/migration-plan.d.mts +14 -5
  36. package/dist/commands/migration-plan.d.mts.map +1 -1
  37. package/dist/commands/migration-plan.mjs +1 -347
  38. package/dist/commands/migration-ref.d.mts +1 -1
  39. package/dist/commands/migration-ref.d.mts.map +1 -1
  40. package/dist/commands/migration-ref.mjs +7 -12
  41. package/dist/commands/migration-ref.mjs.map +1 -1
  42. package/dist/commands/migration-show.d.mts +13 -7
  43. package/dist/commands/migration-show.d.mts.map +1 -1
  44. package/dist/commands/migration-show.mjs +34 -36
  45. package/dist/commands/migration-show.mjs.map +1 -1
  46. package/dist/commands/migration-status.d.mts +23 -5
  47. package/dist/commands/migration-status.d.mts.map +1 -1
  48. package/dist/commands/migration-status.mjs +2 -4
  49. package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
  50. package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
  51. package/dist/config-loader.d.mts +0 -1
  52. package/dist/config-loader.d.mts.map +1 -1
  53. package/dist/config-loader.mjs +2 -3
  54. package/dist/contract-emit-B3ChISB_.mjs +338 -0
  55. package/dist/contract-emit-B3ChISB_.mjs.map +1 -0
  56. package/dist/contract-emit-DkMqO7f2.mjs +148 -0
  57. package/dist/contract-emit-DkMqO7f2.mjs.map +1 -0
  58. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-CF6ogEJ_.mjs} +4 -6
  59. package/dist/contract-enrichment-CF6ogEJ_.mjs.map +1 -0
  60. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BDKAE0B0.mjs} +12 -22
  61. package/dist/contract-infer-BDKAE0B0.mjs.map +1 -0
  62. package/dist/db-verify-B4TdDKOI.mjs +403 -0
  63. package/dist/db-verify-B4TdDKOI.mjs.map +1 -0
  64. package/dist/exports/config-types.mjs +1 -2
  65. package/dist/exports/control-api.d.mts +287 -29
  66. package/dist/exports/control-api.d.mts.map +1 -1
  67. package/dist/exports/control-api.mjs +4 -6
  68. package/dist/exports/index.d.mts.map +1 -1
  69. package/dist/exports/index.mjs +28 -30
  70. package/dist/exports/index.mjs.map +1 -1
  71. package/dist/exports/init-output.d.mts +2 -4
  72. package/dist/exports/init-output.d.mts.map +1 -1
  73. package/dist/exports/init-output.mjs +2 -3
  74. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-gwAHl7ml.mjs} +3 -4
  75. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-gwAHl7ml.mjs.map} +1 -1
  76. package/dist/{init-C5220SY9.mjs → init-Deo7U8_U.mjs} +26 -35
  77. package/dist/init-Deo7U8_U.mjs.map +1 -0
  78. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-BAgQMYpD.mjs} +10 -11
  79. package/dist/inspect-live-schema-BAgQMYpD.mjs.map +1 -0
  80. package/dist/migration-cli.d.mts +41 -12
  81. package/dist/migration-cli.d.mts.map +1 -1
  82. package/dist/migration-cli.mjs +309 -86
  83. package/dist/migration-cli.mjs.map +1 -1
  84. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B8J702Uh.mjs} +8 -9
  85. package/dist/migration-command-scaffold-B8J702Uh.mjs.map +1 -0
  86. package/dist/migration-plan-BcKNnTM7.mjs +530 -0
  87. package/dist/migration-plan-BcKNnTM7.mjs.map +1 -0
  88. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CjwB2of-.mjs} +117 -64
  89. package/dist/migration-status-CjwB2of-.mjs.map +1 -0
  90. package/dist/{migrations-Bo5WtTla.mjs → migrations-CIK94AJf.mjs} +43 -23
  91. package/dist/migrations-CIK94AJf.mjs.map +1 -0
  92. package/dist/{output-BpcQrnnq.mjs → output-DnjfCC_u.mjs} +9 -3
  93. package/dist/output-DnjfCC_u.mjs.map +1 -0
  94. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-xASh41wr.mjs} +2 -2
  95. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
  96. package/dist/{result-handler-Ba3zWQsI.mjs → result-handler-DWb1rFS-.mjs} +52 -27
  97. package/dist/result-handler-DWb1rFS-.mjs.map +1 -0
  98. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-zaRDhJnP.mjs} +2 -6
  99. package/dist/{terminal-ui-C3ZLwQxK.mjs.map → terminal-ui-zaRDhJnP.mjs.map} +1 -1
  100. package/dist/{verify-Bkycc-Tf.mjs → verify-BEIa9638.mjs} +3 -4
  101. package/dist/verify-BEIa9638.mjs.map +1 -0
  102. package/package.json +28 -26
  103. package/src/cli.ts +32 -6
  104. package/src/commands/contract-emit.ts +67 -163
  105. package/src/commands/contract-infer.ts +7 -20
  106. package/src/commands/db-init.ts +14 -3
  107. package/src/commands/db-update.ts +8 -4
  108. package/src/commands/db-verify.ts +47 -15
  109. package/src/commands/init/index.ts +1 -1
  110. package/src/commands/init/init.ts +2 -2
  111. package/src/commands/init/templates/code-templates.ts +12 -4
  112. package/src/commands/inspect-live-schema.ts +10 -5
  113. package/src/commands/migration-apply.ts +92 -71
  114. package/src/commands/migration-new.ts +42 -45
  115. package/src/commands/migration-plan.ts +147 -64
  116. package/src/commands/migration-ref.ts +8 -7
  117. package/src/commands/migration-show.ts +60 -41
  118. package/src/commands/migration-status.ts +196 -60
  119. package/src/config-path-validation.ts +0 -1
  120. package/src/control-api/client.ts +69 -1
  121. package/src/control-api/contract-enrichment.ts +6 -4
  122. package/src/control-api/operations/contract-emit.ts +198 -115
  123. package/src/control-api/operations/db-apply-aggregate.ts +446 -0
  124. package/src/control-api/operations/db-init.ts +51 -253
  125. package/src/control-api/operations/db-update.ts +66 -183
  126. package/src/control-api/operations/db-verify.ts +342 -0
  127. package/src/control-api/operations/migration-apply.ts +37 -9
  128. package/src/control-api/types.ts +125 -7
  129. package/src/exports/control-api.ts +15 -3
  130. package/src/load-ts-contract.ts +28 -26
  131. package/src/migration-cli.ts +445 -122
  132. package/src/utils/cli-errors.ts +49 -2
  133. package/src/utils/combine-schema-results.ts +84 -0
  134. package/src/utils/command-helpers.ts +69 -25
  135. package/src/utils/contract-space-aggregate-loader.ts +236 -0
  136. package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
  137. package/src/utils/contract-space-migrate-pass.ts +156 -0
  138. package/src/utils/emit-queue.ts +26 -0
  139. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  140. package/src/utils/formatters/migrations.ts +62 -26
  141. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  142. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  143. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  144. package/dist/client-CrsnY58k.mjs +0 -997
  145. package/dist/client-CrsnY58k.mjs.map +0 -1
  146. package/dist/commands/db-verify.mjs.map +0 -1
  147. package/dist/commands/migration-plan.mjs.map +0 -1
  148. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  149. package/dist/contract-emit--feXyNd7.mjs +0 -4
  150. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  151. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  152. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  153. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  154. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  155. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  156. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  157. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  158. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  159. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  160. package/dist/init-C5220SY9.mjs.map +0 -1
  161. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  162. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  163. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  164. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  165. package/dist/output-BpcQrnnq.mjs.map +0 -1
  166. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  167. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  168. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  169. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
  170. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  171. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -2,20 +2,30 @@ import type { Contract } from '@prisma-next/contract/types';
2
2
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
3
  import type {
4
4
  ControlDriverInstance,
5
+ ControlExtensionDescriptor,
5
6
  ControlFamilyInstance,
6
- MigrationPlan,
7
- MigrationPlannerResult,
8
- MigrationRunnerResult,
9
7
  TargetMigrationsCapability,
10
8
  } from '@prisma-next/framework-components/control';
11
9
  import { ifDefined } from '@prisma-next/utils/defined';
12
- import { notOk, ok } from '@prisma-next/utils/result';
13
- import type { DbInitResult, DbInitSuccess, OnControlProgress } from '../types';
14
- import { extractOperationStatements } from './extract-operation-statements';
15
- import { createOperationCallbacks, stripOperations } from './migration-helpers';
10
+ import type { DbInitResult, OnControlProgress } from '../types';
11
+ import { executeAggregateApply } from './db-apply-aggregate';
16
12
 
17
13
  /**
18
- * Options for executing dbInit operation.
14
+ * Options for executing the `db init` operation.
15
+ *
16
+ * `db init` runs the loader → planner → runner pipeline:
17
+ *
18
+ * 1. {@link executeAggregateApply} loads a `ContractSpaceAggregate` via
19
+ * {@link import('@prisma-next/migration-tools/aggregate').loadContractSpaceAggregate}
20
+ * from the supplied descriptor set + on-disk on-disk artefacts.
21
+ * 2. The aggregate planner runs with `callerPolicy.ignoreGraphFor`
22
+ * locked to the app member — synth strategy for the app, graph-walk
23
+ * for every extension.
24
+ * 3. The runner's `executeAcrossSpaces` applies the per-space plans
25
+ * inside one outer transaction.
26
+ *
27
+ * `extensionPacks` mirrors `Config.extensionPacks` (descriptor list).
28
+ * The loader (sub-spec § Loader) is the sole descriptor-import boundary.
19
29
  */
20
30
  export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extends string> {
21
31
  readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
@@ -28,261 +38,49 @@ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extend
28
38
  ControlFamilyInstance<TFamilyId, unknown>
29
39
  >;
30
40
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
41
+ /**
42
+ * On-disk migrations directory the aggregate loader reads on-disk
43
+ * artefacts from. Required.
44
+ */
45
+ readonly migrationsDir: string;
46
+ /**
47
+ * Resolved adapter target id. Threaded through to the loader for
48
+ * target-consistency checks across descriptors and the app contract.
49
+ */
50
+ readonly targetId: TTargetId;
51
+ /**
52
+ * Declared extension descriptors. Defaults to an empty list, which
53
+ * routes through the same loader → planner → runner pipeline with no
54
+ * extension members in the aggregate.
55
+ */
56
+ readonly extensionPacks?: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
31
57
  /** Optional progress callback for observing operation progress */
32
58
  readonly onProgress?: OnControlProgress;
33
59
  }
34
60
 
35
61
  /**
36
- * Executes the dbInit operation.
62
+ * Execute `db init` against the configured contract.
37
63
  *
38
- * This is the core logic extracted from the CLI command, without any file I/O,
39
- * process.exit(), or console output. It uses the Result pattern to return
40
- * success or failure details.
41
- *
42
- * @param options - The options for executing dbInit
43
- * @returns Result with DbInitSuccess on success, DbInitFailure on failure
64
+ * Routes through the loader planner runner pipeline (sub-spec
65
+ * "Commit-by-commit § Commit 4"). Always additive-only; destructive
66
+ * changes belong to `db update`.
44
67
  */
45
68
  export async function executeDbInit<TFamilyId extends string, TTargetId extends string>(
46
69
  options: ExecuteDbInitOptions<TFamilyId, TTargetId>,
47
70
  ): Promise<DbInitResult> {
48
- const { driver, familyInstance, contract, mode, migrations, frameworkComponents, onProgress } =
49
- options;
50
-
51
- // Create planner and runner from target migrations capability
52
- const planner = migrations.createPlanner(familyInstance);
53
- const runner = migrations.createRunner(familyInstance);
54
-
55
- // Introspect live schema
56
- const introspectSpanId = 'introspect';
57
- onProgress?.({
58
- action: 'dbInit',
59
- kind: 'spanStart',
60
- spanId: introspectSpanId,
61
- label: 'Introspecting database schema',
62
- });
63
- const schemaIR = await familyInstance.introspect({ driver });
64
- onProgress?.({
65
- action: 'dbInit',
66
- kind: 'spanEnd',
67
- spanId: introspectSpanId,
68
- outcome: 'ok',
69
- });
70
-
71
- // Policy for init mode (additive only)
72
- const policy = { allowedOperationClasses: ['additive'] as const };
73
-
74
- // Plan migration
75
- const planSpanId = 'plan';
76
- onProgress?.({
77
- action: 'dbInit',
78
- kind: 'spanStart',
79
- spanId: planSpanId,
80
- label: 'Planning migration',
81
- });
82
- const plannerResult: MigrationPlannerResult = await planner.plan({
83
- contract,
84
- schema: schemaIR,
85
- policy,
86
- // `db init` does not produce a `migration.ts`, so the from-hash on the
87
- // resulting plan is never surfaced to authoring — pass empty string.
88
- fromHash: '',
89
- frameworkComponents,
90
- });
91
-
92
- if (plannerResult.kind === 'failure') {
93
- onProgress?.({
94
- action: 'dbInit',
95
- kind: 'spanEnd',
96
- spanId: planSpanId,
97
- outcome: 'error',
98
- });
99
- return notOk({
100
- code: 'PLANNING_FAILED' as const,
101
- summary: 'Migration planning failed due to conflicts',
102
- conflicts: plannerResult.conflicts,
103
- why: undefined,
104
- meta: undefined,
105
- });
106
- }
107
-
108
- const migrationPlan: MigrationPlan = plannerResult.plan;
109
- onProgress?.({
110
- action: 'dbInit',
111
- kind: 'spanEnd',
112
- spanId: planSpanId,
113
- outcome: 'ok',
114
- });
115
-
116
- // Check for existing marker - handle idempotency and mismatch errors
117
- const checkMarkerSpanId = 'checkMarker';
118
- onProgress?.({
119
- action: 'dbInit',
120
- kind: 'spanStart',
121
- spanId: checkMarkerSpanId,
122
- label: 'Checking database signature',
123
- });
124
- const existingMarker = await familyInstance.readMarker({ driver });
125
- if (existingMarker) {
126
- const markerMatchesDestination =
127
- existingMarker.storageHash === migrationPlan.destination.storageHash &&
128
- (!migrationPlan.destination.profileHash ||
129
- existingMarker.profileHash === migrationPlan.destination.profileHash);
130
-
131
- if (markerMatchesDestination) {
132
- // Already at destination - return success with no operations
133
- onProgress?.({
134
- action: 'dbInit',
135
- kind: 'spanEnd',
136
- spanId: checkMarkerSpanId,
137
- outcome: 'skipped',
138
- });
139
- const result: DbInitSuccess = {
140
- mode,
141
- plan: { operations: [] },
142
- destination: {
143
- storageHash: migrationPlan.destination.storageHash,
144
- ...ifDefined('profileHash', migrationPlan.destination.profileHash),
145
- },
146
- ...ifDefined(
147
- 'execution',
148
- mode === 'apply' ? { operationsPlanned: 0, operationsExecuted: 0 } : undefined,
149
- ),
150
- ...ifDefined(
151
- 'marker',
152
- mode === 'apply'
153
- ? {
154
- storageHash: existingMarker.storageHash,
155
- profileHash: existingMarker.profileHash,
156
- }
157
- : undefined,
158
- ),
159
- summary: 'Database already at target contract state',
160
- };
161
- return ok(result);
162
- }
163
-
164
- // Marker exists but doesn't match destination - fail
165
- onProgress?.({
166
- action: 'dbInit',
167
- kind: 'spanEnd',
168
- spanId: checkMarkerSpanId,
169
- outcome: 'error',
170
- });
171
- return notOk({
172
- code: 'MARKER_ORIGIN_MISMATCH' as const,
173
- summary: 'Existing contract marker does not match plan destination',
174
- marker: {
175
- storageHash: existingMarker.storageHash,
176
- profileHash: existingMarker.profileHash,
177
- },
178
- destination: {
179
- storageHash: migrationPlan.destination.storageHash,
180
- profileHash: migrationPlan.destination.profileHash,
181
- },
182
- why: undefined,
183
- conflicts: undefined,
184
- meta: undefined,
185
- });
186
- }
187
-
188
- onProgress?.({
189
- action: 'dbInit',
190
- kind: 'spanEnd',
191
- spanId: checkMarkerSpanId,
192
- outcome: 'ok',
193
- });
194
-
195
- // Plan mode - don't execute
196
- if (mode === 'plan') {
197
- const planSql = extractOperationStatements(familyInstance.familyId, migrationPlan.operations);
198
- const result: DbInitSuccess = {
199
- mode: 'plan',
200
- plan: {
201
- operations: stripOperations(migrationPlan.operations),
202
- ...ifDefined('sql', planSql),
203
- },
204
- destination: {
205
- storageHash: migrationPlan.destination.storageHash,
206
- ...ifDefined('profileHash', migrationPlan.destination.profileHash),
207
- },
208
- summary: `Planned ${migrationPlan.operations.length} operation(s)`,
209
- };
210
- return ok(result);
211
- }
212
-
213
- // Apply mode - execute runner
214
- const applySpanId = 'apply';
215
- onProgress?.({
71
+ const result = await executeAggregateApply<TFamilyId, TTargetId>({
72
+ driver: options.driver,
73
+ familyInstance: options.familyInstance,
74
+ contract: options.contract,
75
+ mode: options.mode,
76
+ migrations: options.migrations,
77
+ frameworkComponents: options.frameworkComponents,
78
+ migrationsDir: options.migrationsDir,
79
+ targetId: options.targetId,
80
+ extensionPacks: options.extensionPacks ?? [],
81
+ policy: { allowedOperationClasses: ['additive'] },
216
82
  action: 'dbInit',
217
- kind: 'spanStart',
218
- spanId: applySpanId,
219
- label: 'Applying migration plan',
83
+ ...ifDefined('onProgress', options.onProgress),
220
84
  });
221
-
222
- const callbacks = createOperationCallbacks(onProgress, 'dbInit', applySpanId);
223
-
224
- const runnerResult: MigrationRunnerResult = await runner.execute({
225
- plan: migrationPlan,
226
- driver,
227
- destinationContract: contract,
228
- policy,
229
- ...ifDefined('callbacks', callbacks),
230
- // db init plans and applies back-to-back from a fresh introspection, so per-operation
231
- // pre/postchecks and the idempotency probe are usually redundant overhead. We still
232
- // enforce marker/origin compatibility and a full schema verification after apply.
233
- executionChecks: {
234
- prechecks: false,
235
- postchecks: false,
236
- idempotencyChecks: false,
237
- },
238
- frameworkComponents,
239
- });
240
-
241
- if (!runnerResult.ok) {
242
- onProgress?.({
243
- action: 'dbInit',
244
- kind: 'spanEnd',
245
- spanId: applySpanId,
246
- outcome: 'error',
247
- });
248
- return notOk({
249
- code: 'RUNNER_FAILED' as const,
250
- summary: runnerResult.failure.summary,
251
- why: runnerResult.failure.why,
252
- meta: runnerResult.failure.meta,
253
- conflicts: undefined,
254
- });
255
- }
256
-
257
- const execution = runnerResult.value;
258
-
259
- onProgress?.({
260
- action: 'dbInit',
261
- kind: 'spanEnd',
262
- spanId: applySpanId,
263
- outcome: 'ok',
264
- });
265
-
266
- const result: DbInitSuccess = {
267
- mode: 'apply',
268
- plan: {
269
- operations: stripOperations(migrationPlan.operations),
270
- },
271
- destination: {
272
- storageHash: migrationPlan.destination.storageHash,
273
- ...ifDefined('profileHash', migrationPlan.destination.profileHash),
274
- },
275
- execution: {
276
- operationsPlanned: execution.operationsPlanned,
277
- operationsExecuted: execution.operationsExecuted,
278
- },
279
- marker: migrationPlan.destination.profileHash
280
- ? {
281
- storageHash: migrationPlan.destination.storageHash,
282
- profileHash: migrationPlan.destination.profileHash,
283
- }
284
- : { storageHash: migrationPlan.destination.storageHash },
285
- summary: `Applied ${execution.operationsExecuted} operation(s), database signed`,
286
- };
287
- return ok(result);
85
+ return result as DbInitResult;
288
86
  }
@@ -2,25 +2,27 @@ import type { Contract } from '@prisma-next/contract/types';
2
2
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
3
  import type {
4
4
  ControlDriverInstance,
5
+ ControlExtensionDescriptor,
5
6
  ControlFamilyInstance,
6
- MigrationPlannerResult,
7
- MigrationRunnerResult,
8
7
  TargetMigrationsCapability,
9
8
  } from '@prisma-next/framework-components/control';
10
9
  import { ifDefined } from '@prisma-next/utils/defined';
11
- import { notOk, ok } from '@prisma-next/utils/result';
12
- import type { DbUpdateResult, DbUpdateSuccess, OnControlProgress } from '../types';
13
- import { extractOperationStatements } from './extract-operation-statements';
14
- import { createOperationCallbacks, stripOperations } from './migration-helpers';
10
+ import { notOk } from '@prisma-next/utils/result';
11
+ import type { DbUpdateResult, OnControlProgress } from '../types';
12
+ import { executeAggregateApply } from './db-apply-aggregate';
15
13
 
16
- // F12: db update allows additive, widening, and destructive operations.
17
14
  const DB_UPDATE_POLICY = {
18
15
  allowedOperationClasses: ['additive', 'widening', 'destructive'] as const,
19
16
  } as const;
20
17
 
21
18
  /**
22
- * Options for the executeDbUpdate operation.
23
- * Config-agnostic: receives pre-resolved driver, family, contract, and migrations capability.
19
+ * Options for the `db update` operation.
20
+ *
21
+ * Same loader → planner → runner pipeline as `db init`, but with the
22
+ * widened operation policy (additive + widening + destructive). The
23
+ * destructive-change confirmation gate runs at this layer: when
24
+ * `mode === 'apply'` and `acceptDataLoss` is `false`, the operation
25
+ * pre-plans, surfaces destructive ops to the caller, and aborts.
24
26
  */
25
27
  export interface ExecuteDbUpdateOptions<TFamilyId extends string, TTargetId extends string> {
26
28
  readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
@@ -34,190 +36,71 @@ export interface ExecuteDbUpdateOptions<TFamilyId extends string, TTargetId exte
34
36
  >;
35
37
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
36
38
  readonly acceptDataLoss?: boolean;
37
- /** Optional progress callback for observing operation progress. */
39
+ readonly migrationsDir: string;
40
+ readonly targetId: TTargetId;
41
+ readonly extensionPacks?: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
38
42
  readonly onProgress?: OnControlProgress;
39
43
  }
40
44
 
41
45
  /**
42
- * Executes the db update operation: introspect plan → (optionally) apply → marker.
46
+ * Execute `db update` against the configured contract.
43
47
  *
44
- * db update is a pure reconciliation command: it introspects the live schema, plans the diff
45
- * to the destination contract, and applies operations. The marker is bookkeeping only written
46
- * after apply so that `verify` and `db init` can reference it, but never read or validated
47
- * by db update itself. The runner creates the marker table if it does not exist.
48
+ * Routes through the loader planner runner pipeline. Destructive
49
+ * operations require either `acceptDataLoss: true` or a prior
50
+ * `mode: 'plan'` invocation that surfaces the destructive ops; the
51
+ * confirmation gate is implemented here so the lower-level applier
52
+ * remains policy-agnostic.
48
53
  */
49
54
  export async function executeDbUpdate<TFamilyId extends string, TTargetId extends string>(
50
55
  options: ExecuteDbUpdateOptions<TFamilyId, TTargetId>,
51
56
  ): Promise<DbUpdateResult> {
52
- const { driver, familyInstance, contract, mode, migrations, frameworkComponents, onProgress } =
53
- options;
54
-
55
- const planner = migrations.createPlanner(familyInstance);
56
- const runner = migrations.createRunner(familyInstance);
57
-
58
- const introspectSpanId = 'introspect';
59
- onProgress?.({
60
- action: 'dbUpdate',
61
- kind: 'spanStart',
62
- spanId: introspectSpanId,
63
- label: 'Introspecting database schema',
64
- });
65
- const schemaIR = await familyInstance.introspect({ driver });
66
- onProgress?.({
67
- action: 'dbUpdate',
68
- kind: 'spanEnd',
69
- spanId: introspectSpanId,
70
- outcome: 'ok',
71
- });
72
-
73
- const policy = DB_UPDATE_POLICY;
74
-
75
- const planSpanId = 'plan';
76
- onProgress?.({
77
- action: 'dbUpdate',
78
- kind: 'spanStart',
79
- spanId: planSpanId,
80
- label: 'Planning migration',
81
- });
82
- const plannerResult: MigrationPlannerResult = await planner.plan({
83
- contract,
84
- schema: schemaIR,
85
- policy,
86
- // `db update` does not produce a `migration.ts`, so the from-hash on the
87
- // resulting plan is never surfaced to authoring — pass empty string.
88
- fromHash: '',
89
- frameworkComponents,
90
- });
91
- if (plannerResult.kind === 'failure') {
92
- onProgress?.({
93
- action: 'dbUpdate',
94
- kind: 'spanEnd',
95
- spanId: planSpanId,
96
- outcome: 'error',
97
- });
98
- return notOk({
99
- code: 'PLANNING_FAILED',
100
- summary: 'Migration planning failed due to conflicts',
101
- conflicts: plannerResult.conflicts,
102
- why: undefined,
103
- meta: undefined,
104
- });
105
- }
106
- onProgress?.({
107
- action: 'dbUpdate',
108
- kind: 'spanEnd',
109
- spanId: planSpanId,
110
- outcome: 'ok',
111
- });
112
-
113
- const migrationPlan = plannerResult.plan;
114
-
115
- if (mode === 'plan') {
116
- const planSql = extractOperationStatements(familyInstance.familyId, migrationPlan.operations);
117
- const result: DbUpdateSuccess = {
118
- mode: 'plan',
119
- plan: {
120
- operations: stripOperations(migrationPlan.operations),
121
- ...(planSql !== undefined ? { sql: planSql } : {}),
122
- },
123
- destination: {
124
- storageHash: migrationPlan.destination.storageHash,
125
- ...ifDefined('profileHash', migrationPlan.destination.profileHash),
126
- },
127
- summary: `Planned ${migrationPlan.operations.length} operation(s)`,
128
- };
129
- return ok(result);
130
- }
131
-
132
- // When applying, require explicit acceptance for destructive operations
133
- if (!options.acceptDataLoss) {
134
- const destructiveOps = migrationPlan.operations
135
- .filter((op) => op.operationClass === 'destructive')
136
- .map((op) => ({ id: op.id, label: op.label }));
137
- if (destructiveOps.length > 0) {
138
- return notOk({
139
- code: 'DESTRUCTIVE_CHANGES',
140
- summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,
141
- why: 'Destructive operations require confirmation — re-run with -y to accept',
142
- conflicts: undefined,
143
- meta: { destructiveOperations: destructiveOps },
144
- });
145
- }
146
- }
147
-
148
- const applySpanId = 'apply';
149
- onProgress?.({
150
- action: 'dbUpdate',
151
- kind: 'spanStart',
152
- spanId: applySpanId,
153
- label: 'Applying migration plan',
154
- });
155
-
156
- const callbacks = createOperationCallbacks(onProgress, 'dbUpdate', applySpanId);
157
-
158
- const runnerResult: MigrationRunnerResult = await runner.execute({
159
- plan: migrationPlan,
160
- driver,
161
- destinationContract: contract,
162
- policy,
163
- ...(callbacks ? { callbacks } : {}),
164
- // db update plans and applies from a single introspection pass, so per-operation pre/postchecks
165
- // and idempotency probes are intentionally disabled to avoid redundant roundtrips.
166
- executionChecks: {
167
- prechecks: false,
168
- postchecks: false,
169
- idempotencyChecks: false,
170
- },
171
- frameworkComponents,
172
- });
173
-
174
- if (!runnerResult.ok) {
175
- onProgress?.({
176
- action: 'dbUpdate',
177
- kind: 'spanEnd',
178
- spanId: applySpanId,
179
- outcome: 'error',
180
- });
181
- return notOk({
182
- code: 'RUNNER_FAILED',
183
- summary: runnerResult.failure.summary,
184
- why: runnerResult.failure.why,
185
- meta: runnerResult.failure.meta,
186
- conflicts: undefined,
187
- });
57
+ const sharedInputs = {
58
+ driver: options.driver,
59
+ familyInstance: options.familyInstance,
60
+ contract: options.contract,
61
+ migrations: options.migrations,
62
+ frameworkComponents: options.frameworkComponents,
63
+ migrationsDir: options.migrationsDir,
64
+ targetId: options.targetId,
65
+ extensionPacks: options.extensionPacks ?? [],
66
+ policy: DB_UPDATE_POLICY,
67
+ action: 'dbUpdate' as const,
68
+ ...ifDefined('onProgress', options.onProgress),
69
+ };
70
+ if (options.mode === 'apply' && !options.acceptDataLoss) {
71
+ const gate = await guardDestructiveChanges<TFamilyId, TTargetId>(sharedInputs);
72
+ if (gate !== null) return gate;
188
73
  }
74
+ return (await executeAggregateApply<TFamilyId, TTargetId>({
75
+ ...sharedInputs,
76
+ mode: options.mode,
77
+ })) as DbUpdateResult;
78
+ }
189
79
 
190
- const execution = runnerResult.value;
191
- onProgress?.({
192
- action: 'dbUpdate',
193
- kind: 'spanEnd',
194
- spanId: applySpanId,
195
- outcome: 'ok',
80
+ /**
81
+ * Pre-plan once when running `db update apply` without `acceptDataLoss`.
82
+ * Surfaces destructive operations across every space; if any are
83
+ * planned, returns a `DESTRUCTIVE_CHANGES` failure that the CLI shows
84
+ * as a confirmation prompt. Returns `null` when the apply is safe to
85
+ * run.
86
+ */
87
+ async function guardDestructiveChanges<TFamilyId extends string, TTargetId extends string>(
88
+ sharedInputs: Omit<Parameters<typeof executeAggregateApply<TFamilyId, TTargetId>>[0], 'mode'>,
89
+ ): Promise<DbUpdateResult | null> {
90
+ const planResult = (await executeAggregateApply<TFamilyId, TTargetId>({
91
+ ...sharedInputs,
92
+ mode: 'plan',
93
+ })) as DbUpdateResult;
94
+ if (!planResult.ok) return planResult;
95
+ const destructiveOps = planResult.value.plan.operations
96
+ .filter((op) => op.operationClass === 'destructive')
97
+ .map((op) => ({ id: op.id, label: op.label }));
98
+ if (destructiveOps.length === 0) return null;
99
+ return notOk({
100
+ code: 'DESTRUCTIVE_CHANGES',
101
+ summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,
102
+ why: 'Destructive operations require confirmation — re-run with -y to accept',
103
+ conflicts: undefined,
104
+ meta: { destructiveOperations: destructiveOps },
196
105
  });
197
-
198
- const result: DbUpdateSuccess = {
199
- mode: 'apply',
200
- plan: {
201
- operations: stripOperations(migrationPlan.operations),
202
- },
203
- destination: {
204
- storageHash: migrationPlan.destination.storageHash,
205
- ...ifDefined('profileHash', migrationPlan.destination.profileHash),
206
- },
207
- execution: {
208
- operationsPlanned: execution.operationsPlanned,
209
- operationsExecuted: execution.operationsExecuted,
210
- },
211
- marker: migrationPlan.destination.profileHash
212
- ? {
213
- storageHash: migrationPlan.destination.storageHash,
214
- profileHash: migrationPlan.destination.profileHash,
215
- }
216
- : { storageHash: migrationPlan.destination.storageHash },
217
- summary:
218
- execution.operationsExecuted === 0
219
- ? 'Database already matches contract, signature updated'
220
- : `Applied ${execution.operationsExecuted} operation(s), signature updated`,
221
- };
222
- return ok(result);
223
106
  }