@prisma-next/family-mongo 0.5.0-dev.4 → 0.5.0-dev.6

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 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
 
@@ -134,6 +135,7 @@ Run `node migration.ts` to produce `ops.json` and `migration.json`. Use `--dry-r
134
135
  - `src/exports/control.ts`: control-plane entrypoint
135
136
  - `src/exports/migration.ts`: migration authoring entrypoint
136
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`
137
139
 
138
140
  ## Dependencies
139
141
 
@@ -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> {
@@ -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":";;;;;UAgCiB,0BAAA,SACP,+BAA+B,gBACrC,kBAAkB;2CACqB;AAH3C;AACyC,iBAkSzB,yBAAA,CAlSyB,aAAA,EAkSgB,YAlShB,CAAA,EAkS+B,0BAlS/B;;;cCV5B,uBAAuB,iCAAiC;;;;;;ADSrE;;;;;;;AAmSgB,cE3SH,qBF2S4B,EE3SL,0BF2SoC,CAAA,OAAA,EAAA,OAA0B,EExShG,0BFwSgG,CAAA"}
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,329 +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, VERIFY_CODE_SCHEMA_FAILURE, VERIFY_CODE_TARGET_MISMATCH } from "@prisma-next/framework-components/control";
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
10
 
11
- //#region src/core/schema-diff.ts
12
- function diffMongoSchemas(live, expected, strict) {
13
- const issues = [];
14
- const collectionChildren = [];
15
- let pass = 0;
16
- let warn = 0;
17
- let fail = 0;
18
- const allNames = new Set([...live.collectionNames, ...expected.collectionNames]);
19
- for (const name of [...allNames].sort()) {
20
- const liveColl = live.collection(name);
21
- const expectedColl = expected.collection(name);
22
- if (!liveColl && expectedColl) {
23
- issues.push({
24
- kind: "missing_table",
25
- table: name,
26
- message: `Collection "${name}" is missing from the database`
27
- });
28
- collectionChildren.push({
29
- status: "fail",
30
- kind: "collection",
31
- name,
32
- contractPath: `storage.collections.${name}`,
33
- code: "MISSING_COLLECTION",
34
- message: `Collection "${name}" is missing`,
35
- expected: name,
36
- actual: null,
37
- children: []
38
- });
39
- fail++;
40
- continue;
41
- }
42
- if (liveColl && !expectedColl) {
43
- const status = strict ? "fail" : "warn";
44
- issues.push({
45
- kind: "extra_table",
46
- table: name,
47
- message: `Extra collection "${name}" exists in the database but not in the contract`
48
- });
49
- collectionChildren.push({
50
- status,
51
- kind: "collection",
52
- name,
53
- contractPath: `storage.collections.${name}`,
54
- code: "EXTRA_COLLECTION",
55
- message: `Extra collection "${name}" found`,
56
- expected: null,
57
- actual: name,
58
- children: []
59
- });
60
- if (status === "fail") fail++;
61
- else warn++;
62
- continue;
63
- }
64
- const lc = liveColl;
65
- const ec = expectedColl;
66
- const indexChildren = diffIndexes(name, lc, ec, strict, issues);
67
- const validatorChildren = diffValidator(name, lc, ec, strict, issues);
68
- const optionsChildren = diffOptions(name, lc, ec, strict, issues);
69
- const children = [
70
- ...indexChildren,
71
- ...validatorChildren,
72
- ...optionsChildren
73
- ];
74
- const worstStatus = children.reduce((s, c) => c.status === "fail" ? "fail" : c.status === "warn" && s !== "fail" ? "warn" : s, "pass");
75
- for (const c of children) if (c.status === "pass") pass++;
76
- else if (c.status === "warn") warn++;
77
- else fail++;
78
- if (children.length === 0) pass++;
79
- collectionChildren.push({
80
- status: worstStatus,
81
- kind: "collection",
82
- name,
83
- contractPath: `storage.collections.${name}`,
84
- code: worstStatus === "pass" ? "MATCH" : "DRIFT",
85
- message: worstStatus === "pass" ? `Collection "${name}" matches` : `Collection "${name}" has drift`,
86
- expected: name,
87
- actual: name,
88
- children
89
- });
90
- }
91
- const rootStatus = fail > 0 ? "fail" : warn > 0 ? "warn" : "pass";
92
- const totalNodes = pass + warn + fail + collectionChildren.length;
93
- return {
94
- root: {
95
- status: rootStatus,
96
- kind: "root",
97
- name: "mongo-schema",
98
- contractPath: "storage",
99
- code: rootStatus === "pass" ? "MATCH" : "DRIFT",
100
- message: rootStatus === "pass" ? "Schema matches" : "Schema has drift",
101
- expected: null,
102
- actual: null,
103
- children: collectionChildren
104
- },
105
- issues,
106
- counts: {
107
- pass,
108
- warn,
109
- fail,
110
- totalNodes
111
- }
112
- };
113
- }
114
- function buildIndexLookupKey(index) {
115
- const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(",");
116
- const opts = [
117
- index.unique ? "unique" : "",
118
- index.sparse ? "sparse" : "",
119
- index.expireAfterSeconds != null ? `ttl:${index.expireAfterSeconds}` : "",
120
- index.partialFilterExpression ? `pfe:${canonicalize(index.partialFilterExpression)}` : "",
121
- index.wildcardProjection ? `wp:${canonicalize(index.wildcardProjection)}` : "",
122
- index.collation ? `col:${canonicalize(index.collation)}` : "",
123
- index.weights ? `wt:${canonicalize(index.weights)}` : "",
124
- index.default_language ? `dl:${index.default_language}` : "",
125
- index.language_override ? `lo:${index.language_override}` : ""
126
- ].filter(Boolean).join(";");
127
- return opts ? `${keys}|${opts}` : keys;
128
- }
129
- function formatIndexName(index) {
130
- return index.keys.map((k) => `${k.field}:${k.direction}`).join(", ");
131
- }
132
- function diffIndexes(collName, live, expected, strict, issues) {
133
- const nodes = [];
134
- const liveLookup = /* @__PURE__ */ new Map();
135
- for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);
136
- const expectedLookup = /* @__PURE__ */ new Map();
137
- for (const idx of expected.indexes) expectedLookup.set(buildIndexLookupKey(idx), idx);
138
- for (const [key, idx] of expectedLookup) if (liveLookup.has(key)) nodes.push({
139
- status: "pass",
140
- kind: "index",
141
- name: formatIndexName(idx),
142
- contractPath: `storage.collections.${collName}.indexes`,
143
- code: "MATCH",
144
- message: `Index ${formatIndexName(idx)} matches`,
145
- expected: key,
146
- actual: key,
147
- children: []
148
- });
149
- else {
150
- issues.push({
151
- kind: "index_mismatch",
152
- table: collName,
153
- indexOrConstraint: formatIndexName(idx),
154
- message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`
155
- });
156
- nodes.push({
157
- status: "fail",
158
- kind: "index",
159
- name: formatIndexName(idx),
160
- contractPath: `storage.collections.${collName}.indexes`,
161
- code: "MISSING_INDEX",
162
- message: `Index ${formatIndexName(idx)} missing`,
163
- expected: key,
164
- actual: null,
165
- children: []
166
- });
167
- }
168
- for (const [key, idx] of liveLookup) if (!expectedLookup.has(key)) {
169
- const status = strict ? "fail" : "warn";
170
- issues.push({
171
- kind: "extra_index",
172
- table: collName,
173
- indexOrConstraint: formatIndexName(idx),
174
- message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`
175
- });
176
- nodes.push({
177
- status,
178
- kind: "index",
179
- name: formatIndexName(idx),
180
- contractPath: `storage.collections.${collName}.indexes`,
181
- code: "EXTRA_INDEX",
182
- message: `Extra index ${formatIndexName(idx)}`,
183
- expected: null,
184
- actual: key,
185
- children: []
186
- });
187
- }
188
- return nodes;
189
- }
190
- function diffValidator(collName, live, expected, strict, issues) {
191
- if (!live.validator && !expected.validator) return [];
192
- if (expected.validator && !live.validator) {
193
- issues.push({
194
- kind: "type_missing",
195
- table: collName,
196
- message: `Validator missing on collection "${collName}"`
197
- });
198
- return [{
199
- status: "fail",
200
- kind: "validator",
201
- name: "validator",
202
- contractPath: `storage.collections.${collName}.validator`,
203
- code: "MISSING_VALIDATOR",
204
- message: "Validator missing",
205
- expected: canonicalize(expected.validator.jsonSchema),
206
- actual: null,
207
- children: []
208
- }];
209
- }
210
- if (!expected.validator && live.validator) {
211
- const status = strict ? "fail" : "warn";
212
- issues.push({
213
- kind: "extra_validator",
214
- table: collName,
215
- message: `Extra validator on collection "${collName}"`
216
- });
217
- return [{
218
- status,
219
- kind: "validator",
220
- name: "validator",
221
- contractPath: `storage.collections.${collName}.validator`,
222
- code: "EXTRA_VALIDATOR",
223
- message: "Extra validator found",
224
- expected: null,
225
- actual: canonicalize(live.validator.jsonSchema),
226
- children: []
227
- }];
228
- }
229
- const liveVal = live.validator;
230
- const expectedVal = expected.validator;
231
- const liveSchema = canonicalize(liveVal.jsonSchema);
232
- const expectedSchema = canonicalize(expectedVal.jsonSchema);
233
- if (liveSchema !== expectedSchema || liveVal.validationLevel !== expectedVal.validationLevel || liveVal.validationAction !== expectedVal.validationAction) {
234
- issues.push({
235
- kind: "type_mismatch",
236
- table: collName,
237
- expected: expectedSchema,
238
- actual: liveSchema,
239
- message: `Validator mismatch on collection "${collName}"`
240
- });
241
- return [{
242
- status: "fail",
243
- kind: "validator",
244
- name: "validator",
245
- contractPath: `storage.collections.${collName}.validator`,
246
- code: "VALIDATOR_MISMATCH",
247
- message: "Validator mismatch",
248
- expected: {
249
- jsonSchema: expectedVal.jsonSchema,
250
- validationLevel: expectedVal.validationLevel,
251
- validationAction: expectedVal.validationAction
252
- },
253
- actual: {
254
- jsonSchema: liveVal.jsonSchema,
255
- validationLevel: liveVal.validationLevel,
256
- validationAction: liveVal.validationAction
257
- },
258
- children: []
259
- }];
260
- }
261
- return [{
262
- status: "pass",
263
- kind: "validator",
264
- name: "validator",
265
- contractPath: `storage.collections.${collName}.validator`,
266
- code: "MATCH",
267
- message: "Validator matches",
268
- expected: expectedSchema,
269
- actual: liveSchema,
270
- children: []
271
- }];
272
- }
273
- function diffOptions(collName, live, expected, strict, issues) {
274
- if (!live.options && !expected.options) return [];
275
- if (!expected.options && live.options) {
276
- const status = strict ? "fail" : "warn";
277
- issues.push({
278
- kind: "type_mismatch",
279
- table: collName,
280
- actual: canonicalize(live.options),
281
- message: `Extra collection options on "${collName}"`
282
- });
283
- return [{
284
- status,
285
- kind: "options",
286
- name: "options",
287
- contractPath: `storage.collections.${collName}.options`,
288
- code: "EXTRA_OPTIONS",
289
- message: "Extra collection options found",
290
- expected: null,
291
- actual: live.options,
292
- children: []
293
- }];
294
- }
295
- if (deepEqual(live.options, expected.options)) return [{
296
- status: "pass",
297
- kind: "options",
298
- name: "options",
299
- contractPath: `storage.collections.${collName}.options`,
300
- code: "MATCH",
301
- message: "Collection options match",
302
- expected: canonicalize(expected.options),
303
- actual: canonicalize(live.options),
304
- children: []
305
- }];
306
- issues.push({
307
- kind: "type_mismatch",
308
- table: collName,
309
- expected: canonicalize(expected.options),
310
- actual: canonicalize(live.options),
311
- message: `Collection options mismatch on "${collName}"`
312
- });
313
- return [{
314
- status: "fail",
315
- kind: "options",
316
- name: "options",
317
- contractPath: `storage.collections.${collName}.options`,
318
- code: "OPTIONS_MISMATCH",
319
- message: "Collection options mismatch",
320
- expected: expected.options,
321
- actual: live.options,
322
- children: []
323
- }];
324
- }
325
-
326
- //#endregion
327
11
  //#region src/core/schema-to-view.ts
328
12
  function mongoSchemaToView(schema) {
329
13
  const collectionNodes = schema.collections.map((collection) => collectionToSchemaNode(collection.name, collection));
@@ -484,31 +168,17 @@ var MongoFamilyInstance = class {
484
168
  }
485
169
  async schemaVerify(options) {
486
170
  const { driver, contract: rawContract, strict, contractPath, configPath } = options;
487
- const startTime = Date.now();
488
171
  const contract = validateMongoContract(rawContract).contract;
489
- const { root, issues, counts } = diffMongoSchemas(await introspectSchema(extractDb$1(driver)), contractToMongoSchemaIR(contract), strict);
490
- const ok = counts.fail === 0;
491
- return {
492
- ok,
493
- ...ifDefined("code", ok ? void 0 : VERIFY_CODE_SCHEMA_FAILURE),
494
- summary: ok ? "Schema matches contract" : `Schema verification found ${counts.fail} issue(s)`,
495
- contract: {
496
- storageHash: contract.storage.storageHash,
497
- ...ifDefined("profileHash", contract.profileHash)
498
- },
499
- target: { expected: contract.target },
500
- schema: {
501
- issues,
502
- root,
503
- counts
504
- },
505
- meta: {
506
- ...ifDefined("contractPath", contractPath),
507
- ...ifDefined("configPath", configPath),
508
- strict
509
- },
510
- timings: { total: Date.now() - startTime }
511
- };
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
+ });
512
182
  }
513
183
  async sign(options) {
514
184
  const { driver, contract: rawContract, contractPath, configPath } = options;
@@ -639,12 +309,15 @@ const mongoTargetDescriptor = {
639
309
  createPlanner(_family) {
640
310
  return new MongoMigrationPlanner();
641
311
  },
642
- createRunner(_family) {
312
+ createRunner(family) {
643
313
  let cachedDeps;
644
314
  return { async execute(options) {
645
- cachedDeps ??= createMongoRunnerDeps(options.driver, MongoDriverImpl.fromDb(extractDb(options.driver)));
315
+ cachedDeps ??= createMongoRunnerDeps(options.driver, MongoDriverImpl.fromDb(extractDb(options.driver)), family);
646
316
  const { driver: _, ...runnerOptions } = options;
647
- return new MongoMigrationRunner(cachedDeps).execute(runnerOptions);
317
+ return new MongoMigrationRunner(cachedDeps).execute({
318
+ ...runnerOptions,
319
+ destinationContract: runnerOptions.destinationContract
320
+ });
648
321
  } };
649
322
  },
650
323
  contractToSchema(contract) {
@@ -1 +1 @@
1
- {"version":3,"file":"control.mjs","names":["issues: SchemaIssue[]","collectionChildren: SchemaVerificationNode[]","nodes: SchemaVerificationNode[]","children: SchemaTreeNode[]","options: string[]","validatorChildren: SchemaTreeNode[]","optLabels: string[]","extractDb","previousHashes: { storageHash?: string; profileHash?: string } | undefined","summary: string","mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>","mongoTargetDescriptor: MigratableTargetDescriptor<\n 'mongo',\n 'mongo',\n MongoControlFamilyInstance\n>","cachedDeps: MongoRunnerDependencies | undefined"],"sources":["../src/core/schema-diff.ts","../src/core/schema-to-view.ts","../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/mongo-target-descriptor.ts"],"sourcesContent":["import type {\n SchemaIssue,\n SchemaVerificationNode,\n} from '@prisma-next/framework-components/control';\nimport type {\n MongoSchemaCollection,\n MongoSchemaIndex,\n MongoSchemaIR,\n} from '@prisma-next/mongo-schema-ir';\nimport { canonicalize, deepEqual } from '@prisma-next/mongo-schema-ir';\n\nexport function diffMongoSchemas(\n live: MongoSchemaIR,\n expected: MongoSchemaIR,\n strict: boolean,\n): {\n root: SchemaVerificationNode;\n issues: SchemaIssue[];\n counts: { pass: number; warn: number; fail: number; totalNodes: number };\n} {\n const issues: SchemaIssue[] = [];\n const collectionChildren: SchemaVerificationNode[] = [];\n let pass = 0;\n let warn = 0;\n let fail = 0;\n\n const allNames = new Set([...live.collectionNames, ...expected.collectionNames]);\n\n for (const name of [...allNames].sort()) {\n const liveColl = live.collection(name);\n const expectedColl = expected.collection(name);\n\n if (!liveColl && expectedColl) {\n issues.push({\n kind: 'missing_table',\n table: name,\n message: `Collection \"${name}\" is missing from the database`,\n });\n collectionChildren.push({\n status: 'fail',\n kind: 'collection',\n name,\n contractPath: `storage.collections.${name}`,\n code: 'MISSING_COLLECTION',\n message: `Collection \"${name}\" is missing`,\n expected: name,\n actual: null,\n children: [],\n });\n fail++;\n continue;\n }\n\n if (liveColl && !expectedColl) {\n const status = strict ? 'fail' : 'warn';\n issues.push({\n kind: 'extra_table',\n table: name,\n message: `Extra collection \"${name}\" exists in the database but not in the contract`,\n });\n collectionChildren.push({\n status,\n kind: 'collection',\n name,\n contractPath: `storage.collections.${name}`,\n code: 'EXTRA_COLLECTION',\n message: `Extra collection \"${name}\" found`,\n expected: null,\n actual: name,\n children: [],\n });\n if (status === 'fail') fail++;\n else warn++;\n continue;\n }\n\n const lc = liveColl as MongoSchemaCollection;\n const ec = expectedColl as MongoSchemaCollection;\n const indexChildren = diffIndexes(name, lc, ec, strict, issues);\n const validatorChildren = diffValidator(name, lc, ec, strict, issues);\n const optionsChildren = diffOptions(name, lc, ec, strict, issues);\n const children = [...indexChildren, ...validatorChildren, ...optionsChildren];\n\n const worstStatus = children.reduce<'pass' | 'warn' | 'fail'>(\n (s, c) => (c.status === 'fail' ? 'fail' : c.status === 'warn' && s !== 'fail' ? 'warn' : s),\n 'pass',\n );\n\n for (const c of children) {\n if (c.status === 'pass') pass++;\n else if (c.status === 'warn') warn++;\n else fail++;\n }\n\n if (children.length === 0) {\n pass++;\n }\n\n collectionChildren.push({\n status: worstStatus,\n kind: 'collection',\n name,\n contractPath: `storage.collections.${name}`,\n code: worstStatus === 'pass' ? 'MATCH' : 'DRIFT',\n message:\n worstStatus === 'pass' ? `Collection \"${name}\" matches` : `Collection \"${name}\" has drift`,\n expected: name,\n actual: name,\n children,\n });\n }\n\n const rootStatus = fail > 0 ? 'fail' : warn > 0 ? 'warn' : 'pass';\n const totalNodes = pass + warn + fail + collectionChildren.length;\n\n const root: SchemaVerificationNode = {\n status: rootStatus,\n kind: 'root',\n name: 'mongo-schema',\n contractPath: 'storage',\n code: rootStatus === 'pass' ? 'MATCH' : 'DRIFT',\n message: rootStatus === 'pass' ? 'Schema matches' : 'Schema has drift',\n expected: null,\n actual: null,\n children: collectionChildren,\n };\n\n return { root, issues, counts: { pass, warn, fail, totalNodes } };\n}\n\nfunction buildIndexLookupKey(index: MongoSchemaIndex): string {\n const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(',');\n const opts = [\n index.unique ? 'unique' : '',\n index.sparse ? 'sparse' : '',\n index.expireAfterSeconds != null ? `ttl:${index.expireAfterSeconds}` : '',\n index.partialFilterExpression ? `pfe:${canonicalize(index.partialFilterExpression)}` : '',\n index.wildcardProjection ? `wp:${canonicalize(index.wildcardProjection)}` : '',\n index.collation ? `col:${canonicalize(index.collation)}` : '',\n index.weights ? `wt:${canonicalize(index.weights)}` : '',\n index.default_language ? `dl:${index.default_language}` : '',\n index.language_override ? `lo:${index.language_override}` : '',\n ]\n .filter(Boolean)\n .join(';');\n return opts ? `${keys}|${opts}` : keys;\n}\n\nfunction formatIndexName(index: MongoSchemaIndex): string {\n return index.keys.map((k) => `${k.field}:${k.direction}`).join(', ');\n}\n\nfunction diffIndexes(\n collName: string,\n live: MongoSchemaCollection,\n expected: MongoSchemaCollection,\n strict: boolean,\n issues: SchemaIssue[],\n): SchemaVerificationNode[] {\n const nodes: SchemaVerificationNode[] = [];\n const liveLookup = new Map<string, MongoSchemaIndex>();\n for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);\n\n const expectedLookup = new Map<string, MongoSchemaIndex>();\n for (const idx of expected.indexes) expectedLookup.set(buildIndexLookupKey(idx), idx);\n\n for (const [key, idx] of expectedLookup) {\n if (liveLookup.has(key)) {\n nodes.push({\n status: 'pass',\n kind: 'index',\n name: formatIndexName(idx),\n contractPath: `storage.collections.${collName}.indexes`,\n code: 'MATCH',\n message: `Index ${formatIndexName(idx)} matches`,\n expected: key,\n actual: key,\n children: [],\n });\n } else {\n issues.push({\n kind: 'index_mismatch',\n table: collName,\n indexOrConstraint: formatIndexName(idx),\n message: `Index ${formatIndexName(idx)} missing on collection \"${collName}\"`,\n });\n nodes.push({\n status: 'fail',\n kind: 'index',\n name: formatIndexName(idx),\n contractPath: `storage.collections.${collName}.indexes`,\n code: 'MISSING_INDEX',\n message: `Index ${formatIndexName(idx)} missing`,\n expected: key,\n actual: null,\n children: [],\n });\n }\n }\n\n for (const [key, idx] of liveLookup) {\n if (!expectedLookup.has(key)) {\n const status = strict ? 'fail' : 'warn';\n issues.push({\n kind: 'extra_index',\n table: collName,\n indexOrConstraint: formatIndexName(idx),\n message: `Extra index ${formatIndexName(idx)} on collection \"${collName}\"`,\n });\n nodes.push({\n status,\n kind: 'index',\n name: formatIndexName(idx),\n contractPath: `storage.collections.${collName}.indexes`,\n code: 'EXTRA_INDEX',\n message: `Extra index ${formatIndexName(idx)}`,\n expected: null,\n actual: key,\n children: [],\n });\n }\n }\n\n return nodes;\n}\n\nfunction diffValidator(\n collName: string,\n live: MongoSchemaCollection,\n expected: MongoSchemaCollection,\n strict: boolean,\n issues: SchemaIssue[],\n): SchemaVerificationNode[] {\n if (!live.validator && !expected.validator) return [];\n\n if (expected.validator && !live.validator) {\n issues.push({\n kind: 'type_missing',\n table: collName,\n message: `Validator missing on collection \"${collName}\"`,\n });\n return [\n {\n status: 'fail',\n kind: 'validator',\n name: 'validator',\n contractPath: `storage.collections.${collName}.validator`,\n code: 'MISSING_VALIDATOR',\n message: 'Validator missing',\n expected: canonicalize(expected.validator.jsonSchema),\n actual: null,\n children: [],\n },\n ];\n }\n\n if (!expected.validator && live.validator) {\n const status = strict ? 'fail' : 'warn';\n issues.push({\n kind: 'extra_validator',\n table: collName,\n message: `Extra validator on collection \"${collName}\"`,\n });\n return [\n {\n status,\n kind: 'validator',\n name: 'validator',\n contractPath: `storage.collections.${collName}.validator`,\n code: 'EXTRA_VALIDATOR',\n message: 'Extra validator found',\n expected: null,\n actual: canonicalize(live.validator.jsonSchema),\n children: [],\n },\n ];\n }\n\n const liveVal = live.validator as NonNullable<typeof live.validator>;\n const expectedVal = expected.validator as NonNullable<typeof expected.validator>;\n const liveSchema = canonicalize(liveVal.jsonSchema);\n const expectedSchema = canonicalize(expectedVal.jsonSchema);\n\n if (\n liveSchema !== expectedSchema ||\n liveVal.validationLevel !== expectedVal.validationLevel ||\n liveVal.validationAction !== expectedVal.validationAction\n ) {\n issues.push({\n kind: 'type_mismatch',\n table: collName,\n expected: expectedSchema,\n actual: liveSchema,\n message: `Validator mismatch on collection \"${collName}\"`,\n });\n return [\n {\n status: 'fail',\n kind: 'validator',\n name: 'validator',\n contractPath: `storage.collections.${collName}.validator`,\n code: 'VALIDATOR_MISMATCH',\n message: 'Validator mismatch',\n expected: {\n jsonSchema: expectedVal.jsonSchema,\n validationLevel: expectedVal.validationLevel,\n validationAction: expectedVal.validationAction,\n },\n actual: {\n jsonSchema: liveVal.jsonSchema,\n validationLevel: liveVal.validationLevel,\n validationAction: liveVal.validationAction,\n },\n children: [],\n },\n ];\n }\n\n return [\n {\n status: 'pass',\n kind: 'validator',\n name: 'validator',\n contractPath: `storage.collections.${collName}.validator`,\n code: 'MATCH',\n message: 'Validator matches',\n expected: expectedSchema,\n actual: liveSchema,\n children: [],\n },\n ];\n}\n\nfunction diffOptions(\n collName: string,\n live: MongoSchemaCollection,\n expected: MongoSchemaCollection,\n strict: boolean,\n issues: SchemaIssue[],\n): SchemaVerificationNode[] {\n if (!live.options && !expected.options) return [];\n\n if (!expected.options && live.options) {\n const status = strict ? 'fail' : 'warn';\n issues.push({\n kind: 'type_mismatch',\n table: collName,\n actual: canonicalize(live.options),\n message: `Extra collection options on \"${collName}\"`,\n });\n return [\n {\n status,\n kind: 'options',\n name: 'options',\n contractPath: `storage.collections.${collName}.options`,\n code: 'EXTRA_OPTIONS',\n message: 'Extra collection options found',\n expected: null,\n actual: live.options,\n children: [],\n },\n ];\n }\n\n if (deepEqual(live.options, expected.options)) {\n return [\n {\n status: 'pass',\n kind: 'options',\n name: 'options',\n contractPath: `storage.collections.${collName}.options`,\n code: 'MATCH',\n message: 'Collection options match',\n expected: canonicalize(expected.options),\n actual: canonicalize(live.options),\n children: [],\n },\n ];\n }\n\n issues.push({\n kind: 'type_mismatch',\n table: collName,\n expected: canonicalize(expected.options),\n actual: canonicalize(live.options),\n message: `Collection options mismatch on \"${collName}\"`,\n });\n return [\n {\n status: 'fail',\n kind: 'options',\n name: 'options',\n contractPath: `storage.collections.${collName}.options`,\n code: 'OPTIONS_MISMATCH',\n message: 'Collection options mismatch',\n expected: expected.options,\n actual: live.options,\n children: [],\n },\n ];\n}\n","import type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport { SchemaTreeNode } from '@prisma-next/framework-components/control';\nimport type { MongoSchemaCollection, MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport function mongoSchemaToView(schema: MongoSchemaIR): CoreSchemaView {\n const collectionNodes = schema.collections.map((collection) =>\n collectionToSchemaNode(collection.name, collection),\n );\n\n return {\n root: new SchemaTreeNode({\n kind: 'root',\n id: 'mongo-schema',\n label: 'database',\n ...ifDefined('children', collectionNodes.length > 0 ? collectionNodes : undefined),\n }),\n };\n}\n\nfunction collectionToSchemaNode(name: string, collection: MongoSchemaCollection): SchemaTreeNode {\n const children: SchemaTreeNode[] = [];\n\n for (const index of collection.indexes) {\n const keysSummary = index.keys\n .map((k) => {\n if (k.direction === 1) return k.field;\n if (k.direction === -1) return `${k.field} desc`;\n return `${k.field} ${k.direction}`;\n })\n .join(', ');\n const prefix = index.unique ? 'unique index' : 'index';\n const options: string[] = [];\n if (index.sparse) options.push('sparse');\n if (index.expireAfterSeconds != null) options.push(`ttl: ${index.expireAfterSeconds}s`);\n if (index.partialFilterExpression) options.push('partial');\n const optsSuffix = options.length > 0 ? ` (${options.join(', ')})` : '';\n\n children.push(\n new SchemaTreeNode({\n kind: 'index',\n id: `index-${name}-${index.keys.map((k) => `${k.field}_${k.direction}`).join('_')}`,\n label: `${prefix} (${keysSummary})${optsSuffix}`,\n meta: {\n keys: index.keys,\n unique: index.unique,\n ...ifDefined('sparse', index.sparse || undefined),\n ...ifDefined('expireAfterSeconds', index.expireAfterSeconds ?? undefined),\n ...ifDefined('partialFilterExpression', index.partialFilterExpression ?? undefined),\n },\n }),\n );\n }\n\n if (collection.validator) {\n const validatorChildren: SchemaTreeNode[] = [];\n const jsonSchema = collection.validator.jsonSchema as Record<string, unknown>;\n const properties = jsonSchema['properties'] as\n | Record<string, Record<string, unknown>>\n | undefined;\n const required = new Set((jsonSchema['required'] as string[] | undefined) ?? []);\n\n if (properties) {\n for (const [propName, propDef] of Object.entries(properties)) {\n const bsonType = (propDef['bsonType'] as string) ?? 'unknown';\n const suffix = required.has(propName) ? ' (required)' : '';\n validatorChildren.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `field-${name}-${propName}`,\n label: `${propName}: ${bsonType}${suffix}`,\n }),\n );\n }\n }\n\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `validator-${name}`,\n label: `validator (level: ${collection.validator.validationLevel}, action: ${collection.validator.validationAction})`,\n meta: {\n validationLevel: collection.validator.validationLevel,\n validationAction: collection.validator.validationAction,\n jsonSchema: collection.validator.jsonSchema,\n },\n ...ifDefined('children', validatorChildren.length > 0 ? validatorChildren : undefined),\n }),\n );\n }\n\n if (collection.options) {\n const opts = collection.options;\n const optLabels: string[] = [];\n if (opts.capped) optLabels.push('capped');\n if (opts.timeseries) optLabels.push('timeseries');\n if (opts.collation) optLabels.push('collation');\n if (opts.changeStreamPreAndPostImages) optLabels.push('changeStreamPreAndPostImages');\n if (opts.clusteredIndex) optLabels.push('clusteredIndex');\n\n if (optLabels.length > 0) {\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `options-${name}`,\n label: `options (${optLabels.join(', ')})`,\n meta: {\n ...ifDefined('capped', opts.capped ?? undefined),\n ...ifDefined('timeseries', opts.timeseries ?? undefined),\n ...ifDefined('collation', opts.collation ?? undefined),\n ...ifDefined(\n 'changeStreamPreAndPostImages',\n opts.changeStreamPreAndPostImages ?? undefined,\n ),\n ...ifDefined('clusteredIndex', opts.clusteredIndex ?? undefined),\n },\n }),\n );\n }\n }\n\n return new SchemaTreeNode({\n kind: 'collection',\n id: `collection-${name}`,\n label: `collection ${name}`,\n ...ifDefined('children', children.length > 0 ? children : undefined),\n });\n}\n","import { introspectSchema } from '@prisma-next/adapter-mongo/control';\nimport type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlStack,\n CoreSchemaView,\n SchemaViewCapable,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport {\n VERIFY_CODE_HASH_MISMATCH,\n VERIFY_CODE_MARKER_MISSING,\n VERIFY_CODE_SCHEMA_FAILURE,\n VERIFY_CODE_TARGET_MISMATCH,\n} from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport { validateMongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport {\n contractToMongoSchemaIR,\n initMarker,\n readMarker,\n updateMarker,\n} from '@prisma-next/target-mongo/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { Db } from 'mongodb';\nimport { diffMongoSchemas } from './schema-diff';\nimport { mongoSchemaToView } from './schema-to-view';\n\nexport interface MongoControlFamilyInstance\n extends ControlFamilyInstance<'mongo', MongoSchemaIR>,\n SchemaViewCapable<MongoSchemaIR> {\n validateContract(contractJson: unknown): Contract;\n}\n\nfunction extractDb(driver: ControlDriverInstance<'mongo', string>): Db {\n const mongoDriver = driver as ControlDriverInstance<'mongo', string> & { db?: Db };\n if (!mongoDriver.db) {\n throw new Error(\n 'Mongo control driver does not expose a db property. ' +\n 'Use createMongoControlDriver() from @prisma-next/adapter-mongo/control.',\n );\n }\n return mongoDriver.db;\n}\n\nclass MongoFamilyInstance implements MongoControlFamilyInstance {\n readonly familyId = 'mongo' as const;\n\n validateContract(contractJson: unknown): Contract {\n const validated = validateMongoContract<MongoContract>(contractJson);\n // MongoContract and Contract share structure but are typed independently;\n // validateMongoContract guarantees the shape, so the double cast is safe.\n return validated.contract as unknown as Contract;\n }\n\n async verify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly expectedTargetId: string;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<VerifyDatabaseResult> {\n const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n const contractTarget = contract.target;\n\n const baseOpts = {\n contractStorageHash,\n contractProfileHash,\n expectedTargetId,\n contractPath,\n ...ifDefined('configPath', configPath),\n };\n\n if (contractTarget !== expectedTargetId) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_TARGET_MISMATCH,\n summary: 'Target mismatch',\n actualTargetId: contractTarget,\n totalTime: Date.now() - startTime,\n });\n }\n\n const db = extractDb(driver);\n const marker = await readMarker(db);\n\n if (!marker) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_MARKER_MISSING,\n summary: 'Marker missing',\n totalTime: Date.now() - startTime,\n });\n }\n\n if (marker.storageHash !== contractStorageHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n if (contractProfileHash && marker.profileHash !== contractProfileHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n return buildVerifyResult({\n ...baseOpts,\n ok: true,\n summary: 'Database matches contract',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n async schemaVerify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly strict: boolean;\n readonly contractPath: string;\n readonly configPath?: string;\n readonly frameworkComponents: ReadonlyArray<unknown>;\n }): Promise<VerifyDatabaseSchemaResult> {\n const { driver, contract: rawContract, strict, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const db = extractDb(driver);\n const liveIR = await introspectSchema(db);\n const expectedIR = contractToMongoSchemaIR(contract);\n\n const { root, issues, counts } = diffMongoSchemas(liveIR, expectedIR, strict);\n\n const ok = counts.fail === 0;\n\n return {\n ok,\n ...ifDefined('code', ok ? undefined : VERIFY_CODE_SCHEMA_FAILURE),\n summary: ok ? 'Schema matches contract' : `Schema verification found ${counts.fail} issue(s)`,\n contract: {\n storageHash: contract.storage.storageHash,\n ...ifDefined('profileHash', contract.profileHash),\n },\n target: { expected: contract.target },\n schema: { issues, root, counts },\n meta: {\n ...ifDefined('contractPath', contractPath),\n ...ifDefined('configPath', configPath),\n strict,\n },\n timings: { total: Date.now() - startTime },\n };\n }\n\n async sign(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<SignDatabaseResult> {\n const { driver, contract: rawContract, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n\n const db = extractDb(driver);\n\n const existingMarker = await readMarker(db);\n\n let markerCreated = false;\n let markerUpdated = false;\n let previousHashes: { storageHash?: string; profileHash?: string } | undefined;\n\n if (!existingMarker) {\n await initMarker(db, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n markerCreated = true;\n } else {\n const storageHashMatches = existingMarker.storageHash === contractStorageHash;\n const profileHashMatches = existingMarker.profileHash === contractProfileHash;\n\n if (!storageHashMatches || !profileHashMatches) {\n previousHashes = {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n };\n const updated = await updateMarker(db, existingMarker.storageHash, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n if (!updated) {\n throw new Error('CAS conflict: marker was modified by another process during sign');\n }\n markerUpdated = true;\n }\n }\n\n let summary: string;\n if (markerCreated) {\n summary = 'Database signed (marker created)';\n } else if (markerUpdated) {\n summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? 'unknown'})`;\n } else {\n summary = 'Database already signed with this contract';\n }\n\n return {\n ok: true,\n summary,\n contract: {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n },\n target: {\n expected: contract.target,\n actual: contract.target,\n },\n marker: {\n created: markerCreated,\n updated: markerUpdated,\n ...ifDefined('previous', previousHashes),\n },\n meta: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n timings: {\n total: Date.now() - startTime,\n },\n };\n }\n\n async readMarker(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n }): Promise<ContractMarkerRecord | null> {\n const db = extractDb(options.driver);\n return readMarker(db);\n }\n\n async introspect(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract?: unknown;\n }): Promise<MongoSchemaIR> {\n const db = extractDb(options.driver);\n return introspectSchema(db);\n }\n\n toSchemaView(schema: MongoSchemaIR): CoreSchemaView {\n return mongoSchemaToView(schema);\n }\n}\n\nfunction buildVerifyResult(opts: {\n ok: boolean;\n code?: string;\n summary: string;\n contractStorageHash: string;\n contractProfileHash?: string;\n marker?: ContractMarkerRecord;\n expectedTargetId: string;\n actualTargetId?: string;\n contractPath: string;\n configPath?: string;\n totalTime: number;\n}): VerifyDatabaseResult {\n return {\n ok: opts.ok,\n ...ifDefined('code', opts.code),\n summary: opts.summary,\n contract: {\n storageHash: opts.contractStorageHash,\n ...ifDefined('profileHash', opts.contractProfileHash),\n },\n ...ifDefined(\n 'marker',\n opts.marker\n ? { storageHash: opts.marker.storageHash, profileHash: opts.marker.profileHash }\n : undefined,\n ),\n target: {\n expected: opts.expectedTargetId,\n ...ifDefined('actual', opts.actualTargetId),\n },\n meta: {\n contractPath: opts.contractPath,\n ...ifDefined('configPath', opts.configPath),\n },\n timings: { total: opts.totalTime },\n };\n}\n\nexport function createMongoFamilyInstance(_controlStack: ControlStack): MongoControlFamilyInstance {\n return new MongoFamilyInstance();\n}\n","import type {\n ControlFamilyDescriptor,\n ControlStack,\n} from '@prisma-next/framework-components/control';\nimport { mongoEmission } from '@prisma-next/mongo-emitter';\nimport { createMongoFamilyInstance, type MongoControlFamilyInstance } from './control-instance';\n\nclass MongoFamilyDescriptor\n implements ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>\n{\n readonly kind = 'family' as const;\n readonly id = 'mongo';\n readonly familyId = 'mongo' as const;\n readonly version = '0.0.1';\n readonly emission = mongoEmission;\n\n create<TTargetId extends string>(\n stack: ControlStack<'mongo', TTargetId>,\n ): MongoControlFamilyInstance {\n return createMongoFamilyInstance(stack);\n }\n}\n\nexport const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance> =\n new MongoFamilyDescriptor();\n","import { createMongoRunnerDeps, extractDb } from '@prisma-next/adapter-mongo/control';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { MongoDriverImpl } from '@prisma-next/driver-mongo';\nimport type { MigratableTargetDescriptor } from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport {\n contractToMongoSchemaIR,\n MongoMigrationPlanner,\n MongoMigrationRunner,\n type MongoRunnerDependencies,\n} from '@prisma-next/target-mongo/control';\nimport mongoTargetDescriptorMeta from '@prisma-next/target-mongo/pack';\nimport type { MongoControlFamilyInstance } from './control-instance';\n\n/**\n * `migration.ts` default-exports a `Migration` subclass whose `operations`\n * getter returns the ordered list of operations and whose `describe()`\n * returns the manifest identity metadata. `MongoMigrationPlanner.plan()`\n * returns a `MigrationPlanWithAuthoringSurface` that knows how to render\n * itself back to such a file; `MongoMigrationPlanner.emptyMigration()`\n * returns the same shape for `migration new`. Users run the scaffolded\n * `migration.ts` directly (via `node migration.ts`) to self-emit\n * `ops.json` and attest the `migrationId`.\n */\nexport const mongoTargetDescriptor: MigratableTargetDescriptor<\n 'mongo',\n 'mongo',\n MongoControlFamilyInstance\n> = {\n ...mongoTargetDescriptorMeta,\n migrations: {\n createPlanner(_family: MongoControlFamilyInstance) {\n return new MongoMigrationPlanner();\n },\n createRunner(_family: MongoControlFamilyInstance) {\n // Deps are bound to the first driver passed to execute() and cached for\n // subsequent calls. Callers must not change the driver between calls.\n let cachedDeps: MongoRunnerDependencies | undefined;\n return {\n async execute(options) {\n cachedDeps ??= createMongoRunnerDeps(\n options.driver,\n MongoDriverImpl.fromDb(extractDb(options.driver)),\n );\n const { driver: _, ...runnerOptions } = options;\n const runner = new MongoMigrationRunner(cachedDeps);\n return runner.execute(runnerOptions);\n },\n };\n },\n contractToSchema(contract: Contract | null) {\n return contractToMongoSchemaIR(contract as MongoContract | null);\n },\n },\n create() {\n return { familyId: 'mongo' as const, targetId: 'mongo' as const };\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"control.mjs","names":["children: SchemaTreeNode[]","options: string[]","validatorChildren: SchemaTreeNode[]","optLabels: string[]","extractDb","previousHashes: { storageHash?: string; profileHash?: string } | undefined","summary: string","mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>","mongoTargetDescriptor: MigratableTargetDescriptor<\n 'mongo',\n 'mongo',\n MongoControlFamilyInstance\n>","cachedDeps: MongoRunnerDependencies | undefined"],"sources":["../src/core/schema-to-view.ts","../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/mongo-target-descriptor.ts"],"sourcesContent":["import type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport { SchemaTreeNode } from '@prisma-next/framework-components/control';\nimport type { MongoSchemaCollection, MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport function mongoSchemaToView(schema: MongoSchemaIR): CoreSchemaView {\n const collectionNodes = schema.collections.map((collection) =>\n collectionToSchemaNode(collection.name, collection),\n );\n\n return {\n root: new SchemaTreeNode({\n kind: 'root',\n id: 'mongo-schema',\n label: 'database',\n ...ifDefined('children', collectionNodes.length > 0 ? collectionNodes : undefined),\n }),\n };\n}\n\nfunction collectionToSchemaNode(name: string, collection: MongoSchemaCollection): SchemaTreeNode {\n const children: SchemaTreeNode[] = [];\n\n for (const index of collection.indexes) {\n const keysSummary = index.keys\n .map((k) => {\n if (k.direction === 1) return k.field;\n if (k.direction === -1) return `${k.field} desc`;\n return `${k.field} ${k.direction}`;\n })\n .join(', ');\n const prefix = index.unique ? 'unique index' : 'index';\n const options: string[] = [];\n if (index.sparse) options.push('sparse');\n if (index.expireAfterSeconds != null) options.push(`ttl: ${index.expireAfterSeconds}s`);\n if (index.partialFilterExpression) options.push('partial');\n const optsSuffix = options.length > 0 ? ` (${options.join(', ')})` : '';\n\n children.push(\n new SchemaTreeNode({\n kind: 'index',\n id: `index-${name}-${index.keys.map((k) => `${k.field}_${k.direction}`).join('_')}`,\n label: `${prefix} (${keysSummary})${optsSuffix}`,\n meta: {\n keys: index.keys,\n unique: index.unique,\n ...ifDefined('sparse', index.sparse || undefined),\n ...ifDefined('expireAfterSeconds', index.expireAfterSeconds ?? undefined),\n ...ifDefined('partialFilterExpression', index.partialFilterExpression ?? undefined),\n },\n }),\n );\n }\n\n if (collection.validator) {\n const validatorChildren: SchemaTreeNode[] = [];\n const jsonSchema = collection.validator.jsonSchema as Record<string, unknown>;\n const properties = jsonSchema['properties'] as\n | Record<string, Record<string, unknown>>\n | undefined;\n const required = new Set((jsonSchema['required'] as string[] | undefined) ?? []);\n\n if (properties) {\n for (const [propName, propDef] of Object.entries(properties)) {\n const bsonType = (propDef['bsonType'] as string) ?? 'unknown';\n const suffix = required.has(propName) ? ' (required)' : '';\n validatorChildren.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `field-${name}-${propName}`,\n label: `${propName}: ${bsonType}${suffix}`,\n }),\n );\n }\n }\n\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `validator-${name}`,\n label: `validator (level: ${collection.validator.validationLevel}, action: ${collection.validator.validationAction})`,\n meta: {\n validationLevel: collection.validator.validationLevel,\n validationAction: collection.validator.validationAction,\n jsonSchema: collection.validator.jsonSchema,\n },\n ...ifDefined('children', validatorChildren.length > 0 ? validatorChildren : undefined),\n }),\n );\n }\n\n if (collection.options) {\n const opts = collection.options;\n const optLabels: string[] = [];\n if (opts.capped) optLabels.push('capped');\n if (opts.timeseries) optLabels.push('timeseries');\n if (opts.collation) optLabels.push('collation');\n if (opts.changeStreamPreAndPostImages) optLabels.push('changeStreamPreAndPostImages');\n if (opts.clusteredIndex) optLabels.push('clusteredIndex');\n\n if (optLabels.length > 0) {\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `options-${name}`,\n label: `options (${optLabels.join(', ')})`,\n meta: {\n ...ifDefined('capped', opts.capped ?? undefined),\n ...ifDefined('timeseries', opts.timeseries ?? undefined),\n ...ifDefined('collation', opts.collation ?? undefined),\n ...ifDefined(\n 'changeStreamPreAndPostImages',\n opts.changeStreamPreAndPostImages ?? undefined,\n ),\n ...ifDefined('clusteredIndex', opts.clusteredIndex ?? undefined),\n },\n }),\n );\n }\n }\n\n return new SchemaTreeNode({\n kind: 'collection',\n id: `collection-${name}`,\n label: `collection ${name}`,\n ...ifDefined('children', children.length > 0 ? children : undefined),\n });\n}\n","import { introspectSchema } from '@prisma-next/adapter-mongo/control';\nimport type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlStack,\n CoreSchemaView,\n SchemaViewCapable,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport {\n VERIFY_CODE_HASH_MISMATCH,\n VERIFY_CODE_MARKER_MISSING,\n VERIFY_CODE_TARGET_MISMATCH,\n} from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport { validateMongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { initMarker, readMarker, updateMarker } from '@prisma-next/target-mongo/control';\nimport { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { Db } from 'mongodb';\nimport { mongoSchemaToView } from './schema-to-view';\n\nexport interface MongoControlFamilyInstance\n extends ControlFamilyInstance<'mongo', MongoSchemaIR>,\n SchemaViewCapable<MongoSchemaIR> {\n validateContract(contractJson: unknown): Contract;\n}\n\nfunction extractDb(driver: ControlDriverInstance<'mongo', string>): Db {\n const mongoDriver = driver as ControlDriverInstance<'mongo', string> & { db?: Db };\n if (!mongoDriver.db) {\n throw new Error(\n 'Mongo control driver does not expose a db property. ' +\n 'Use createMongoControlDriver() from @prisma-next/adapter-mongo/control.',\n );\n }\n return mongoDriver.db;\n}\n\nclass MongoFamilyInstance implements MongoControlFamilyInstance {\n readonly familyId = 'mongo' as const;\n\n validateContract(contractJson: unknown): Contract {\n const validated = validateMongoContract<MongoContract>(contractJson);\n // MongoContract and Contract share structure but are typed independently;\n // validateMongoContract guarantees the shape, so the double cast is safe.\n return validated.contract as unknown as Contract;\n }\n\n async verify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly expectedTargetId: string;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<VerifyDatabaseResult> {\n const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n const contractTarget = contract.target;\n\n const baseOpts = {\n contractStorageHash,\n contractProfileHash,\n expectedTargetId,\n contractPath,\n ...ifDefined('configPath', configPath),\n };\n\n if (contractTarget !== expectedTargetId) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_TARGET_MISMATCH,\n summary: 'Target mismatch',\n actualTargetId: contractTarget,\n totalTime: Date.now() - startTime,\n });\n }\n\n const db = extractDb(driver);\n const marker = await readMarker(db);\n\n if (!marker) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_MARKER_MISSING,\n summary: 'Marker missing',\n totalTime: Date.now() - startTime,\n });\n }\n\n if (marker.storageHash !== contractStorageHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n if (contractProfileHash && marker.profileHash !== contractProfileHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n return buildVerifyResult({\n ...baseOpts,\n ok: true,\n summary: 'Database matches contract',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n async schemaVerify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly strict: boolean;\n readonly contractPath: string;\n readonly configPath?: string;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;\n }): Promise<VerifyDatabaseSchemaResult> {\n const { driver, contract: rawContract, strict, contractPath, configPath } = options;\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const db = extractDb(driver);\n const liveIR = await introspectSchema(db);\n\n return verifyMongoSchema({\n contract,\n schema: liveIR,\n strict,\n frameworkComponents: options.frameworkComponents,\n context: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n });\n }\n\n async sign(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<SignDatabaseResult> {\n const { driver, contract: rawContract, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n\n const db = extractDb(driver);\n\n const existingMarker = await readMarker(db);\n\n let markerCreated = false;\n let markerUpdated = false;\n let previousHashes: { storageHash?: string; profileHash?: string } | undefined;\n\n if (!existingMarker) {\n await initMarker(db, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n markerCreated = true;\n } else {\n const storageHashMatches = existingMarker.storageHash === contractStorageHash;\n const profileHashMatches = existingMarker.profileHash === contractProfileHash;\n\n if (!storageHashMatches || !profileHashMatches) {\n previousHashes = {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n };\n const updated = await updateMarker(db, existingMarker.storageHash, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n if (!updated) {\n throw new Error('CAS conflict: marker was modified by another process during sign');\n }\n markerUpdated = true;\n }\n }\n\n let summary: string;\n if (markerCreated) {\n summary = 'Database signed (marker created)';\n } else if (markerUpdated) {\n summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? 'unknown'})`;\n } else {\n summary = 'Database already signed with this contract';\n }\n\n return {\n ok: true,\n summary,\n contract: {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n },\n target: {\n expected: contract.target,\n actual: contract.target,\n },\n marker: {\n created: markerCreated,\n updated: markerUpdated,\n ...ifDefined('previous', previousHashes),\n },\n meta: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n timings: {\n total: Date.now() - startTime,\n },\n };\n }\n\n async readMarker(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n }): Promise<ContractMarkerRecord | null> {\n const db = extractDb(options.driver);\n return readMarker(db);\n }\n\n async introspect(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract?: unknown;\n }): Promise<MongoSchemaIR> {\n const db = extractDb(options.driver);\n return introspectSchema(db);\n }\n\n toSchemaView(schema: MongoSchemaIR): CoreSchemaView {\n return mongoSchemaToView(schema);\n }\n}\n\nfunction buildVerifyResult(opts: {\n ok: boolean;\n code?: string;\n summary: string;\n contractStorageHash: string;\n contractProfileHash?: string;\n marker?: ContractMarkerRecord;\n expectedTargetId: string;\n actualTargetId?: string;\n contractPath: string;\n configPath?: string;\n totalTime: number;\n}): VerifyDatabaseResult {\n return {\n ok: opts.ok,\n ...ifDefined('code', opts.code),\n summary: opts.summary,\n contract: {\n storageHash: opts.contractStorageHash,\n ...ifDefined('profileHash', opts.contractProfileHash),\n },\n ...ifDefined(\n 'marker',\n opts.marker\n ? { storageHash: opts.marker.storageHash, profileHash: opts.marker.profileHash }\n : undefined,\n ),\n target: {\n expected: opts.expectedTargetId,\n ...ifDefined('actual', opts.actualTargetId),\n },\n meta: {\n contractPath: opts.contractPath,\n ...ifDefined('configPath', opts.configPath),\n },\n timings: { total: opts.totalTime },\n };\n}\n\nexport function createMongoFamilyInstance(_controlStack: ControlStack): MongoControlFamilyInstance {\n return new MongoFamilyInstance();\n}\n","import type {\n ControlFamilyDescriptor,\n ControlStack,\n} from '@prisma-next/framework-components/control';\nimport { mongoEmission } from '@prisma-next/mongo-emitter';\nimport { createMongoFamilyInstance, type MongoControlFamilyInstance } from './control-instance';\n\nclass MongoFamilyDescriptor\n implements ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>\n{\n readonly kind = 'family' as const;\n readonly id = 'mongo';\n readonly familyId = 'mongo' as const;\n readonly version = '0.0.1';\n readonly emission = mongoEmission;\n\n create<TTargetId extends string>(\n stack: ControlStack<'mongo', TTargetId>,\n ): MongoControlFamilyInstance {\n return createMongoFamilyInstance(stack);\n }\n}\n\nexport const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance> =\n new MongoFamilyDescriptor();\n","import { createMongoRunnerDeps, extractDb } from '@prisma-next/adapter-mongo/control';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { MongoDriverImpl } from '@prisma-next/driver-mongo';\nimport type {\n MigratableTargetDescriptor,\n MigrationRunner,\n} from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport {\n contractToMongoSchemaIR,\n MongoMigrationPlanner,\n MongoMigrationRunner,\n type MongoRunnerDependencies,\n} from '@prisma-next/target-mongo/control';\nimport mongoTargetDescriptorMeta from '@prisma-next/target-mongo/pack';\nimport type { MongoControlFamilyInstance } from './control-instance';\n\n/**\n * `migration.ts` default-exports a `Migration` subclass whose `operations`\n * getter returns the ordered list of operations and whose `describe()`\n * returns the manifest identity metadata. `MongoMigrationPlanner.plan()`\n * returns a `MigrationPlanWithAuthoringSurface` that knows how to render\n * itself back to such a file; `MongoMigrationPlanner.emptyMigration()`\n * returns the same shape for `migration new`. Users run the scaffolded\n * `migration.ts` directly (via `node migration.ts`) to self-emit\n * `ops.json` and attest the `migrationId`.\n */\nexport const mongoTargetDescriptor: MigratableTargetDescriptor<\n 'mongo',\n 'mongo',\n MongoControlFamilyInstance\n> = {\n ...mongoTargetDescriptorMeta,\n migrations: {\n createPlanner(_family: MongoControlFamilyInstance) {\n return new MongoMigrationPlanner();\n },\n createRunner(family: MongoControlFamilyInstance) {\n // Deps are bound to the first driver passed to execute() and cached for\n // subsequent calls. Callers must not change the driver between calls.\n let cachedDeps: MongoRunnerDependencies | undefined;\n const runner: MigrationRunner<'mongo', 'mongo'> = {\n async execute(options) {\n cachedDeps ??= createMongoRunnerDeps(\n options.driver,\n MongoDriverImpl.fromDb(extractDb(options.driver)),\n family,\n );\n const { driver: _, ...runnerOptions } = options;\n // The framework `MigrationRunner` interface types `destinationContract`\n // as `unknown`; the Mongo runner narrows to `MongoContract`. Validation\n // happens upstream — `migration apply` calls\n // `familyInstance.validateContract(migration.toContract)` before\n // routing the contract here (see\n // `packages/1-framework/3-tooling/cli/src/control-api/operations/migration-apply.ts`),\n // so this cast simply preserves the framework signature without\n // weakening the runner's typed surface or duplicating validation.\n return new MongoMigrationRunner(cachedDeps).execute({\n ...runnerOptions,\n destinationContract: runnerOptions.destinationContract as MongoContract,\n });\n },\n };\n return runner;\n },\n contractToSchema(contract: Contract | null) {\n return contractToMongoSchemaIR(contract as MongoContract | null);\n },\n },\n create() {\n return { familyId: 'mongo' as const, targetId: 'mongo' as const };\n },\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { VerifyMongoSchemaOptions, verifyMongoSchema } from "@prisma-next/target-mongo/schema-verify";
2
+ export { type VerifyMongoSchemaOptions, verifyMongoSchema };
@@ -0,0 +1,3 @@
1
+ import { verifyMongoSchema } from "@prisma-next/target-mongo/schema-verify";
2
+
3
+ export { verifyMongoSchema };
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
1
  {
2
2
  "name": "@prisma-next/family-mongo",
3
- "version": "0.5.0-dev.4",
3
+ "version": "0.5.0-dev.6",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Mongo family descriptor for Prisma Next",
7
7
  "dependencies": {
8
8
  "mongodb": "^6.16.0",
9
9
  "pathe": "^2.0.3",
10
- "@prisma-next/adapter-mongo": "0.5.0-dev.4",
11
- "@prisma-next/contract": "0.5.0-dev.4",
12
- "@prisma-next/errors": "0.5.0-dev.4",
13
- "@prisma-next/driver-mongo": "0.5.0-dev.4",
14
- "@prisma-next/framework-components": "0.5.0-dev.4",
15
- "@prisma-next/mongo-contract": "0.5.0-dev.4",
16
- "@prisma-next/emitter": "0.5.0-dev.4",
17
- "@prisma-next/mongo-emitter": "0.5.0-dev.4",
18
- "@prisma-next/migration-tools": "0.5.0-dev.4",
19
- "@prisma-next/mongo-query-ast": "0.5.0-dev.4",
20
- "@prisma-next/mongo-schema-ir": "0.5.0-dev.4",
21
- "@prisma-next/target-mongo": "0.5.0-dev.4",
22
- "@prisma-next/utils": "0.5.0-dev.4"
10
+ "@prisma-next/contract": "0.5.0-dev.6",
11
+ "@prisma-next/emitter": "0.5.0-dev.6",
12
+ "@prisma-next/errors": "0.5.0-dev.6",
13
+ "@prisma-next/driver-mongo": "0.5.0-dev.6",
14
+ "@prisma-next/framework-components": "0.5.0-dev.6",
15
+ "@prisma-next/migration-tools": "0.5.0-dev.6",
16
+ "@prisma-next/mongo-contract": "0.5.0-dev.6",
17
+ "@prisma-next/adapter-mongo": "0.5.0-dev.6",
18
+ "@prisma-next/mongo-emitter": "0.5.0-dev.6",
19
+ "@prisma-next/mongo-query-ast": "0.5.0-dev.6",
20
+ "@prisma-next/mongo-schema-ir": "0.5.0-dev.6",
21
+ "@prisma-next/target-mongo": "0.5.0-dev.6",
22
+ "@prisma-next/utils": "0.5.0-dev.6"
23
23
  },
24
24
  "devDependencies": {
25
25
  "tsdown": "0.18.4",
26
26
  "typescript": "5.9.3",
27
27
  "vitest": "4.0.17",
28
- "@prisma-next/tsconfig": "0.0.0",
29
- "@prisma-next/mongo-contract-ts": "0.5.0-dev.4",
30
- "@prisma-next/tsdown": "0.0.0"
28
+ "@prisma-next/tsdown": "0.0.0",
29
+ "@prisma-next/mongo-contract-ts": "0.5.0-dev.6",
30
+ "@prisma-next/tsconfig": "0.0.0"
31
31
  },
32
32
  "files": [
33
33
  "dist",
@@ -45,6 +45,7 @@
45
45
  "./control": "./dist/control.mjs",
46
46
  "./migration": "./dist/migration.mjs",
47
47
  "./pack": "./dist/pack.mjs",
48
+ "./schema-verify": "./dist/schema-verify.mjs",
48
49
  "./package.json": "./package.json"
49
50
  },
50
51
  "scripts": {
@@ -1,5 +1,6 @@
1
1
  import { introspectSchema } from '@prisma-next/adapter-mongo/control';
2
2
  import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
3
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
3
4
  import type {
4
5
  ControlDriverInstance,
5
6
  ControlFamilyInstance,
@@ -13,21 +14,15 @@ import type {
13
14
  import {
14
15
  VERIFY_CODE_HASH_MISMATCH,
15
16
  VERIFY_CODE_MARKER_MISSING,
16
- VERIFY_CODE_SCHEMA_FAILURE,
17
17
  VERIFY_CODE_TARGET_MISMATCH,
18
18
  } from '@prisma-next/framework-components/control';
19
19
  import type { MongoContract } from '@prisma-next/mongo-contract';
20
20
  import { validateMongoContract } from '@prisma-next/mongo-contract';
21
21
  import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
22
- import {
23
- contractToMongoSchemaIR,
24
- initMarker,
25
- readMarker,
26
- updateMarker,
27
- } from '@prisma-next/target-mongo/control';
22
+ import { initMarker, readMarker, updateMarker } from '@prisma-next/target-mongo/control';
23
+ import { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';
28
24
  import { ifDefined } from '@prisma-next/utils/defined';
29
25
  import type { Db } from 'mongodb';
30
- import { diffMongoSchemas } from './schema-diff';
31
26
  import { mongoSchemaToView } from './schema-to-view';
32
27
 
33
28
  export interface MongoControlFamilyInstance
@@ -143,39 +138,26 @@ class MongoFamilyInstance implements MongoControlFamilyInstance {
143
138
  readonly strict: boolean;
144
139
  readonly contractPath: string;
145
140
  readonly configPath?: string;
146
- readonly frameworkComponents: ReadonlyArray<unknown>;
141
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;
147
142
  }): Promise<VerifyDatabaseSchemaResult> {
148
143
  const { driver, contract: rawContract, strict, contractPath, configPath } = options;
149
- const startTime = Date.now();
150
144
 
151
145
  const validated = validateMongoContract<MongoContract>(rawContract);
152
146
  const contract = validated.contract;
153
147
 
154
148
  const db = extractDb(driver);
155
149
  const liveIR = await introspectSchema(db);
156
- const expectedIR = contractToMongoSchemaIR(contract);
157
-
158
- const { root, issues, counts } = diffMongoSchemas(liveIR, expectedIR, strict);
159
150
 
160
- const ok = counts.fail === 0;
161
-
162
- return {
163
- ok,
164
- ...ifDefined('code', ok ? undefined : VERIFY_CODE_SCHEMA_FAILURE),
165
- summary: ok ? 'Schema matches contract' : `Schema verification found ${counts.fail} issue(s)`,
166
- contract: {
167
- storageHash: contract.storage.storageHash,
168
- ...ifDefined('profileHash', contract.profileHash),
169
- },
170
- target: { expected: contract.target },
171
- schema: { issues, root, counts },
172
- meta: {
173
- ...ifDefined('contractPath', contractPath),
151
+ return verifyMongoSchema({
152
+ contract,
153
+ schema: liveIR,
154
+ strict,
155
+ frameworkComponents: options.frameworkComponents,
156
+ context: {
157
+ contractPath,
174
158
  ...ifDefined('configPath', configPath),
175
- strict,
176
159
  },
177
- timings: { total: Date.now() - startTime },
178
- };
160
+ });
179
161
  }
180
162
 
181
163
  async sign(options: {
@@ -1,7 +1,10 @@
1
1
  import { createMongoRunnerDeps, extractDb } from '@prisma-next/adapter-mongo/control';
2
2
  import type { Contract } from '@prisma-next/contract/types';
3
3
  import { MongoDriverImpl } from '@prisma-next/driver-mongo';
4
- import type { MigratableTargetDescriptor } from '@prisma-next/framework-components/control';
4
+ import type {
5
+ MigratableTargetDescriptor,
6
+ MigrationRunner,
7
+ } from '@prisma-next/framework-components/control';
5
8
  import type { MongoContract } from '@prisma-next/mongo-contract';
6
9
  import {
7
10
  contractToMongoSchemaIR,
@@ -32,21 +35,33 @@ export const mongoTargetDescriptor: MigratableTargetDescriptor<
32
35
  createPlanner(_family: MongoControlFamilyInstance) {
33
36
  return new MongoMigrationPlanner();
34
37
  },
35
- createRunner(_family: MongoControlFamilyInstance) {
38
+ createRunner(family: MongoControlFamilyInstance) {
36
39
  // Deps are bound to the first driver passed to execute() and cached for
37
40
  // subsequent calls. Callers must not change the driver between calls.
38
41
  let cachedDeps: MongoRunnerDependencies | undefined;
39
- return {
42
+ const runner: MigrationRunner<'mongo', 'mongo'> = {
40
43
  async execute(options) {
41
44
  cachedDeps ??= createMongoRunnerDeps(
42
45
  options.driver,
43
46
  MongoDriverImpl.fromDb(extractDb(options.driver)),
47
+ family,
44
48
  );
45
49
  const { driver: _, ...runnerOptions } = options;
46
- const runner = new MongoMigrationRunner(cachedDeps);
47
- return runner.execute(runnerOptions);
50
+ // The framework `MigrationRunner` interface types `destinationContract`
51
+ // as `unknown`; the Mongo runner narrows to `MongoContract`. Validation
52
+ // happens upstream — `migration apply` calls
53
+ // `familyInstance.validateContract(migration.toContract)` before
54
+ // routing the contract here (see
55
+ // `packages/1-framework/3-tooling/cli/src/control-api/operations/migration-apply.ts`),
56
+ // so this cast simply preserves the framework signature without
57
+ // weakening the runner's typed surface or duplicating validation.
58
+ return new MongoMigrationRunner(cachedDeps).execute({
59
+ ...runnerOptions,
60
+ destinationContract: runnerOptions.destinationContract as MongoContract,
61
+ });
48
62
  },
49
63
  };
64
+ return runner;
50
65
  },
51
66
  contractToSchema(contract: Contract | null) {
52
67
  return contractToMongoSchemaIR(contract as MongoContract | null);
@@ -0,0 +1,2 @@
1
+ export type { VerifyMongoSchemaOptions } from '@prisma-next/target-mongo/schema-verify';
2
+ export { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';
@@ -1,402 +0,0 @@
1
- import type {
2
- SchemaIssue,
3
- SchemaVerificationNode,
4
- } from '@prisma-next/framework-components/control';
5
- import type {
6
- MongoSchemaCollection,
7
- MongoSchemaIndex,
8
- MongoSchemaIR,
9
- } from '@prisma-next/mongo-schema-ir';
10
- import { canonicalize, deepEqual } from '@prisma-next/mongo-schema-ir';
11
-
12
- export function diffMongoSchemas(
13
- live: MongoSchemaIR,
14
- expected: MongoSchemaIR,
15
- strict: boolean,
16
- ): {
17
- root: SchemaVerificationNode;
18
- issues: SchemaIssue[];
19
- counts: { pass: number; warn: number; fail: number; totalNodes: number };
20
- } {
21
- const issues: SchemaIssue[] = [];
22
- const collectionChildren: SchemaVerificationNode[] = [];
23
- let pass = 0;
24
- let warn = 0;
25
- let fail = 0;
26
-
27
- const allNames = new Set([...live.collectionNames, ...expected.collectionNames]);
28
-
29
- for (const name of [...allNames].sort()) {
30
- const liveColl = live.collection(name);
31
- const expectedColl = expected.collection(name);
32
-
33
- if (!liveColl && expectedColl) {
34
- issues.push({
35
- kind: 'missing_table',
36
- table: name,
37
- message: `Collection "${name}" is missing from the database`,
38
- });
39
- collectionChildren.push({
40
- status: 'fail',
41
- kind: 'collection',
42
- name,
43
- contractPath: `storage.collections.${name}`,
44
- code: 'MISSING_COLLECTION',
45
- message: `Collection "${name}" is missing`,
46
- expected: name,
47
- actual: null,
48
- children: [],
49
- });
50
- fail++;
51
- continue;
52
- }
53
-
54
- if (liveColl && !expectedColl) {
55
- const status = strict ? 'fail' : 'warn';
56
- issues.push({
57
- kind: 'extra_table',
58
- table: name,
59
- message: `Extra collection "${name}" exists in the database but not in the contract`,
60
- });
61
- collectionChildren.push({
62
- status,
63
- kind: 'collection',
64
- name,
65
- contractPath: `storage.collections.${name}`,
66
- code: 'EXTRA_COLLECTION',
67
- message: `Extra collection "${name}" found`,
68
- expected: null,
69
- actual: name,
70
- children: [],
71
- });
72
- if (status === 'fail') fail++;
73
- else warn++;
74
- continue;
75
- }
76
-
77
- const lc = liveColl as MongoSchemaCollection;
78
- const ec = expectedColl as MongoSchemaCollection;
79
- const indexChildren = diffIndexes(name, lc, ec, strict, issues);
80
- const validatorChildren = diffValidator(name, lc, ec, strict, issues);
81
- const optionsChildren = diffOptions(name, lc, ec, strict, issues);
82
- const children = [...indexChildren, ...validatorChildren, ...optionsChildren];
83
-
84
- const worstStatus = children.reduce<'pass' | 'warn' | 'fail'>(
85
- (s, c) => (c.status === 'fail' ? 'fail' : c.status === 'warn' && s !== 'fail' ? 'warn' : s),
86
- 'pass',
87
- );
88
-
89
- for (const c of children) {
90
- if (c.status === 'pass') pass++;
91
- else if (c.status === 'warn') warn++;
92
- else fail++;
93
- }
94
-
95
- if (children.length === 0) {
96
- pass++;
97
- }
98
-
99
- collectionChildren.push({
100
- status: worstStatus,
101
- kind: 'collection',
102
- name,
103
- contractPath: `storage.collections.${name}`,
104
- code: worstStatus === 'pass' ? 'MATCH' : 'DRIFT',
105
- message:
106
- worstStatus === 'pass' ? `Collection "${name}" matches` : `Collection "${name}" has drift`,
107
- expected: name,
108
- actual: name,
109
- children,
110
- });
111
- }
112
-
113
- const rootStatus = fail > 0 ? 'fail' : warn > 0 ? 'warn' : 'pass';
114
- const totalNodes = pass + warn + fail + collectionChildren.length;
115
-
116
- const root: SchemaVerificationNode = {
117
- status: rootStatus,
118
- kind: 'root',
119
- name: 'mongo-schema',
120
- contractPath: 'storage',
121
- code: rootStatus === 'pass' ? 'MATCH' : 'DRIFT',
122
- message: rootStatus === 'pass' ? 'Schema matches' : 'Schema has drift',
123
- expected: null,
124
- actual: null,
125
- children: collectionChildren,
126
- };
127
-
128
- return { root, issues, counts: { pass, warn, fail, totalNodes } };
129
- }
130
-
131
- function buildIndexLookupKey(index: MongoSchemaIndex): string {
132
- const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(',');
133
- const opts = [
134
- index.unique ? 'unique' : '',
135
- index.sparse ? 'sparse' : '',
136
- index.expireAfterSeconds != null ? `ttl:${index.expireAfterSeconds}` : '',
137
- index.partialFilterExpression ? `pfe:${canonicalize(index.partialFilterExpression)}` : '',
138
- index.wildcardProjection ? `wp:${canonicalize(index.wildcardProjection)}` : '',
139
- index.collation ? `col:${canonicalize(index.collation)}` : '',
140
- index.weights ? `wt:${canonicalize(index.weights)}` : '',
141
- index.default_language ? `dl:${index.default_language}` : '',
142
- index.language_override ? `lo:${index.language_override}` : '',
143
- ]
144
- .filter(Boolean)
145
- .join(';');
146
- return opts ? `${keys}|${opts}` : keys;
147
- }
148
-
149
- function formatIndexName(index: MongoSchemaIndex): string {
150
- return index.keys.map((k) => `${k.field}:${k.direction}`).join(', ');
151
- }
152
-
153
- function diffIndexes(
154
- collName: string,
155
- live: MongoSchemaCollection,
156
- expected: MongoSchemaCollection,
157
- strict: boolean,
158
- issues: SchemaIssue[],
159
- ): SchemaVerificationNode[] {
160
- const nodes: SchemaVerificationNode[] = [];
161
- const liveLookup = new Map<string, MongoSchemaIndex>();
162
- for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);
163
-
164
- const expectedLookup = new Map<string, MongoSchemaIndex>();
165
- for (const idx of expected.indexes) expectedLookup.set(buildIndexLookupKey(idx), idx);
166
-
167
- for (const [key, idx] of expectedLookup) {
168
- if (liveLookup.has(key)) {
169
- nodes.push({
170
- status: 'pass',
171
- kind: 'index',
172
- name: formatIndexName(idx),
173
- contractPath: `storage.collections.${collName}.indexes`,
174
- code: 'MATCH',
175
- message: `Index ${formatIndexName(idx)} matches`,
176
- expected: key,
177
- actual: key,
178
- children: [],
179
- });
180
- } else {
181
- issues.push({
182
- kind: 'index_mismatch',
183
- table: collName,
184
- indexOrConstraint: formatIndexName(idx),
185
- message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`,
186
- });
187
- nodes.push({
188
- status: 'fail',
189
- kind: 'index',
190
- name: formatIndexName(idx),
191
- contractPath: `storage.collections.${collName}.indexes`,
192
- code: 'MISSING_INDEX',
193
- message: `Index ${formatIndexName(idx)} missing`,
194
- expected: key,
195
- actual: null,
196
- children: [],
197
- });
198
- }
199
- }
200
-
201
- for (const [key, idx] of liveLookup) {
202
- if (!expectedLookup.has(key)) {
203
- const status = strict ? 'fail' : 'warn';
204
- issues.push({
205
- kind: 'extra_index',
206
- table: collName,
207
- indexOrConstraint: formatIndexName(idx),
208
- message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`,
209
- });
210
- nodes.push({
211
- status,
212
- kind: 'index',
213
- name: formatIndexName(idx),
214
- contractPath: `storage.collections.${collName}.indexes`,
215
- code: 'EXTRA_INDEX',
216
- message: `Extra index ${formatIndexName(idx)}`,
217
- expected: null,
218
- actual: key,
219
- children: [],
220
- });
221
- }
222
- }
223
-
224
- return nodes;
225
- }
226
-
227
- function diffValidator(
228
- collName: string,
229
- live: MongoSchemaCollection,
230
- expected: MongoSchemaCollection,
231
- strict: boolean,
232
- issues: SchemaIssue[],
233
- ): SchemaVerificationNode[] {
234
- if (!live.validator && !expected.validator) return [];
235
-
236
- if (expected.validator && !live.validator) {
237
- issues.push({
238
- kind: 'type_missing',
239
- table: collName,
240
- message: `Validator missing on collection "${collName}"`,
241
- });
242
- return [
243
- {
244
- status: 'fail',
245
- kind: 'validator',
246
- name: 'validator',
247
- contractPath: `storage.collections.${collName}.validator`,
248
- code: 'MISSING_VALIDATOR',
249
- message: 'Validator missing',
250
- expected: canonicalize(expected.validator.jsonSchema),
251
- actual: null,
252
- children: [],
253
- },
254
- ];
255
- }
256
-
257
- if (!expected.validator && live.validator) {
258
- const status = strict ? 'fail' : 'warn';
259
- issues.push({
260
- kind: 'extra_validator',
261
- table: collName,
262
- message: `Extra validator on collection "${collName}"`,
263
- });
264
- return [
265
- {
266
- status,
267
- kind: 'validator',
268
- name: 'validator',
269
- contractPath: `storage.collections.${collName}.validator`,
270
- code: 'EXTRA_VALIDATOR',
271
- message: 'Extra validator found',
272
- expected: null,
273
- actual: canonicalize(live.validator.jsonSchema),
274
- children: [],
275
- },
276
- ];
277
- }
278
-
279
- const liveVal = live.validator as NonNullable<typeof live.validator>;
280
- const expectedVal = expected.validator as NonNullable<typeof expected.validator>;
281
- const liveSchema = canonicalize(liveVal.jsonSchema);
282
- const expectedSchema = canonicalize(expectedVal.jsonSchema);
283
-
284
- if (
285
- liveSchema !== expectedSchema ||
286
- liveVal.validationLevel !== expectedVal.validationLevel ||
287
- liveVal.validationAction !== expectedVal.validationAction
288
- ) {
289
- issues.push({
290
- kind: 'type_mismatch',
291
- table: collName,
292
- expected: expectedSchema,
293
- actual: liveSchema,
294
- message: `Validator mismatch on collection "${collName}"`,
295
- });
296
- return [
297
- {
298
- status: 'fail',
299
- kind: 'validator',
300
- name: 'validator',
301
- contractPath: `storage.collections.${collName}.validator`,
302
- code: 'VALIDATOR_MISMATCH',
303
- message: 'Validator mismatch',
304
- expected: {
305
- jsonSchema: expectedVal.jsonSchema,
306
- validationLevel: expectedVal.validationLevel,
307
- validationAction: expectedVal.validationAction,
308
- },
309
- actual: {
310
- jsonSchema: liveVal.jsonSchema,
311
- validationLevel: liveVal.validationLevel,
312
- validationAction: liveVal.validationAction,
313
- },
314
- children: [],
315
- },
316
- ];
317
- }
318
-
319
- return [
320
- {
321
- status: 'pass',
322
- kind: 'validator',
323
- name: 'validator',
324
- contractPath: `storage.collections.${collName}.validator`,
325
- code: 'MATCH',
326
- message: 'Validator matches',
327
- expected: expectedSchema,
328
- actual: liveSchema,
329
- children: [],
330
- },
331
- ];
332
- }
333
-
334
- function diffOptions(
335
- collName: string,
336
- live: MongoSchemaCollection,
337
- expected: MongoSchemaCollection,
338
- strict: boolean,
339
- issues: SchemaIssue[],
340
- ): SchemaVerificationNode[] {
341
- if (!live.options && !expected.options) return [];
342
-
343
- if (!expected.options && live.options) {
344
- const status = strict ? 'fail' : 'warn';
345
- issues.push({
346
- kind: 'type_mismatch',
347
- table: collName,
348
- actual: canonicalize(live.options),
349
- message: `Extra collection options on "${collName}"`,
350
- });
351
- return [
352
- {
353
- status,
354
- kind: 'options',
355
- name: 'options',
356
- contractPath: `storage.collections.${collName}.options`,
357
- code: 'EXTRA_OPTIONS',
358
- message: 'Extra collection options found',
359
- expected: null,
360
- actual: live.options,
361
- children: [],
362
- },
363
- ];
364
- }
365
-
366
- if (deepEqual(live.options, expected.options)) {
367
- return [
368
- {
369
- status: 'pass',
370
- kind: 'options',
371
- name: 'options',
372
- contractPath: `storage.collections.${collName}.options`,
373
- code: 'MATCH',
374
- message: 'Collection options match',
375
- expected: canonicalize(expected.options),
376
- actual: canonicalize(live.options),
377
- children: [],
378
- },
379
- ];
380
- }
381
-
382
- issues.push({
383
- kind: 'type_mismatch',
384
- table: collName,
385
- expected: canonicalize(expected.options),
386
- actual: canonicalize(live.options),
387
- message: `Collection options mismatch on "${collName}"`,
388
- });
389
- return [
390
- {
391
- status: 'fail',
392
- kind: 'options',
393
- name: 'options',
394
- contractPath: `storage.collections.${collName}.options`,
395
- code: 'OPTIONS_MISMATCH',
396
- message: 'Collection options mismatch',
397
- expected: expected.options,
398
- actual: live.options,
399
- children: [],
400
- },
401
- ];
402
- }