@prisma-next/target-mongo 0.5.0-dev.2 → 0.5.0-dev.20
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 +2 -0
- package/dist/control.d.mts +36 -13
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +79 -70
- package/dist/control.mjs.map +1 -1
- package/dist/{migration-factories-gwi81C8u.mjs → migration-factories-IG0vjM_u.mjs} +3 -1
- package/dist/migration-factories-IG0vjM_u.mjs.map +1 -0
- package/dist/migration.d.mts +7 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +1 -1
- package/dist/{op-factory-call-BjNAcPSF.d.mts → op-factory-call-CVgzmLJh.d.mts} +1 -1
- package/dist/{op-factory-call-BjNAcPSF.d.mts.map → op-factory-call-CVgzmLJh.d.mts.map} +1 -1
- package/dist/schema-verify.d.mts +22 -0
- package/dist/schema-verify.d.mts.map +1 -0
- package/dist/schema-verify.mjs +3 -0
- package/dist/verify-mongo-schema-P0TRBJNs.mjs +582 -0
- package/dist/verify-mongo-schema-P0TRBJNs.mjs.map +1 -0
- package/package.json +17 -13
- package/src/core/marker-ledger.ts +90 -20
- package/src/core/migration-factories.ts +8 -0
- package/src/core/mongo-runner.ts +74 -42
- package/src/core/schema-diff.ts +402 -0
- package/src/core/schema-verify/canonicalize-introspection.ts +389 -0
- package/src/core/schema-verify/verify-mongo-schema.ts +60 -0
- package/src/exports/schema-verify.ts +2 -0
- package/dist/migration-factories-gwi81C8u.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ MongoDB target pack for Prisma Next.
|
|
|
14
14
|
- `./pack`: pure target pack ref used by `@prisma-next/family-mongo` and `@prisma-next/mongo-contract-ts`
|
|
15
15
|
- `./codec-types`: base Mongo codec type map
|
|
16
16
|
- `./migration`: factory functions (the `Migration` base class is in `@prisma-next/family-mongo/migration`)
|
|
17
|
+
- `./control`: `MongoMigrationRunner` and `createMongoRunnerDeps` for runtime migration execution
|
|
18
|
+
- `./schema-verify`: pure `verifyMongoSchema(...)` (no DB I/O); composes `contractToMongoSchemaIR` and `diffMongoSchemas` so the runner's post-apply verify step and `MongoFamilyInstance.schemaVerify` agree on "matches the contract" by construction
|
|
17
19
|
|
|
18
20
|
## Usage
|
|
19
21
|
|
package/dist/control.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as DropCollectionCall, c as schemaCollectionToCreateCollectionOptions, i as CreateIndexCall, l as schemaIndexToCreateIndexOptions, n as CollModMeta, o as DropIndexCall, r as CreateCollectionCall, s as OpFactoryCall, t as CollModCall } from "./op-factory-call-
|
|
1
|
+
import { a as DropCollectionCall, c as schemaCollectionToCreateCollectionOptions, i as CreateIndexCall, l as schemaIndexToCreateIndexOptions, n as CollModMeta, o as DropIndexCall, r as CreateCollectionCall, s as OpFactoryCall, t as CollModCall } from "./op-factory-call-CVgzmLJh.mjs";
|
|
2
2
|
import { MongoSchemaIR } from "@prisma-next/mongo-schema-ir";
|
|
3
3
|
import { MongoQueryPlan } from "@prisma-next/mongo-query-ast/execution";
|
|
4
4
|
import { AnyMongoMigrationOperation, MongoAndExpr, MongoDdlCommandVisitor, MongoExistsExpr, MongoExprFilter, MongoFieldFilter, MongoFilterExpr, MongoFilterVisitor, MongoInspectionCommandVisitor, MongoMigrationPlanOperation, MongoNotExpr, MongoOrExpr } from "@prisma-next/mongo-query-ast/control";
|
|
5
5
|
import { Migration, MigrationMeta } from "@prisma-next/migration-tools/migration";
|
|
6
|
+
import { MigrationOperationPolicy, MigrationPlan, MigrationPlanOperation, MigrationPlanWithAuthoringSurface, MigrationPlanner, MigrationPlannerConflict, MigrationPlannerResult, MigrationRunnerExecutionChecks, MigrationRunnerResult, MigrationScaffoldContext, OperationContext } from "@prisma-next/framework-components/control";
|
|
6
7
|
import { MongoContract } from "@prisma-next/mongo-contract";
|
|
7
|
-
import { MigrationOperationPolicy, MigrationPlan, MigrationPlanOperation, MigrationPlanWithAuthoringSurface, MigrationPlanner, MigrationPlannerConflict, MigrationPlannerResult, MigrationRunnerExecutionChecks, MigrationRunnerResult, MigrationScaffoldContext } from "@prisma-next/framework-components/control";
|
|
8
8
|
import { ContractMarkerRecord } from "@prisma-next/contract/types";
|
|
9
9
|
import { Db } from "mongodb";
|
|
10
10
|
import { TargetBoundComponentDescriptor } from "@prisma-next/framework-components/components";
|
|
@@ -33,10 +33,21 @@ declare function readMarker(db: Db): Promise<ContractMarkerRecord | null>;
|
|
|
33
33
|
declare function initMarker(db: Db, destination: {
|
|
34
34
|
readonly storageHash: string;
|
|
35
35
|
readonly profileHash: string;
|
|
36
|
+
readonly invariants?: readonly string[];
|
|
36
37
|
}): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Updates the marker doc atomically (CAS on `expectedFrom`).
|
|
40
|
+
*
|
|
41
|
+
* `destination.invariants`:
|
|
42
|
+
* - `undefined` → existing field left untouched.
|
|
43
|
+
* - explicit value → merged into the existing field server-side via an
|
|
44
|
+
* aggregation pipeline (`$setUnion + $sortArray`), atomic at the
|
|
45
|
+
* document level. `[]` is a no-op merge.
|
|
46
|
+
*/
|
|
37
47
|
declare function updateMarker(db: Db, expectedFrom: string, destination: {
|
|
38
48
|
readonly storageHash: string;
|
|
39
49
|
readonly profileHash: string;
|
|
50
|
+
readonly invariants?: readonly string[];
|
|
40
51
|
}): Promise<boolean>;
|
|
41
52
|
declare function writeLedgerEntry(db: Db, entry: {
|
|
42
53
|
readonly edgeId: string;
|
|
@@ -89,10 +100,12 @@ interface MarkerOperations {
|
|
|
89
100
|
initMarker(destination: {
|
|
90
101
|
readonly storageHash: string;
|
|
91
102
|
readonly profileHash: string;
|
|
103
|
+
readonly invariants?: readonly string[];
|
|
92
104
|
}): Promise<void>;
|
|
93
105
|
updateMarker(expectedFrom: string, destination: {
|
|
94
106
|
readonly storageHash: string;
|
|
95
107
|
readonly profileHash: string;
|
|
108
|
+
readonly invariants?: readonly string[];
|
|
96
109
|
}): Promise<boolean>;
|
|
97
110
|
writeLedgerEntry(entry: {
|
|
98
111
|
readonly edgeId: string;
|
|
@@ -106,21 +119,31 @@ interface MongoRunnerDependencies {
|
|
|
106
119
|
readonly adapter: MongoAdapter;
|
|
107
120
|
readonly driver: MongoDriver;
|
|
108
121
|
readonly markerOps: MarkerOperations;
|
|
122
|
+
readonly introspectSchema: () => Promise<MongoSchemaIR>;
|
|
123
|
+
}
|
|
124
|
+
interface MongoMigrationRunnerExecuteOptions {
|
|
125
|
+
readonly plan: MigrationPlan;
|
|
126
|
+
readonly destinationContract: MongoContract;
|
|
127
|
+
readonly policy: MigrationOperationPolicy;
|
|
128
|
+
readonly callbacks?: {
|
|
129
|
+
onOperationStart?(op: MigrationPlanOperation): void;
|
|
130
|
+
onOperationComplete?(op: MigrationPlanOperation): void;
|
|
131
|
+
};
|
|
132
|
+
readonly executionChecks?: MigrationRunnerExecutionChecks;
|
|
133
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', 'mongo'>>;
|
|
134
|
+
readonly strictVerification?: boolean;
|
|
135
|
+
readonly context?: OperationContext;
|
|
136
|
+
/**
|
|
137
|
+
* Invariant ids contributed by this apply (the migration's `providedInvariants`).
|
|
138
|
+
* The runner unions these into `marker.invariants` atomically with the marker write.
|
|
139
|
+
* Defaults to `[]` for marker-only flows.
|
|
140
|
+
*/
|
|
141
|
+
readonly invariants?: readonly string[];
|
|
109
142
|
}
|
|
110
143
|
declare class MongoMigrationRunner {
|
|
111
144
|
private readonly deps;
|
|
112
145
|
constructor(deps: MongoRunnerDependencies);
|
|
113
|
-
execute(options:
|
|
114
|
-
readonly plan: MigrationPlan;
|
|
115
|
-
readonly destinationContract: unknown;
|
|
116
|
-
readonly policy: MigrationOperationPolicy;
|
|
117
|
-
readonly callbacks?: {
|
|
118
|
-
onOperationStart?(op: MigrationPlanOperation): void;
|
|
119
|
-
onOperationComplete?(op: MigrationPlanOperation): void;
|
|
120
|
-
};
|
|
121
|
-
readonly executionChecks?: MigrationRunnerExecutionChecks;
|
|
122
|
-
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', 'mongo'>>;
|
|
123
|
-
}): Promise<MigrationRunnerResult>;
|
|
146
|
+
execute(options: MongoMigrationRunnerExecuteOptions): Promise<MigrationRunnerResult>;
|
|
124
147
|
private executeDataTransform;
|
|
125
148
|
private evaluateDataTransformChecks;
|
|
126
149
|
private evaluateChecks;
|
package/dist/control.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/contract-to-schema.ts","../src/core/ddl-formatter.ts","../src/core/filter-evaluator.ts","../src/core/marker-ledger.ts","../src/core/mongo-ops-serializer.ts","../src/core/mongo-planner.ts","../src/core/mongo-runner.ts","../src/core/planner-produced-migration.ts","../src/core/render-ops.ts","../src/core/render-typescript.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;iBAoDgB,uBAAA,WAAkC,uBAAuB;;;iBC6CzD,qBAAA,sBAA2C;;;cC/C9C,eAAA,YAA2B;;mBAGrB,sBAAsB;cAK3B;YAKF;WAID;YAIC;eAIG;cAKD;;;;
|
|
1
|
+
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/contract-to-schema.ts","../src/core/ddl-formatter.ts","../src/core/filter-evaluator.ts","../src/core/marker-ledger.ts","../src/core/mongo-ops-serializer.ts","../src/core/mongo-planner.ts","../src/core/mongo-runner.ts","../src/core/planner-produced-migration.ts","../src/core/render-ops.ts","../src/core/render-typescript.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/control.mjs
CHANGED
|
@@ -1,54 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as contractToMongoSchemaIR, t as verifyMongoSchema } from "./verify-mongo-schema-P0TRBJNs.mjs";
|
|
2
|
+
import { a as dropCollection, n as createCollection, o as dropIndex, r as createIndex, t as collMod } from "./migration-factories-IG0vjM_u.mjs";
|
|
3
|
+
import { canonicalize, deepEqual } from "@prisma-next/mongo-schema-ir";
|
|
3
4
|
import { AggregateCommand, MongoAddFieldsStage, MongoLimitStage, MongoLookupStage, MongoMatchStage, MongoMergeStage, MongoProjectStage, MongoSortStage, RawAggregateCommand, RawDeleteManyCommand, RawDeleteOneCommand, RawFindOneAndDeleteCommand, RawFindOneAndUpdateCommand, RawInsertManyCommand, RawInsertOneCommand, RawUpdateManyCommand, RawUpdateOneCommand } from "@prisma-next/mongo-query-ast/execution";
|
|
5
|
+
import { type } from "arktype";
|
|
4
6
|
import { CollModCommand, CreateCollectionCommand, CreateIndexCommand, DropCollectionCommand, DropIndexCommand, ListCollectionsCommand, ListIndexesCommand, MongoAndExpr, MongoExistsExpr, MongoFieldFilter, MongoNotExpr, MongoOrExpr } from "@prisma-next/mongo-query-ast/control";
|
|
5
7
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
6
|
-
import { type } from "arktype";
|
|
7
8
|
import { TsExpression, jsonToTsSource, renderImports } from "@prisma-next/ts-render";
|
|
8
9
|
import { Migration } from "@prisma-next/migration-tools/migration";
|
|
9
10
|
import { detectScaffoldRuntime, shebangLineFor } from "@prisma-next/migration-tools/migration-ts";
|
|
10
11
|
import { errorRunnerFailed } from "@prisma-next/errors/execution";
|
|
11
12
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
12
13
|
|
|
13
|
-
//#region src/core/contract-to-schema.ts
|
|
14
|
-
function convertIndex(index) {
|
|
15
|
-
return new MongoSchemaIndex({
|
|
16
|
-
keys: index.keys,
|
|
17
|
-
unique: index.unique,
|
|
18
|
-
sparse: index.sparse,
|
|
19
|
-
expireAfterSeconds: index.expireAfterSeconds,
|
|
20
|
-
partialFilterExpression: index.partialFilterExpression,
|
|
21
|
-
wildcardProjection: index.wildcardProjection,
|
|
22
|
-
collation: index.collation,
|
|
23
|
-
weights: index.weights,
|
|
24
|
-
default_language: index.default_language,
|
|
25
|
-
language_override: index.language_override
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
function convertValidator(v) {
|
|
29
|
-
return new MongoSchemaValidator({
|
|
30
|
-
jsonSchema: v.jsonSchema,
|
|
31
|
-
validationLevel: v.validationLevel,
|
|
32
|
-
validationAction: v.validationAction
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function convertOptions(o) {
|
|
36
|
-
return new MongoSchemaCollectionOptions(o);
|
|
37
|
-
}
|
|
38
|
-
function convertCollection(name, def) {
|
|
39
|
-
return new MongoSchemaCollection({
|
|
40
|
-
name,
|
|
41
|
-
indexes: (def.indexes ?? []).map(convertIndex),
|
|
42
|
-
...def.validator != null && { validator: convertValidator(def.validator) },
|
|
43
|
-
...def.options != null && { options: convertOptions(def.options) }
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
function contractToMongoSchemaIR(contract) {
|
|
47
|
-
if (!contract) return new MongoSchemaIR([]);
|
|
48
|
-
return new MongoSchemaIR(Object.entries(contract.storage.collections).map(([name, def]) => convertCollection(name, def)));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
14
|
//#region src/core/ddl-formatter.ts
|
|
53
15
|
function formatKeySpec(keys) {
|
|
54
16
|
return `{ ${keys.map((k) => `${JSON.stringify(k.field)}: ${JSON.stringify(k.direction)}`).join(", ")} }`;
|
|
@@ -176,6 +138,31 @@ var FilterEvaluator = class {
|
|
|
176
138
|
//#region src/core/marker-ledger.ts
|
|
177
139
|
const COLLECTION = "_prisma_migrations";
|
|
178
140
|
const MARKER_ID = "marker";
|
|
141
|
+
const MongoMarkerDocSchema = type({
|
|
142
|
+
storageHash: "string",
|
|
143
|
+
profileHash: "string",
|
|
144
|
+
"contractJson?": "unknown | null",
|
|
145
|
+
"canonicalVersion?": "number | null",
|
|
146
|
+
"updatedAt?": "Date",
|
|
147
|
+
"appTag?": "string | null",
|
|
148
|
+
"meta?": type({ "[string]": "unknown" }).or("null"),
|
|
149
|
+
"invariants?": type("string").array(),
|
|
150
|
+
"+": "delete"
|
|
151
|
+
});
|
|
152
|
+
function parseMongoMarkerDoc(doc) {
|
|
153
|
+
const result = MongoMarkerDocSchema(doc);
|
|
154
|
+
if (result instanceof type.errors) throw new Error(`Invalid marker doc on ${COLLECTION}: ${result.summary}`);
|
|
155
|
+
return {
|
|
156
|
+
storageHash: result.storageHash,
|
|
157
|
+
profileHash: result.profileHash,
|
|
158
|
+
contractJson: result.contractJson ?? null,
|
|
159
|
+
canonicalVersion: result.canonicalVersion ?? null,
|
|
160
|
+
updatedAt: result.updatedAt ?? /* @__PURE__ */ new Date(),
|
|
161
|
+
appTag: result.appTag ?? null,
|
|
162
|
+
meta: result.meta ?? {},
|
|
163
|
+
invariants: result.invariants ?? []
|
|
164
|
+
};
|
|
165
|
+
}
|
|
179
166
|
async function executeAggregate(db, cmd) {
|
|
180
167
|
return db.collection(cmd.collection).aggregate(cmd.pipeline).toArray();
|
|
181
168
|
}
|
|
@@ -188,15 +175,7 @@ async function executeFindOneAndUpdate(db, cmd) {
|
|
|
188
175
|
async function readMarker(db) {
|
|
189
176
|
const doc = (await executeAggregate(db, new RawAggregateCommand(COLLECTION, [{ $match: { _id: MARKER_ID } }, { $limit: 1 }])))[0];
|
|
190
177
|
if (!doc) return null;
|
|
191
|
-
return
|
|
192
|
-
storageHash: doc["storageHash"],
|
|
193
|
-
profileHash: doc["profileHash"],
|
|
194
|
-
contractJson: doc["contractJson"] ?? null,
|
|
195
|
-
canonicalVersion: doc["canonicalVersion"] ?? null,
|
|
196
|
-
updatedAt: doc["updatedAt"],
|
|
197
|
-
appTag: doc["appTag"] ?? null,
|
|
198
|
-
meta: doc["meta"] ?? {}
|
|
199
|
-
};
|
|
178
|
+
return parseMongoMarkerDoc(doc);
|
|
200
179
|
}
|
|
201
180
|
async function initMarker(db, destination) {
|
|
202
181
|
await executeInsertOne(db, new RawInsertOneCommand(COLLECTION, {
|
|
@@ -207,18 +186,36 @@ async function initMarker(db, destination) {
|
|
|
207
186
|
canonicalVersion: null,
|
|
208
187
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
209
188
|
appTag: null,
|
|
210
|
-
meta: {}
|
|
189
|
+
meta: {},
|
|
190
|
+
invariants: destination.invariants ?? []
|
|
211
191
|
}));
|
|
212
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Updates the marker doc atomically (CAS on `expectedFrom`).
|
|
195
|
+
*
|
|
196
|
+
* `destination.invariants`:
|
|
197
|
+
* - `undefined` → existing field left untouched.
|
|
198
|
+
* - explicit value → merged into the existing field server-side via an
|
|
199
|
+
* aggregation pipeline (`$setUnion + $sortArray`), atomic at the
|
|
200
|
+
* document level. `[]` is a no-op merge.
|
|
201
|
+
*/
|
|
213
202
|
async function updateMarker(db, expectedFrom, destination) {
|
|
214
|
-
|
|
215
|
-
_id: MARKER_ID,
|
|
216
|
-
storageHash: expectedFrom
|
|
217
|
-
}, { $set: {
|
|
203
|
+
const setBase = {
|
|
218
204
|
storageHash: destination.storageHash,
|
|
219
205
|
profileHash: destination.profileHash,
|
|
220
206
|
updatedAt: /* @__PURE__ */ new Date()
|
|
221
|
-
}
|
|
207
|
+
};
|
|
208
|
+
const update = destination.invariants === void 0 ? { $set: setBase } : [{ $set: {
|
|
209
|
+
...setBase,
|
|
210
|
+
invariants: { $sortArray: {
|
|
211
|
+
input: { $setUnion: [{ $ifNull: ["$invariants", []] }, destination.invariants] },
|
|
212
|
+
sortBy: 1
|
|
213
|
+
} }
|
|
214
|
+
} }];
|
|
215
|
+
return await executeFindOneAndUpdate(db, new RawFindOneAndUpdateCommand(COLLECTION, {
|
|
216
|
+
_id: MARKER_ID,
|
|
217
|
+
storageHash: expectedFrom
|
|
218
|
+
}, update, false)) !== null;
|
|
222
219
|
}
|
|
223
220
|
async function writeLedgerEntry(db, entry) {
|
|
224
221
|
await executeInsertOne(db, new RawInsertOneCommand(COLLECTION, {
|
|
@@ -1128,9 +1125,6 @@ function planMutableOptionsDiffCall(collName, origin, dest) {
|
|
|
1128
1125
|
//#endregion
|
|
1129
1126
|
//#region src/core/mongo-runner.ts
|
|
1130
1127
|
const READ_ONLY_CHECK_COMMAND_KINDS = new Set(["aggregate", "rawAggregate"]);
|
|
1131
|
-
function hasProfileHash(value) {
|
|
1132
|
-
return typeof value === "object" && value !== null && Object.hasOwn(value, "profileHash") && typeof value.profileHash === "string";
|
|
1133
|
-
}
|
|
1134
1128
|
function runnerFailure(code, summary, opts) {
|
|
1135
1129
|
return notOk({
|
|
1136
1130
|
code,
|
|
@@ -1182,22 +1176,40 @@ var MongoMigrationRunner = class {
|
|
|
1182
1176
|
}
|
|
1183
1177
|
}
|
|
1184
1178
|
const destination = options.plan.destination;
|
|
1185
|
-
const profileHash =
|
|
1186
|
-
|
|
1179
|
+
const profileHash = options.destinationContract.profileHash ?? destination.storageHash;
|
|
1180
|
+
const incomingInvariants = Array.from(new Set(options.invariants ?? [])).sort();
|
|
1181
|
+
const existingInvariantSet = new Set(existingMarker?.invariants ?? []);
|
|
1182
|
+
const incomingIsSubsetOfExisting = incomingInvariants.every((id) => existingInvariantSet.has(id));
|
|
1183
|
+
const markerAlreadyAtDestination = existingMarker !== null && existingMarker.storageHash === destination.storageHash && existingMarker.profileHash === profileHash;
|
|
1184
|
+
if (operationsExecuted === 0 && markerAlreadyAtDestination && incomingIsSubsetOfExisting) return ok({
|
|
1187
1185
|
operationsPlanned: operations.length,
|
|
1188
1186
|
operationsExecuted
|
|
1189
1187
|
});
|
|
1188
|
+
const liveSchema = await this.deps.introspectSchema();
|
|
1189
|
+
const verifyResult = verifyMongoSchema({
|
|
1190
|
+
contract: options.destinationContract,
|
|
1191
|
+
schema: liveSchema,
|
|
1192
|
+
strict: options.strictVerification ?? true,
|
|
1193
|
+
frameworkComponents: options.frameworkComponents,
|
|
1194
|
+
...options.context ? { context: options.context } : {}
|
|
1195
|
+
});
|
|
1196
|
+
if (!verifyResult.ok) return runnerFailure("SCHEMA_VERIFY_FAILED", verifyResult.summary, {
|
|
1197
|
+
why: "The resulting database schema does not satisfy the destination contract.",
|
|
1198
|
+
meta: { issues: verifyResult.schema.issues }
|
|
1199
|
+
});
|
|
1190
1200
|
if (existingMarker) {
|
|
1191
1201
|
if (!await markerOps.updateMarker(existingMarker.storageHash, {
|
|
1192
1202
|
storageHash: destination.storageHash,
|
|
1193
|
-
profileHash
|
|
1203
|
+
profileHash,
|
|
1204
|
+
invariants: incomingInvariants
|
|
1194
1205
|
})) return runnerFailure("MARKER_CAS_FAILURE", "Marker was modified by another process during migration execution.", { meta: {
|
|
1195
1206
|
expectedStorageHash: existingMarker.storageHash,
|
|
1196
1207
|
destinationStorageHash: destination.storageHash
|
|
1197
1208
|
} });
|
|
1198
1209
|
} else await markerOps.initMarker({
|
|
1199
1210
|
storageHash: destination.storageHash,
|
|
1200
|
-
profileHash
|
|
1211
|
+
profileHash,
|
|
1212
|
+
invariants: incomingInvariants
|
|
1201
1213
|
});
|
|
1202
1214
|
const originHash = existingMarker?.storageHash ?? "";
|
|
1203
1215
|
await markerOps.writeLedgerEntry({
|
|
@@ -1224,7 +1236,7 @@ var MongoMigrationRunner = class {
|
|
|
1224
1236
|
};
|
|
1225
1237
|
}
|
|
1226
1238
|
for (const plan of op.run) {
|
|
1227
|
-
const wireCommand = adapter.lower(plan);
|
|
1239
|
+
const wireCommand = await adapter.lower(plan);
|
|
1228
1240
|
for await (const _ of driver.execute(wireCommand));
|
|
1229
1241
|
}
|
|
1230
1242
|
if (runPostchecks && op.postcheck.length > 0) {
|
|
@@ -1250,7 +1262,7 @@ var MongoMigrationRunner = class {
|
|
|
1250
1262
|
collection: check.source.collection
|
|
1251
1263
|
}
|
|
1252
1264
|
});
|
|
1253
|
-
const wireCommand = adapter.lower(check.source);
|
|
1265
|
+
const wireCommand = await adapter.lower(check.source);
|
|
1254
1266
|
let matchFound = false;
|
|
1255
1267
|
for await (const row of driver.execute(wireCommand)) if (filterEvaluator.evaluate(check.filter, row)) {
|
|
1256
1268
|
matchFound = true;
|
|
@@ -1283,10 +1295,7 @@ var MongoMigrationRunner = class {
|
|
|
1283
1295
|
}
|
|
1284
1296
|
ensureMarkerCompatibility(marker, plan) {
|
|
1285
1297
|
const origin = plan.origin ?? null;
|
|
1286
|
-
if (!origin)
|
|
1287
|
-
if (marker) return runnerFailure("MARKER_ORIGIN_MISMATCH", "Database already has a contract marker but the plan has no origin. This would silently overwrite the existing marker.", { meta: { markerStorageHash: marker.storageHash } });
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1298
|
+
if (!origin) return;
|
|
1290
1299
|
if (!marker) return runnerFailure("MARKER_ORIGIN_MISMATCH", `Missing contract marker: expected origin storage hash ${origin.storageHash}.`, { meta: { expectedOriginStorageHash: origin.storageHash } });
|
|
1291
1300
|
if (marker.storageHash !== origin.storageHash) return runnerFailure("MARKER_ORIGIN_MISMATCH", `Existing contract marker (${marker.storageHash}) does not match plan origin (${origin.storageHash}).`, { meta: {
|
|
1292
1301
|
markerStorageHash: marker.storageHash,
|