@prisma-next/cli 0.3.0-pr.99.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +381 -128
  3. package/dist/agent-skill-mongo.md +106 -0
  4. package/dist/agent-skill-postgres.md +106 -0
  5. package/dist/cli-errors-BDCYR5ap.mjs +4 -0
  6. package/dist/cli-errors-DStABy9d.d.mts +3 -0
  7. package/dist/cli.d.mts +1 -0
  8. package/dist/cli.js +1 -2910
  9. package/dist/cli.mjs +261 -0
  10. package/dist/cli.mjs.map +1 -0
  11. package/dist/client-DiUkJAeN.mjs +987 -0
  12. package/dist/client-DiUkJAeN.mjs.map +1 -0
  13. package/dist/commands/contract-emit.d.mts +7 -0
  14. package/dist/commands/contract-emit.d.mts.map +1 -0
  15. package/dist/commands/contract-emit.mjs +9 -0
  16. package/dist/commands/contract-infer.d.mts +7 -0
  17. package/dist/commands/contract-infer.d.mts.map +1 -0
  18. package/dist/commands/contract-infer.mjs +10 -0
  19. package/dist/commands/db-init.d.mts +7 -0
  20. package/dist/commands/db-init.d.mts.map +1 -0
  21. package/dist/commands/db-init.mjs +126 -0
  22. package/dist/commands/db-init.mjs.map +1 -0
  23. package/dist/commands/db-schema.d.mts +7 -0
  24. package/dist/commands/db-schema.d.mts.map +1 -0
  25. package/dist/commands/db-schema.mjs +56 -0
  26. package/dist/commands/db-schema.mjs.map +1 -0
  27. package/dist/commands/db-sign.d.mts +7 -0
  28. package/dist/commands/db-sign.d.mts.map +1 -0
  29. package/dist/commands/db-sign.mjs +137 -0
  30. package/dist/commands/db-sign.mjs.map +1 -0
  31. package/dist/commands/db-update.d.mts +7 -0
  32. package/dist/commands/db-update.d.mts.map +1 -0
  33. package/dist/commands/db-update.mjs +123 -0
  34. package/dist/commands/db-update.mjs.map +1 -0
  35. package/dist/commands/db-verify.d.mts +7 -0
  36. package/dist/commands/db-verify.d.mts.map +1 -0
  37. package/dist/commands/db-verify.mjs +323 -0
  38. package/dist/commands/db-verify.mjs.map +1 -0
  39. package/dist/commands/migration-apply.d.mts +36 -0
  40. package/dist/commands/migration-apply.d.mts.map +1 -0
  41. package/dist/commands/migration-apply.mjs +245 -0
  42. package/dist/commands/migration-apply.mjs.map +1 -0
  43. package/dist/commands/migration-new.d.mts +8 -0
  44. package/dist/commands/migration-new.d.mts.map +1 -0
  45. package/dist/commands/migration-new.mjs +152 -0
  46. package/dist/commands/migration-new.mjs.map +1 -0
  47. package/dist/commands/migration-plan.d.mts +47 -0
  48. package/dist/commands/migration-plan.d.mts.map +1 -0
  49. package/dist/commands/migration-plan.mjs +313 -0
  50. package/dist/commands/migration-plan.mjs.map +1 -0
  51. package/dist/commands/migration-ref.d.mts +43 -0
  52. package/dist/commands/migration-ref.d.mts.map +1 -0
  53. package/dist/commands/migration-ref.mjs +195 -0
  54. package/dist/commands/migration-ref.mjs.map +1 -0
  55. package/dist/commands/migration-show.d.mts +28 -0
  56. package/dist/commands/migration-show.d.mts.map +1 -0
  57. package/dist/commands/migration-show.mjs +140 -0
  58. package/dist/commands/migration-show.mjs.map +1 -0
  59. package/dist/commands/migration-status.d.mts +86 -0
  60. package/dist/commands/migration-status.d.mts.map +1 -0
  61. package/dist/commands/migration-status.mjs +9 -0
  62. package/dist/commands/migration-verify.d.mts +16 -0
  63. package/dist/commands/migration-verify.d.mts.map +1 -0
  64. package/dist/commands/migration-verify.mjs +110 -0
  65. package/dist/commands/migration-verify.mjs.map +1 -0
  66. package/dist/config-loader-C4VXKl8f.mjs +43 -0
  67. package/dist/config-loader-C4VXKl8f.mjs.map +1 -0
  68. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  69. package/dist/config-loader.d.mts.map +1 -0
  70. package/dist/config-loader.mjs +3 -0
  71. package/dist/contract-emit-D2wDXfyo.mjs +191 -0
  72. package/dist/contract-emit-D2wDXfyo.mjs.map +1 -0
  73. package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
  74. package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -0
  75. package/dist/contract-emit-kN-IkKTE.mjs +6 -0
  76. package/dist/contract-enrichment-CGW6mm-E.mjs +79 -0
  77. package/dist/contract-enrichment-CGW6mm-E.mjs.map +1 -0
  78. package/dist/contract-infer-DozZT511.mjs +90 -0
  79. package/dist/contract-infer-DozZT511.mjs.map +1 -0
  80. package/dist/exports/config-types.d.mts +2 -0
  81. package/dist/exports/config-types.mjs +3 -0
  82. package/dist/exports/control-api.d.mts +624 -0
  83. package/dist/exports/control-api.d.mts.map +1 -0
  84. package/dist/exports/control-api.mjs +8 -0
  85. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
  86. package/dist/exports/index.d.mts.map +1 -0
  87. package/dist/exports/index.mjs +142 -0
  88. package/dist/exports/index.mjs.map +1 -0
  89. package/dist/extract-operation-statements-DZUJNmL3.mjs +13 -0
  90. package/dist/extract-operation-statements-DZUJNmL3.mjs.map +1 -0
  91. package/dist/extract-sql-ddl-DDMX-9mz.mjs +26 -0
  92. package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +1 -0
  93. package/dist/framework-components-BAsliT4V.mjs +59 -0
  94. package/dist/framework-components-BAsliT4V.mjs.map +1 -0
  95. package/dist/init-6Pvm_esG.mjs +430 -0
  96. package/dist/init-6Pvm_esG.mjs.map +1 -0
  97. package/dist/inspect-live-schema-BYnhztxZ.mjs +91 -0
  98. package/dist/inspect-live-schema-BYnhztxZ.mjs.map +1 -0
  99. package/dist/migration-command-scaffold-CntCcntR.mjs +105 -0
  100. package/dist/migration-command-scaffold-CntCcntR.mjs.map +1 -0
  101. package/dist/migration-status-CJANY4yr.mjs +1583 -0
  102. package/dist/migration-status-CJANY4yr.mjs.map +1 -0
  103. package/dist/migrations-DTZBYXm1.mjs +173 -0
  104. package/dist/migrations-DTZBYXm1.mjs.map +1 -0
  105. package/dist/progress-adapter-B-YvmcDu.mjs +43 -0
  106. package/dist/progress-adapter-B-YvmcDu.mjs.map +1 -0
  107. package/dist/quick-reference-mongo.md +93 -0
  108. package/dist/quick-reference-postgres.md +91 -0
  109. package/dist/result-handler-oK_vA-Fn.mjs +697 -0
  110. package/dist/result-handler-oK_vA-Fn.mjs.map +1 -0
  111. package/dist/terminal-ui-C5k88MmW.mjs +274 -0
  112. package/dist/terminal-ui-C5k88MmW.mjs.map +1 -0
  113. package/dist/validate-contract-deps-esa-VQ0h.mjs +37 -0
  114. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +1 -0
  115. package/dist/verify-DlFQ2FOw.mjs +385 -0
  116. package/dist/verify-DlFQ2FOw.mjs.map +1 -0
  117. package/package.json +87 -40
  118. package/src/cli.ts +118 -58
  119. package/src/commands/contract-emit.ts +101 -78
  120. package/src/commands/contract-infer-paths.ts +32 -0
  121. package/src/commands/contract-infer.ts +143 -0
  122. package/src/commands/db-init.ts +97 -219
  123. package/src/commands/db-schema.ts +77 -0
  124. package/src/commands/db-sign.ts +46 -73
  125. package/src/commands/db-update.ts +236 -0
  126. package/src/commands/db-verify.ts +409 -119
  127. package/src/commands/init/detect-package-manager.ts +47 -0
  128. package/src/commands/init/index.ts +21 -0
  129. package/src/commands/init/init.ts +203 -0
  130. package/src/commands/init/templates/agent-skill-mongo.md +106 -0
  131. package/src/commands/init/templates/agent-skill-postgres.md +106 -0
  132. package/src/commands/init/templates/agent-skill.ts +19 -0
  133. package/src/commands/init/templates/code-templates.ts +168 -0
  134. package/src/commands/init/templates/quick-reference-mongo.md +93 -0
  135. package/src/commands/init/templates/quick-reference-postgres.md +91 -0
  136. package/src/commands/init/templates/quick-reference.ts +19 -0
  137. package/src/commands/init/templates/render.ts +20 -0
  138. package/src/commands/init/templates/tsconfig.ts +35 -0
  139. package/src/commands/inspect-live-schema.ts +170 -0
  140. package/src/commands/migration-apply.ts +427 -0
  141. package/src/commands/migration-new.ts +260 -0
  142. package/src/commands/migration-plan.ts +519 -0
  143. package/src/commands/migration-ref.ts +305 -0
  144. package/src/commands/migration-show.ts +246 -0
  145. package/src/commands/migration-status.ts +864 -0
  146. package/src/commands/migration-verify.ts +180 -0
  147. package/src/config-loader.ts +13 -3
  148. package/src/control-api/client.ts +205 -183
  149. package/src/control-api/contract-enrichment.ts +119 -0
  150. package/src/control-api/errors.ts +9 -0
  151. package/src/control-api/operations/contract-emit.ts +181 -0
  152. package/src/control-api/operations/db-init.ts +53 -49
  153. package/src/control-api/operations/db-update.ts +220 -0
  154. package/src/control-api/operations/extract-operation-statements.ts +14 -0
  155. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  156. package/src/control-api/operations/migration-apply.ts +191 -0
  157. package/src/control-api/operations/migration-helpers.ts +49 -0
  158. package/src/control-api/types.ts +274 -52
  159. package/src/exports/config-types.ts +4 -3
  160. package/src/exports/control-api.ts +15 -5
  161. package/src/load-ts-contract.ts +30 -19
  162. package/src/utils/cli-errors.ts +14 -8
  163. package/src/utils/command-helpers.ts +302 -3
  164. package/src/utils/formatters/emit.ts +67 -0
  165. package/src/utils/formatters/errors.ts +82 -0
  166. package/src/utils/formatters/graph-migration-mapper.ts +240 -0
  167. package/src/utils/formatters/graph-render.ts +1323 -0
  168. package/src/utils/formatters/graph-types.ts +120 -0
  169. package/src/utils/formatters/help.ts +380 -0
  170. package/src/utils/formatters/helpers.ts +28 -0
  171. package/src/utils/formatters/migrations.ts +346 -0
  172. package/src/utils/formatters/styled.ts +212 -0
  173. package/src/utils/formatters/verify.ts +621 -0
  174. package/src/utils/framework-components.ts +13 -10
  175. package/src/utils/global-flags.ts +41 -23
  176. package/src/utils/migration-command-scaffold.ts +184 -0
  177. package/src/utils/migration-types.ts +12 -0
  178. package/src/utils/progress-adapter.ts +18 -29
  179. package/src/utils/result-handler.ts +12 -13
  180. package/src/utils/shutdown.ts +92 -0
  181. package/src/utils/suggest-command.ts +31 -0
  182. package/src/utils/terminal-ui.ts +276 -0
  183. package/src/utils/validate-contract-deps.ts +49 -0
  184. package/dist/chunk-AGOTG4L3.js +0 -965
  185. package/dist/chunk-AGOTG4L3.js.map +0 -1
  186. package/dist/chunk-HLLI4YL7.js +0 -180
  187. package/dist/chunk-HLLI4YL7.js.map +0 -1
  188. package/dist/chunk-HWYQOCAJ.js +0 -47
  189. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  190. package/dist/chunk-VG2R7DGF.js +0 -735
  191. package/dist/chunk-VG2R7DGF.js.map +0 -1
  192. package/dist/cli.d.ts +0 -2
  193. package/dist/cli.d.ts.map +0 -1
  194. package/dist/cli.js.map +0 -1
  195. package/dist/commands/contract-emit.d.ts +0 -3
  196. package/dist/commands/contract-emit.d.ts.map +0 -1
  197. package/dist/commands/contract-emit.js +0 -10
  198. package/dist/commands/contract-emit.js.map +0 -1
  199. package/dist/commands/db-init.d.ts +0 -3
  200. package/dist/commands/db-init.d.ts.map +0 -1
  201. package/dist/commands/db-init.js +0 -257
  202. package/dist/commands/db-init.js.map +0 -1
  203. package/dist/commands/db-introspect.d.ts +0 -3
  204. package/dist/commands/db-introspect.d.ts.map +0 -1
  205. package/dist/commands/db-introspect.js +0 -155
  206. package/dist/commands/db-introspect.js.map +0 -1
  207. package/dist/commands/db-schema-verify.d.ts +0 -3
  208. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  209. package/dist/commands/db-schema-verify.js +0 -171
  210. package/dist/commands/db-schema-verify.js.map +0 -1
  211. package/dist/commands/db-sign.d.ts +0 -3
  212. package/dist/commands/db-sign.d.ts.map +0 -1
  213. package/dist/commands/db-sign.js +0 -195
  214. package/dist/commands/db-sign.js.map +0 -1
  215. package/dist/commands/db-verify.d.ts +0 -3
  216. package/dist/commands/db-verify.d.ts.map +0 -1
  217. package/dist/commands/db-verify.js +0 -193
  218. package/dist/commands/db-verify.js.map +0 -1
  219. package/dist/config-loader.d.ts.map +0 -1
  220. package/dist/config-loader.js +0 -7
  221. package/dist/config-loader.js.map +0 -1
  222. package/dist/control-api/client.d.ts +0 -13
  223. package/dist/control-api/client.d.ts.map +0 -1
  224. package/dist/control-api/operations/db-init.d.ts +0 -29
  225. package/dist/control-api/operations/db-init.d.ts.map +0 -1
  226. package/dist/control-api/types.d.ts +0 -387
  227. package/dist/control-api/types.d.ts.map +0 -1
  228. package/dist/exports/config-types.d.ts +0 -3
  229. package/dist/exports/config-types.d.ts.map +0 -1
  230. package/dist/exports/config-types.js +0 -6
  231. package/dist/exports/config-types.js.map +0 -1
  232. package/dist/exports/control-api.d.ts +0 -13
  233. package/dist/exports/control-api.d.ts.map +0 -1
  234. package/dist/exports/control-api.js +0 -7
  235. package/dist/exports/control-api.js.map +0 -1
  236. package/dist/exports/index.d.ts +0 -4
  237. package/dist/exports/index.d.ts.map +0 -1
  238. package/dist/exports/index.js +0 -176
  239. package/dist/exports/index.js.map +0 -1
  240. package/dist/load-ts-contract.d.ts.map +0 -1
  241. package/dist/utils/cli-errors.d.ts +0 -7
  242. package/dist/utils/cli-errors.d.ts.map +0 -1
  243. package/dist/utils/command-helpers.d.ts +0 -12
  244. package/dist/utils/command-helpers.d.ts.map +0 -1
  245. package/dist/utils/framework-components.d.ts +0 -70
  246. package/dist/utils/framework-components.d.ts.map +0 -1
  247. package/dist/utils/global-flags.d.ts +0 -25
  248. package/dist/utils/global-flags.d.ts.map +0 -1
  249. package/dist/utils/output.d.ts +0 -142
  250. package/dist/utils/output.d.ts.map +0 -1
  251. package/dist/utils/progress-adapter.d.ts +0 -26
  252. package/dist/utils/progress-adapter.d.ts.map +0 -1
  253. package/dist/utils/result-handler.d.ts +0 -15
  254. package/dist/utils/result-handler.d.ts.map +0 -1
  255. package/src/commands/db-introspect.ts +0 -227
  256. package/src/commands/db-schema-verify.ts +0 -238
  257. package/src/utils/output.ts +0 -1471
@@ -1,25 +1,42 @@
1
- import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';
2
- import type { CoreSchemaView } from '@prisma-next/core-control-plane/schema-view';
3
- 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';
4
4
  import type {
5
5
  ControlDriverInstance,
6
6
  ControlFamilyInstance,
7
- ControlPlaneStack,
7
+ ControlStack,
8
+ CoreSchemaView,
8
9
  SignDatabaseResult,
9
10
  VerifyDatabaseResult,
10
11
  VerifyDatabaseSchemaResult,
11
- } 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';
12
19
  import { notOk, ok } from '@prisma-next/utils/result';
13
20
  import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
21
+ import { enrichContract } from './contract-enrichment';
22
+ import { ContractValidationError } from './errors';
14
23
  import { executeDbInit } from './operations/db-init';
24
+ import { executeDbUpdate } from './operations/db-update';
25
+ import { executeMigrationApply } from './operations/migration-apply';
15
26
  import type {
27
+ ControlActionName,
16
28
  ControlClient,
17
29
  ControlClientOptions,
18
30
  DbInitOptions,
19
31
  DbInitResult,
32
+ DbUpdateOptions,
33
+ DbUpdateResult,
20
34
  EmitOptions,
21
35
  EmitResult,
22
36
  IntrospectOptions,
37
+ MigrationApplyOptions,
38
+ MigrationApplyResult,
39
+ OnControlProgress,
23
40
  SchemaVerifyOptions,
24
41
  SignOptions,
25
42
  VerifyOptions,
@@ -45,9 +62,9 @@ export function createControlClient(options: ControlClientOptions): ControlClien
45
62
  */
46
63
  class ControlClientImpl implements ControlClient {
47
64
  private readonly options: ControlClientOptions;
48
- private stack: ControlPlaneStack<string, string> | null = null;
65
+ private stack: ControlStack | null = null;
49
66
  private driver: ControlDriverInstance<string, string> | null = null;
50
- private familyInstance: ControlFamilyInstance<string> | null = null;
67
+ private familyInstance: ControlFamilyInstance<string, unknown> | null = null;
51
68
  private frameworkComponents: ReadonlyArray<
52
69
  TargetBoundComponentDescriptor<string, string>
53
70
  > | null = null;
@@ -64,15 +81,14 @@ class ControlClientImpl implements ControlClient {
64
81
  return; // Idempotent
65
82
  }
66
83
 
67
- // Create the control plane stack
68
- this.stack = createControlPlaneStack({
84
+ this.stack = createControlStack({
85
+ family: this.options.family,
69
86
  target: this.options.target,
70
87
  adapter: this.options.adapter,
71
88
  driver: this.options.driver,
72
89
  extensionPacks: this.options.extensionPacks,
73
90
  });
74
91
 
75
- // Create family instance using the stack
76
92
  this.familyInstance = this.options.family.create(this.stack);
77
93
 
78
94
  // Validate and type-narrow framework components
@@ -113,12 +129,8 @@ class ControlClientImpl implements ControlClient {
113
129
  );
114
130
  }
115
131
 
116
- // Create driver instance
117
- // Cast through any since connection type is driver-specific at runtime.
118
- // The driver descriptor is typed with any for TConnection in ControlClientOptions,
119
- // but createControlPlaneStack defaults it to string. We bridge this at runtime.
120
132
  // biome-ignore lint/suspicious/noExplicitAny: required for runtime connection type flexibility
121
- this.driver = await this.stack?.driver.create(resolvedConnection as any);
133
+ this.driver = await this.stack.driver.create(resolvedConnection as any);
122
134
  }
123
135
 
124
136
  async close(): Promise<void> {
@@ -130,7 +142,7 @@ class ControlClientImpl implements ControlClient {
130
142
 
131
143
  private async ensureConnected(): Promise<{
132
144
  driver: ControlDriverInstance<string, string>;
133
- familyInstance: ControlFamilyInstance<string>;
145
+ familyInstance: ControlFamilyInstance<string, unknown>;
134
146
  frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>;
135
147
  }> {
136
148
  // Auto-init if needed
@@ -151,47 +163,47 @@ class ControlClientImpl implements ControlClient {
151
163
  };
152
164
  }
153
165
 
154
- async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {
155
- const { onProgress } = options;
156
-
157
- // Connect with progress span if connection provided
158
- if (options.connection !== undefined) {
159
- onProgress?.({
160
- action: 'verify',
161
- kind: 'spanStart',
162
- spanId: 'connect',
163
- label: 'Connecting to database...',
164
- });
165
- try {
166
- await this.connect(options.connection);
167
- onProgress?.({
168
- action: 'verify',
169
- kind: 'spanEnd',
170
- spanId: 'connect',
171
- outcome: 'ok',
172
- });
173
- } catch (error) {
174
- onProgress?.({
175
- action: 'verify',
176
- kind: 'spanEnd',
177
- spanId: 'connect',
178
- outcome: 'error',
179
- });
180
- throw error;
181
- }
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;
182
184
  }
185
+ }
183
186
 
187
+ async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {
188
+ const { onProgress } = options;
189
+ await this.connectWithProgress(options.connection, 'verify', onProgress);
184
190
  const { driver, familyInstance } = await this.ensureConnected();
185
191
 
186
192
  // Validate contract using family instance
187
- 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
+ }
188
200
 
189
201
  // Emit verify span
190
202
  onProgress?.({
191
203
  action: 'verify',
192
204
  kind: 'spanStart',
193
205
  spanId: 'verify',
194
- label: 'Verifying contract marker...',
206
+ label: 'Verifying database marker...',
195
207
  });
196
208
 
197
209
  try {
@@ -201,7 +213,7 @@ class ControlClientImpl implements ControlClient {
201
213
  // metadata for error reporting.
202
214
  const result = await familyInstance.verify({
203
215
  driver,
204
- contractIR,
216
+ contract,
205
217
  expectedTargetId: this.options.target.targetId,
206
218
  contractPath: '',
207
219
  });
@@ -227,38 +239,17 @@ class ControlClientImpl implements ControlClient {
227
239
 
228
240
  async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {
229
241
  const { onProgress } = options;
230
-
231
- // Connect with progress span if connection provided
232
- if (options.connection !== undefined) {
233
- onProgress?.({
234
- action: 'schemaVerify',
235
- kind: 'spanStart',
236
- spanId: 'connect',
237
- label: 'Connecting to database...',
238
- });
239
- try {
240
- await this.connect(options.connection);
241
- onProgress?.({
242
- action: 'schemaVerify',
243
- kind: 'spanEnd',
244
- spanId: 'connect',
245
- outcome: 'ok',
246
- });
247
- } catch (error) {
248
- onProgress?.({
249
- action: 'schemaVerify',
250
- kind: 'spanEnd',
251
- spanId: 'connect',
252
- outcome: 'error',
253
- });
254
- throw error;
255
- }
256
- }
257
-
242
+ await this.connectWithProgress(options.connection, 'schemaVerify', onProgress);
258
243
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
259
244
 
260
245
  // Validate contract using family instance
261
- 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
+ }
262
253
 
263
254
  // Emit schemaVerify span
264
255
  onProgress?.({
@@ -272,7 +263,7 @@ class ControlClientImpl implements ControlClient {
272
263
  // Delegate to family instance schemaVerify method
273
264
  const result = await familyInstance.schemaVerify({
274
265
  driver,
275
- contractIR,
266
+ contract,
276
267
  strict: options.strict ?? false,
277
268
  contractPath: '',
278
269
  frameworkComponents,
@@ -299,38 +290,17 @@ class ControlClientImpl implements ControlClient {
299
290
 
300
291
  async sign(options: SignOptions): Promise<SignDatabaseResult> {
301
292
  const { onProgress } = options;
302
-
303
- // Connect with progress span if connection provided
304
- if (options.connection !== undefined) {
305
- onProgress?.({
306
- action: 'sign',
307
- kind: 'spanStart',
308
- spanId: 'connect',
309
- label: 'Connecting to database...',
310
- });
311
- try {
312
- await this.connect(options.connection);
313
- onProgress?.({
314
- action: 'sign',
315
- kind: 'spanEnd',
316
- spanId: 'connect',
317
- outcome: 'ok',
318
- });
319
- } catch (error) {
320
- onProgress?.({
321
- action: 'sign',
322
- kind: 'spanEnd',
323
- spanId: 'connect',
324
- outcome: 'error',
325
- });
326
- throw error;
327
- }
328
- }
329
-
293
+ await this.connectWithProgress(options.connection, 'sign', onProgress);
330
294
  const { driver, familyInstance } = await this.ensureConnected();
331
295
 
332
296
  // Validate contract using family instance
333
- 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
+ }
334
304
 
335
305
  // Emit sign span
336
306
  onProgress?.({
@@ -344,9 +314,9 @@ class ControlClientImpl implements ControlClient {
344
314
  // Delegate to family instance sign method
345
315
  const result = await familyInstance.sign({
346
316
  driver,
347
- contractIR,
317
+ contract,
348
318
  contractPath: options.contractPath ?? '',
349
- ...(options.configPath ? { configPath: options.configPath } : {}),
319
+ ...ifDefined('configPath', options.configPath),
350
320
  });
351
321
 
352
322
  onProgress?.({
@@ -370,86 +340,91 @@ class ControlClientImpl implements ControlClient {
370
340
 
371
341
  async dbInit(options: DbInitOptions): Promise<DbInitResult> {
372
342
  const { onProgress } = options;
343
+ await this.connectWithProgress(options.connection, 'dbInit', onProgress);
344
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
373
345
 
374
- // Connect with progress span if connection provided
375
- if (options.connection !== undefined) {
376
- onProgress?.({
377
- action: 'dbInit',
378
- kind: 'spanStart',
379
- spanId: 'connect',
380
- label: 'Connecting to database...',
381
- });
382
- try {
383
- await this.connect(options.connection);
384
- onProgress?.({
385
- action: 'dbInit',
386
- kind: 'spanEnd',
387
- spanId: 'connect',
388
- outcome: 'ok',
389
- });
390
- } catch (error) {
391
- onProgress?.({
392
- action: 'dbInit',
393
- kind: 'spanEnd',
394
- spanId: 'connect',
395
- outcome: 'error',
396
- });
397
- throw error;
398
- }
346
+ if (!hasMigrations(this.options.target)) {
347
+ throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
399
348
  }
400
349
 
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
+ }
357
+
358
+ return executeDbInit({
359
+ driver,
360
+ familyInstance,
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);
401
372
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
402
373
 
403
- // Check target supports migrations
404
- if (!this.options.target.migrations) {
374
+ if (!hasMigrations(this.options.target)) {
405
375
  throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
406
376
  }
407
377
 
408
- // Validate contract using family instance
409
- const contractIR = familyInstance.validateContractIR(options.contractIR);
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
+ }
410
385
 
411
- // Delegate to extracted dbInit operation
412
- return executeDbInit({
386
+ return executeDbUpdate({
413
387
  driver,
414
388
  familyInstance,
415
- contractIR,
389
+ contract,
416
390
  mode: options.mode,
417
391
  migrations: this.options.target.migrations,
418
392
  frameworkComponents,
419
- ...(onProgress ? { onProgress } : {}),
393
+ ...ifDefined('acceptDataLoss', options.acceptDataLoss),
394
+ ...ifDefined('onProgress', onProgress),
420
395
  });
421
396
  }
422
397
 
423
- async introspect(options?: IntrospectOptions): Promise<unknown> {
424
- const onProgress = options?.onProgress;
398
+ async readMarker(): Promise<ContractMarkerRecord | null> {
399
+ const { driver, familyInstance } = await this.ensureConnected();
400
+ return familyInstance.readMarker({ driver });
401
+ }
425
402
 
426
- // Connect with progress span if connection provided
427
- if (options?.connection !== undefined) {
428
- onProgress?.({
429
- action: 'introspect',
430
- kind: 'spanStart',
431
- spanId: 'connect',
432
- label: 'Connecting to database...',
433
- });
434
- try {
435
- await this.connect(options.connection);
436
- onProgress?.({
437
- action: 'introspect',
438
- kind: 'spanEnd',
439
- spanId: 'connect',
440
- outcome: 'ok',
441
- });
442
- } catch (error) {
443
- onProgress?.({
444
- action: 'introspect',
445
- kind: 'spanEnd',
446
- spanId: 'connect',
447
- outcome: 'error',
448
- });
449
- throw error;
450
- }
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`);
451
410
  }
452
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 } : {}),
422
+ });
423
+ }
424
+
425
+ async introspect(options?: IntrospectOptions): Promise<unknown> {
426
+ const onProgress = options?.onProgress;
427
+ await this.connectWithProgress(options?.connection, 'introspect', onProgress);
453
428
  const { driver, familyInstance } = await this.ensureConnected();
454
429
 
455
430
  // TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented
@@ -488,7 +463,7 @@ class ControlClientImpl implements ControlClient {
488
463
 
489
464
  toSchemaView(schemaIR: unknown): CoreSchemaView | undefined {
490
465
  this.init();
491
- if (this.familyInstance?.toSchemaView) {
466
+ if (this.familyInstance && hasSchemaView(this.familyInstance)) {
492
467
  return this.familyInstance.toSchemaView(schemaIR);
493
468
  }
494
469
  return undefined;
@@ -505,7 +480,6 @@ class ControlClientImpl implements ControlClient {
505
480
  throw new Error('Family instance was not initialized. This is a bug.');
506
481
  }
507
482
 
508
- // Resolve contract source
509
483
  let contractRaw: unknown;
510
484
  onProgress?.({
511
485
  action: 'emit',
@@ -515,14 +489,27 @@ class ControlClientImpl implements ControlClient {
515
489
  });
516
490
 
517
491
  try {
518
- switch (contractConfig.source.kind) {
519
- case 'loader':
520
- contractRaw = await contractConfig.source.load();
521
- break;
522
- case 'value':
523
- contractRaw = contractConfig.source.value;
524
- break;
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
+ });
525
511
  }
512
+ contractRaw = providerResult.value;
526
513
 
527
514
  onProgress?.({
528
515
  action: 'emit',
@@ -538,10 +525,20 @@ class ControlClientImpl implements ControlClient {
538
525
  outcome: 'error',
539
526
  });
540
527
 
528
+ const message = error instanceof Error ? error.message : String(error);
541
529
  return notOk({
542
530
  code: 'CONTRACT_SOURCE_INVALID',
543
531
  summary: 'Failed to resolve contract source',
544
- why: error instanceof Error ? error.message : String(error),
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
+ },
545
542
  meta: undefined,
546
543
  });
547
544
  }
@@ -555,7 +552,31 @@ class ControlClientImpl implements ControlClient {
555
552
  });
556
553
 
557
554
  try {
558
- const emitResult = await this.familyInstance.emitContract({ contractIR: contractRaw });
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
+ );
559
580
 
560
581
  onProgress?.({
561
582
  action: 'emit',
@@ -565,10 +586,11 @@ class ControlClientImpl implements ControlClient {
565
586
  });
566
587
 
567
588
  return ok({
568
- coreHash: emitResult.coreHash,
569
- profileHash: emitResult.profileHash,
570
- contractJson: emitResult.contractJson,
571
- contractDts: emitResult.contractDts,
589
+ storageHash: result.storageHash,
590
+ ...ifDefined('executionHash', result.executionHash),
591
+ profileHash: result.profileHash,
592
+ contractJson: result.contractJson,
593
+ contractDts: result.contractDts,
572
594
  });
573
595
  } catch (error) {
574
596
  onProgress?.({
@@ -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
+ }