@prisma-next/target-mongo 0.4.1 → 0.4.3
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 +4 -1
- package/dist/control.d.mts +43 -22
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +126 -110
- package/dist/control.mjs.map +1 -1
- package/dist/descriptor-meta-D9_5quQi.mjs +14 -0
- package/dist/descriptor-meta-D9_5quQi.mjs.map +1 -0
- package/dist/{migration-factories-gwi81C8u.mjs → migration-factories-CoNYWrd1.mjs} +3 -1
- package/dist/migration-factories-CoNYWrd1.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--nK5dk8n.d.mts} +1 -1
- package/dist/{op-factory-call-BjNAcPSF.d.mts.map → op-factory-call--nK5dk8n.d.mts.map} +1 -1
- package/dist/pack.mjs +1 -11
- package/dist/pack.mjs.map +1 -1
- package/dist/runtime.d.mts +20 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +28 -0
- package/dist/runtime.mjs.map +1 -0
- 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-Daa7BMJY.mjs +582 -0
- package/dist/verify-mongo-schema-Daa7BMJY.mjs.map +1 -0
- package/package.json +19 -13
- package/src/core/marker-ledger.ts +90 -20
- package/src/core/migration-factories.ts +8 -0
- package/src/core/mongo-ops-serializer.ts +0 -8
- package/src/core/mongo-planner.ts +8 -2
- package/src/core/mongo-runner.ts +105 -70
- package/src/core/planner-produced-migration.ts +0 -1
- package/src/core/render-typescript.ts +28 -16
- 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/runtime.ts +38 -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
|
|
|
@@ -33,6 +35,7 @@ const contract = defineContract({
|
|
|
33
35
|
### Migration authoring
|
|
34
36
|
|
|
35
37
|
```typescript
|
|
38
|
+
import { MigrationCLI } from '@prisma-next/cli/migration-cli';
|
|
36
39
|
import { Migration } from '@prisma-next/family-mongo/migration';
|
|
37
40
|
import { createIndex, createCollection } from '@prisma-next/target-mongo/migration';
|
|
38
41
|
|
|
@@ -49,7 +52,7 @@ class UsersMigration extends Migration {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
export default UsersMigration;
|
|
52
|
-
|
|
55
|
+
MigrationCLI.run(import.meta.url, UsersMigration);
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
Run `tsx migration.ts` to produce `ops.json` and `migration.json` (when `describe()` is implemented). Use `--dry-run` to preview without writing.
|
package/dist/control.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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--nK5dk8n.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 {
|
|
8
|
-
import { ContractMarkerRecord } from "@prisma-next/contract/types";
|
|
8
|
+
import { Contract, ContractMarkerRecord } from "@prisma-next/contract/types";
|
|
9
9
|
import { Db } from "mongodb";
|
|
10
10
|
import { TargetBoundComponentDescriptor } from "@prisma-next/framework-components/components";
|
|
11
11
|
import { MongoAdapter, MongoDriver } from "@prisma-next/mongo-lowering";
|
|
@@ -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;
|
|
@@ -68,7 +79,12 @@ declare class MongoMigrationPlanner implements MigrationPlanner<'mongo', 'mongo'
|
|
|
68
79
|
readonly contract: unknown;
|
|
69
80
|
readonly schema: unknown;
|
|
70
81
|
readonly policy: MigrationOperationPolicy;
|
|
71
|
-
|
|
82
|
+
/**
|
|
83
|
+
* The "from" contract (state the planner assumes the database starts at),
|
|
84
|
+
* or `null` for reconciliation flows. Used to populate `describe().from`
|
|
85
|
+
* on the produced plan as `fromContract?.storage.storageHash ?? null`.
|
|
86
|
+
*/
|
|
87
|
+
readonly fromContract: Contract | null;
|
|
72
88
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', 'mongo'>>;
|
|
73
89
|
}): MigrationPlannerResult;
|
|
74
90
|
/**
|
|
@@ -89,10 +105,12 @@ interface MarkerOperations {
|
|
|
89
105
|
initMarker(destination: {
|
|
90
106
|
readonly storageHash: string;
|
|
91
107
|
readonly profileHash: string;
|
|
108
|
+
readonly invariants?: readonly string[];
|
|
92
109
|
}): Promise<void>;
|
|
93
110
|
updateMarker(expectedFrom: string, destination: {
|
|
94
111
|
readonly storageHash: string;
|
|
95
112
|
readonly profileHash: string;
|
|
113
|
+
readonly invariants?: readonly string[];
|
|
96
114
|
}): Promise<boolean>;
|
|
97
115
|
writeLedgerEntry(entry: {
|
|
98
116
|
readonly edgeId: string;
|
|
@@ -106,21 +124,25 @@ interface MongoRunnerDependencies {
|
|
|
106
124
|
readonly adapter: MongoAdapter;
|
|
107
125
|
readonly driver: MongoDriver;
|
|
108
126
|
readonly markerOps: MarkerOperations;
|
|
127
|
+
readonly introspectSchema: () => Promise<MongoSchemaIR>;
|
|
128
|
+
}
|
|
129
|
+
interface MongoMigrationRunnerExecuteOptions {
|
|
130
|
+
readonly plan: MigrationPlan;
|
|
131
|
+
readonly destinationContract: MongoContract;
|
|
132
|
+
readonly policy: MigrationOperationPolicy;
|
|
133
|
+
readonly callbacks?: {
|
|
134
|
+
onOperationStart?(op: MigrationPlanOperation): void;
|
|
135
|
+
onOperationComplete?(op: MigrationPlanOperation): void;
|
|
136
|
+
};
|
|
137
|
+
readonly executionChecks?: MigrationRunnerExecutionChecks;
|
|
138
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', 'mongo'>>;
|
|
139
|
+
readonly strictVerification?: boolean;
|
|
140
|
+
readonly context?: OperationContext;
|
|
109
141
|
}
|
|
110
142
|
declare class MongoMigrationRunner {
|
|
111
143
|
private readonly deps;
|
|
112
144
|
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>;
|
|
145
|
+
execute(options: MongoMigrationRunnerExecuteOptions): Promise<MigrationRunnerResult>;
|
|
124
146
|
private executeDataTransform;
|
|
125
147
|
private evaluateDataTransformChecks;
|
|
126
148
|
private evaluateChecks;
|
|
@@ -161,9 +183,8 @@ declare function renderOps(calls: ReadonlyArray<OpFactoryCall>): MongoMigrationP
|
|
|
161
183
|
//#endregion
|
|
162
184
|
//#region src/core/render-typescript.d.ts
|
|
163
185
|
interface RenderMigrationMeta {
|
|
164
|
-
readonly from: string;
|
|
186
|
+
readonly from: string | null;
|
|
165
187
|
readonly to: string;
|
|
166
|
-
readonly kind?: string;
|
|
167
188
|
readonly labels?: readonly string[];
|
|
168
189
|
}
|
|
169
190
|
/**
|
|
@@ -172,16 +193,16 @@ interface RenderMigrationMeta {
|
|
|
172
193
|
* `Migration` (i.e. `MongoMigration`) from `@prisma-next/family-mongo`, and
|
|
173
194
|
* implements the abstract `operations` and `describe` members. `meta` is
|
|
174
195
|
* always rendered — `describe()` is part of the `Migration` contract, so
|
|
175
|
-
* even an empty stub must satisfy it; callers pass
|
|
176
|
-
* migration-new scaffold.
|
|
196
|
+
* even an empty stub must satisfy it; callers pass `from: null` for a
|
|
197
|
+
* baseline `migration-new` scaffold (and a real `to` hash either way).
|
|
177
198
|
*
|
|
178
199
|
* The walk is polymorphic: each call node contributes its own
|
|
179
200
|
* `renderTypeScript()` expression and declares its own
|
|
180
201
|
* `importRequirements()`. The top-level renderer aggregates imports
|
|
181
202
|
* across all nodes and emits one `import { … } from "…"` line per module.
|
|
182
|
-
* The `Migration`
|
|
183
|
-
*
|
|
184
|
-
*
|
|
203
|
+
* The `Migration` and `MigrationCLI` imports are always emitted — they're
|
|
204
|
+
* structural to the rendered scaffold (extends `Migration`, calls
|
|
205
|
+
* `MigrationCLI.run`), not driven by any node.
|
|
185
206
|
*/
|
|
186
207
|
declare function renderCallsToTypeScript(calls: ReadonlyArray<OpFactoryCall>, meta: RenderMigrationMeta): string;
|
|
187
208
|
//#endregion
|
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-Daa7BMJY.mjs";
|
|
2
|
+
import { a as dropCollection, n as createCollection, o as dropIndex, r as createIndex, t as collMod } from "./migration-factories-CoNYWrd1.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, {
|
|
@@ -358,13 +355,9 @@ const QueryPlanJson = type({
|
|
|
358
355
|
target: "string",
|
|
359
356
|
storageHash: "string",
|
|
360
357
|
lane: "string",
|
|
361
|
-
paramDescriptors: "unknown[]",
|
|
362
358
|
"targetFamily?": "string",
|
|
363
359
|
"profileHash?": "string",
|
|
364
|
-
"annotations?": "Record<string, unknown>"
|
|
365
|
-
"refs?": "Record<string, unknown>",
|
|
366
|
-
"projection?": "Record<string, string> | string[]",
|
|
367
|
-
"projectionTypes?": "Record<string, string>"
|
|
360
|
+
"annotations?": "Record<string, unknown>"
|
|
368
361
|
})
|
|
369
362
|
});
|
|
370
363
|
const CheckJson = type({
|
|
@@ -528,13 +521,9 @@ function deserializeMongoQueryPlan(json) {
|
|
|
528
521
|
target: m.target,
|
|
529
522
|
storageHash: m.storageHash,
|
|
530
523
|
lane: m.lane,
|
|
531
|
-
paramDescriptors: m.paramDescriptors,
|
|
532
524
|
...ifDefined("targetFamily", m.targetFamily),
|
|
533
525
|
...ifDefined("profileHash", m.profileHash),
|
|
534
|
-
...ifDefined("annotations", m.annotations)
|
|
535
|
-
...ifDefined("refs", m.refs),
|
|
536
|
-
...ifDefined("projection", m.projection),
|
|
537
|
-
...ifDefined("projectionTypes", m.projectionTypes)
|
|
526
|
+
...ifDefined("annotations", m.annotations)
|
|
538
527
|
};
|
|
539
528
|
return {
|
|
540
529
|
collection: data.collection,
|
|
@@ -827,26 +816,45 @@ function renderOps(calls) {
|
|
|
827
816
|
|
|
828
817
|
//#endregion
|
|
829
818
|
//#region src/core/render-typescript.ts
|
|
830
|
-
|
|
819
|
+
/**
|
|
820
|
+
* Always-present base imports for the rendered scaffold:
|
|
821
|
+
*
|
|
822
|
+
* - `Migration` from `@prisma-next/family-mongo/migration` — the
|
|
823
|
+
* user-facing Mongo `Migration` base; subclasses don't need to
|
|
824
|
+
* redeclare `targetId` or thread family/target generics.
|
|
825
|
+
* - `MigrationCLI` from `@prisma-next/cli/migration-cli` — the
|
|
826
|
+
* migration-file CLI entrypoint that loads `prisma-next.config.ts`,
|
|
827
|
+
* assembles a `ControlStack`, and instantiates the migration class.
|
|
828
|
+
* The migration file owns this dependency directly: pulling CLI
|
|
829
|
+
* machinery in at script run time is acceptable because the script's
|
|
830
|
+
* whole purpose is to be invoked from the project that owns the
|
|
831
|
+
* config. (Mirrors the postgres facade pattern; pulling `MigrationCLI`
|
|
832
|
+
* into `@prisma-next/family-mongo/migration` so a Mongo migration only
|
|
833
|
+
* needs one import is tracked separately as a follow-up.)
|
|
834
|
+
*/
|
|
835
|
+
const BASE_IMPORTS = [{
|
|
831
836
|
moduleSpecifier: "@prisma-next/family-mongo/migration",
|
|
832
837
|
symbol: "Migration"
|
|
833
|
-
}
|
|
838
|
+
}, {
|
|
839
|
+
moduleSpecifier: "@prisma-next/cli/migration-cli",
|
|
840
|
+
symbol: "MigrationCLI"
|
|
841
|
+
}];
|
|
834
842
|
/**
|
|
835
843
|
* Render a list of Mongo `OpFactoryCall`s as a `migration.ts`
|
|
836
844
|
* source string. The result is shebanged, extends the user-facing
|
|
837
845
|
* `Migration` (i.e. `MongoMigration`) from `@prisma-next/family-mongo`, and
|
|
838
846
|
* implements the abstract `operations` and `describe` members. `meta` is
|
|
839
847
|
* always rendered — `describe()` is part of the `Migration` contract, so
|
|
840
|
-
* even an empty stub must satisfy it; callers pass
|
|
841
|
-
* migration-new scaffold.
|
|
848
|
+
* even an empty stub must satisfy it; callers pass `from: null` for a
|
|
849
|
+
* baseline `migration-new` scaffold (and a real `to` hash either way).
|
|
842
850
|
*
|
|
843
851
|
* The walk is polymorphic: each call node contributes its own
|
|
844
852
|
* `renderTypeScript()` expression and declares its own
|
|
845
853
|
* `importRequirements()`. The top-level renderer aggregates imports
|
|
846
854
|
* across all nodes and emits one `import { … } from "…"` line per module.
|
|
847
|
-
* The `Migration`
|
|
848
|
-
*
|
|
849
|
-
*
|
|
855
|
+
* The `Migration` and `MigrationCLI` imports are always emitted — they're
|
|
856
|
+
* structural to the rendered scaffold (extends `Migration`, calls
|
|
857
|
+
* `MigrationCLI.run`), not driven by any node.
|
|
850
858
|
*/
|
|
851
859
|
function renderCallsToTypeScript(calls, meta) {
|
|
852
860
|
const imports = buildImports(calls);
|
|
@@ -865,12 +873,12 @@ function renderCallsToTypeScript(calls, meta) {
|
|
|
865
873
|
"}",
|
|
866
874
|
"",
|
|
867
875
|
"export default M;",
|
|
868
|
-
"
|
|
876
|
+
"MigrationCLI.run(import.meta.url, M);",
|
|
869
877
|
""
|
|
870
878
|
].join("\n");
|
|
871
879
|
}
|
|
872
880
|
function buildImports(calls) {
|
|
873
|
-
const requirements = [
|
|
881
|
+
const requirements = [...BASE_IMPORTS];
|
|
874
882
|
for (const call of calls) for (const req of call.importRequirements()) requirements.push(req);
|
|
875
883
|
return renderImports(requirements);
|
|
876
884
|
}
|
|
@@ -880,7 +888,6 @@ function buildDescribeMethod(meta) {
|
|
|
880
888
|
lines.push(" return {");
|
|
881
889
|
lines.push(` from: ${JSON.stringify(meta.from)},`);
|
|
882
890
|
lines.push(` to: ${JSON.stringify(meta.to)},`);
|
|
883
|
-
if (meta.kind) lines.push(` kind: ${JSON.stringify(meta.kind)},`);
|
|
884
891
|
if (meta.labels && meta.labels.length > 0) lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
|
|
885
892
|
lines.push(" };");
|
|
886
893
|
lines.push(" }");
|
|
@@ -927,7 +934,6 @@ var PlannerProducedMongoMigration = class extends Migration {
|
|
|
927
934
|
return renderCallsToTypeScript(this.calls, {
|
|
928
935
|
from: this.meta.from,
|
|
929
936
|
to: this.meta.to,
|
|
930
|
-
...ifDefined("kind", this.meta.kind),
|
|
931
937
|
...ifDefined("labels", this.meta.labels)
|
|
932
938
|
});
|
|
933
939
|
}
|
|
@@ -1049,7 +1055,7 @@ var MongoMigrationPlanner = class {
|
|
|
1049
1055
|
return {
|
|
1050
1056
|
kind: "success",
|
|
1051
1057
|
plan: new PlannerProducedMongoMigration(result.calls, {
|
|
1052
|
-
from: options.
|
|
1058
|
+
from: options.fromContract?.storage.storageHash ?? null,
|
|
1053
1059
|
to: contract.storage.storageHash
|
|
1054
1060
|
})
|
|
1055
1061
|
};
|
|
@@ -1109,9 +1115,6 @@ function planMutableOptionsDiffCall(collName, origin, dest) {
|
|
|
1109
1115
|
//#endregion
|
|
1110
1116
|
//#region src/core/mongo-runner.ts
|
|
1111
1117
|
const READ_ONLY_CHECK_COMMAND_KINDS = new Set(["aggregate", "rawAggregate"]);
|
|
1112
|
-
function hasProfileHash(value) {
|
|
1113
|
-
return typeof value === "object" && value !== null && Object.hasOwn(value, "profileHash") && typeof value.profileHash === "string";
|
|
1114
|
-
}
|
|
1115
1118
|
function runnerFailure(code, summary, opts) {
|
|
1116
1119
|
return notOk({
|
|
1117
1120
|
code,
|
|
@@ -1163,29 +1166,45 @@ var MongoMigrationRunner = class {
|
|
|
1163
1166
|
}
|
|
1164
1167
|
}
|
|
1165
1168
|
const destination = options.plan.destination;
|
|
1166
|
-
const profileHash =
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1169
|
+
const profileHash = options.destinationContract.profileHash ?? destination.storageHash;
|
|
1170
|
+
const incomingInvariants = options.plan.providedInvariants ?? [];
|
|
1171
|
+
const existingInvariantSet = new Set(existingMarker?.invariants ?? []);
|
|
1172
|
+
const incomingIsSubsetOfExisting = incomingInvariants.every((id) => existingInvariantSet.has(id));
|
|
1173
|
+
const markerAlreadyAtDestination = existingMarker !== null && existingMarker.storageHash === destination.storageHash && existingMarker.profileHash === profileHash;
|
|
1174
|
+
if (!(operationsExecuted === 0 && markerAlreadyAtDestination && incomingIsSubsetOfExisting)) {
|
|
1175
|
+
const liveSchema = await this.deps.introspectSchema();
|
|
1176
|
+
const verifyResult = verifyMongoSchema({
|
|
1177
|
+
contract: options.destinationContract,
|
|
1178
|
+
schema: liveSchema,
|
|
1179
|
+
strict: options.strictVerification ?? true,
|
|
1180
|
+
frameworkComponents: options.frameworkComponents,
|
|
1181
|
+
...options.context ? { context: options.context } : {}
|
|
1182
|
+
});
|
|
1183
|
+
if (!verifyResult.ok) return runnerFailure("SCHEMA_VERIFY_FAILED", verifyResult.summary, {
|
|
1184
|
+
why: "The resulting database schema does not satisfy the destination contract.",
|
|
1185
|
+
meta: { issues: verifyResult.schema.issues }
|
|
1186
|
+
});
|
|
1187
|
+
if (existingMarker) {
|
|
1188
|
+
if (!await markerOps.updateMarker(existingMarker.storageHash, {
|
|
1189
|
+
storageHash: destination.storageHash,
|
|
1190
|
+
profileHash,
|
|
1191
|
+
invariants: incomingInvariants
|
|
1192
|
+
})) return runnerFailure("MARKER_CAS_FAILURE", "Marker was modified by another process during migration execution.", { meta: {
|
|
1193
|
+
expectedStorageHash: existingMarker.storageHash,
|
|
1194
|
+
destinationStorageHash: destination.storageHash
|
|
1195
|
+
} });
|
|
1196
|
+
} else await markerOps.initMarker({
|
|
1173
1197
|
storageHash: destination.storageHash,
|
|
1174
|
-
profileHash
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
await markerOps.writeLedgerEntry({
|
|
1185
|
-
edgeId: `${originHash}->${destination.storageHash}`,
|
|
1186
|
-
from: originHash,
|
|
1187
|
-
to: destination.storageHash
|
|
1188
|
-
});
|
|
1198
|
+
profileHash,
|
|
1199
|
+
invariants: incomingInvariants
|
|
1200
|
+
});
|
|
1201
|
+
const originHash = existingMarker?.storageHash ?? "";
|
|
1202
|
+
await markerOps.writeLedgerEntry({
|
|
1203
|
+
edgeId: `${originHash}->${destination.storageHash}`,
|
|
1204
|
+
from: originHash,
|
|
1205
|
+
to: destination.storageHash
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1189
1208
|
return ok({
|
|
1190
1209
|
operationsPlanned: operations.length,
|
|
1191
1210
|
operationsExecuted
|
|
@@ -1205,7 +1224,7 @@ var MongoMigrationRunner = class {
|
|
|
1205
1224
|
};
|
|
1206
1225
|
}
|
|
1207
1226
|
for (const plan of op.run) {
|
|
1208
|
-
const wireCommand = adapter.lower(plan);
|
|
1227
|
+
const wireCommand = await adapter.lower(plan, {});
|
|
1209
1228
|
for await (const _ of driver.execute(wireCommand));
|
|
1210
1229
|
}
|
|
1211
1230
|
if (runPostchecks && op.postcheck.length > 0) {
|
|
@@ -1231,7 +1250,7 @@ var MongoMigrationRunner = class {
|
|
|
1231
1250
|
collection: check.source.collection
|
|
1232
1251
|
}
|
|
1233
1252
|
});
|
|
1234
|
-
const wireCommand = adapter.lower(check.source);
|
|
1253
|
+
const wireCommand = await adapter.lower(check.source, {});
|
|
1235
1254
|
let matchFound = false;
|
|
1236
1255
|
for await (const row of driver.execute(wireCommand)) if (filterEvaluator.evaluate(check.filter, row)) {
|
|
1237
1256
|
matchFound = true;
|
|
@@ -1264,10 +1283,7 @@ var MongoMigrationRunner = class {
|
|
|
1264
1283
|
}
|
|
1265
1284
|
ensureMarkerCompatibility(marker, plan) {
|
|
1266
1285
|
const origin = plan.origin ?? null;
|
|
1267
|
-
if (!origin)
|
|
1268
|
-
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 } });
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1286
|
+
if (!origin) return;
|
|
1271
1287
|
if (!marker) return runnerFailure("MARKER_ORIGIN_MISMATCH", `Missing contract marker: expected origin storage hash ${origin.storageHash}.`, { meta: { expectedOriginStorageHash: origin.storageHash } });
|
|
1272
1288
|
if (marker.storageHash !== origin.storageHash) return runnerFailure("MARKER_ORIGIN_MISMATCH", `Existing contract marker (${marker.storageHash}) does not match plan origin (${origin.storageHash}).`, { meta: {
|
|
1273
1289
|
markerStorageHash: marker.storageHash,
|