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