@prisma-next/cli 0.3.0-dev.5 → 0.3.0-dev.52

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 (130) hide show
  1. package/README.md +145 -74
  2. package/dist/cli.d.mts +1 -0
  3. package/dist/cli.js +1 -2376
  4. package/dist/cli.mjs +169 -0
  5. package/dist/cli.mjs.map +1 -0
  6. package/dist/client-BSZKpZTF.mjs +711 -0
  7. package/dist/client-BSZKpZTF.mjs.map +1 -0
  8. package/dist/commands/contract-emit.d.mts +7 -0
  9. package/dist/commands/contract-emit.d.mts.map +1 -0
  10. package/dist/commands/contract-emit.mjs +147 -0
  11. package/dist/commands/contract-emit.mjs.map +1 -0
  12. package/dist/commands/db-init.d.mts +7 -0
  13. package/dist/commands/db-init.d.mts.map +1 -0
  14. package/dist/commands/db-init.mjs +179 -0
  15. package/dist/commands/db-init.mjs.map +1 -0
  16. package/dist/commands/db-introspect.d.mts +7 -0
  17. package/dist/commands/db-introspect.d.mts.map +1 -0
  18. package/dist/commands/db-introspect.mjs +120 -0
  19. package/dist/commands/db-introspect.mjs.map +1 -0
  20. package/dist/commands/db-schema-verify.d.mts +7 -0
  21. package/dist/commands/db-schema-verify.d.mts.map +1 -0
  22. package/dist/commands/db-schema-verify.mjs +116 -0
  23. package/dist/commands/db-schema-verify.mjs.map +1 -0
  24. package/dist/commands/db-sign.d.mts +7 -0
  25. package/dist/commands/db-sign.d.mts.map +1 -0
  26. package/dist/commands/db-sign.mjs +138 -0
  27. package/dist/commands/db-sign.mjs.map +1 -0
  28. package/dist/commands/db-verify.d.mts +7 -0
  29. package/dist/commands/db-verify.d.mts.map +1 -0
  30. package/dist/commands/db-verify.mjs +129 -0
  31. package/dist/commands/db-verify.mjs.map +1 -0
  32. package/dist/config-loader-BJ8HsEdA.mjs +42 -0
  33. package/dist/config-loader-BJ8HsEdA.mjs.map +1 -0
  34. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  35. package/dist/config-loader.d.mts.map +1 -0
  36. package/dist/config-loader.mjs +3 -0
  37. package/dist/exports/config-types.d.mts +2 -0
  38. package/dist/exports/config-types.mjs +3 -0
  39. package/dist/exports/control-api.d.mts +433 -0
  40. package/dist/exports/control-api.d.mts.map +1 -0
  41. package/dist/exports/control-api.mjs +96 -0
  42. package/dist/exports/control-api.mjs.map +1 -0
  43. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
  44. package/dist/exports/index.d.mts.map +1 -0
  45. package/dist/exports/index.mjs +132 -0
  46. package/dist/exports/index.mjs.map +1 -0
  47. package/dist/result-handler-BZPY7HX4.mjs +1029 -0
  48. package/dist/result-handler-BZPY7HX4.mjs.map +1 -0
  49. package/package.json +48 -37
  50. package/src/commands/contract-emit.ts +205 -111
  51. package/src/commands/db-init.ts +258 -359
  52. package/src/commands/db-introspect.ts +151 -184
  53. package/src/commands/db-schema-verify.ts +151 -149
  54. package/src/commands/db-sign.ts +202 -200
  55. package/src/commands/db-verify.ts +181 -155
  56. package/src/control-api/client.ts +610 -0
  57. package/src/control-api/operations/contract-emit.ts +161 -0
  58. package/src/control-api/operations/db-init.ts +281 -0
  59. package/src/control-api/types.ts +475 -0
  60. package/src/exports/control-api.ts +48 -0
  61. package/src/load-ts-contract.ts +16 -11
  62. package/src/utils/cli-errors.ts +1 -1
  63. package/src/utils/framework-components.ts +11 -30
  64. package/src/utils/output.ts +16 -10
  65. package/src/utils/progress-adapter.ts +86 -0
  66. package/dist/chunk-464LNZCE.js +0 -134
  67. package/dist/chunk-464LNZCE.js.map +0 -1
  68. package/dist/chunk-BZMBKEEQ.js +0 -997
  69. package/dist/chunk-BZMBKEEQ.js.map +0 -1
  70. package/dist/chunk-HWYQOCAJ.js +0 -47
  71. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  72. package/dist/chunk-ZKYEJROM.js +0 -94
  73. package/dist/chunk-ZKYEJROM.js.map +0 -1
  74. package/dist/cli.d.ts +0 -2
  75. package/dist/cli.d.ts.map +0 -1
  76. package/dist/cli.js.map +0 -1
  77. package/dist/commands/contract-emit.d.ts +0 -3
  78. package/dist/commands/contract-emit.d.ts.map +0 -1
  79. package/dist/commands/contract-emit.js +0 -9
  80. package/dist/commands/contract-emit.js.map +0 -1
  81. package/dist/commands/db-init.d.ts +0 -3
  82. package/dist/commands/db-init.d.ts.map +0 -1
  83. package/dist/commands/db-init.js +0 -341
  84. package/dist/commands/db-init.js.map +0 -1
  85. package/dist/commands/db-introspect.d.ts +0 -3
  86. package/dist/commands/db-introspect.d.ts.map +0 -1
  87. package/dist/commands/db-introspect.js +0 -190
  88. package/dist/commands/db-introspect.js.map +0 -1
  89. package/dist/commands/db-schema-verify.d.ts +0 -3
  90. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  91. package/dist/commands/db-schema-verify.js +0 -164
  92. package/dist/commands/db-schema-verify.js.map +0 -1
  93. package/dist/commands/db-sign.d.ts +0 -3
  94. package/dist/commands/db-sign.d.ts.map +0 -1
  95. package/dist/commands/db-sign.js +0 -199
  96. package/dist/commands/db-sign.js.map +0 -1
  97. package/dist/commands/db-verify.d.ts +0 -3
  98. package/dist/commands/db-verify.d.ts.map +0 -1
  99. package/dist/commands/db-verify.js +0 -173
  100. package/dist/commands/db-verify.js.map +0 -1
  101. package/dist/config-loader.d.ts.map +0 -1
  102. package/dist/config-loader.js +0 -7
  103. package/dist/config-loader.js.map +0 -1
  104. package/dist/exports/config-types.d.ts +0 -3
  105. package/dist/exports/config-types.d.ts.map +0 -1
  106. package/dist/exports/config-types.js +0 -6
  107. package/dist/exports/config-types.js.map +0 -1
  108. package/dist/exports/index.d.ts +0 -4
  109. package/dist/exports/index.d.ts.map +0 -1
  110. package/dist/exports/index.js +0 -175
  111. package/dist/exports/index.js.map +0 -1
  112. package/dist/load-ts-contract.d.ts.map +0 -1
  113. package/dist/utils/action.d.ts +0 -16
  114. package/dist/utils/action.d.ts.map +0 -1
  115. package/dist/utils/cli-errors.d.ts +0 -7
  116. package/dist/utils/cli-errors.d.ts.map +0 -1
  117. package/dist/utils/command-helpers.d.ts +0 -12
  118. package/dist/utils/command-helpers.d.ts.map +0 -1
  119. package/dist/utils/framework-components.d.ts +0 -81
  120. package/dist/utils/framework-components.d.ts.map +0 -1
  121. package/dist/utils/global-flags.d.ts +0 -25
  122. package/dist/utils/global-flags.d.ts.map +0 -1
  123. package/dist/utils/output.d.ts +0 -142
  124. package/dist/utils/output.d.ts.map +0 -1
  125. package/dist/utils/result-handler.d.ts +0 -15
  126. package/dist/utils/result-handler.d.ts.map +0 -1
  127. package/dist/utils/spinner.d.ts +0 -29
  128. package/dist/utils/spinner.d.ts.map +0 -1
  129. package/src/utils/action.ts +0 -43
  130. package/src/utils/spinner.ts +0 -67
@@ -0,0 +1,161 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';
3
+ import { abortable } from '@prisma-next/utils/abortable';
4
+ import { ifDefined } from '@prisma-next/utils/defined';
5
+ import { dirname, isAbsolute, join, resolve } from 'pathe';
6
+ import { loadConfig } from '../../config-loader';
7
+ import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
8
+ import type { ContractEmitOptions, ContractEmitResult } from '../types';
9
+
10
+ interface ProviderFailureLike {
11
+ readonly summary: string;
12
+ readonly diagnostics: readonly unknown[];
13
+ readonly meta?: unknown;
14
+ }
15
+
16
+ function isRecord(value: unknown): value is Record<string, unknown> {
17
+ return typeof value === 'object' && value !== null;
18
+ }
19
+
20
+ function isAbortError(error: unknown): boolean {
21
+ return isRecord(error) && typeof error['name'] === 'string' && error['name'] === 'AbortError';
22
+ }
23
+
24
+ function isProviderFailureLike(value: unknown): value is ProviderFailureLike {
25
+ return (
26
+ isRecord(value) && typeof value['summary'] === 'string' && Array.isArray(value['diagnostics'])
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Executes the contract emit operation.
32
+ *
33
+ * This is an offline operation that:
34
+ * 1. Loads the Prisma Next config from the specified path
35
+ * 2. Resolves the contract source from config
36
+ * 3. Creates a control plane stack and family instance
37
+ * 4. Emits contract artifacts (JSON and DTS)
38
+ * 5. Writes files to the paths specified in config
39
+ *
40
+ * Supports AbortSignal for cancellation, enabling "last change wins" behavior.
41
+ *
42
+ * @param options - Options including configPath and optional signal
43
+ * @returns File paths and hashes of emitted artifacts
44
+ * @throws If config loading fails, contract is invalid, or file I/O fails
45
+ * @throws signal.reason if cancelled via AbortSignal (typically DOMException with name 'AbortError')
46
+ */
47
+ export async function executeContractEmit(
48
+ options: ContractEmitOptions,
49
+ ): Promise<ContractEmitResult> {
50
+ const { configPath, signal = new AbortController().signal } = options;
51
+ const unlessAborted = abortable(signal);
52
+
53
+ // Load config using the existing config loader
54
+ const config = await unlessAborted(loadConfig(configPath));
55
+
56
+ // Validate contract config is present
57
+ if (!config.contract) {
58
+ throw errorContractConfigMissing({
59
+ why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
60
+ });
61
+ }
62
+
63
+ const contractConfig = config.contract;
64
+
65
+ // Validate output path is present and ends with .json
66
+ if (!contractConfig.output) {
67
+ throw errorContractConfigMissing({
68
+ why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
69
+ });
70
+ }
71
+ if (!contractConfig.output.endsWith('.json')) {
72
+ throw errorContractConfigMissing({
73
+ why: 'Contract config output path must end with .json (e.g., "src/prisma/contract.json")',
74
+ });
75
+ }
76
+
77
+ // Validate source exists and is callable
78
+ if (typeof contractConfig.source !== 'function') {
79
+ throw errorContractConfigMissing({
80
+ why: 'Contract config must include a valid source provider function',
81
+ });
82
+ }
83
+
84
+ // Normalize configPath and resolve artifact paths relative to config file directory
85
+ const normalizedConfigPath = resolve(configPath);
86
+ const configDir = dirname(normalizedConfigPath);
87
+ const outputJsonPath = isAbsolute(contractConfig.output)
88
+ ? contractConfig.output
89
+ : join(configDir, contractConfig.output);
90
+ // Colocate .d.ts with .json (contract.json → contract.d.ts)
91
+ const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;
92
+
93
+ let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;
94
+ try {
95
+ providerResult = await unlessAborted(contractConfig.source());
96
+ } catch (error) {
97
+ if (signal.aborted || isAbortError(error)) {
98
+ throw error;
99
+ }
100
+ throw errorRuntime('Failed to resolve contract source', {
101
+ why: error instanceof Error ? error.message : String(error),
102
+ fix: 'Ensure contract.source resolves to ok(contractIR) or returns structured diagnostics.',
103
+ });
104
+ }
105
+
106
+ if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {
107
+ throw errorRuntime('Failed to resolve contract source', {
108
+ why: 'Contract source provider returned malformed result shape.',
109
+ fix: 'Ensure contract.source resolves to ok(contractIR) or notOk({ summary, diagnostics }).',
110
+ });
111
+ }
112
+
113
+ if (providerResult.ok && !('value' in providerResult)) {
114
+ throw errorRuntime('Failed to resolve contract source', {
115
+ why: 'Contract source provider returned malformed success result: missing value.',
116
+ fix: 'Ensure contract.source success payload is ok(contractIR).',
117
+ });
118
+ }
119
+
120
+ if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {
121
+ throw errorRuntime('Failed to resolve contract source', {
122
+ why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',
123
+ fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',
124
+ });
125
+ }
126
+
127
+ if (!providerResult.ok) {
128
+ throw errorRuntime('Failed to resolve contract source', {
129
+ why: providerResult.failure.summary,
130
+ fix: 'Fix contract source diagnostics and return ok(contractIR).',
131
+ meta: {
132
+ diagnostics: providerResult.failure.diagnostics,
133
+ ...ifDefined('providerMeta', providerResult.failure.meta),
134
+ },
135
+ });
136
+ }
137
+
138
+ // Create control plane stack from config
139
+ const stack = createControlPlaneStack(config);
140
+ const familyInstance = config.family.create(stack);
141
+
142
+ // Emit contract via family instance
143
+ const emitResult = await unlessAborted(
144
+ familyInstance.emitContract({ contractIR: providerResult.value }),
145
+ );
146
+
147
+ // Create directory if needed and write files (both colocated)
148
+ await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
149
+ await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));
150
+ await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));
151
+
152
+ return {
153
+ storageHash: emitResult.storageHash,
154
+ ...ifDefined('executionHash', emitResult.executionHash),
155
+ profileHash: emitResult.profileHash,
156
+ files: {
157
+ json: outputJsonPath,
158
+ dts: outputDtsPath,
159
+ },
160
+ };
161
+ }
@@ -0,0 +1,281 @@
1
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';
2
+ import type { ContractIR } from '@prisma-next/contract/ir';
3
+ import type {
4
+ ControlDriverInstance,
5
+ ControlFamilyInstance,
6
+ MigrationPlan,
7
+ MigrationPlannerResult,
8
+ MigrationPlanOperation,
9
+ MigrationRunnerResult,
10
+ TargetMigrationsCapability,
11
+ } from '@prisma-next/core-control-plane/types';
12
+ import { notOk, ok } from '@prisma-next/utils/result';
13
+ import type { DbInitResult, DbInitSuccess, OnControlProgress } from '../types';
14
+
15
+ /**
16
+ * Options for executing dbInit operation.
17
+ */
18
+ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extends string> {
19
+ readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
20
+ readonly familyInstance: ControlFamilyInstance<TFamilyId>;
21
+ readonly contractIR: ContractIR;
22
+ readonly mode: 'plan' | 'apply';
23
+ readonly migrations: TargetMigrationsCapability<
24
+ TFamilyId,
25
+ TTargetId,
26
+ ControlFamilyInstance<TFamilyId>
27
+ >;
28
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
29
+ /** Optional progress callback for observing operation progress */
30
+ readonly onProgress?: OnControlProgress;
31
+ }
32
+
33
+ /**
34
+ * Executes the dbInit operation.
35
+ *
36
+ * This is the core logic extracted from the CLI command, without any file I/O,
37
+ * process.exit(), or console output. It uses the Result pattern to return
38
+ * success or failure details.
39
+ *
40
+ * @param options - The options for executing dbInit
41
+ * @returns Result with DbInitSuccess on success, DbInitFailure on failure
42
+ */
43
+ export async function executeDbInit<TFamilyId extends string, TTargetId extends string>(
44
+ options: ExecuteDbInitOptions<TFamilyId, TTargetId>,
45
+ ): Promise<DbInitResult> {
46
+ const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } =
47
+ options;
48
+
49
+ // Create planner and runner from target migrations capability
50
+ const planner = migrations.createPlanner(familyInstance);
51
+ const runner = migrations.createRunner(familyInstance);
52
+
53
+ // Introspect live schema
54
+ const introspectSpanId = 'introspect';
55
+ onProgress?.({
56
+ action: 'dbInit',
57
+ kind: 'spanStart',
58
+ spanId: introspectSpanId,
59
+ label: 'Introspecting database schema',
60
+ });
61
+ const schemaIR = await familyInstance.introspect({ driver });
62
+ onProgress?.({
63
+ action: 'dbInit',
64
+ kind: 'spanEnd',
65
+ spanId: introspectSpanId,
66
+ outcome: 'ok',
67
+ });
68
+
69
+ // Policy for init mode (additive only)
70
+ const policy = { allowedOperationClasses: ['additive'] as const };
71
+
72
+ // Plan migration
73
+ const planSpanId = 'plan';
74
+ onProgress?.({
75
+ action: 'dbInit',
76
+ kind: 'spanStart',
77
+ spanId: planSpanId,
78
+ label: 'Planning migration',
79
+ });
80
+ const plannerResult: MigrationPlannerResult = await planner.plan({
81
+ contract: contractIR,
82
+ schema: schemaIR,
83
+ policy,
84
+ frameworkComponents,
85
+ });
86
+
87
+ if (plannerResult.kind === 'failure') {
88
+ onProgress?.({
89
+ action: 'dbInit',
90
+ kind: 'spanEnd',
91
+ spanId: planSpanId,
92
+ outcome: 'error',
93
+ });
94
+ return notOk({
95
+ code: 'PLANNING_FAILED' as const,
96
+ summary: 'Migration planning failed due to conflicts',
97
+ conflicts: plannerResult.conflicts,
98
+ why: undefined,
99
+ meta: undefined,
100
+ });
101
+ }
102
+
103
+ const migrationPlan: MigrationPlan = plannerResult.plan;
104
+ onProgress?.({
105
+ action: 'dbInit',
106
+ kind: 'spanEnd',
107
+ spanId: planSpanId,
108
+ outcome: 'ok',
109
+ });
110
+
111
+ // Check for existing marker - handle idempotency and mismatch errors
112
+ const checkMarkerSpanId = 'checkMarker';
113
+ onProgress?.({
114
+ action: 'dbInit',
115
+ kind: 'spanStart',
116
+ spanId: checkMarkerSpanId,
117
+ label: 'Checking contract marker',
118
+ });
119
+ const existingMarker = await familyInstance.readMarker({ driver });
120
+ if (existingMarker) {
121
+ const markerMatchesDestination =
122
+ existingMarker.storageHash === migrationPlan.destination.storageHash &&
123
+ (!migrationPlan.destination.profileHash ||
124
+ existingMarker.profileHash === migrationPlan.destination.profileHash);
125
+
126
+ if (markerMatchesDestination) {
127
+ // Already at destination - return success with no operations
128
+ onProgress?.({
129
+ action: 'dbInit',
130
+ kind: 'spanEnd',
131
+ spanId: checkMarkerSpanId,
132
+ outcome: 'skipped',
133
+ });
134
+ const result: DbInitSuccess = {
135
+ mode,
136
+ plan: { operations: [] },
137
+ ...(mode === 'apply'
138
+ ? {
139
+ execution: { operationsPlanned: 0, operationsExecuted: 0 },
140
+ marker: {
141
+ storageHash: existingMarker.storageHash,
142
+ profileHash: existingMarker.profileHash,
143
+ },
144
+ }
145
+ : {}),
146
+ summary: 'Database already at target contract state',
147
+ };
148
+ return ok(result);
149
+ }
150
+
151
+ // Marker exists but doesn't match destination - fail
152
+ onProgress?.({
153
+ action: 'dbInit',
154
+ kind: 'spanEnd',
155
+ spanId: checkMarkerSpanId,
156
+ outcome: 'error',
157
+ });
158
+ return notOk({
159
+ code: 'MARKER_ORIGIN_MISMATCH' as const,
160
+ summary: 'Existing contract marker does not match plan destination',
161
+ marker: {
162
+ storageHash: existingMarker.storageHash,
163
+ profileHash: existingMarker.profileHash,
164
+ },
165
+ destination: {
166
+ storageHash: migrationPlan.destination.storageHash,
167
+ profileHash: migrationPlan.destination.profileHash,
168
+ },
169
+ why: undefined,
170
+ conflicts: undefined,
171
+ meta: undefined,
172
+ });
173
+ }
174
+
175
+ onProgress?.({
176
+ action: 'dbInit',
177
+ kind: 'spanEnd',
178
+ spanId: checkMarkerSpanId,
179
+ outcome: 'ok',
180
+ });
181
+
182
+ // Plan mode - don't execute
183
+ if (mode === 'plan') {
184
+ const result: DbInitSuccess = {
185
+ mode: 'plan',
186
+ plan: { operations: migrationPlan.operations },
187
+ summary: `Planned ${migrationPlan.operations.length} operation(s)`,
188
+ };
189
+ return ok(result);
190
+ }
191
+
192
+ // Apply mode - execute runner
193
+ const applySpanId = 'apply';
194
+ onProgress?.({
195
+ action: 'dbInit',
196
+ kind: 'spanStart',
197
+ spanId: applySpanId,
198
+ label: 'Applying migration plan',
199
+ });
200
+
201
+ const callbacks = onProgress
202
+ ? {
203
+ onOperationStart: (op: MigrationPlanOperation) => {
204
+ onProgress({
205
+ action: 'dbInit',
206
+ kind: 'spanStart',
207
+ spanId: `operation:${op.id}`,
208
+ parentSpanId: applySpanId,
209
+ label: op.label,
210
+ });
211
+ },
212
+ onOperationComplete: (op: MigrationPlanOperation) => {
213
+ onProgress({
214
+ action: 'dbInit',
215
+ kind: 'spanEnd',
216
+ spanId: `operation:${op.id}`,
217
+ outcome: 'ok',
218
+ });
219
+ },
220
+ }
221
+ : undefined;
222
+
223
+ const runnerResult: MigrationRunnerResult = await runner.execute({
224
+ plan: migrationPlan,
225
+ driver,
226
+ destinationContract: contractIR,
227
+ policy,
228
+ ...(callbacks ? { callbacks } : {}),
229
+ // db init plans and applies back-to-back from a fresh introspection, so per-operation
230
+ // pre/postchecks and the idempotency probe are usually redundant overhead. We still
231
+ // enforce marker/origin compatibility and a full schema verification after apply.
232
+ executionChecks: {
233
+ prechecks: false,
234
+ postchecks: false,
235
+ idempotencyChecks: false,
236
+ },
237
+ frameworkComponents,
238
+ });
239
+
240
+ if (!runnerResult.ok) {
241
+ onProgress?.({
242
+ action: 'dbInit',
243
+ kind: 'spanEnd',
244
+ spanId: applySpanId,
245
+ outcome: 'error',
246
+ });
247
+ return notOk({
248
+ code: 'RUNNER_FAILED' as const,
249
+ summary: runnerResult.failure.summary,
250
+ why: runnerResult.failure.why,
251
+ meta: runnerResult.failure.meta,
252
+ conflicts: undefined,
253
+ });
254
+ }
255
+
256
+ const execution = runnerResult.value;
257
+
258
+ onProgress?.({
259
+ action: 'dbInit',
260
+ kind: 'spanEnd',
261
+ spanId: applySpanId,
262
+ outcome: 'ok',
263
+ });
264
+
265
+ const result: DbInitSuccess = {
266
+ mode: 'apply',
267
+ plan: { operations: migrationPlan.operations },
268
+ execution: {
269
+ operationsPlanned: execution.operationsPlanned,
270
+ operationsExecuted: execution.operationsExecuted,
271
+ },
272
+ marker: migrationPlan.destination.profileHash
273
+ ? {
274
+ storageHash: migrationPlan.destination.storageHash,
275
+ profileHash: migrationPlan.destination.profileHash,
276
+ }
277
+ : { storageHash: migrationPlan.destination.storageHash },
278
+ summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,
279
+ };
280
+ return ok(result);
281
+ }