@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
@@ -1,21 +1,42 @@
1
- import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';
2
- import { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';
1
+ import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import { emit as emitContractArtifacts } from '@prisma-next/emitter';
3
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
4
  import type {
4
5
  ControlDriverInstance,
5
6
  ControlFamilyInstance,
6
- ControlPlaneStack,
7
+ ControlStack,
8
+ CoreSchemaView,
7
9
  SignDatabaseResult,
8
10
  VerifyDatabaseResult,
9
11
  VerifyDatabaseSchemaResult,
10
- } from '@prisma-next/core-control-plane/types';
12
+ } from '@prisma-next/framework-components/control';
13
+ import {
14
+ createControlStack,
15
+ hasMigrations,
16
+ hasSchemaView,
17
+ } from '@prisma-next/framework-components/control';
18
+ import { ifDefined } from '@prisma-next/utils/defined';
19
+ import { notOk, ok } from '@prisma-next/utils/result';
11
20
  import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
21
+ import { enrichContract } from './contract-enrichment';
22
+ import { ContractValidationError } from './errors';
12
23
  import { executeDbInit } from './operations/db-init';
24
+ import { executeDbUpdate } from './operations/db-update';
25
+ import { executeMigrationApply } from './operations/migration-apply';
13
26
  import type {
27
+ ControlActionName,
14
28
  ControlClient,
15
29
  ControlClientOptions,
16
30
  DbInitOptions,
17
31
  DbInitResult,
32
+ DbUpdateOptions,
33
+ DbUpdateResult,
34
+ EmitOptions,
35
+ EmitResult,
18
36
  IntrospectOptions,
37
+ MigrationApplyOptions,
38
+ MigrationApplyResult,
39
+ OnControlProgress,
19
40
  SchemaVerifyOptions,
20
41
  SignOptions,
21
42
  VerifyOptions,
@@ -41,9 +62,9 @@ export function createControlClient(options: ControlClientOptions): ControlClien
41
62
  */
42
63
  class ControlClientImpl implements ControlClient {
43
64
  private readonly options: ControlClientOptions;
44
- private stack: ControlPlaneStack<string, string> | null = null;
65
+ private stack: ControlStack | null = null;
45
66
  private driver: ControlDriverInstance<string, string> | null = null;
46
- private familyInstance: ControlFamilyInstance<string> | null = null;
67
+ private familyInstance: ControlFamilyInstance<string, unknown> | null = null;
47
68
  private frameworkComponents: ReadonlyArray<
48
69
  TargetBoundComponentDescriptor<string, string>
49
70
  > | null = null;
@@ -60,15 +81,14 @@ class ControlClientImpl implements ControlClient {
60
81
  return; // Idempotent
61
82
  }
62
83
 
63
- // Create the control plane stack
64
- this.stack = createControlPlaneStack({
84
+ this.stack = createControlStack({
85
+ family: this.options.family,
65
86
  target: this.options.target,
66
87
  adapter: this.options.adapter,
67
88
  driver: this.options.driver,
68
89
  extensionPacks: this.options.extensionPacks,
69
90
  });
70
91
 
71
- // Create family instance using the stack
72
92
  this.familyInstance = this.options.family.create(this.stack);
73
93
 
74
94
  // Validate and type-narrow framework components
@@ -109,12 +129,8 @@ class ControlClientImpl implements ControlClient {
109
129
  );
110
130
  }
111
131
 
112
- // Create driver instance
113
- // Cast through any since connection type is driver-specific at runtime.
114
- // The driver descriptor is typed with any for TConnection in ControlClientOptions,
115
- // but createControlPlaneStack defaults it to string. We bridge this at runtime.
116
132
  // biome-ignore lint/suspicious/noExplicitAny: required for runtime connection type flexibility
117
- this.driver = await this.stack?.driver.create(resolvedConnection as any);
133
+ this.driver = await this.stack.driver.create(resolvedConnection as any);
118
134
  }
119
135
 
120
136
  async close(): Promise<void> {
@@ -126,7 +142,7 @@ class ControlClientImpl implements ControlClient {
126
142
 
127
143
  private async ensureConnected(): Promise<{
128
144
  driver: ControlDriverInstance<string, string>;
129
- familyInstance: ControlFamilyInstance<string>;
145
+ familyInstance: ControlFamilyInstance<string, unknown>;
130
146
  frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>;
131
147
  }> {
132
148
  // Auto-init if needed
@@ -147,83 +163,449 @@ class ControlClientImpl implements ControlClient {
147
163
  };
148
164
  }
149
165
 
166
+ private async connectWithProgress(
167
+ connection: unknown | undefined,
168
+ action: ControlActionName,
169
+ onProgress?: OnControlProgress,
170
+ ): Promise<void> {
171
+ if (connection === undefined) return;
172
+ onProgress?.({
173
+ action,
174
+ kind: 'spanStart',
175
+ spanId: 'connect',
176
+ label: 'Connecting to database...',
177
+ });
178
+ try {
179
+ await this.connect(connection);
180
+ onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'ok' });
181
+ } catch (error) {
182
+ onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'error' });
183
+ throw error;
184
+ }
185
+ }
186
+
150
187
  async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {
188
+ const { onProgress } = options;
189
+ await this.connectWithProgress(options.connection, 'verify', onProgress);
151
190
  const { driver, familyInstance } = await this.ensureConnected();
152
191
 
153
192
  // Validate contract using family instance
154
- const contractIR = familyInstance.validateContractIR(options.contractIR);
193
+ let contract: Contract;
194
+ try {
195
+ contract = familyInstance.validateContract(options.contract);
196
+ } catch (error) {
197
+ const message = error instanceof Error ? error.message : String(error);
198
+ throw new ContractValidationError(message, error);
199
+ }
155
200
 
156
- // Delegate to family instance verify method
157
- // Note: We pass empty strings for contractPath/configPath since the programmatic
158
- // API doesn't deal with file paths. The family instance accepts these as optional
159
- // metadata for error reporting.
160
- return familyInstance.verify({
161
- driver,
162
- contractIR,
163
- expectedTargetId: this.options.target.targetId,
164
- contractPath: '',
201
+ // Emit verify span
202
+ onProgress?.({
203
+ action: 'verify',
204
+ kind: 'spanStart',
205
+ spanId: 'verify',
206
+ label: 'Verifying database marker...',
165
207
  });
208
+
209
+ try {
210
+ // Delegate to family instance verify method
211
+ // Note: We pass empty strings for contractPath/configPath since the programmatic
212
+ // API doesn't deal with file paths. The family instance accepts these as optional
213
+ // metadata for error reporting.
214
+ const result = await familyInstance.verify({
215
+ driver,
216
+ contract,
217
+ expectedTargetId: this.options.target.targetId,
218
+ contractPath: '',
219
+ });
220
+
221
+ onProgress?.({
222
+ action: 'verify',
223
+ kind: 'spanEnd',
224
+ spanId: 'verify',
225
+ outcome: result.ok ? 'ok' : 'error',
226
+ });
227
+
228
+ return result;
229
+ } catch (error) {
230
+ onProgress?.({
231
+ action: 'verify',
232
+ kind: 'spanEnd',
233
+ spanId: 'verify',
234
+ outcome: 'error',
235
+ });
236
+ throw error;
237
+ }
166
238
  }
167
239
 
168
240
  async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {
241
+ const { onProgress } = options;
242
+ await this.connectWithProgress(options.connection, 'schemaVerify', onProgress);
169
243
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
170
244
 
171
245
  // Validate contract using family instance
172
- const contractIR = familyInstance.validateContractIR(options.contractIR);
246
+ let contract: Contract;
247
+ try {
248
+ contract = familyInstance.validateContract(options.contract);
249
+ } catch (error) {
250
+ const message = error instanceof Error ? error.message : String(error);
251
+ throw new ContractValidationError(message, error);
252
+ }
173
253
 
174
- // Delegate to family instance schemaVerify method
175
- return familyInstance.schemaVerify({
176
- driver,
177
- contractIR,
178
- strict: options.strict ?? false,
179
- contractPath: '',
180
- frameworkComponents,
254
+ // Emit schemaVerify span
255
+ onProgress?.({
256
+ action: 'schemaVerify',
257
+ kind: 'spanStart',
258
+ spanId: 'schemaVerify',
259
+ label: 'Verifying database schema...',
181
260
  });
261
+
262
+ try {
263
+ // Delegate to family instance schemaVerify method
264
+ const result = await familyInstance.schemaVerify({
265
+ driver,
266
+ contract,
267
+ strict: options.strict ?? false,
268
+ contractPath: '',
269
+ frameworkComponents,
270
+ });
271
+
272
+ onProgress?.({
273
+ action: 'schemaVerify',
274
+ kind: 'spanEnd',
275
+ spanId: 'schemaVerify',
276
+ outcome: result.ok ? 'ok' : 'error',
277
+ });
278
+
279
+ return result;
280
+ } catch (error) {
281
+ onProgress?.({
282
+ action: 'schemaVerify',
283
+ kind: 'spanEnd',
284
+ spanId: 'schemaVerify',
285
+ outcome: 'error',
286
+ });
287
+ throw error;
288
+ }
182
289
  }
183
290
 
184
291
  async sign(options: SignOptions): Promise<SignDatabaseResult> {
292
+ const { onProgress } = options;
293
+ await this.connectWithProgress(options.connection, 'sign', onProgress);
185
294
  const { driver, familyInstance } = await this.ensureConnected();
186
295
 
187
296
  // Validate contract using family instance
188
- const contractIR = familyInstance.validateContractIR(options.contractIR);
297
+ let contract: Contract;
298
+ try {
299
+ contract = familyInstance.validateContract(options.contract);
300
+ } catch (error) {
301
+ const message = error instanceof Error ? error.message : String(error);
302
+ throw new ContractValidationError(message, error);
303
+ }
189
304
 
190
- // Delegate to family instance sign method
191
- return familyInstance.sign({
192
- driver,
193
- contractIR,
194
- contractPath: '',
305
+ // Emit sign span
306
+ onProgress?.({
307
+ action: 'sign',
308
+ kind: 'spanStart',
309
+ spanId: 'sign',
310
+ label: 'Signing database...',
195
311
  });
312
+
313
+ try {
314
+ // Delegate to family instance sign method
315
+ const result = await familyInstance.sign({
316
+ driver,
317
+ contract,
318
+ contractPath: options.contractPath ?? '',
319
+ ...ifDefined('configPath', options.configPath),
320
+ });
321
+
322
+ onProgress?.({
323
+ action: 'sign',
324
+ kind: 'spanEnd',
325
+ spanId: 'sign',
326
+ outcome: 'ok',
327
+ });
328
+
329
+ return result;
330
+ } catch (error) {
331
+ onProgress?.({
332
+ action: 'sign',
333
+ kind: 'spanEnd',
334
+ spanId: 'sign',
335
+ outcome: 'error',
336
+ });
337
+ throw error;
338
+ }
196
339
  }
197
340
 
198
341
  async dbInit(options: DbInitOptions): Promise<DbInitResult> {
342
+ const { onProgress } = options;
343
+ await this.connectWithProgress(options.connection, 'dbInit', onProgress);
199
344
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
200
345
 
201
- // Check target supports migrations
202
- if (!this.options.target.migrations) {
346
+ if (!hasMigrations(this.options.target)) {
203
347
  throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
204
348
  }
205
349
 
206
- // Validate contract using family instance
207
- const contractIR = familyInstance.validateContractIR(options.contractIR);
350
+ let contract: Contract;
351
+ try {
352
+ contract = familyInstance.validateContract(options.contract);
353
+ } catch (error) {
354
+ const message = error instanceof Error ? error.message : String(error);
355
+ throw new ContractValidationError(message, error);
356
+ }
208
357
 
209
- // Delegate to extracted dbInit operation
210
358
  return executeDbInit({
211
359
  driver,
212
360
  familyInstance,
213
- contractIR,
361
+ contract,
362
+ mode: options.mode,
363
+ migrations: this.options.target.migrations,
364
+ frameworkComponents,
365
+ ...ifDefined('onProgress', onProgress),
366
+ });
367
+ }
368
+
369
+ async dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult> {
370
+ const { onProgress } = options;
371
+ await this.connectWithProgress(options.connection, 'dbUpdate', onProgress);
372
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
373
+
374
+ if (!hasMigrations(this.options.target)) {
375
+ throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
376
+ }
377
+
378
+ let contract: Contract;
379
+ try {
380
+ contract = familyInstance.validateContract(options.contract);
381
+ } catch (error) {
382
+ const message = error instanceof Error ? error.message : String(error);
383
+ throw new ContractValidationError(message, error);
384
+ }
385
+
386
+ return executeDbUpdate({
387
+ driver,
388
+ familyInstance,
389
+ contract,
214
390
  mode: options.mode,
215
391
  migrations: this.options.target.migrations,
216
392
  frameworkComponents,
393
+ ...ifDefined('acceptDataLoss', options.acceptDataLoss),
394
+ ...ifDefined('onProgress', onProgress),
395
+ });
396
+ }
397
+
398
+ async readMarker(): Promise<ContractMarkerRecord | null> {
399
+ const { driver, familyInstance } = await this.ensureConnected();
400
+ return familyInstance.readMarker({ driver });
401
+ }
402
+
403
+ async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {
404
+ const { onProgress } = options;
405
+ await this.connectWithProgress(options.connection, 'migrationApply', onProgress);
406
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
407
+
408
+ if (!hasMigrations(this.options.target)) {
409
+ throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
410
+ }
411
+
412
+ return executeMigrationApply({
413
+ driver,
414
+ familyInstance,
415
+ originHash: options.originHash,
416
+ destinationHash: options.destinationHash,
417
+ pendingMigrations: options.pendingMigrations,
418
+ migrations: this.options.target.migrations,
419
+ frameworkComponents,
420
+ targetId: this.options.target.targetId,
421
+ ...(onProgress ? { onProgress } : {}),
217
422
  });
218
423
  }
219
424
 
220
425
  async introspect(options?: IntrospectOptions): Promise<unknown> {
426
+ const onProgress = options?.onProgress;
427
+ await this.connectWithProgress(options?.connection, 'introspect', onProgress);
221
428
  const { driver, familyInstance } = await this.ensureConnected();
222
429
 
223
430
  // TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented
224
431
  const _schema = options?.schema;
225
432
  void _schema;
226
433
 
227
- return familyInstance.introspect({ driver });
434
+ // Emit introspect span
435
+ onProgress?.({
436
+ action: 'introspect',
437
+ kind: 'spanStart',
438
+ spanId: 'introspect',
439
+ label: 'Introspecting database schema...',
440
+ });
441
+
442
+ try {
443
+ const result = await familyInstance.introspect({ driver });
444
+
445
+ onProgress?.({
446
+ action: 'introspect',
447
+ kind: 'spanEnd',
448
+ spanId: 'introspect',
449
+ outcome: 'ok',
450
+ });
451
+
452
+ return result;
453
+ } catch (error) {
454
+ onProgress?.({
455
+ action: 'introspect',
456
+ kind: 'spanEnd',
457
+ spanId: 'introspect',
458
+ outcome: 'error',
459
+ });
460
+ throw error;
461
+ }
462
+ }
463
+
464
+ toSchemaView(schemaIR: unknown): CoreSchemaView | undefined {
465
+ this.init();
466
+ if (this.familyInstance && hasSchemaView(this.familyInstance)) {
467
+ return this.familyInstance.toSchemaView(schemaIR);
468
+ }
469
+ return undefined;
470
+ }
471
+
472
+ async emit(options: EmitOptions): Promise<EmitResult> {
473
+ const { onProgress, contractConfig } = options;
474
+
475
+ // Ensure initialized (creates stack and family instance)
476
+ // emit() does NOT require a database connection
477
+ this.init();
478
+
479
+ if (!this.familyInstance) {
480
+ throw new Error('Family instance was not initialized. This is a bug.');
481
+ }
482
+
483
+ let contractRaw: unknown;
484
+ onProgress?.({
485
+ action: 'emit',
486
+ kind: 'spanStart',
487
+ spanId: 'resolveSource',
488
+ label: 'Resolving contract source...',
489
+ });
490
+
491
+ try {
492
+ const sourceContext = {
493
+ composedExtensionPacks: (this.options.extensionPacks ?? []).map((p) => p.id),
494
+ };
495
+ const providerResult = await contractConfig.sourceProvider(sourceContext);
496
+ if (!providerResult.ok) {
497
+ onProgress?.({
498
+ action: 'emit',
499
+ kind: 'spanEnd',
500
+ spanId: 'resolveSource',
501
+ outcome: 'error',
502
+ });
503
+
504
+ return notOk({
505
+ code: 'CONTRACT_SOURCE_INVALID',
506
+ summary: providerResult.failure.summary,
507
+ why: providerResult.failure.summary,
508
+ meta: providerResult.failure.meta,
509
+ diagnostics: providerResult.failure,
510
+ });
511
+ }
512
+ contractRaw = providerResult.value;
513
+
514
+ onProgress?.({
515
+ action: 'emit',
516
+ kind: 'spanEnd',
517
+ spanId: 'resolveSource',
518
+ outcome: 'ok',
519
+ });
520
+ } catch (error) {
521
+ onProgress?.({
522
+ action: 'emit',
523
+ kind: 'spanEnd',
524
+ spanId: 'resolveSource',
525
+ outcome: 'error',
526
+ });
527
+
528
+ const message = error instanceof Error ? error.message : String(error);
529
+ return notOk({
530
+ code: 'CONTRACT_SOURCE_INVALID',
531
+ summary: 'Failed to resolve contract source',
532
+ why: message,
533
+ diagnostics: {
534
+ summary: 'Contract source provider threw an exception',
535
+ diagnostics: [
536
+ {
537
+ code: 'PROVIDER_THROW',
538
+ message,
539
+ },
540
+ ],
541
+ },
542
+ meta: undefined,
543
+ });
544
+ }
545
+
546
+ // Emit contract
547
+ onProgress?.({
548
+ action: 'emit',
549
+ kind: 'spanStart',
550
+ spanId: 'emit',
551
+ label: 'Emitting contract...',
552
+ });
553
+
554
+ try {
555
+ const enrichedIR = enrichContract(contractRaw as Contract, this.frameworkComponents ?? []);
556
+
557
+ try {
558
+ this.familyInstance.validateContract(enrichedIR);
559
+ } catch (error) {
560
+ onProgress?.({
561
+ action: 'emit',
562
+ kind: 'spanEnd',
563
+ spanId: 'emit',
564
+ outcome: 'error',
565
+ });
566
+ const message = error instanceof Error ? error.message : String(error);
567
+ return notOk({
568
+ code: 'CONTRACT_VALIDATION_FAILED',
569
+ summary: 'Contract validation failed',
570
+ why: message,
571
+ meta: undefined,
572
+ });
573
+ }
574
+
575
+ const result = await emitContractArtifacts(
576
+ enrichedIR,
577
+ this.stack!,
578
+ this.options.family.emission,
579
+ );
580
+
581
+ onProgress?.({
582
+ action: 'emit',
583
+ kind: 'spanEnd',
584
+ spanId: 'emit',
585
+ outcome: 'ok',
586
+ });
587
+
588
+ return ok({
589
+ storageHash: result.storageHash,
590
+ ...ifDefined('executionHash', result.executionHash),
591
+ profileHash: result.profileHash,
592
+ contractJson: result.contractJson,
593
+ contractDts: result.contractDts,
594
+ });
595
+ } catch (error) {
596
+ onProgress?.({
597
+ action: 'emit',
598
+ kind: 'spanEnd',
599
+ spanId: 'emit',
600
+ outcome: 'error',
601
+ });
602
+
603
+ return notOk({
604
+ code: 'EMIT_FAILED',
605
+ summary: 'Failed to emit contract',
606
+ why: error instanceof Error ? error.message : String(error),
607
+ meta: undefined,
608
+ });
609
+ }
228
610
  }
229
611
  }
@@ -0,0 +1,119 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
+
4
+ type CapabilityMatrix = Record<string, Record<string, boolean>>;
5
+
6
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+
10
+ function sortDeep(value: unknown): unknown {
11
+ if (Array.isArray(value)) {
12
+ return value.map(sortDeep);
13
+ }
14
+ if (!isPlainObject(value)) {
15
+ return value;
16
+ }
17
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
18
+ const next: Record<string, unknown> = {};
19
+ for (const [key, child] of entries) {
20
+ next[key] = sortDeep(child);
21
+ }
22
+ return next;
23
+ }
24
+
25
+ function sortDeepTyped<T>(value: T): T {
26
+ return sortDeep(value) as T;
27
+ }
28
+
29
+ function extractCapabilityMatrix(value: unknown): CapabilityMatrix {
30
+ if (!isPlainObject(value)) return {};
31
+
32
+ const out: CapabilityMatrix = {};
33
+ for (const [namespace, maybeCaps] of Object.entries(value)) {
34
+ if (!isPlainObject(maybeCaps)) continue;
35
+ const caps: Record<string, boolean> = {};
36
+ for (const [key, flag] of Object.entries(maybeCaps)) {
37
+ if (typeof flag === 'boolean') {
38
+ caps[key] = flag;
39
+ }
40
+ }
41
+ if (Object.keys(caps).length > 0) {
42
+ out[namespace] = caps;
43
+ }
44
+ }
45
+
46
+ return out;
47
+ }
48
+
49
+ function mergeCapabilities(left: CapabilityMatrix, right: CapabilityMatrix): CapabilityMatrix {
50
+ const next: CapabilityMatrix = { ...left };
51
+ for (const [namespace, capabilities] of Object.entries(right)) {
52
+ next[namespace] = {
53
+ ...(left[namespace] ?? {}),
54
+ ...capabilities,
55
+ };
56
+ }
57
+ return next;
58
+ }
59
+
60
+ function extractExtensionPackMeta(
61
+ component: TargetBoundComponentDescriptor<string, string>,
62
+ ): Record<string, unknown> {
63
+ const { kind, id, version, capabilities, types } = component;
64
+ const base: Record<string, unknown> = {
65
+ kind,
66
+ id,
67
+ familyId: component.familyId,
68
+ targetId: component.targetId,
69
+ version,
70
+ };
71
+ if (capabilities) {
72
+ base['capabilities'] = capabilities;
73
+ }
74
+ if (types) {
75
+ if (types.codecTypes) {
76
+ const { controlPlaneHooks: _, codecInstances: _ci, ...cleanedCodecTypes } = types.codecTypes;
77
+ base['types'] = { ...types, codecTypes: cleanedCodecTypes };
78
+ } else {
79
+ base['types'] = types;
80
+ }
81
+ }
82
+ return base;
83
+ }
84
+
85
+ /**
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.
89
+ */
90
+ export function enrichContract(
91
+ ir: Contract,
92
+ components: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>,
93
+ ): Contract {
94
+ let mergedCapabilities = ir.capabilities;
95
+ const extensionPacksMeta: Record<string, unknown> = {};
96
+
97
+ for (const component of components) {
98
+ if (component.capabilities) {
99
+ mergedCapabilities = mergeCapabilities(
100
+ mergedCapabilities,
101
+ extractCapabilityMatrix(component.capabilities),
102
+ );
103
+ }
104
+ if (component.kind === 'extension') {
105
+ extensionPacksMeta[component.id] = extractExtensionPackMeta(component);
106
+ }
107
+ }
108
+
109
+ const extensionPacks =
110
+ Object.keys(extensionPacksMeta).length > 0
111
+ ? { ...ir.extensionPacks, ...extensionPacksMeta }
112
+ : ir.extensionPacks;
113
+
114
+ return {
115
+ ...ir,
116
+ capabilities: sortDeepTyped(mergedCapabilities),
117
+ extensionPacks: sortDeepTyped(extensionPacks),
118
+ };
119
+ }