@prisma-next/cli 0.3.0-dev.12 → 0.3.0-dev.122

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