@prisma-next/family-mongo 0.4.0-dev.9 → 0.4.2
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 +6 -3
- package/dist/control.d.mts +4 -10
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +22 -448
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +9 -3
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -1
- package/dist/migration.mjs.map +1 -1
- package/dist/schema-verify.d.mts +2 -0
- package/dist/schema-verify.mjs +3 -0
- package/package.json +17 -16
- package/src/core/control-instance.ts +12 -30
- package/src/core/mongo-migration.ts +9 -3
- package/src/core/mongo-target-descriptor.ts +23 -16
- package/src/exports/schema-verify.ts +2 -0
- package/src/core/mongo-emit.ts +0 -135
- package/src/core/schema-diff.ts +0 -402
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ This package is the Mongo family integration point for both control-plane assemb
|
|
|
23
23
|
- `./control`: control-plane entrypoint exporting `mongoFamilyDescriptor`, `mongoTargetDescriptor`, `createMongoFamilyInstance`, and `MongoControlFamilyInstance`
|
|
24
24
|
- `./migration`: migration authoring — `Migration` class, factory functions, and strategies (re-exported from `@prisma-next/target-mongo/migration`)
|
|
25
25
|
- `./pack`: pure pack ref for TypeScript authoring flows such as `@prisma-next/mongo-contract-ts/contract-builder`
|
|
26
|
+
- `./schema-verify`: re-exports the pure `verifyMongoSchema(...)` from `@prisma-next/target-mongo/schema-verify`. `MongoFamilyInstance.schemaVerify` (i.e. `db verify --schema-only`) and the `MongoMigrationRunner` post-apply verify step both call into this shared verifier, so both surfaces agree on "matches the contract" by construction
|
|
26
27
|
|
|
27
28
|
## Usage
|
|
28
29
|
|
|
@@ -99,15 +100,16 @@ The current `contract.ts` slice supports roots and collections, typed reference
|
|
|
99
100
|
### Migration authoring
|
|
100
101
|
|
|
101
102
|
```typescript
|
|
103
|
+
import { MigrationCLI } from "@prisma-next/cli/migration-cli"
|
|
102
104
|
import { Migration, createIndex, createCollection }
|
|
103
105
|
from "@prisma-next/family-mongo/migration"
|
|
104
106
|
|
|
105
107
|
class AddUsersCollection extends Migration {
|
|
106
|
-
describe() {
|
|
108
|
+
override describe() {
|
|
107
109
|
return { from: "abc123", to: "def456", labels: ["add-users"] }
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
override get operations() {
|
|
111
113
|
return [
|
|
112
114
|
createCollection("users", {
|
|
113
115
|
validator: { $jsonSchema: { required: ["email"] } },
|
|
@@ -119,7 +121,7 @@ class AddUsersCollection extends Migration {
|
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
export default AddUsersCollection;
|
|
122
|
-
|
|
124
|
+
MigrationCLI.run(import.meta.url, AddUsersCollection);
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
Run `node migration.ts` to produce `ops.json` and `migration.json`. Use `--dry-run` to preview without writing.
|
|
@@ -133,6 +135,7 @@ Run `node migration.ts` to produce `ops.json` and `migration.json`. Use `--dry-r
|
|
|
133
135
|
- `src/exports/control.ts`: control-plane entrypoint
|
|
134
136
|
- `src/exports/migration.ts`: migration authoring entrypoint
|
|
135
137
|
- `src/exports/pack.ts`: authoring-time family pack ref
|
|
138
|
+
- `src/exports/schema-verify.ts`: schema-verify entrypoint that re-exports from `@prisma-next/target-mongo/schema-verify`
|
|
136
139
|
|
|
137
140
|
## Dependencies
|
|
138
141
|
|
package/dist/control.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ControlFamilyDescriptor, ControlFamilyInstance, ControlStack, MigratableTargetDescriptor, SchemaViewCapable } from "@prisma-next/framework-components/control";
|
|
2
|
-
import { MongoSchemaIR } from "@prisma-next/mongo-schema-ir";
|
|
3
2
|
import { Contract } from "@prisma-next/contract/types";
|
|
3
|
+
import { MongoSchemaIR } from "@prisma-next/mongo-schema-ir";
|
|
4
4
|
|
|
5
5
|
//#region src/core/control-instance.d.ts
|
|
6
6
|
interface MongoControlFamilyInstance extends ControlFamilyInstance<'mongo', MongoSchemaIR>, SchemaViewCapable<MongoSchemaIR> {
|
|
@@ -13,20 +13,14 @@ declare const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoContr
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/core/mongo-target-descriptor.d.ts
|
|
15
15
|
/**
|
|
16
|
-
* The Mongo target uses the **class-flow** migration authoring strategy.
|
|
17
|
-
*
|
|
18
16
|
* `migration.ts` default-exports a `Migration` subclass whose `operations`
|
|
19
17
|
* getter returns the ordered list of operations and whose `describe()`
|
|
20
18
|
* returns the manifest identity metadata. `MongoMigrationPlanner.plan()`
|
|
21
19
|
* returns a `MigrationPlanWithAuthoringSurface` that knows how to render
|
|
22
20
|
* itself back to such a file; `MongoMigrationPlanner.emptyMigration()`
|
|
23
|
-
* returns the same shape for `migration new`.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* The descriptor-flow hooks (`planWithDescriptors`, `resolveDescriptors`,
|
|
27
|
-
* `renderDescriptorTypeScript`) are intentionally omitted — the CLI's
|
|
28
|
-
* `migrationStrategy` selector routes Mongo down the class-flow path by
|
|
29
|
-
* observing their absence.
|
|
21
|
+
* returns the same shape for `migration new`. Users run the scaffolded
|
|
22
|
+
* `migration.ts` directly (via `node migration.ts`) to self-emit
|
|
23
|
+
* `ops.json` and attest the `migrationId`.
|
|
30
24
|
*/
|
|
31
25
|
declare const mongoTargetDescriptor: MigratableTargetDescriptor<'mongo', 'mongo', MongoControlFamilyInstance>;
|
|
32
26
|
//#endregion
|
package/dist/control.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/mongo-target-descriptor.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/mongo-target-descriptor.ts"],"sourcesContent":[],"mappings":";;;;;UA2BiB,0BAAA,SACP,+BAA+B,gBACrC,kBAAkB;2CACqB;AAH3C;AACyC,iBAqRzB,yBAAA,CArRyB,aAAA,EAqRgB,YArRhB,CAAA,EAqR+B,0BArR/B;;;cCL5B,uBAAuB,iCAAiC;;;;;;ADIrE;;;;;;;AAsRgB,cEtRH,qBFsR4B,EEtRL,0BFsRoC,CAAA,OAAA,EAAA,OAA0B,EEnRhG,0BFmRgG,CAAA"}
|
package/dist/control.mjs
CHANGED
|
@@ -1,335 +1,13 @@
|
|
|
1
1
|
import { mongoEmission } from "@prisma-next/mongo-emitter";
|
|
2
2
|
import { createMongoRunnerDeps, extractDb, introspectSchema } from "@prisma-next/adapter-mongo/control";
|
|
3
|
-
import { SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING,
|
|
3
|
+
import { SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH } from "@prisma-next/framework-components/control";
|
|
4
4
|
import { validateMongoContract } from "@prisma-next/mongo-contract";
|
|
5
5
|
import { MongoMigrationPlanner, MongoMigrationRunner, contractToMongoSchemaIR, initMarker, readMarker, updateMarker } from "@prisma-next/target-mongo/control";
|
|
6
|
+
import { verifyMongoSchema } from "@prisma-next/target-mongo/schema-verify";
|
|
6
7
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
7
|
-
import { canonicalize, deepEqual } from "@prisma-next/mongo-schema-ir";
|
|
8
8
|
import { MongoDriverImpl } from "@prisma-next/driver-mongo";
|
|
9
9
|
import mongoTargetDescriptorMeta from "@prisma-next/target-mongo/pack";
|
|
10
|
-
import { stat } from "node:fs/promises";
|
|
11
|
-
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { errorMigrationFileMissing, errorMigrationInvalidDefaultExport, errorMigrationPlanNotArray } from "@prisma-next/errors/migration";
|
|
13
|
-
import { writeMigrationOps } from "@prisma-next/migration-tools/io";
|
|
14
|
-
import { Migration } from "@prisma-next/migration-tools/migration";
|
|
15
|
-
import { join } from "pathe";
|
|
16
10
|
|
|
17
|
-
//#region src/core/schema-diff.ts
|
|
18
|
-
function diffMongoSchemas(live, expected, strict) {
|
|
19
|
-
const issues = [];
|
|
20
|
-
const collectionChildren = [];
|
|
21
|
-
let pass = 0;
|
|
22
|
-
let warn = 0;
|
|
23
|
-
let fail = 0;
|
|
24
|
-
const allNames = new Set([...live.collectionNames, ...expected.collectionNames]);
|
|
25
|
-
for (const name of [...allNames].sort()) {
|
|
26
|
-
const liveColl = live.collection(name);
|
|
27
|
-
const expectedColl = expected.collection(name);
|
|
28
|
-
if (!liveColl && expectedColl) {
|
|
29
|
-
issues.push({
|
|
30
|
-
kind: "missing_table",
|
|
31
|
-
table: name,
|
|
32
|
-
message: `Collection "${name}" is missing from the database`
|
|
33
|
-
});
|
|
34
|
-
collectionChildren.push({
|
|
35
|
-
status: "fail",
|
|
36
|
-
kind: "collection",
|
|
37
|
-
name,
|
|
38
|
-
contractPath: `storage.collections.${name}`,
|
|
39
|
-
code: "MISSING_COLLECTION",
|
|
40
|
-
message: `Collection "${name}" is missing`,
|
|
41
|
-
expected: name,
|
|
42
|
-
actual: null,
|
|
43
|
-
children: []
|
|
44
|
-
});
|
|
45
|
-
fail++;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (liveColl && !expectedColl) {
|
|
49
|
-
const status = strict ? "fail" : "warn";
|
|
50
|
-
issues.push({
|
|
51
|
-
kind: "extra_table",
|
|
52
|
-
table: name,
|
|
53
|
-
message: `Extra collection "${name}" exists in the database but not in the contract`
|
|
54
|
-
});
|
|
55
|
-
collectionChildren.push({
|
|
56
|
-
status,
|
|
57
|
-
kind: "collection",
|
|
58
|
-
name,
|
|
59
|
-
contractPath: `storage.collections.${name}`,
|
|
60
|
-
code: "EXTRA_COLLECTION",
|
|
61
|
-
message: `Extra collection "${name}" found`,
|
|
62
|
-
expected: null,
|
|
63
|
-
actual: name,
|
|
64
|
-
children: []
|
|
65
|
-
});
|
|
66
|
-
if (status === "fail") fail++;
|
|
67
|
-
else warn++;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const lc = liveColl;
|
|
71
|
-
const ec = expectedColl;
|
|
72
|
-
const indexChildren = diffIndexes(name, lc, ec, strict, issues);
|
|
73
|
-
const validatorChildren = diffValidator(name, lc, ec, strict, issues);
|
|
74
|
-
const optionsChildren = diffOptions(name, lc, ec, strict, issues);
|
|
75
|
-
const children = [
|
|
76
|
-
...indexChildren,
|
|
77
|
-
...validatorChildren,
|
|
78
|
-
...optionsChildren
|
|
79
|
-
];
|
|
80
|
-
const worstStatus = children.reduce((s, c) => c.status === "fail" ? "fail" : c.status === "warn" && s !== "fail" ? "warn" : s, "pass");
|
|
81
|
-
for (const c of children) if (c.status === "pass") pass++;
|
|
82
|
-
else if (c.status === "warn") warn++;
|
|
83
|
-
else fail++;
|
|
84
|
-
if (children.length === 0) pass++;
|
|
85
|
-
collectionChildren.push({
|
|
86
|
-
status: worstStatus,
|
|
87
|
-
kind: "collection",
|
|
88
|
-
name,
|
|
89
|
-
contractPath: `storage.collections.${name}`,
|
|
90
|
-
code: worstStatus === "pass" ? "MATCH" : "DRIFT",
|
|
91
|
-
message: worstStatus === "pass" ? `Collection "${name}" matches` : `Collection "${name}" has drift`,
|
|
92
|
-
expected: name,
|
|
93
|
-
actual: name,
|
|
94
|
-
children
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
const rootStatus = fail > 0 ? "fail" : warn > 0 ? "warn" : "pass";
|
|
98
|
-
const totalNodes = pass + warn + fail + collectionChildren.length;
|
|
99
|
-
return {
|
|
100
|
-
root: {
|
|
101
|
-
status: rootStatus,
|
|
102
|
-
kind: "root",
|
|
103
|
-
name: "mongo-schema",
|
|
104
|
-
contractPath: "storage",
|
|
105
|
-
code: rootStatus === "pass" ? "MATCH" : "DRIFT",
|
|
106
|
-
message: rootStatus === "pass" ? "Schema matches" : "Schema has drift",
|
|
107
|
-
expected: null,
|
|
108
|
-
actual: null,
|
|
109
|
-
children: collectionChildren
|
|
110
|
-
},
|
|
111
|
-
issues,
|
|
112
|
-
counts: {
|
|
113
|
-
pass,
|
|
114
|
-
warn,
|
|
115
|
-
fail,
|
|
116
|
-
totalNodes
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function buildIndexLookupKey(index) {
|
|
121
|
-
const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(",");
|
|
122
|
-
const opts = [
|
|
123
|
-
index.unique ? "unique" : "",
|
|
124
|
-
index.sparse ? "sparse" : "",
|
|
125
|
-
index.expireAfterSeconds != null ? `ttl:${index.expireAfterSeconds}` : "",
|
|
126
|
-
index.partialFilterExpression ? `pfe:${canonicalize(index.partialFilterExpression)}` : "",
|
|
127
|
-
index.wildcardProjection ? `wp:${canonicalize(index.wildcardProjection)}` : "",
|
|
128
|
-
index.collation ? `col:${canonicalize(index.collation)}` : "",
|
|
129
|
-
index.weights ? `wt:${canonicalize(index.weights)}` : "",
|
|
130
|
-
index.default_language ? `dl:${index.default_language}` : "",
|
|
131
|
-
index.language_override ? `lo:${index.language_override}` : ""
|
|
132
|
-
].filter(Boolean).join(";");
|
|
133
|
-
return opts ? `${keys}|${opts}` : keys;
|
|
134
|
-
}
|
|
135
|
-
function formatIndexName(index) {
|
|
136
|
-
return index.keys.map((k) => `${k.field}:${k.direction}`).join(", ");
|
|
137
|
-
}
|
|
138
|
-
function diffIndexes(collName, live, expected, strict, issues) {
|
|
139
|
-
const nodes = [];
|
|
140
|
-
const liveLookup = /* @__PURE__ */ new Map();
|
|
141
|
-
for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);
|
|
142
|
-
const expectedLookup = /* @__PURE__ */ new Map();
|
|
143
|
-
for (const idx of expected.indexes) expectedLookup.set(buildIndexLookupKey(idx), idx);
|
|
144
|
-
for (const [key, idx] of expectedLookup) if (liveLookup.has(key)) nodes.push({
|
|
145
|
-
status: "pass",
|
|
146
|
-
kind: "index",
|
|
147
|
-
name: formatIndexName(idx),
|
|
148
|
-
contractPath: `storage.collections.${collName}.indexes`,
|
|
149
|
-
code: "MATCH",
|
|
150
|
-
message: `Index ${formatIndexName(idx)} matches`,
|
|
151
|
-
expected: key,
|
|
152
|
-
actual: key,
|
|
153
|
-
children: []
|
|
154
|
-
});
|
|
155
|
-
else {
|
|
156
|
-
issues.push({
|
|
157
|
-
kind: "index_mismatch",
|
|
158
|
-
table: collName,
|
|
159
|
-
indexOrConstraint: formatIndexName(idx),
|
|
160
|
-
message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`
|
|
161
|
-
});
|
|
162
|
-
nodes.push({
|
|
163
|
-
status: "fail",
|
|
164
|
-
kind: "index",
|
|
165
|
-
name: formatIndexName(idx),
|
|
166
|
-
contractPath: `storage.collections.${collName}.indexes`,
|
|
167
|
-
code: "MISSING_INDEX",
|
|
168
|
-
message: `Index ${formatIndexName(idx)} missing`,
|
|
169
|
-
expected: key,
|
|
170
|
-
actual: null,
|
|
171
|
-
children: []
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
for (const [key, idx] of liveLookup) if (!expectedLookup.has(key)) {
|
|
175
|
-
const status = strict ? "fail" : "warn";
|
|
176
|
-
issues.push({
|
|
177
|
-
kind: "extra_index",
|
|
178
|
-
table: collName,
|
|
179
|
-
indexOrConstraint: formatIndexName(idx),
|
|
180
|
-
message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`
|
|
181
|
-
});
|
|
182
|
-
nodes.push({
|
|
183
|
-
status,
|
|
184
|
-
kind: "index",
|
|
185
|
-
name: formatIndexName(idx),
|
|
186
|
-
contractPath: `storage.collections.${collName}.indexes`,
|
|
187
|
-
code: "EXTRA_INDEX",
|
|
188
|
-
message: `Extra index ${formatIndexName(idx)}`,
|
|
189
|
-
expected: null,
|
|
190
|
-
actual: key,
|
|
191
|
-
children: []
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
return nodes;
|
|
195
|
-
}
|
|
196
|
-
function diffValidator(collName, live, expected, strict, issues) {
|
|
197
|
-
if (!live.validator && !expected.validator) return [];
|
|
198
|
-
if (expected.validator && !live.validator) {
|
|
199
|
-
issues.push({
|
|
200
|
-
kind: "type_missing",
|
|
201
|
-
table: collName,
|
|
202
|
-
message: `Validator missing on collection "${collName}"`
|
|
203
|
-
});
|
|
204
|
-
return [{
|
|
205
|
-
status: "fail",
|
|
206
|
-
kind: "validator",
|
|
207
|
-
name: "validator",
|
|
208
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
209
|
-
code: "MISSING_VALIDATOR",
|
|
210
|
-
message: "Validator missing",
|
|
211
|
-
expected: canonicalize(expected.validator.jsonSchema),
|
|
212
|
-
actual: null,
|
|
213
|
-
children: []
|
|
214
|
-
}];
|
|
215
|
-
}
|
|
216
|
-
if (!expected.validator && live.validator) {
|
|
217
|
-
const status = strict ? "fail" : "warn";
|
|
218
|
-
issues.push({
|
|
219
|
-
kind: "extra_validator",
|
|
220
|
-
table: collName,
|
|
221
|
-
message: `Extra validator on collection "${collName}"`
|
|
222
|
-
});
|
|
223
|
-
return [{
|
|
224
|
-
status,
|
|
225
|
-
kind: "validator",
|
|
226
|
-
name: "validator",
|
|
227
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
228
|
-
code: "EXTRA_VALIDATOR",
|
|
229
|
-
message: "Extra validator found",
|
|
230
|
-
expected: null,
|
|
231
|
-
actual: canonicalize(live.validator.jsonSchema),
|
|
232
|
-
children: []
|
|
233
|
-
}];
|
|
234
|
-
}
|
|
235
|
-
const liveVal = live.validator;
|
|
236
|
-
const expectedVal = expected.validator;
|
|
237
|
-
const liveSchema = canonicalize(liveVal.jsonSchema);
|
|
238
|
-
const expectedSchema = canonicalize(expectedVal.jsonSchema);
|
|
239
|
-
if (liveSchema !== expectedSchema || liveVal.validationLevel !== expectedVal.validationLevel || liveVal.validationAction !== expectedVal.validationAction) {
|
|
240
|
-
issues.push({
|
|
241
|
-
kind: "type_mismatch",
|
|
242
|
-
table: collName,
|
|
243
|
-
expected: expectedSchema,
|
|
244
|
-
actual: liveSchema,
|
|
245
|
-
message: `Validator mismatch on collection "${collName}"`
|
|
246
|
-
});
|
|
247
|
-
return [{
|
|
248
|
-
status: "fail",
|
|
249
|
-
kind: "validator",
|
|
250
|
-
name: "validator",
|
|
251
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
252
|
-
code: "VALIDATOR_MISMATCH",
|
|
253
|
-
message: "Validator mismatch",
|
|
254
|
-
expected: {
|
|
255
|
-
jsonSchema: expectedVal.jsonSchema,
|
|
256
|
-
validationLevel: expectedVal.validationLevel,
|
|
257
|
-
validationAction: expectedVal.validationAction
|
|
258
|
-
},
|
|
259
|
-
actual: {
|
|
260
|
-
jsonSchema: liveVal.jsonSchema,
|
|
261
|
-
validationLevel: liveVal.validationLevel,
|
|
262
|
-
validationAction: liveVal.validationAction
|
|
263
|
-
},
|
|
264
|
-
children: []
|
|
265
|
-
}];
|
|
266
|
-
}
|
|
267
|
-
return [{
|
|
268
|
-
status: "pass",
|
|
269
|
-
kind: "validator",
|
|
270
|
-
name: "validator",
|
|
271
|
-
contractPath: `storage.collections.${collName}.validator`,
|
|
272
|
-
code: "MATCH",
|
|
273
|
-
message: "Validator matches",
|
|
274
|
-
expected: expectedSchema,
|
|
275
|
-
actual: liveSchema,
|
|
276
|
-
children: []
|
|
277
|
-
}];
|
|
278
|
-
}
|
|
279
|
-
function diffOptions(collName, live, expected, strict, issues) {
|
|
280
|
-
if (!live.options && !expected.options) return [];
|
|
281
|
-
if (!expected.options && live.options) {
|
|
282
|
-
const status = strict ? "fail" : "warn";
|
|
283
|
-
issues.push({
|
|
284
|
-
kind: "type_mismatch",
|
|
285
|
-
table: collName,
|
|
286
|
-
actual: canonicalize(live.options),
|
|
287
|
-
message: `Extra collection options on "${collName}"`
|
|
288
|
-
});
|
|
289
|
-
return [{
|
|
290
|
-
status,
|
|
291
|
-
kind: "options",
|
|
292
|
-
name: "options",
|
|
293
|
-
contractPath: `storage.collections.${collName}.options`,
|
|
294
|
-
code: "EXTRA_OPTIONS",
|
|
295
|
-
message: "Extra collection options found",
|
|
296
|
-
expected: null,
|
|
297
|
-
actual: live.options,
|
|
298
|
-
children: []
|
|
299
|
-
}];
|
|
300
|
-
}
|
|
301
|
-
if (deepEqual(live.options, expected.options)) return [{
|
|
302
|
-
status: "pass",
|
|
303
|
-
kind: "options",
|
|
304
|
-
name: "options",
|
|
305
|
-
contractPath: `storage.collections.${collName}.options`,
|
|
306
|
-
code: "MATCH",
|
|
307
|
-
message: "Collection options match",
|
|
308
|
-
expected: canonicalize(expected.options),
|
|
309
|
-
actual: canonicalize(live.options),
|
|
310
|
-
children: []
|
|
311
|
-
}];
|
|
312
|
-
issues.push({
|
|
313
|
-
kind: "type_mismatch",
|
|
314
|
-
table: collName,
|
|
315
|
-
expected: canonicalize(expected.options),
|
|
316
|
-
actual: canonicalize(live.options),
|
|
317
|
-
message: `Collection options mismatch on "${collName}"`
|
|
318
|
-
});
|
|
319
|
-
return [{
|
|
320
|
-
status: "fail",
|
|
321
|
-
kind: "options",
|
|
322
|
-
name: "options",
|
|
323
|
-
contractPath: `storage.collections.${collName}.options`,
|
|
324
|
-
code: "OPTIONS_MISMATCH",
|
|
325
|
-
message: "Collection options mismatch",
|
|
326
|
-
expected: expected.options,
|
|
327
|
-
actual: live.options,
|
|
328
|
-
children: []
|
|
329
|
-
}];
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
//#endregion
|
|
333
11
|
//#region src/core/schema-to-view.ts
|
|
334
12
|
function mongoSchemaToView(schema) {
|
|
335
13
|
const collectionNodes = schema.collections.map((collection) => collectionToSchemaNode(collection.name, collection));
|
|
@@ -490,31 +168,17 @@ var MongoFamilyInstance = class {
|
|
|
490
168
|
}
|
|
491
169
|
async schemaVerify(options) {
|
|
492
170
|
const { driver, contract: rawContract, strict, contractPath, configPath } = options;
|
|
493
|
-
const startTime = Date.now();
|
|
494
171
|
const contract = validateMongoContract(rawContract).contract;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
target: { expected: contract.target },
|
|
506
|
-
schema: {
|
|
507
|
-
issues,
|
|
508
|
-
root,
|
|
509
|
-
counts
|
|
510
|
-
},
|
|
511
|
-
meta: {
|
|
512
|
-
...ifDefined("contractPath", contractPath),
|
|
513
|
-
...ifDefined("configPath", configPath),
|
|
514
|
-
strict
|
|
515
|
-
},
|
|
516
|
-
timings: { total: Date.now() - startTime }
|
|
517
|
-
};
|
|
172
|
+
return verifyMongoSchema({
|
|
173
|
+
contract,
|
|
174
|
+
schema: await introspectSchema(extractDb$1(driver)),
|
|
175
|
+
strict,
|
|
176
|
+
frameworkComponents: options.frameworkComponents,
|
|
177
|
+
context: {
|
|
178
|
+
contractPath,
|
|
179
|
+
...ifDefined("configPath", configPath)
|
|
180
|
+
}
|
|
181
|
+
});
|
|
518
182
|
}
|
|
519
183
|
async sign(options) {
|
|
520
184
|
const { driver, contract: rawContract, contractPath, configPath } = options;
|
|
@@ -627,109 +291,17 @@ var MongoFamilyDescriptor = class {
|
|
|
627
291
|
};
|
|
628
292
|
const mongoFamilyDescriptor = new MongoFamilyDescriptor();
|
|
629
293
|
|
|
630
|
-
//#endregion
|
|
631
|
-
//#region src/core/mongo-emit.ts
|
|
632
|
-
/**
|
|
633
|
-
* Mongo's in-process implementation of the `emit` capability on
|
|
634
|
-
* `TargetMigrationsCapability`. Invoked by the framework's class-flow emit
|
|
635
|
-
* dispatcher in `@prisma-next/cli/lib/migration-emit` — see that module's
|
|
636
|
-
* preamble for the cross-cutting story (when the CLI dispatches here, who
|
|
637
|
-
* attests `migration.json`, why both flows produce byte-identical artifacts,
|
|
638
|
-
* and the relationship to the self-emitting `Migration.run` shebang path).
|
|
639
|
-
*
|
|
640
|
-
* Mongo-specific responsibilities of this helper:
|
|
641
|
-
*
|
|
642
|
-
* - Accept two authoring shapes for `migration.ts`'s default export, both
|
|
643
|
-
* adhering to the `MigrationPlan` interface:
|
|
644
|
-
*
|
|
645
|
-
* 1. Class subclass (canonical, scaffolded form):
|
|
646
|
-
* class M extends Migration {
|
|
647
|
-
* override get operations() { return [...]; }
|
|
648
|
-
* override describe() { return { from, to }; }
|
|
649
|
-
* }
|
|
650
|
-
* export default M;
|
|
651
|
-
* Migration.run(import.meta.url, M);
|
|
652
|
-
*
|
|
653
|
-
* 2. Factory function returning a MigrationPlan-shaped object:
|
|
654
|
-
* export default () => ({
|
|
655
|
-
* targetId: 'mongo',
|
|
656
|
-
* destination: { storageHash: '...' },
|
|
657
|
-
* operations: [createCollection("users")],
|
|
658
|
-
* });
|
|
659
|
-
*
|
|
660
|
-
* Only the class form is scaffolded; the factory form is supported for
|
|
661
|
-
* authors who prefer it.
|
|
662
|
-
* - Dynamic-import the file so structured errors thrown during evaluation
|
|
663
|
-
* (notably `placeholder(...)`) surface to the CLI as real exceptions.
|
|
664
|
-
* - Dispatch on the default export's shape and validate the factory return
|
|
665
|
-
* is `MigrationPlan`-shaped.
|
|
666
|
-
* - Persist `ops.json` via the framework I/O helper and return the
|
|
667
|
-
* operations to the caller (which performs attestation).
|
|
668
|
-
*/
|
|
669
|
-
const MIGRATION_TS_FILE = "migration.ts";
|
|
670
|
-
/**
|
|
671
|
-
* Implementation of `TargetMigrationsCapability.emit` for Mongo.
|
|
672
|
-
*
|
|
673
|
-
* Loads `<dir>/migration.ts` and dispatches on the default export's shape:
|
|
674
|
-
* if it is a `Migration` subclass, instantiates it; otherwise invokes it as a
|
|
675
|
-
* factory function (sync or async) and validates the returned value is
|
|
676
|
-
* `MigrationPlan`-shaped. In both cases reads `.operations` to produce the
|
|
677
|
-
* operations list, writes `ops.json`, and returns the operations for the
|
|
678
|
-
* framework helper to render. Attestation of `migration.json` is the
|
|
679
|
-
* caller's responsibility: the framework's `emitMigration` helper calls
|
|
680
|
-
* `attestMigration` after this function returns. This capability MUST NOT
|
|
681
|
-
* call `attestMigration` itself, to avoid double-attestation when the helper
|
|
682
|
-
* drives emit.
|
|
683
|
-
*/
|
|
684
|
-
async function mongoEmit(options) {
|
|
685
|
-
const filePath = join(options.dir, MIGRATION_TS_FILE);
|
|
686
|
-
try {
|
|
687
|
-
await stat(filePath);
|
|
688
|
-
} catch {
|
|
689
|
-
throw errorMigrationFileMissing(options.dir);
|
|
690
|
-
}
|
|
691
|
-
const MigrationExport = (await import(pathToFileURL(filePath).href)).default;
|
|
692
|
-
if (typeof MigrationExport !== "function") throw errorMigrationInvalidDefaultExport(options.dir, `default export of type ${typeof MigrationExport}`);
|
|
693
|
-
let plan;
|
|
694
|
-
if (MigrationExport.prototype instanceof Migration) plan = new MigrationExport();
|
|
695
|
-
else {
|
|
696
|
-
let factoryResult;
|
|
697
|
-
try {
|
|
698
|
-
factoryResult = await MigrationExport();
|
|
699
|
-
} catch (error) {
|
|
700
|
-
if (error instanceof TypeError && /cannot be invoked without 'new'/i.test(error.message)) throw errorMigrationInvalidDefaultExport(options.dir, "a default export that does not extend Migration (from @prisma-next/migration-tools/migration)");
|
|
701
|
-
throw error;
|
|
702
|
-
}
|
|
703
|
-
if (typeof factoryResult !== "object" || factoryResult === null || !("operations" in factoryResult)) throw errorMigrationInvalidDefaultExport(options.dir, `factory must return a MigrationPlan-shaped object; got ${describeValue(factoryResult)}`);
|
|
704
|
-
plan = factoryResult;
|
|
705
|
-
}
|
|
706
|
-
const operations = plan.operations;
|
|
707
|
-
if (!Array.isArray(operations)) throw errorMigrationPlanNotArray(options.dir, describeValue(operations));
|
|
708
|
-
await writeMigrationOps(options.dir, operations);
|
|
709
|
-
return operations;
|
|
710
|
-
}
|
|
711
|
-
function describeValue(value) {
|
|
712
|
-
if (value === null) return "null";
|
|
713
|
-
return `a value of type ${typeof value}`;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
294
|
//#endregion
|
|
717
295
|
//#region src/core/mongo-target-descriptor.ts
|
|
718
296
|
/**
|
|
719
|
-
* The Mongo target uses the **class-flow** migration authoring strategy.
|
|
720
|
-
*
|
|
721
297
|
* `migration.ts` default-exports a `Migration` subclass whose `operations`
|
|
722
298
|
* getter returns the ordered list of operations and whose `describe()`
|
|
723
299
|
* returns the manifest identity metadata. `MongoMigrationPlanner.plan()`
|
|
724
300
|
* returns a `MigrationPlanWithAuthoringSurface` that knows how to render
|
|
725
301
|
* itself back to such a file; `MongoMigrationPlanner.emptyMigration()`
|
|
726
|
-
* returns the same shape for `migration new`.
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* The descriptor-flow hooks (`planWithDescriptors`, `resolveDescriptors`,
|
|
730
|
-
* `renderDescriptorTypeScript`) are intentionally omitted — the CLI's
|
|
731
|
-
* `migrationStrategy` selector routes Mongo down the class-flow path by
|
|
732
|
-
* observing their absence.
|
|
302
|
+
* returns the same shape for `migration new`. Users run the scaffolded
|
|
303
|
+
* `migration.ts` directly (via `node migration.ts`) to self-emit
|
|
304
|
+
* `ops.json` and attest the `migrationId`.
|
|
733
305
|
*/
|
|
734
306
|
const mongoTargetDescriptor = {
|
|
735
307
|
...mongoTargetDescriptorMeta,
|
|
@@ -737,18 +309,20 @@ const mongoTargetDescriptor = {
|
|
|
737
309
|
createPlanner(_family) {
|
|
738
310
|
return new MongoMigrationPlanner();
|
|
739
311
|
},
|
|
740
|
-
createRunner(
|
|
312
|
+
createRunner(family) {
|
|
741
313
|
let cachedDeps;
|
|
742
314
|
return { async execute(options) {
|
|
743
|
-
cachedDeps ??= createMongoRunnerDeps(options.driver, MongoDriverImpl.fromDb(extractDb(options.driver)));
|
|
315
|
+
cachedDeps ??= createMongoRunnerDeps(options.driver, MongoDriverImpl.fromDb(extractDb(options.driver)), family);
|
|
744
316
|
const { driver: _, ...runnerOptions } = options;
|
|
745
|
-
return new MongoMigrationRunner(cachedDeps).execute(
|
|
317
|
+
return new MongoMigrationRunner(cachedDeps).execute({
|
|
318
|
+
...runnerOptions,
|
|
319
|
+
destinationContract: runnerOptions.destinationContract
|
|
320
|
+
});
|
|
746
321
|
} };
|
|
747
322
|
},
|
|
748
323
|
contractToSchema(contract) {
|
|
749
324
|
return contractToMongoSchemaIR(contract);
|
|
750
|
-
}
|
|
751
|
-
emit: mongoEmit
|
|
325
|
+
}
|
|
752
326
|
},
|
|
753
327
|
create() {
|
|
754
328
|
return {
|