@prisma-next/cli 0.3.0-dev.15 → 0.3.0-dev.162

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