@prisma-next/cli 0.5.0-dev.7 → 0.5.0-dev.71
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/README.md +56 -21
- package/dist/cli-errors-D3_sMh2K.mjs +33 -0
- package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
- package/dist/cli-errors-QH8kf-C2.d.mts +3 -0
- package/dist/cli.mjs +16 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/client-0ZX24FXF.mjs +1398 -0
- package/dist/client-0ZX24FXF.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -4
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -4
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +14 -13
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +5 -7
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +8 -9
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +13 -13
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -321
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +64 -66
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts +0 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +33 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -347
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +7 -12
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +34 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -4
- package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
- package/dist/config-loader.d.mts +0 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +2 -3
- package/dist/contract-emit-B3ChISB_.mjs +338 -0
- package/dist/contract-emit-B3ChISB_.mjs.map +1 -0
- package/dist/contract-emit-DkMqO7f2.mjs +148 -0
- package/dist/contract-emit-DkMqO7f2.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-CF6ogEJ_.mjs} +4 -6
- package/dist/contract-enrichment-CF6ogEJ_.mjs.map +1 -0
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BDKAE0B0.mjs} +12 -22
- package/dist/contract-infer-BDKAE0B0.mjs.map +1 -0
- package/dist/db-verify-B4TdDKOI.mjs +403 -0
- package/dist/db-verify-B4TdDKOI.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +287 -29
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +4 -6
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +28 -30
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +2 -4
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +2 -3
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-gwAHl7ml.mjs} +3 -4
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-gwAHl7ml.mjs.map} +1 -1
- package/dist/{init-C5220SY9.mjs → init-Deo7U8_U.mjs} +26 -35
- package/dist/init-Deo7U8_U.mjs.map +1 -0
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-BAgQMYpD.mjs} +10 -11
- package/dist/inspect-live-schema-BAgQMYpD.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -12
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +309 -86
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B8J702Uh.mjs} +8 -9
- package/dist/migration-command-scaffold-B8J702Uh.mjs.map +1 -0
- package/dist/migration-plan-BcKNnTM7.mjs +530 -0
- package/dist/migration-plan-BcKNnTM7.mjs.map +1 -0
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CjwB2of-.mjs} +117 -64
- package/dist/migration-status-CjwB2of-.mjs.map +1 -0
- package/dist/{migrations-Bo5WtTla.mjs → migrations-CIK94AJf.mjs} +43 -23
- package/dist/migrations-CIK94AJf.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-DnjfCC_u.mjs} +9 -3
- package/dist/output-DnjfCC_u.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-xASh41wr.mjs} +2 -2
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
- package/dist/{result-handler-Ba3zWQsI.mjs → result-handler-DWb1rFS-.mjs} +52 -27
- package/dist/result-handler-DWb1rFS-.mjs.map +1 -0
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-zaRDhJnP.mjs} +2 -6
- package/dist/{terminal-ui-C3ZLwQxK.mjs.map → terminal-ui-zaRDhJnP.mjs.map} +1 -1
- package/dist/{verify-Bkycc-Tf.mjs → verify-BEIa9638.mjs} +3 -4
- package/dist/verify-BEIa9638.mjs.map +1 -0
- package/package.json +28 -26
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +14 -3
- package/src/commands/db-update.ts +8 -4
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/init/templates/code-templates.ts +12 -4
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +92 -71
- package/src/commands/migration-new.ts +42 -45
- package/src/commands/migration-plan.ts +147 -64
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +60 -41
- package/src/commands/migration-status.ts +196 -60
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +69 -1
- package/src/control-api/contract-enrichment.ts +6 -4
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-apply-aggregate.ts +446 -0
- package/src/control-api/operations/db-init.ts +51 -253
- package/src/control-api/operations/db-update.ts +66 -183
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/operations/migration-apply.ts +37 -9
- package/src/control-api/types.ts +125 -7
- package/src/exports/control-api.ts +15 -3
- package/src/load-ts-contract.ts +28 -26
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +69 -25
- package/src/utils/contract-space-aggregate-loader.ts +236 -0
- package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
- package/src/utils/contract-space-migrate-pass.ts +156 -0
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs +0 -997
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/config-loader-C25b63rJ.mjs.map +0 -1
- package/dist/contract-emit--feXyNd7.mjs +0 -4
- package/dist/contract-emit-NJ01hiiv.mjs +0 -195
- package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
- package/dist/contract-emit-V5SSitUT.mjs +0 -122
- package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
- package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
- package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
- package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
- package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
- package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
- package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
- package/dist/init-C5220SY9.mjs.map +0 -1
- package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
- package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
- package/dist/output-BpcQrnnq.mjs.map +0 -1
- package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
- package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
- package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
- package/dist/verify-Bkycc-Tf.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
3
|
+
import type {
|
|
4
|
+
ControlDriverInstance,
|
|
5
|
+
ControlExtensionDescriptor,
|
|
6
|
+
ControlFamilyInstance,
|
|
7
|
+
VerifyDatabaseSchemaResult,
|
|
8
|
+
} from '@prisma-next/framework-components/control';
|
|
9
|
+
import {
|
|
10
|
+
type AggregateVerifierOutput,
|
|
11
|
+
type ContractSpaceMember,
|
|
12
|
+
verifyAggregate,
|
|
13
|
+
} from '@prisma-next/migration-tools/aggregate';
|
|
14
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
15
|
+
import { CliStructuredError } from '../../utils/cli-errors';
|
|
16
|
+
import {
|
|
17
|
+
type BuildAggregateInputs,
|
|
18
|
+
buildContractSpaceAggregate,
|
|
19
|
+
} from '../../utils/contract-space-aggregate-loader';
|
|
20
|
+
import type { OnControlProgress } from '../types';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Span IDs emitted via `onProgress` during the aggregate verify flow.
|
|
24
|
+
* Mirrors the span identifiers used by the legacy precheck / marker-check
|
|
25
|
+
* helpers so structured-output renderers and progress tests keep working.
|
|
26
|
+
*/
|
|
27
|
+
const SPAN_IDS = {
|
|
28
|
+
introspect: 'introspect',
|
|
29
|
+
verify: 'verify',
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Inputs for the aggregate `db verify` operation.
|
|
34
|
+
*
|
|
35
|
+
* Loader → verifier pipeline. The loader (sole descriptor-import
|
|
36
|
+
* boundary) builds a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate};
|
|
37
|
+
* the aggregate verifier bundles `markerCheck` + per-space pre-projected
|
|
38
|
+
* `schemaCheck`. `mode: 'strict' | 'lenient'` maps directly to the user
|
|
39
|
+
* facing `--strict` flag.
|
|
40
|
+
*/
|
|
41
|
+
export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId extends string> {
|
|
42
|
+
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
43
|
+
readonly familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
|
|
44
|
+
readonly contract: Contract;
|
|
45
|
+
readonly migrationsDir: string;
|
|
46
|
+
readonly targetId: TTargetId;
|
|
47
|
+
readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
48
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
|
|
49
|
+
readonly mode: 'strict' | 'lenient';
|
|
50
|
+
readonly skipSchema: boolean;
|
|
51
|
+
readonly skipMarker: boolean;
|
|
52
|
+
readonly onProgress?: OnControlProgress;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Result of the aggregate verify operation.
|
|
57
|
+
*
|
|
58
|
+
* Marker-check failures are surfaced as a {@link CliStructuredError}
|
|
59
|
+
* (same envelope code `5002` the legacy `runContractSpaceVerifierMarkerCheck`
|
|
60
|
+
* emitted, so downstream tooling and integration tests assert on the
|
|
61
|
+
* same shape).
|
|
62
|
+
*
|
|
63
|
+
* On success, the per-space schema results are returned for the CLI to
|
|
64
|
+
* render. When `skipSchema` is true (`--marker-only`), the schema map
|
|
65
|
+
* is empty.
|
|
66
|
+
*/
|
|
67
|
+
export interface ExecuteDbVerifySuccess {
|
|
68
|
+
readonly schemaResults: ReadonlyMap<string, VerifyDatabaseSchemaResult>;
|
|
69
|
+
readonly memberOrder: readonly string[];
|
|
70
|
+
readonly appSpaceId: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type ExecuteDbVerifyResult = Result<ExecuteDbVerifySuccess, CliStructuredError>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Loader → verifier pipeline shared by `db verify` modes (`full`,
|
|
77
|
+
* `marker-only`, `schema-only`).
|
|
78
|
+
*
|
|
79
|
+
* 1. **Load**: build a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate}
|
|
80
|
+
* from descriptors + on-disk on-disk artefacts. Layout / drift /
|
|
81
|
+
* integrity / disjointness violations short-circuit with a
|
|
82
|
+
* structured CLI error.
|
|
83
|
+
* 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
|
|
84
|
+
* schema introspection.
|
|
85
|
+
* 3. **Verify**: {@link verifyAggregate} returns per-space
|
|
86
|
+
* `markerCheck` + per-space pre-projected `schemaCheck` (closes F23).
|
|
87
|
+
* Marker mismatches map to `CliStructuredError` (code `5002`) so
|
|
88
|
+
* callers (CLI command) can render and exit. Schema results are
|
|
89
|
+
* returned to the caller verbatim.
|
|
90
|
+
*/
|
|
91
|
+
export async function executeDbVerify<TFamilyId extends string, TTargetId extends string>(
|
|
92
|
+
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
93
|
+
): Promise<ExecuteDbVerifyResult> {
|
|
94
|
+
const { driver, familyInstance, onProgress, skipSchema, skipMarker } = options;
|
|
95
|
+
const loaded = await buildContractSpaceAggregate(buildLoadInputs(options));
|
|
96
|
+
if (!loaded.ok) return notOk(loaded.failure);
|
|
97
|
+
const aggregate = loaded.value;
|
|
98
|
+
|
|
99
|
+
const markersBySpaceId = await familyInstance.readAllMarkers({ driver });
|
|
100
|
+
const schemaIntrospection = skipSchema
|
|
101
|
+
? null
|
|
102
|
+
: await runIntrospection({ driver, familyInstance, onProgress });
|
|
103
|
+
|
|
104
|
+
emitVerifySpan(onProgress, 'spanStart');
|
|
105
|
+
const verifyResult = verifyAggregate({
|
|
106
|
+
aggregate,
|
|
107
|
+
markersBySpaceId,
|
|
108
|
+
schemaIntrospection,
|
|
109
|
+
mode: options.mode,
|
|
110
|
+
verifySchemaForMember: createPerMemberVerifier(options),
|
|
111
|
+
});
|
|
112
|
+
return finaliseVerifyResult({ verifyResult, aggregate, skipMarker, onProgress });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildLoadInputs<TFamilyId extends string, TTargetId extends string>(
|
|
116
|
+
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
117
|
+
): BuildAggregateInputs<TFamilyId, TTargetId> {
|
|
118
|
+
return {
|
|
119
|
+
targetId: options.targetId,
|
|
120
|
+
migrationsDir: options.migrationsDir,
|
|
121
|
+
appContract: options.contract,
|
|
122
|
+
extensionPacks: options.extensionPacks,
|
|
123
|
+
validateContract: (json) => options.familyInstance.validateContract(json),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function runIntrospection<TFamilyId extends string, TTargetId extends string>(args: {
|
|
128
|
+
driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
129
|
+
familyInstance: ControlFamilyInstance<TFamilyId, unknown>;
|
|
130
|
+
onProgress: OnControlProgress | undefined;
|
|
131
|
+
}): Promise<unknown> {
|
|
132
|
+
const { driver, familyInstance, onProgress } = args;
|
|
133
|
+
onProgress?.({
|
|
134
|
+
action: 'dbVerify',
|
|
135
|
+
kind: 'spanStart',
|
|
136
|
+
spanId: SPAN_IDS.introspect,
|
|
137
|
+
label: 'Introspecting database schema',
|
|
138
|
+
});
|
|
139
|
+
try {
|
|
140
|
+
const result = await familyInstance.introspect({ driver });
|
|
141
|
+
onProgress?.({
|
|
142
|
+
action: 'dbVerify',
|
|
143
|
+
kind: 'spanEnd',
|
|
144
|
+
spanId: SPAN_IDS.introspect,
|
|
145
|
+
outcome: 'ok',
|
|
146
|
+
});
|
|
147
|
+
return result;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
onProgress?.({
|
|
150
|
+
action: 'dbVerify',
|
|
151
|
+
kind: 'spanEnd',
|
|
152
|
+
spanId: SPAN_IDS.introspect,
|
|
153
|
+
outcome: 'error',
|
|
154
|
+
});
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build the per-member schema callback handed to the aggregate verifier.
|
|
161
|
+
* When `skipSchema` is true the callback short-circuits with a synthetic
|
|
162
|
+
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
|
|
163
|
+
* without invoking the family's verification path.
|
|
164
|
+
*/
|
|
165
|
+
function createPerMemberVerifier<TFamilyId extends string, TTargetId extends string>(
|
|
166
|
+
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
167
|
+
): (
|
|
168
|
+
projectedSchema: unknown,
|
|
169
|
+
member: ContractSpaceMember,
|
|
170
|
+
verifyMode: 'strict' | 'lenient',
|
|
171
|
+
) => VerifyDatabaseSchemaResult {
|
|
172
|
+
const { skipSchema, familyInstance, frameworkComponents } = options;
|
|
173
|
+
return (projectedSchema, member, verifyMode) => {
|
|
174
|
+
if (skipSchema) return buildSkippedSchemaResult(member);
|
|
175
|
+
return familyInstance.schemaVerifyAgainstSchema({
|
|
176
|
+
contract: member.contract,
|
|
177
|
+
// The family's `TSchemaIR` is opaque to migration-tools; the
|
|
178
|
+
// aggregate verifier passes through whatever we hand it. The
|
|
179
|
+
// family expects its own IR shape on the way back.
|
|
180
|
+
schema: projectedSchema as never,
|
|
181
|
+
strict: verifyMode === 'strict',
|
|
182
|
+
frameworkComponents,
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function emitVerifySpan(
|
|
188
|
+
onProgress: OnControlProgress | undefined,
|
|
189
|
+
kind: 'spanStart' | 'spanEndOk' | 'spanEndError',
|
|
190
|
+
): void {
|
|
191
|
+
if (kind === 'spanStart') {
|
|
192
|
+
onProgress?.({
|
|
193
|
+
action: 'dbVerify',
|
|
194
|
+
kind: 'spanStart',
|
|
195
|
+
spanId: SPAN_IDS.verify,
|
|
196
|
+
label: 'Verifying contract spaces',
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
onProgress?.({
|
|
201
|
+
action: 'dbVerify',
|
|
202
|
+
kind: 'spanEnd',
|
|
203
|
+
spanId: SPAN_IDS.verify,
|
|
204
|
+
outcome: kind === 'spanEndOk' ? 'ok' : 'error',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Map an {@link AggregateVerifierOutput} to the operation's
|
|
210
|
+
* {@link ExecuteDbVerifyResult}, applying the `skipMarker` policy used
|
|
211
|
+
* by the CLI's `--schema-only` mode.
|
|
212
|
+
*/
|
|
213
|
+
function finaliseVerifyResult(args: {
|
|
214
|
+
verifyResult: AggregateVerifierOutput<VerifyDatabaseSchemaResult>;
|
|
215
|
+
aggregate: {
|
|
216
|
+
readonly app: { readonly spaceId: string };
|
|
217
|
+
readonly extensions: ReadonlyArray<{ readonly spaceId: string }>;
|
|
218
|
+
};
|
|
219
|
+
skipMarker: boolean;
|
|
220
|
+
onProgress: OnControlProgress | undefined;
|
|
221
|
+
}): ExecuteDbVerifyResult {
|
|
222
|
+
const { verifyResult, aggregate, skipMarker, onProgress } = args;
|
|
223
|
+
if (!verifyResult.ok) {
|
|
224
|
+
emitVerifySpan(onProgress, 'spanEndError');
|
|
225
|
+
return notOk(
|
|
226
|
+
new CliStructuredError('5002', 'Aggregate verifier introspection failed', {
|
|
227
|
+
domain: 'MIG',
|
|
228
|
+
why: verifyResult.failure.detail,
|
|
229
|
+
fix: 'Check database connectivity and the introspection tooling.',
|
|
230
|
+
docsUrl: 'https://pris.ly/contract-spaces',
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const markerError = skipMarker
|
|
235
|
+
? null
|
|
236
|
+
: mapMarkerCheckFailures(aggregate.app.spaceId, verifyResult.value.markerCheck);
|
|
237
|
+
if (markerError !== null) {
|
|
238
|
+
emitVerifySpan(onProgress, 'spanEndError');
|
|
239
|
+
return notOk(markerError);
|
|
240
|
+
}
|
|
241
|
+
emitVerifySpan(onProgress, 'spanEndOk');
|
|
242
|
+
return ok({
|
|
243
|
+
schemaResults: verifyResult.value.schemaCheck.perSpace,
|
|
244
|
+
memberOrder: [aggregate.app.spaceId, ...aggregate.extensions.map((e) => e.spaceId)],
|
|
245
|
+
appSpaceId: aggregate.app.spaceId,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSchemaResult {
|
|
250
|
+
const profileHash = (member.contract as { profileHash?: string }).profileHash;
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
summary: 'Schema verification skipped',
|
|
254
|
+
contract: {
|
|
255
|
+
storageHash: member.headRef.hash,
|
|
256
|
+
...(profileHash ? { profileHash } : {}),
|
|
257
|
+
},
|
|
258
|
+
target: { expected: member.contract.target },
|
|
259
|
+
schema: {
|
|
260
|
+
issues: [],
|
|
261
|
+
root: {
|
|
262
|
+
status: 'pass',
|
|
263
|
+
kind: 'skipped',
|
|
264
|
+
name: member.spaceId,
|
|
265
|
+
contractPath: '',
|
|
266
|
+
code: 'SKIPPED',
|
|
267
|
+
message: 'Schema verification skipped',
|
|
268
|
+
expected: undefined,
|
|
269
|
+
actual: undefined,
|
|
270
|
+
children: [],
|
|
271
|
+
},
|
|
272
|
+
counts: { pass: 0, warn: 0, fail: 0, totalNodes: 0 },
|
|
273
|
+
},
|
|
274
|
+
timings: { total: 0 },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Translate per-space marker check failures and orphan markers into a
|
|
280
|
+
* single CLI structured error envelope. Preserves the legacy code
|
|
281
|
+
* `5002` (was emitted by `runContractSpaceVerifierMarkerCheck`).
|
|
282
|
+
*/
|
|
283
|
+
function mapMarkerCheckFailures(
|
|
284
|
+
appSpaceId: string,
|
|
285
|
+
section: {
|
|
286
|
+
readonly perSpace: ReadonlyMap<
|
|
287
|
+
string,
|
|
288
|
+
| { readonly kind: 'ok' }
|
|
289
|
+
| { readonly kind: 'absent' }
|
|
290
|
+
| { readonly kind: 'hashMismatch'; readonly markerHash: string; readonly expected: string }
|
|
291
|
+
| { readonly kind: 'missingInvariants'; readonly missing: readonly string[] }
|
|
292
|
+
>;
|
|
293
|
+
readonly orphanMarkers: readonly { readonly spaceId: string; readonly row: unknown }[];
|
|
294
|
+
},
|
|
295
|
+
): CliStructuredError | null {
|
|
296
|
+
const violations: Array<{
|
|
297
|
+
kind: string;
|
|
298
|
+
spaceId: string;
|
|
299
|
+
remediation: string;
|
|
300
|
+
}> = [];
|
|
301
|
+
for (const [spaceId, result] of section.perSpace) {
|
|
302
|
+
if (result.kind === 'ok' || result.kind === 'absent') continue;
|
|
303
|
+
if (result.kind === 'hashMismatch') {
|
|
304
|
+
violations.push({
|
|
305
|
+
kind: 'hashMismatch',
|
|
306
|
+
spaceId,
|
|
307
|
+
remediation:
|
|
308
|
+
spaceId === appSpaceId
|
|
309
|
+
? 'Run `prisma-next db update` to advance the marker, or roll the database back to the recorded hash.'
|
|
310
|
+
: `Apply on-disk migrations under \`migrations/${spaceId}/\` to advance the marker, or remove the conflicting marker row.`,
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (result.kind === 'missingInvariants') {
|
|
315
|
+
violations.push({
|
|
316
|
+
kind: 'invariantsMismatch',
|
|
317
|
+
spaceId,
|
|
318
|
+
remediation: `Re-apply the migrations under \`migrations/${spaceId}/\` so the marker carries invariants: ${result.missing.join(', ')}.`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const orphan of section.orphanMarkers) {
|
|
323
|
+
violations.push({
|
|
324
|
+
kind: 'orphanMarker',
|
|
325
|
+
spaceId: orphan.spaceId,
|
|
326
|
+
remediation: `Add the corresponding extension to \`extensionPacks\` in \`prisma-next.config.ts\`, or delete the orphan marker row for "${orphan.spaceId}".`,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (violations.length === 0) return null;
|
|
330
|
+
const lines = violations.map((v) => `- [${v.kind}] ${v.spaceId}: ${v.remediation}`);
|
|
331
|
+
const summary =
|
|
332
|
+
violations.length === 1
|
|
333
|
+
? 'Contract-space verifier found a violation'
|
|
334
|
+
: `Contract-space verifier found violations (${violations.length})`;
|
|
335
|
+
return new CliStructuredError('5002', summary, {
|
|
336
|
+
domain: 'MIG',
|
|
337
|
+
why: `The on-disk \`migrations/\` directory, the \`extensionPacks\` declaration, and the live database marker rows are not in agreement.\n${lines.join('\n')}`,
|
|
338
|
+
fix: violations[0]?.remediation ?? 'Review and reconcile the violations listed above.',
|
|
339
|
+
docsUrl: 'https://pris.ly/contract-spaces',
|
|
340
|
+
meta: { violations },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
MigrationRunnerResult,
|
|
6
6
|
TargetMigrationsCapability,
|
|
7
7
|
} from '@prisma-next/framework-components/control';
|
|
8
|
+
import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
|
|
8
9
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
9
10
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
10
11
|
import type {
|
|
@@ -30,6 +31,21 @@ export interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetI
|
|
|
30
31
|
readonly onProgress?: OnControlProgress;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Apply a sequence of migration packages against the configured driver.
|
|
36
|
+
*
|
|
37
|
+
* Validates the path's continuity (origin → ... → destination, no gaps),
|
|
38
|
+
* then drives the family/target's migration runner over each package's
|
|
39
|
+
* operations in order, surfacing per-migration progress through `onProgress`.
|
|
40
|
+
*
|
|
41
|
+
* The `pendingMigrations` parameter is trusted input. Callers are responsible
|
|
42
|
+
* for upstream verification of the originating migration packages — typically
|
|
43
|
+
* by loading them via `readMigrationPackage` from
|
|
44
|
+
* `@prisma-next/migration-tools/io`, which performs hash-integrity checks at
|
|
45
|
+
* the load boundary. This operation does not re-verify the packages and
|
|
46
|
+
* assumes the `(metadata, ops)` pairs on disk have not been tampered with
|
|
47
|
+
* since emit.
|
|
48
|
+
*/
|
|
33
49
|
export async function executeMigrationApply<TFamilyId extends string, TTargetId extends string>(
|
|
34
50
|
options: ExecuteMigrationApplyOptions<TFamilyId, TTargetId>,
|
|
35
51
|
): Promise<MigrationApplyResult> {
|
|
@@ -64,15 +80,19 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
64
80
|
|
|
65
81
|
const firstMigration = pendingMigrations[0]!;
|
|
66
82
|
const lastMigration = pendingMigrations[pendingMigrations.length - 1]!;
|
|
67
|
-
|
|
83
|
+
// Manifest `from` is `string | null` (null = baseline). The live-marker
|
|
84
|
+
// layer encodes "no prior state" as EMPTY_CONTRACT_HASH; bridge here so the
|
|
85
|
+
// string comparisons below work uniformly.
|
|
86
|
+
const firstFromMarker = firstMigration.from ?? EMPTY_CONTRACT_HASH;
|
|
87
|
+
if (firstFromMarker !== originHash || lastMigration.to !== destinationHash) {
|
|
68
88
|
return notOk({
|
|
69
89
|
code: 'MIGRATION_PATH_NOT_FOUND' as const,
|
|
70
90
|
summary: 'Migration apply path does not match requested origin and destination',
|
|
71
|
-
why: `Path resolved as ${
|
|
91
|
+
why: `Path resolved as ${firstFromMarker} -> ${lastMigration.to}, but requested ${originHash} -> ${destinationHash}`,
|
|
72
92
|
meta: {
|
|
73
93
|
originHash,
|
|
74
94
|
destinationHash,
|
|
75
|
-
pathOrigin:
|
|
95
|
+
pathOrigin: firstFromMarker,
|
|
76
96
|
pathDestination: lastMigration.to,
|
|
77
97
|
},
|
|
78
98
|
});
|
|
@@ -81,18 +101,19 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
81
101
|
for (let i = 1; i < pendingMigrations.length; i++) {
|
|
82
102
|
const previous = pendingMigrations[i - 1]!;
|
|
83
103
|
const current = pendingMigrations[i]!;
|
|
84
|
-
|
|
104
|
+
const currentFromMarker = current.from ?? EMPTY_CONTRACT_HASH;
|
|
105
|
+
if (previous.to !== currentFromMarker) {
|
|
85
106
|
return notOk({
|
|
86
107
|
code: 'MIGRATION_PATH_NOT_FOUND' as const,
|
|
87
108
|
summary: 'Migration apply path contains a discontinuity between adjacent migrations',
|
|
88
|
-
why: `Migration "${previous.dirName}" ends at ${previous.to}, but next migration "${current.dirName}" starts at ${
|
|
109
|
+
why: `Migration "${previous.dirName}" ends at ${previous.to}, but next migration "${current.dirName}" starts at ${currentFromMarker}`,
|
|
89
110
|
meta: {
|
|
90
111
|
originHash,
|
|
91
112
|
destinationHash,
|
|
92
113
|
previousDirName: previous.dirName,
|
|
93
114
|
previousTo: previous.to,
|
|
94
115
|
currentDirName: current.dirName,
|
|
95
|
-
currentFrom:
|
|
116
|
+
currentFrom: currentFromMarker,
|
|
96
117
|
discontinuityIndex: i,
|
|
97
118
|
},
|
|
98
119
|
});
|
|
@@ -120,13 +141,20 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
120
141
|
allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] as const,
|
|
121
142
|
};
|
|
122
143
|
|
|
123
|
-
//
|
|
124
|
-
// for a fresh database (no marker present).
|
|
144
|
+
// Manifest `from === null` means "no prior state" — the runner expects
|
|
145
|
+
// `origin: null` for a fresh database (no marker present).
|
|
146
|
+
//
|
|
147
|
+
// M3 will thread the package's owning space id through `MigrationApplyStep`
|
|
148
|
+
// and use it here. Until then, all on-disk migration packages live under
|
|
149
|
+
// the app's `migrations/` directory, so this path is exclusively app-scope
|
|
150
|
+
// and we pass `APP_SPACE_ID` explicitly.
|
|
125
151
|
const plan = {
|
|
126
152
|
targetId,
|
|
127
|
-
|
|
153
|
+
spaceId: APP_SPACE_ID,
|
|
154
|
+
origin: migration.from === null ? null : { storageHash: migration.from },
|
|
128
155
|
destination: { storageHash: migration.to },
|
|
129
156
|
operations,
|
|
157
|
+
providedInvariants: migration.providedInvariants,
|
|
130
158
|
};
|
|
131
159
|
|
|
132
160
|
const destinationContract = familyInstance.validateContract(migration.toContract);
|
package/src/control-api/types.ts
CHANGED
|
@@ -12,11 +12,14 @@ import type {
|
|
|
12
12
|
CoreSchemaView,
|
|
13
13
|
MigrationPlannerConflict,
|
|
14
14
|
MigrationPlanOperation,
|
|
15
|
+
OperationPreview,
|
|
15
16
|
SignDatabaseResult,
|
|
16
17
|
VerifyDatabaseResult,
|
|
17
18
|
VerifyDatabaseSchemaResult,
|
|
18
19
|
} from '@prisma-next/framework-components/control';
|
|
20
|
+
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
19
21
|
import type { Result } from '@prisma-next/utils/result';
|
|
22
|
+
import type { ExecuteDbVerifyResult } from './operations/db-verify';
|
|
20
23
|
|
|
21
24
|
// ============================================================================
|
|
22
25
|
// Client Options
|
|
@@ -63,6 +66,7 @@ export interface ControlClientOptions {
|
|
|
63
66
|
export type ControlActionName =
|
|
64
67
|
| 'dbInit'
|
|
65
68
|
| 'dbUpdate'
|
|
69
|
+
| 'dbVerify'
|
|
66
70
|
| 'migrationApply'
|
|
67
71
|
| 'verify'
|
|
68
72
|
| 'schemaVerify'
|
|
@@ -189,6 +193,13 @@ export interface DbInitOptions {
|
|
|
189
193
|
* The type is driver-specific (e.g., string URL for Postgres).
|
|
190
194
|
*/
|
|
191
195
|
readonly connection?: unknown;
|
|
196
|
+
/**
|
|
197
|
+
* On-disk migrations directory. Always required — every `db init`
|
|
198
|
+
* routes through the per-space flow, which reads on-disk
|
|
199
|
+
* `refs/head.json` and extension destination contracts from this
|
|
200
|
+
* root.
|
|
201
|
+
*/
|
|
202
|
+
readonly migrationsDir: string;
|
|
192
203
|
/** Optional progress callback for observing operation progress */
|
|
193
204
|
readonly onProgress?: OnControlProgress;
|
|
194
205
|
}
|
|
@@ -219,10 +230,35 @@ export interface DbUpdateOptions {
|
|
|
219
230
|
* or re-run with -y/--yes.
|
|
220
231
|
*/
|
|
221
232
|
readonly acceptDataLoss?: boolean;
|
|
233
|
+
/**
|
|
234
|
+
* On-disk migrations directory. Always required — every `db update`
|
|
235
|
+
* routes through the per-space flow, which reads on-disk
|
|
236
|
+
* `refs/head.json` and extension destination contracts from this
|
|
237
|
+
* root.
|
|
238
|
+
*/
|
|
239
|
+
readonly migrationsDir: string;
|
|
222
240
|
/** Optional progress callback for observing operation progress */
|
|
223
241
|
readonly onProgress?: OnControlProgress;
|
|
224
242
|
}
|
|
225
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Options for the dbVerify operation.
|
|
246
|
+
*
|
|
247
|
+
* Drives the loader → aggregate-verifier pipeline. `strict` maps to
|
|
248
|
+
* `verifyAggregate({ mode: 'strict' | 'lenient' })`; `skipSchema`
|
|
249
|
+
* mirrors the `--marker-only` CLI flag and short-circuits the schema
|
|
250
|
+
* portion of the verifier.
|
|
251
|
+
*/
|
|
252
|
+
export interface DbVerifyOptions {
|
|
253
|
+
readonly contract: unknown;
|
|
254
|
+
readonly migrationsDir: string;
|
|
255
|
+
readonly strict: boolean;
|
|
256
|
+
readonly skipSchema: boolean;
|
|
257
|
+
readonly skipMarker: boolean;
|
|
258
|
+
readonly connection?: unknown;
|
|
259
|
+
readonly onProgress?: OnControlProgress;
|
|
260
|
+
}
|
|
261
|
+
|
|
226
262
|
/**
|
|
227
263
|
* Options for the introspect operation.
|
|
228
264
|
*/
|
|
@@ -283,7 +319,14 @@ export interface DbInitSuccess {
|
|
|
283
319
|
readonly label: string;
|
|
284
320
|
readonly operationClass: string;
|
|
285
321
|
}>;
|
|
286
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Family-agnostic textual preview of the planned operations, e.g.
|
|
324
|
+
* `language: 'sql'` for SQL families and `language: 'mongodb-shell'`
|
|
325
|
+
* for the Mongo family. Replaces the previous `sql?: readonly string[]`
|
|
326
|
+
* field; consumers that previously read `plan.sql` should read
|
|
327
|
+
* `plan.preview?.statements.map((s) => s.text)`.
|
|
328
|
+
*/
|
|
329
|
+
readonly preview?: OperationPreview;
|
|
287
330
|
};
|
|
288
331
|
readonly destination: {
|
|
289
332
|
readonly storageHash: string;
|
|
@@ -341,7 +384,14 @@ export interface DbUpdateSuccess {
|
|
|
341
384
|
readonly label: string;
|
|
342
385
|
readonly operationClass: string;
|
|
343
386
|
}>;
|
|
344
|
-
|
|
387
|
+
/**
|
|
388
|
+
* Family-agnostic textual preview of the planned operations, e.g.
|
|
389
|
+
* `language: 'sql'` for SQL families and `language: 'mongodb-shell'`
|
|
390
|
+
* for the Mongo family. Replaces the previous `sql?: readonly string[]`
|
|
391
|
+
* field; consumers that previously read `plan.sql` should read
|
|
392
|
+
* `plan.preview?.statements.map((s) => s.text)`.
|
|
393
|
+
*/
|
|
394
|
+
readonly preview?: OperationPreview;
|
|
345
395
|
};
|
|
346
396
|
readonly destination: {
|
|
347
397
|
readonly storageHash: string;
|
|
@@ -432,10 +482,18 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
|
|
|
432
482
|
*/
|
|
433
483
|
export interface MigrationApplyStep {
|
|
434
484
|
readonly dirName: string;
|
|
435
|
-
readonly from: string;
|
|
485
|
+
readonly from: string | null;
|
|
436
486
|
readonly to: string;
|
|
437
487
|
readonly toContract: Contract;
|
|
438
488
|
readonly operations: readonly MigrationPlanOperation[];
|
|
489
|
+
/**
|
|
490
|
+
* Sorted, deduplicated invariant ids from `migration.json.providedInvariants`.
|
|
491
|
+
* Verified at load time by `readMigrationPackage` (manifest copy must equal
|
|
492
|
+
* the value derived from `ops.json`). The control-api passes this through
|
|
493
|
+
* to the runner via `MigrationPlan.providedInvariants` so target runners
|
|
494
|
+
* read the canonical set instead of re-deriving from `operations`.
|
|
495
|
+
*/
|
|
496
|
+
readonly providedInvariants: readonly string[];
|
|
439
497
|
}
|
|
440
498
|
|
|
441
499
|
/**
|
|
@@ -471,7 +529,7 @@ export interface MigrationApplyOptions {
|
|
|
471
529
|
*/
|
|
472
530
|
export interface MigrationApplyAppliedEntry {
|
|
473
531
|
readonly dirName: string;
|
|
474
|
-
readonly from: string;
|
|
532
|
+
readonly from: string | null;
|
|
475
533
|
readonly to: string;
|
|
476
534
|
readonly operationsExecuted: number;
|
|
477
535
|
}
|
|
@@ -512,18 +570,31 @@ export type MigrationApplyResult = Result<MigrationApplySuccess, MigrationApplyF
|
|
|
512
570
|
|
|
513
571
|
/**
|
|
514
572
|
* Options for the standalone executeContractEmit function.
|
|
515
|
-
*
|
|
516
|
-
*
|
|
573
|
+
*
|
|
574
|
+
* `executeContractEmit` is the canonical publication path for both the
|
|
575
|
+
* `prisma-next contract emit` CLI command and the `@prisma-next/vite-plugin-contract-emit`
|
|
576
|
+
* Vite plugin. Do not duplicate the load → emit → publish dance elsewhere; if a
|
|
577
|
+
* caller needs additional behavior, extend this options shape and update the
|
|
578
|
+
* single implementation rather than building a parallel publication path.
|
|
579
|
+
*
|
|
580
|
+
* Concurrent calls for the same output JSON path are serialized per-output via
|
|
581
|
+
* a FIFO queue; concurrent calls for distinct outputs run in parallel.
|
|
517
582
|
*/
|
|
518
583
|
export interface ContractEmitOptions {
|
|
519
584
|
/** Path to the prisma-next.config.ts file */
|
|
520
585
|
readonly configPath: string;
|
|
521
|
-
/** Optional AbortSignal for
|
|
586
|
+
/** Optional AbortSignal for cancelling the in-flight emit */
|
|
522
587
|
readonly signal?: AbortSignal;
|
|
588
|
+
/** Optional progress callback for observing source-resolution and emit spans */
|
|
589
|
+
readonly onProgress?: OnControlProgress;
|
|
523
590
|
}
|
|
524
591
|
|
|
525
592
|
/**
|
|
526
593
|
* Result from the standalone executeContractEmit function.
|
|
594
|
+
*
|
|
595
|
+
* Always describes the bytes that were just published to disk. Failures throw
|
|
596
|
+
* (config / source-resolution / emit / publish) — callers do not need to
|
|
597
|
+
* branch on a result discriminator.
|
|
527
598
|
*/
|
|
528
599
|
export interface ContractEmitResult {
|
|
529
600
|
/** Hash of the storage contract (schema-level) */
|
|
@@ -539,6 +610,12 @@ export interface ContractEmitResult {
|
|
|
539
610
|
/** Path to the emitted contract.d.ts file */
|
|
540
611
|
readonly dts: string;
|
|
541
612
|
};
|
|
613
|
+
/**
|
|
614
|
+
* Warning surfaced by `validateContractDeps` after a successful publication.
|
|
615
|
+
* Callers (CLI, Vite plugin) decide how to render this; the operation does
|
|
616
|
+
* not write to stderr itself. Undefined when no warning was raised.
|
|
617
|
+
*/
|
|
618
|
+
readonly validationWarning?: string;
|
|
542
619
|
}
|
|
543
620
|
|
|
544
621
|
// ============================================================================
|
|
@@ -634,6 +711,21 @@ export interface ControlClient {
|
|
|
634
711
|
*/
|
|
635
712
|
dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult>;
|
|
636
713
|
|
|
714
|
+
/**
|
|
715
|
+
* Verifies the database against every contract space (app + extensions).
|
|
716
|
+
*
|
|
717
|
+
* Loader → aggregate-verifier pipeline:
|
|
718
|
+
* - The loader catches layout / drift / disjointness violations.
|
|
719
|
+
* - The aggregate verifier surfaces marker-vs-on-disk drift and orphan
|
|
720
|
+
* markers, and (unless `skipSchema` is true) per-space schema
|
|
721
|
+
* verification with pre-projection (closes F23).
|
|
722
|
+
*
|
|
723
|
+
* @returns Result pattern: per-space schema results on success;
|
|
724
|
+
* structured CLI error on marker / loader failure.
|
|
725
|
+
* @throws If not connected or infrastructure failure
|
|
726
|
+
*/
|
|
727
|
+
dbVerify(options: DbVerifyOptions): Promise<ExecuteDbVerifyResult>;
|
|
728
|
+
|
|
637
729
|
/**
|
|
638
730
|
* Reads the contract marker from the database.
|
|
639
731
|
* Returns null if no marker exists (fresh database).
|
|
@@ -642,6 +734,13 @@ export interface ControlClient {
|
|
|
642
734
|
*/
|
|
643
735
|
readMarker(): Promise<ContractMarkerRecord | null>;
|
|
644
736
|
|
|
737
|
+
/**
|
|
738
|
+
* Reads every marker row (one per contract space). Used by the
|
|
739
|
+
* per-space verifier to detect orphan marker rows and marker-vs-on-disk
|
|
740
|
+
* drift after a database connection has been established.
|
|
741
|
+
*/
|
|
742
|
+
readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
|
|
743
|
+
|
|
645
744
|
/**
|
|
646
745
|
* Applies pre-planned on-disk migrations to the database.
|
|
647
746
|
* Each migration runs in its own transaction with full execution checks.
|
|
@@ -672,6 +771,25 @@ export interface ControlClient {
|
|
|
672
771
|
*/
|
|
673
772
|
toSchemaView(schemaIR: unknown): CoreSchemaView | undefined;
|
|
674
773
|
|
|
774
|
+
/**
|
|
775
|
+
* Infers a PSL contract AST from an introspected schema IR.
|
|
776
|
+
* Delegates to the family instance's inferPslContract method.
|
|
777
|
+
*
|
|
778
|
+
* @param schemaIR - The schema IR from introspect()
|
|
779
|
+
* @returns PslDocumentAst if the family supports the capability, undefined otherwise
|
|
780
|
+
*/
|
|
781
|
+
inferPslContract(schemaIR: unknown): PslDocumentAst | undefined;
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Renders a textual preview of a migration plan's operations for the CLI's
|
|
785
|
+
* "DDL preview" output. Delegates to the family instance's
|
|
786
|
+
* `toOperationPreview` method.
|
|
787
|
+
*
|
|
788
|
+
* @param operations - The migration plan operations to render
|
|
789
|
+
* @returns OperationPreview if the family supports the capability, undefined otherwise
|
|
790
|
+
*/
|
|
791
|
+
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview | undefined;
|
|
792
|
+
|
|
675
793
|
/**
|
|
676
794
|
* Emits the contract to JSON and TypeScript declarations.
|
|
677
795
|
* This is an offline operation that does NOT require a database connection.
|