@prisma-next/family-mongo 0.12.0-dev.17 → 0.12.0-dev.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.
@@ -1,7 +1,6 @@
1
1
  import { MongoSchemaCollection, MongoSchemaCollectionOptions, MongoSchemaIR, MongoSchemaIndex, MongoSchemaValidator, canonicalize, deepEqual } from "@prisma-next/mongo-schema-ir";
2
- import { VERIFY_CODE_SCHEMA_FAILURE, dispositionForCategory } from "@prisma-next/framework-components/control";
2
+ import { VERIFY_CODE_SCHEMA_FAILURE } from "@prisma-next/framework-components/control";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
- import { effectiveControlPolicy } from "@prisma-next/contract/types";
5
4
  import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
6
5
  //#region src/core/contract-to-schema.ts
7
6
  function stripIrKind(node) {
@@ -57,72 +56,8 @@ function contractToMongoSchemaIR(contract) {
57
56
  return new MongoSchemaIR(collections);
58
57
  }
59
58
  //#endregion
60
- //#region src/core/schema-verify/verifier-disposition.ts
61
- /**
62
- * Classifies the verifier issue kinds the Mongo schema differ emits into the
63
- * target-neutral categories the framework grades. Mongo only emits the kinds
64
- * listed below (missing/extra collections, missing/extra indexes, missing/extra
65
- * validators, and validator/options mismatches coded as `type_mismatch`); any
66
- * other kind never reaches this classifier and is graded conservatively as a
67
- * declared-incompatible divergence. Mongo owns this mapping rather than
68
- * importing the SQL classifier — the two families share only the framework's
69
- * category grading.
70
- */
71
- function classifyMongoVerifierIssueKind(kind) {
72
- switch (kind) {
73
- case "extra_table": return "extraTopLevelObject";
74
- case "extra_index":
75
- case "extra_validator": return "extraAuxiliary";
76
- case "missing_table":
77
- case "type_missing": return "declaredMissing";
78
- case "index_mismatch":
79
- case "type_mismatch": return "declaredIncompatible";
80
- default: return "declaredIncompatible";
81
- }
82
- }
83
- function verifierDisposition(controlPolicy, issueKind) {
84
- return dispositionForCategory(controlPolicy, classifyMongoVerifierIssueKind(issueKind));
85
- }
86
- //#endregion
87
- //#region src/core/schema-verify/mongo-control-verify-emit.ts
88
- /**
89
- * Reconciles a control-policy disposition with the Mongo family's strict-mode
90
- * contract for live-only extras — the single point where `strict` and the
91
- * control policy meet.
92
- *
93
- * The control policy decides first; only a `fail` is reconciled against the
94
- * caller's base node status. Call sites stamp a live-only extra with
95
- * `strict ? 'fail' : 'warn'` and a declared missing/mismatch with `fail`, so
96
- * this one step encodes the whole matrix:
97
- *
98
- * | live-vs-declared | strict | non-strict |
99
- * |-------------------------------------|----------|------------|
100
- * | declared missing / mismatch | fail | fail |
101
- * | live-only extra (managed/tolerated) | fail | warn |
102
- * | live-only extra (external) | suppress (extras ignored, both modes) |
103
- * | anything (observed) | warn (both modes) |
104
- *
105
- * `tolerated` no longer diverges from `managed` on a non-strict extra index:
106
- * both soften to `warn`, because the softening comes from the base status the
107
- * caller already computed from `strict`, not from per-policy special-casing.
108
- */
109
- function reconcileMongoOutcome(controlPolicy, issueKind, baseStatus) {
110
- const disposition = verifierDisposition(controlPolicy, issueKind);
111
- return disposition === "fail" ? baseStatus : disposition;
112
- }
113
- function emitMongoIssueAndNodeUnderControlPolicy(controlPolicy, issue, node, issues, nodes) {
114
- const outcome = reconcileMongoOutcome(controlPolicy, issue.kind, node.status);
115
- if (outcome === "suppress") return "suppress";
116
- issues.push(issue);
117
- nodes.push({
118
- ...node,
119
- status: outcome
120
- });
121
- return outcome;
122
- }
123
- //#endregion
124
59
  //#region src/core/schema-diff.ts
125
- function diffMongoSchemas(live, expected, strict, collectionControlPolicy) {
60
+ function diffMongoSchemas(live, expected, strict) {
126
61
  const issues = [];
127
62
  const collectionChildren = [];
128
63
  let pass = 0;
@@ -133,11 +68,12 @@ function diffMongoSchemas(live, expected, strict, collectionControlPolicy) {
133
68
  const liveColl = live.collection(name);
134
69
  const expectedColl = expected.collection(name);
135
70
  if (!liveColl && expectedColl) {
136
- const disposition = emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy(name), {
71
+ issues.push({
137
72
  kind: "missing_table",
138
73
  table: name,
139
74
  message: `Collection "${name}" is missing from the database`
140
- }, {
75
+ });
76
+ collectionChildren.push({
141
77
  status: "fail",
142
78
  kind: "collection",
143
79
  name,
@@ -147,18 +83,19 @@ function diffMongoSchemas(live, expected, strict, collectionControlPolicy) {
147
83
  expected: name,
148
84
  actual: null,
149
85
  children: []
150
- }, issues, collectionChildren);
151
- if (disposition === "fail") fail++;
152
- else if (disposition === "warn") warn++;
86
+ });
87
+ fail++;
153
88
  continue;
154
89
  }
155
90
  if (liveColl && !expectedColl) {
156
- const disposition = emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy(name), {
91
+ const status = strict ? "fail" : "warn";
92
+ issues.push({
157
93
  kind: "extra_table",
158
94
  table: name,
159
95
  message: `Extra collection "${name}" exists in the database but not in the contract`
160
- }, {
161
- status: strict ? "fail" : "warn",
96
+ });
97
+ collectionChildren.push({
98
+ status,
162
99
  kind: "collection",
163
100
  name,
164
101
  contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${name}`,
@@ -167,17 +104,16 @@ function diffMongoSchemas(live, expected, strict, collectionControlPolicy) {
167
104
  expected: null,
168
105
  actual: name,
169
106
  children: []
170
- }, issues, collectionChildren);
171
- if (disposition === "fail") fail++;
172
- else if (disposition === "warn") warn++;
107
+ });
108
+ if (status === "fail") fail++;
109
+ else warn++;
173
110
  continue;
174
111
  }
175
112
  const lc = liveColl;
176
113
  const ec = expectedColl;
177
- const controlPolicy = collectionControlPolicy(name);
178
- const indexChildren = diffIndexes(name, lc, ec, strict, controlPolicy, issues);
179
- const validatorChildren = diffValidator(name, lc, ec, strict, controlPolicy, issues);
180
- const optionsChildren = diffOptions(name, lc, ec, strict, controlPolicy, issues);
114
+ const indexChildren = diffIndexes(name, lc, ec, strict, issues);
115
+ const validatorChildren = diffValidator(name, lc, ec, strict, issues);
116
+ const optionsChildren = diffOptions(name, lc, ec, strict, issues);
181
117
  const children = [
182
118
  ...indexChildren,
183
119
  ...validatorChildren,
@@ -241,7 +177,7 @@ function buildIndexLookupKey(index) {
241
177
  function formatIndexName(index) {
242
178
  return index.keys.map((k) => `${k.field}:${k.direction}`).join(", ");
243
179
  }
244
- function diffIndexes(collName, live, expected, strict, collectionControlPolicy, issues) {
180
+ function diffIndexes(collName, live, expected, strict, issues) {
245
181
  const nodes = [];
246
182
  const liveLookup = /* @__PURE__ */ new Map();
247
183
  for (const idx of live.indexes) liveLookup.set(buildIndexLookupKey(idx), idx);
@@ -258,50 +194,56 @@ function diffIndexes(collName, live, expected, strict, collectionControlPolicy,
258
194
  actual: key,
259
195
  children: []
260
196
  });
261
- else emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, {
262
- kind: "index_mismatch",
263
- table: collName,
264
- indexOrConstraint: formatIndexName(idx),
265
- message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`
266
- }, {
267
- status: "fail",
268
- kind: "index",
269
- name: formatIndexName(idx),
270
- contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`,
271
- code: "MISSING_INDEX",
272
- message: `Index ${formatIndexName(idx)} missing`,
273
- expected: key,
274
- actual: null,
275
- children: []
276
- }, issues, nodes);
277
- for (const [key, idx] of liveLookup) if (!expectedLookup.has(key)) emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, {
278
- kind: "extra_index",
279
- table: collName,
280
- indexOrConstraint: formatIndexName(idx),
281
- message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`
282
- }, {
283
- status: strict ? "fail" : "warn",
284
- kind: "index",
285
- name: formatIndexName(idx),
286
- contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`,
287
- code: "EXTRA_INDEX",
288
- message: `Extra index ${formatIndexName(idx)}`,
289
- expected: null,
290
- actual: key,
291
- children: []
292
- }, issues, nodes);
197
+ else {
198
+ issues.push({
199
+ kind: "index_mismatch",
200
+ table: collName,
201
+ indexOrConstraint: formatIndexName(idx),
202
+ message: `Index ${formatIndexName(idx)} missing on collection "${collName}"`
203
+ });
204
+ nodes.push({
205
+ status: "fail",
206
+ kind: "index",
207
+ name: formatIndexName(idx),
208
+ contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`,
209
+ code: "MISSING_INDEX",
210
+ message: `Index ${formatIndexName(idx)} missing`,
211
+ expected: key,
212
+ actual: null,
213
+ children: []
214
+ });
215
+ }
216
+ for (const [key, idx] of liveLookup) if (!expectedLookup.has(key)) {
217
+ const status = strict ? "fail" : "warn";
218
+ issues.push({
219
+ kind: "extra_index",
220
+ table: collName,
221
+ indexOrConstraint: formatIndexName(idx),
222
+ message: `Extra index ${formatIndexName(idx)} on collection "${collName}"`
223
+ });
224
+ nodes.push({
225
+ status,
226
+ kind: "index",
227
+ name: formatIndexName(idx),
228
+ contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.indexes`,
229
+ code: "EXTRA_INDEX",
230
+ message: `Extra index ${formatIndexName(idx)}`,
231
+ expected: null,
232
+ actual: key,
233
+ children: []
234
+ });
235
+ }
293
236
  return nodes;
294
237
  }
295
- function diffValidator(collName, live, expected, strict, collectionControlPolicy, issues) {
238
+ function diffValidator(collName, live, expected, strict, issues) {
296
239
  if (!live.validator && !expected.validator) return [];
297
240
  if (expected.validator && !live.validator) {
298
- const issue = {
241
+ issues.push({
299
242
  kind: "type_missing",
300
243
  table: collName,
301
244
  message: `Validator missing on collection "${collName}"`
302
- };
303
- const nodes = [];
304
- emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, issue, {
245
+ });
246
+ return [{
305
247
  status: "fail",
306
248
  kind: "validator",
307
249
  name: "validator",
@@ -311,18 +253,17 @@ function diffValidator(collName, live, expected, strict, collectionControlPolicy
311
253
  expected: canonicalize(expected.validator.jsonSchema),
312
254
  actual: null,
313
255
  children: []
314
- }, issues, nodes);
315
- return nodes;
256
+ }];
316
257
  }
317
258
  if (!expected.validator && live.validator) {
318
- const issue = {
259
+ const status = strict ? "fail" : "warn";
260
+ issues.push({
319
261
  kind: "extra_validator",
320
262
  table: collName,
321
263
  message: `Extra validator on collection "${collName}"`
322
- };
323
- const nodes = [];
324
- emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, issue, {
325
- status: strict ? "fail" : "warn",
264
+ });
265
+ return [{
266
+ status,
326
267
  kind: "validator",
327
268
  name: "validator",
328
269
  contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.validator`,
@@ -331,23 +272,21 @@ function diffValidator(collName, live, expected, strict, collectionControlPolicy
331
272
  expected: null,
332
273
  actual: canonicalize(live.validator.jsonSchema),
333
274
  children: []
334
- }, issues, nodes);
335
- return nodes;
275
+ }];
336
276
  }
337
277
  const liveVal = live.validator;
338
278
  const expectedVal = expected.validator;
339
279
  const liveSchema = canonicalize(liveVal.jsonSchema);
340
280
  const expectedSchema = canonicalize(expectedVal.jsonSchema);
341
281
  if (liveSchema !== expectedSchema || liveVal.validationLevel !== expectedVal.validationLevel || liveVal.validationAction !== expectedVal.validationAction) {
342
- const issue = {
282
+ issues.push({
343
283
  kind: "type_mismatch",
344
284
  table: collName,
345
285
  expected: expectedSchema,
346
286
  actual: liveSchema,
347
287
  message: `Validator mismatch on collection "${collName}"`
348
- };
349
- const nodes = [];
350
- emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, issue, {
288
+ });
289
+ return [{
351
290
  status: "fail",
352
291
  kind: "validator",
353
292
  name: "validator",
@@ -365,8 +304,7 @@ function diffValidator(collName, live, expected, strict, collectionControlPolicy
365
304
  validationAction: liveVal.validationAction
366
305
  },
367
306
  children: []
368
- }, issues, nodes);
369
- return nodes;
307
+ }];
370
308
  }
371
309
  return [{
372
310
  status: "pass",
@@ -380,18 +318,18 @@ function diffValidator(collName, live, expected, strict, collectionControlPolicy
380
318
  children: []
381
319
  }];
382
320
  }
383
- function diffOptions(collName, live, expected, strict, collectionControlPolicy, issues) {
321
+ function diffOptions(collName, live, expected, strict, issues) {
384
322
  if (!live.options && !expected.options) return [];
385
323
  if (!expected.options && live.options) {
386
- const issue = {
324
+ const status = strict ? "fail" : "warn";
325
+ issues.push({
387
326
  kind: "type_mismatch",
388
327
  table: collName,
389
328
  actual: canonicalize(live.options),
390
329
  message: `Extra collection options on "${collName}"`
391
- };
392
- const nodes = [];
393
- emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, issue, {
394
- status: strict ? "fail" : "warn",
330
+ });
331
+ return [{
332
+ status,
395
333
  kind: "options",
396
334
  name: "options",
397
335
  contractPath: `storage.namespaces.${UNBOUND_NAMESPACE_ID}.collections.${collName}.options`,
@@ -400,8 +338,7 @@ function diffOptions(collName, live, expected, strict, collectionControlPolicy,
400
338
  expected: null,
401
339
  actual: live.options,
402
340
  children: []
403
- }, issues, nodes);
404
- return nodes;
341
+ }];
405
342
  }
406
343
  if (deepEqual(live.options, expected.options)) return [{
407
344
  status: "pass",
@@ -414,15 +351,14 @@ function diffOptions(collName, live, expected, strict, collectionControlPolicy,
414
351
  actual: canonicalize(live.options),
415
352
  children: []
416
353
  }];
417
- const issue = {
354
+ issues.push({
418
355
  kind: "type_mismatch",
419
356
  table: collName,
420
357
  expected: canonicalize(expected.options),
421
358
  actual: canonicalize(live.options),
422
359
  message: `Collection options mismatch on "${collName}"`
423
- };
424
- const nodes = [];
425
- emitMongoIssueAndNodeUnderControlPolicy(collectionControlPolicy, issue, {
360
+ });
361
+ return [{
426
362
  status: "fail",
427
363
  kind: "options",
428
364
  name: "options",
@@ -432,8 +368,7 @@ function diffOptions(collName, live, expected, strict, collectionControlPolicy,
432
368
  expected: expected.options,
433
369
  actual: live.options,
434
370
  children: []
435
- }, issues, nodes);
436
- return nodes;
371
+ }];
437
372
  }
438
373
  //#endregion
439
374
  //#region src/core/schema-verify/canonicalize-introspection.ts
@@ -629,7 +564,7 @@ function verifyMongoSchema(options) {
629
564
  const { contract, schema, strict, context } = options;
630
565
  const startTime = Date.now();
631
566
  const { live: canonicalLive, expected: canonicalExpected } = canonicalizeSchemasForVerification(schema, contractToMongoSchemaIR(contract));
632
- const { root, issues, counts } = diffMongoSchemas(canonicalLive, canonicalExpected, strict, resolveMongoCollectionControlPolicy(contract));
567
+ const { root, issues, counts } = diffMongoSchemas(canonicalLive, canonicalExpected, strict);
633
568
  const ok = counts.fail === 0;
634
569
  const profileHash = typeof contract.profileHash === "string" ? contract.profileHash : "";
635
570
  return {
@@ -654,12 +589,7 @@ function verifyMongoSchema(options) {
654
589
  timings: { total: Date.now() - startTime }
655
590
  };
656
591
  }
657
- function resolveMongoCollectionControlPolicy(contract) {
658
- const collections = contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.collections ?? {};
659
- const defaultControl = contract.defaultControl;
660
- return (collectionName) => effectiveControlPolicy(collections[collectionName]?.control, defaultControl);
661
- }
662
592
  //#endregion
663
593
  export { contractToMongoSchemaIR as i, canonicalizeSchemasForVerification as n, diffMongoSchemas as r, verifyMongoSchema as t };
664
594
 
665
- //# sourceMappingURL=verify-mongo-schema-BXJqoE59.mjs.map
595
+ //# sourceMappingURL=verify-mongo-schema-Bhxvdah3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-mongo-schema-Bhxvdah3.mjs","names":["MongoSchemaIRCtor","MongoSchemaCollectionCtor","MongoSchemaIndexCtor","MongoSchemaCollectionOptionsCtor"],"sources":["../src/core/contract-to-schema.ts","../src/core/schema-diff.ts","../src/core/schema-verify/canonicalize-introspection.ts","../src/core/schema-verify/verify-mongo-schema.ts"],"sourcesContent":["import type {\n MongoCollection,\n MongoCollectionOptions,\n MongoContract,\n MongoIndex,\n MongoValidator,\n} from '@prisma-next/mongo-contract';\nimport {\n MongoSchemaCollection,\n MongoSchemaCollectionOptions,\n MongoSchemaIndex,\n MongoSchemaIR,\n MongoSchemaValidator,\n} from '@prisma-next/mongo-schema-ir';\n\nfunction stripIrKind(node: object): Record<string, unknown> {\n const { kind: _kind, ...rest } = node as Record<string, unknown>;\n return rest;\n}\n\nfunction convertIndex(index: MongoIndex): MongoSchemaIndex {\n return new MongoSchemaIndex({\n keys: index.keys,\n unique: index.unique,\n sparse: index.sparse,\n expireAfterSeconds: index.expireAfterSeconds,\n partialFilterExpression: index.partialFilterExpression,\n wildcardProjection: index.wildcardProjection,\n collation: index.collation,\n weights: index.weights,\n default_language: index.default_language,\n language_override: index.language_override,\n });\n}\n\nfunction convertValidator(v: MongoValidator): MongoSchemaValidator {\n return new MongoSchemaValidator({\n jsonSchema: v.jsonSchema,\n validationLevel: v.validationLevel,\n validationAction: v.validationAction,\n });\n}\n\nfunction convertOptions(o: MongoCollectionOptions): MongoSchemaCollectionOptions {\n return new MongoSchemaCollectionOptions({\n ...(o.capped !== undefined && { capped: o.capped }),\n ...(o.timeseries !== undefined && {\n timeseries: {\n timeField: o.timeseries.timeField,\n ...(o.timeseries.metaField !== undefined && { metaField: o.timeseries.metaField }),\n ...(o.timeseries.granularity !== undefined && { granularity: o.timeseries.granularity }),\n },\n }),\n ...(o.collation !== undefined && {\n collation: stripIrKind(o.collation),\n }),\n ...(o.changeStreamPreAndPostImages !== undefined && {\n changeStreamPreAndPostImages: { enabled: o.changeStreamPreAndPostImages.enabled },\n }),\n ...(o.clusteredIndex !== undefined && { clusteredIndex: o.clusteredIndex }),\n });\n}\n\nfunction convertCollection(name: string, def: MongoCollection): MongoSchemaCollection {\n const indexes = (def.indexes ?? []).map(convertIndex);\n return new MongoSchemaCollection({\n name,\n indexes,\n ...(def.validator != null && { validator: convertValidator(def.validator) }),\n ...(def.options != null && { options: convertOptions(def.options) }),\n });\n}\n\nexport function contractToMongoSchemaIR(contract: MongoContract | null): MongoSchemaIR {\n if (!contract) {\n return new MongoSchemaIR([]);\n }\n\n const collections: MongoSchemaCollection[] = [];\n for (const ns of Object.values(contract.storage.namespaces)) {\n for (const [name, def] of Object.entries(ns.collections)) {\n collections.push(convertCollection(name, def));\n }\n }\n\n return new MongoSchemaIR(collections);\n}\n","import type {\n SchemaIssue,\n SchemaVerificationNode,\n} from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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.namespaces.${UNBOUND_NAMESPACE_ID}.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","/**\n * Canonicalizes a live (introspected) `MongoSchemaIR` against the expected\n * (contract-built) IR before diffing. MongoDB applies server-side defaults\n * to several option/index families that are absent from authored contracts,\n * which would otherwise cause `verifyMongoSchema` to report false-positive\n * drift on a fresh `migrate` run.\n *\n * The normalization is contract-aware where it has to be: server defaults\n * are stripped from the live IR for fields the contract did not specify, so\n * a contract that *does* specify a value still gets compared faithfully.\n *\n * Symmetric defaults — like `changeStreamPreAndPostImages: { enabled: false }`,\n * which is equivalent to \"absent\" on both sides — are stripped from both IRs\n * so either authoring style verifies.\n */\n\nimport type {\n MongoSchemaCollection,\n MongoSchemaCollectionOptions,\n MongoSchemaIndex,\n MongoSchemaIR,\n} from '@prisma-next/mongo-schema-ir';\nimport {\n MongoSchemaCollection as MongoSchemaCollectionCtor,\n MongoSchemaCollectionOptions as MongoSchemaCollectionOptionsCtor,\n MongoSchemaIndex as MongoSchemaIndexCtor,\n MongoSchemaIR as MongoSchemaIRCtor,\n} from '@prisma-next/mongo-schema-ir';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport interface CanonicalizedSchemas {\n readonly live: MongoSchemaIR;\n readonly expected: MongoSchemaIR;\n}\n\nexport function canonicalizeSchemasForVerification(\n live: MongoSchemaIR,\n expected: MongoSchemaIR,\n): CanonicalizedSchemas {\n const expectedByName = new Map<string, MongoSchemaCollection>();\n for (const c of expected.collections) expectedByName.set(c.name, c);\n\n const liveByName = new Map<string, MongoSchemaCollection>();\n for (const c of live.collections) liveByName.set(c.name, c);\n\n const canonicalLive = live.collections.map((c) =>\n canonicalizeLiveCollection(c, expectedByName.get(c.name)),\n );\n const canonicalExpected = expected.collections.map((c) =>\n canonicalizeExpectedCollection(c, liveByName.get(c.name)),\n );\n\n return {\n live: new MongoSchemaIRCtor(canonicalLive),\n expected: new MongoSchemaIRCtor(canonicalExpected),\n };\n}\n\nfunction canonicalizeLiveCollection(\n liveColl: MongoSchemaCollection,\n expectedColl: MongoSchemaCollection | undefined,\n): MongoSchemaCollection {\n const expectedIndexes = expectedColl?.indexes ?? [];\n const indexes = liveColl.indexes.map((idx) =>\n canonicalizeLiveIndex(idx, findExpectedIndexCounterpart(idx, expectedIndexes)),\n );\n\n const options = liveColl.options\n ? canonicalizeLiveOptions(liveColl.options, expectedColl?.options)\n : undefined;\n\n return new MongoSchemaCollectionCtor({\n name: liveColl.name,\n indexes,\n ...ifDefined('validator', liveColl.validator),\n ...ifDefined('options', options),\n });\n}\n\nfunction canonicalizeExpectedCollection(\n expectedColl: MongoSchemaCollection,\n liveColl: MongoSchemaCollection | undefined,\n): MongoSchemaCollection {\n // Symmetric text-index key ordering: a contract-shaped text index preserves\n // the user-authored field order, but the introspected counterpart comes\n // back from MongoDB with `weights` keys in alphabetical order, so we\n // canonicalize both sides to alphabetical text-key order. The order of\n // text fields within the text block is semantically irrelevant — relevance\n // is governed by `weights`, not key order.\n const indexes = expectedColl.indexes.map(canonicalizeTextIndexKeyOrder);\n\n const options = expectedColl.options\n ? canonicalizeExpectedOptions(expectedColl.options, liveColl?.options)\n : undefined;\n\n return new MongoSchemaCollectionCtor({\n name: expectedColl.name,\n indexes,\n ...ifDefined('validator', expectedColl.validator),\n ...ifDefined('options', options),\n });\n}\n\nfunction canonicalizeTextIndexKeyOrder(index: MongoSchemaIndex): MongoSchemaIndex {\n const hasTextKey = index.keys.some((k) => k.direction === 'text');\n if (!hasTextKey) return index;\n return new MongoSchemaIndexCtor({\n keys: sortTextKeys(index.keys),\n unique: index.unique,\n ...ifDefined('sparse', index.sparse),\n ...ifDefined('expireAfterSeconds', index.expireAfterSeconds),\n ...ifDefined('partialFilterExpression', index.partialFilterExpression),\n ...ifDefined('wildcardProjection', index.wildcardProjection),\n ...ifDefined('collation', index.collation),\n ...ifDefined('weights', index.weights),\n ...ifDefined('default_language', index.default_language),\n ...ifDefined('language_override', index.language_override),\n });\n}\n\n/**\n * Returns a copy of `keys` with text-direction entries sorted alphabetically\n * while preserving the relative position of non-text entries. Compound text\n * indexes (`{a: 1, _fts: 'text', _ftsx: 1, b: 1}`) keep their scalar\n * prefix/suffix layout; only the contiguous text block is reordered.\n */\nfunction sortTextKeys(\n keys: ReadonlyArray<{\n readonly field: string;\n readonly direction: 'text' | 1 | -1 | '2dsphere' | '2d' | 'hashed';\n }>,\n): ReadonlyArray<{\n readonly field: string;\n readonly direction: 'text' | 1 | -1 | '2dsphere' | '2d' | 'hashed';\n}> {\n const textEntries = keys.filter((k) => k.direction === 'text');\n if (textEntries.length <= 1) return keys;\n const sortedText = [...textEntries].sort((a, b) => a.field.localeCompare(b.field));\n let textIdx = 0;\n return keys.map((k) => {\n if (k.direction !== 'text') return k;\n const next = sortedText[textIdx++];\n /* v8 ignore next 3 -- @preserve invariant guard: textIdx is always < sortedText.length here because we only consume sortedText for text-direction entries and sortedText is built from the same filter. */\n if (next === undefined) {\n throw new Error('sortTextKeys: text-key counts mismatched');\n }\n return next;\n });\n}\n\nfunction canonicalizeLiveIndex(\n liveIndex: MongoSchemaIndex,\n expectedIndex: MongoSchemaIndex | undefined,\n): MongoSchemaIndex {\n const projectedKeys = sortTextKeys(projectTextIndexKeys(liveIndex));\n const collation = liveIndex.collation\n ? stripUnspecifiedFields(liveIndex.collation, expectedIndex?.collation)\n : liveIndex.collation;\n\n // Text-index server defaults: when the contract did not set\n // `weights`/`default_language`/`language_override`, MongoDB applies\n // `weights = {<field>: 1, ...}` (uniform), `'english'`, and `'language'`\n // respectively. Strip them from live *only* when the live value matches\n // those defaults — preserving non-default live values lets the verifier\n // surface drift when the live index is tampered (e.g. weights tuned\n // out-of-band, custom `default_language`/`language_override`) even though\n // the contract authored neither.\n const weights =\n expectedIndex?.weights === undefined && hasDefaultTextWeights(projectedKeys, liveIndex.weights)\n ? undefined\n : liveIndex.weights;\n const default_language =\n expectedIndex?.default_language === undefined && liveIndex.default_language === 'english'\n ? undefined\n : liveIndex.default_language;\n const language_override =\n expectedIndex?.language_override === undefined && liveIndex.language_override === 'language'\n ? undefined\n : liveIndex.language_override;\n\n return new MongoSchemaIndexCtor({\n keys: projectedKeys,\n unique: liveIndex.unique,\n ...ifDefined('sparse', liveIndex.sparse),\n ...ifDefined('expireAfterSeconds', liveIndex.expireAfterSeconds),\n ...ifDefined('partialFilterExpression', liveIndex.partialFilterExpression),\n ...ifDefined('wildcardProjection', liveIndex.wildcardProjection),\n ...ifDefined('collation', collation),\n ...ifDefined('weights', weights),\n ...ifDefined('default_language', default_language),\n ...ifDefined('language_override', language_override),\n });\n}\n\n/**\n * Locate the contract-side index that corresponds to a live index for the\n * purpose of contract-aware normalization. We deliberately match by the\n * *projected* (contract-shaped) key list — so a live `_fts/_ftsx` index\n * resolves to a contract `{title: 'text', body: 'text'}` index — and pick\n * the first match. Contracts very rarely contain duplicate-key indexes; if\n * we have no counterpart we fall back to no normalization for that index.\n */\nfunction findExpectedIndexCounterpart(\n liveIndex: MongoSchemaIndex,\n expectedIndexes: ReadonlyArray<MongoSchemaIndex>,\n): MongoSchemaIndex | undefined {\n const projectedLiveKeys = sortTextKeys(projectTextIndexKeys(liveIndex));\n const liveKeySig = projectedLiveKeys.map((k) => `${k.field}:${k.direction}`).join(',');\n for (const expected of expectedIndexes) {\n const expectedKeySig = sortTextKeys(expected.keys)\n .map((k) => `${k.field}:${k.direction}`)\n .join(',');\n if (expectedKeySig === liveKeySig) return expected;\n }\n return undefined;\n}\n\n/**\n * MongoDB expands a contract-shaped text index like\n * `[{title: 'text'}, {body: 'text'}]` into its internal weighted vector\n * representation `[{_fts: 'text'}, {_ftsx: 1}]`. We project back to\n * contract-shaped keys via `weights`, iterating in whatever order MongoDB\n * returns them (alphabetical, in practice). `sortTextKeys` is applied\n * downstream to canonicalize the order on both sides, so this projection\n * does not depend on a specific iteration order.\n */\nfunction projectTextIndexKeys(liveIndex: MongoSchemaIndex): ReadonlyArray<{\n readonly field: string;\n readonly direction: 'text' | 1 | -1 | '2dsphere' | '2d' | 'hashed';\n}> {\n const isTextIndex =\n liveIndex.keys.length >= 1 &&\n liveIndex.keys.some((k) => k.field === '_fts' && k.direction === 'text');\n\n if (!isTextIndex || !liveIndex.weights) return liveIndex.keys;\n\n const textKeys = Object.keys(liveIndex.weights).map((field) => ({\n field,\n direction: 'text' as const,\n }));\n\n // Splice the projected text fields into the original `_fts/_ftsx` slot so\n // compound text indexes that mix scalar prefixes *and* suffixes — e.g.\n // `[prefix, _fts, _ftsx, suffix]` — keep their original layout. Flattening\n // scalars first would yield `[prefix, suffix, ...text]`, which `sortTextKeys`\n // (downstream) cannot recover because it only reorders text-direction\n // entries within their existing positions. MongoDB always emits exactly one\n // `_fts`/`_ftsx` pair per index, so we don't need to guard against\n // duplicates.\n type IndexKey = (typeof liveIndex.keys)[number];\n const projectedKeys: IndexKey[] = [];\n for (const key of liveIndex.keys) {\n if (key.field === '_ftsx') continue;\n if (key.field === '_fts') {\n projectedKeys.push(...textKeys);\n continue;\n }\n projectedKeys.push(key);\n }\n return projectedKeys;\n}\n\n/**\n * MongoDB's server-default `weights` for an authored-without-weights text\n * index assigns `1` to every text-direction field. Returns `true` only when\n * `liveWeights` is exactly that uniform shape (every projected text-direction\n * key weighted at `1`) so the canonicalizer leaves non-default weights —\n * including out-of-band relevance tweaks — visible to the verifier.\n *\n * `projectTextIndexKeys` derives text-direction keys from the live weights\n * map, so the count is guaranteed to match; we only have to check the value\n * shape.\n */\nfunction hasDefaultTextWeights(\n projectedKeys: ReadonlyArray<{\n readonly field: string;\n readonly direction: 'text' | 1 | -1 | '2dsphere' | '2d' | 'hashed';\n }>,\n liveWeights: MongoSchemaIndex['weights'],\n): boolean {\n if (liveWeights === undefined) return false;\n const textFields = projectedKeys.filter((k) => k.direction === 'text').map((k) => k.field);\n return textFields.every((field) => liveWeights[field] === 1);\n}\n\nfunction canonicalizeLiveOptions(\n liveOptions: MongoSchemaCollectionOptions,\n expectedOptions: MongoSchemaCollectionOptions | undefined,\n): MongoSchemaCollectionOptions | undefined {\n const collation = liveOptions.collation\n ? stripUnspecifiedFields(liveOptions.collation, expectedOptions?.collation)\n : undefined;\n\n // Timeseries: drop `bucketMaxSpanSeconds` (and any other server-applied\n // extras) when the contract did not specify them.\n const timeseries = liveOptions.timeseries\n ? (stripUnspecifiedFields(\n liveOptions.timeseries as Record<string, unknown>,\n expectedOptions?.timeseries as Record<string, unknown> | undefined,\n ) as MongoSchemaCollectionOptions['timeseries'])\n : undefined;\n\n // ClusteredIndex: drop `key`, `unique`, `v` and any other server-applied\n // extras when the contract did not specify them.\n const clusteredIndex = liveOptions.clusteredIndex\n ? (stripUnspecifiedFields(\n liveOptions.clusteredIndex as Record<string, unknown>,\n expectedOptions?.clusteredIndex as Record<string, unknown> | undefined,\n ) as MongoSchemaCollectionOptions['clusteredIndex'])\n : undefined;\n\n // changeStreamPreAndPostImages: `{enabled: false}` is equivalent to\n // \"absent\". Strip it from live so it round-trips with a contract that\n // omits the field, and is symmetric with the expected-side stripping.\n const changeStreamPreAndPostImages = isDisabledChangeStream(\n liveOptions.changeStreamPreAndPostImages,\n )\n ? undefined\n : liveOptions.changeStreamPreAndPostImages;\n\n const hasMeaningful =\n liveOptions.capped || timeseries || collation || changeStreamPreAndPostImages || clusteredIndex;\n if (!hasMeaningful) return undefined;\n\n return new MongoSchemaCollectionOptionsCtor({\n ...ifDefined('capped', liveOptions.capped),\n ...ifDefined('timeseries', timeseries),\n ...ifDefined('collation', collation),\n ...ifDefined('changeStreamPreAndPostImages', changeStreamPreAndPostImages),\n ...ifDefined('clusteredIndex', clusteredIndex),\n });\n}\n\nfunction canonicalizeExpectedOptions(\n expectedOptions: MongoSchemaCollectionOptions,\n _liveOptions: MongoSchemaCollectionOptions | undefined,\n): MongoSchemaCollectionOptions | undefined {\n // Symmetric: a contract `{enabled: false}` is equivalent to absent.\n const changeStreamPreAndPostImages = isDisabledChangeStream(\n expectedOptions.changeStreamPreAndPostImages,\n )\n ? undefined\n : expectedOptions.changeStreamPreAndPostImages;\n\n const hasMeaningful =\n expectedOptions.capped ||\n expectedOptions.timeseries ||\n expectedOptions.collation ||\n changeStreamPreAndPostImages ||\n expectedOptions.clusteredIndex;\n if (!hasMeaningful) return undefined;\n\n return new MongoSchemaCollectionOptionsCtor({\n ...ifDefined('capped', expectedOptions.capped),\n ...ifDefined('timeseries', expectedOptions.timeseries),\n ...ifDefined('collation', expectedOptions.collation),\n ...ifDefined('changeStreamPreAndPostImages', changeStreamPreAndPostImages),\n ...ifDefined('clusteredIndex', expectedOptions.clusteredIndex),\n });\n}\n\nfunction isDisabledChangeStream(value: { enabled: boolean } | undefined): boolean {\n return value !== undefined && value.enabled === false;\n}\n\n/**\n * Returns a copy of `live` containing only the keys that `expected` defines.\n * Used for option families whose individual sub-fields are server-extended\n * with platform defaults (collation, timeseries, clusteredIndex), so the\n * verifier should compare only what the contract actually authored.\n *\n * When `expected` is `undefined` — i.e. the contract authored nothing for\n * this whole option family but the live IR has it — we return `live`\n * unchanged so the verifier still sees the entire live block and can\n * surface it as drift. (Returning `undefined` here would silently strip a\n * server-attached collation/timeseries/clusteredIndex that the contract\n * never asked for, hiding real drift.)\n */\nfunction stripUnspecifiedFields(\n live: Record<string, unknown>,\n expected: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n if (expected === undefined) return live;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(expected)) {\n if (Object.hasOwn(live, key)) out[key] = live[key];\n }\n return out;\n}\n","import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n OperationContext,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { VERIFY_CODE_SCHEMA_FAILURE } from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { contractToMongoSchemaIR } from '../contract-to-schema';\nimport { diffMongoSchemas } from '../schema-diff';\nimport { canonicalizeSchemasForVerification } from './canonicalize-introspection';\n\nexport interface VerifyMongoSchemaOptions {\n readonly contract: MongoContract;\n readonly schema: MongoSchemaIR;\n readonly strict: boolean;\n readonly context?: OperationContext;\n /**\n * Active framework components participating in this composition. Mongo\n * verification does not currently consult them, but the parameter exists\n * for parity with `verifySqlSchema` so callers can pass the same envelope.\n */\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;\n}\n\nexport function verifyMongoSchema(options: VerifyMongoSchemaOptions): VerifyDatabaseSchemaResult {\n const { contract, schema, strict, context } = options;\n const startTime = Date.now();\n\n const expectedIR = contractToMongoSchemaIR(contract);\n // Strip server-applied defaults (and authored equivalents) before diffing so\n // the verifier compares like-with-like — see `canonicalize-introspection.ts`.\n const { live: canonicalLive, expected: canonicalExpected } = canonicalizeSchemasForVerification(\n schema,\n expectedIR,\n );\n const { root, issues, counts } = diffMongoSchemas(canonicalLive, canonicalExpected, strict);\n\n const ok = counts.fail === 0;\n const profileHash = typeof contract.profileHash === 'string' ? contract.profileHash : '';\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 ...(profileHash ? { profileHash } : {}),\n },\n target: { expected: contract.target },\n schema: { issues, root, counts },\n meta: {\n strict,\n ...ifDefined('contractPath', context?.contractPath),\n ...ifDefined('configPath', context?.configPath),\n },\n timings: { total: Date.now() - startTime },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
2
  "name": "@prisma-next/family-mongo",
3
- "version": "0.12.0-dev.17",
3
+ "version": "0.12.0-dev.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Mongo family descriptor for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/contract": "0.12.0-dev.17",
10
- "@prisma-next/emitter": "0.12.0-dev.17",
11
- "@prisma-next/errors": "0.12.0-dev.17",
12
- "@prisma-next/framework-components": "0.12.0-dev.17",
13
- "@prisma-next/migration-tools": "0.12.0-dev.17",
14
- "@prisma-next/mongo-contract": "0.12.0-dev.17",
15
- "@prisma-next/mongo-emitter": "0.12.0-dev.17",
16
- "@prisma-next/mongo-query-ast": "0.12.0-dev.17",
17
- "@prisma-next/mongo-schema-ir": "0.12.0-dev.17",
18
- "@prisma-next/utils": "0.12.0-dev.17",
9
+ "@prisma-next/contract": "0.12.0-dev.2",
10
+ "@prisma-next/emitter": "0.12.0-dev.2",
11
+ "@prisma-next/errors": "0.12.0-dev.2",
12
+ "@prisma-next/framework-components": "0.12.0-dev.2",
13
+ "@prisma-next/migration-tools": "0.12.0-dev.2",
14
+ "@prisma-next/mongo-contract": "0.12.0-dev.2",
15
+ "@prisma-next/mongo-emitter": "0.12.0-dev.2",
16
+ "@prisma-next/mongo-query-ast": "0.12.0-dev.2",
17
+ "@prisma-next/mongo-schema-ir": "0.12.0-dev.2",
18
+ "@prisma-next/utils": "0.12.0-dev.2",
19
19
  "arktype": "^2.2.0",
20
20
  "pathe": "^2.0.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@prisma-next/mongo-contract-ts": "0.12.0-dev.17",
24
- "@prisma-next/test-utils": "0.12.0-dev.17",
25
- "@prisma-next/tsconfig": "0.12.0-dev.17",
26
- "@prisma-next/tsdown": "0.12.0-dev.17",
23
+ "@prisma-next/mongo-contract-ts": "0.12.0-dev.2",
24
+ "@prisma-next/test-utils": "0.12.0-dev.2",
25
+ "@prisma-next/tsconfig": "0.12.0-dev.2",
26
+ "@prisma-next/tsdown": "0.12.0-dev.2",
27
27
  "tsdown": "0.22.0",
28
28
  "typescript": "5.9.3",
29
29
  "vitest": "4.1.6"
@@ -47,7 +47,6 @@
47
47
  "./ir": "./dist/ir.mjs",
48
48
  "./migration": "./dist/migration.mjs",
49
49
  "./pack": "./dist/pack.mjs",
50
- "./runtime": "./dist/runtime.mjs",
51
50
  "./schema-verify": "./dist/schema-verify.mjs",
52
51
  "./package.json": "./package.json"
53
52
  },
@@ -1,4 +1,4 @@
1
- import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  ControlAdapterDescriptor,
4
4
  ControlAdapterInstance,
@@ -79,25 +79,9 @@ export interface MongoControlAdapter<TTarget extends string = string>
79
79
  writeLedgerEntry(
80
80
  driver: ControlDriverInstance<'mongo', TTarget>,
81
81
  space: string,
82
- entry: {
83
- readonly edgeId: string;
84
- readonly from: string;
85
- readonly to: string;
86
- readonly migrationName: string;
87
- readonly migrationHash: string;
88
- readonly operations: readonly unknown[];
89
- },
82
+ entry: { readonly edgeId: string; readonly from: string; readonly to: string },
90
83
  ): Promise<void>;
91
84
 
92
- /**
93
- * Reads the per-migration ledger journal for `space` in apply order.
94
- * Returns an empty array when no ledger entries exist for that space.
95
- */
96
- readLedger(
97
- driver: ControlDriverInstance<'mongo', TTarget>,
98
- space: string,
99
- ): Promise<readonly LedgerEntryRecord[]>;
100
-
101
85
  /**
102
86
  * Introspects the live database and returns a `MongoSchemaIR`.
103
87
  */
@@ -1,8 +1,4 @@
1
- import type {
2
- Contract,
3
- ContractMarkerRecord,
4
- LedgerEntryRecord,
5
- } from '@prisma-next/contract/types';
1
+ import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
6
2
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
7
3
  import type {
8
4
  ControlDriverInstance,
@@ -80,6 +76,19 @@ function asValidatedMongoContract(contract: unknown): MongoContract {
80
76
  return contract as MongoContract;
81
77
  }
82
78
 
79
+ function isMongoControlAdapter(value: unknown): value is MongoControlAdapter<'mongo'> {
80
+ return (
81
+ typeof value === 'object' &&
82
+ value !== null &&
83
+ 'readMarker' in value &&
84
+ typeof (value as { readMarker: unknown }).readMarker === 'function' &&
85
+ 'readAllMarkers' in value &&
86
+ typeof (value as { readAllMarkers: unknown }).readAllMarkers === 'function' &&
87
+ 'introspectSchema' in value &&
88
+ typeof (value as { introspectSchema: unknown }).introspectSchema === 'function'
89
+ );
90
+ }
91
+
83
92
  function buildVerifyResult(opts: {
84
93
  ok: boolean;
85
94
  code?: string;
@@ -155,7 +164,13 @@ export function createMongoFamilyInstance(controlStack: ControlStack): MongoCont
155
164
  if (!adapter) {
156
165
  throw new Error('Mongo family requires an adapter descriptor in ControlStack');
157
166
  }
158
- return adapter.create(controlStack as ControlStack<'mongo', 'mongo'>);
167
+ const controlAdapter = adapter.create(controlStack as ControlStack<'mongo', 'mongo'>);
168
+ if (!isMongoControlAdapter(controlAdapter)) {
169
+ throw new Error(
170
+ 'Adapter does not implement MongoControlAdapter (missing readMarker, readAllMarkers, or introspectSchema)',
171
+ );
172
+ }
173
+ return controlAdapter;
159
174
  };
160
175
 
161
176
  // The family-level driver type is `ControlDriverInstance<'mongo', string>`,
@@ -356,10 +371,6 @@ export function createMongoFamilyInstance(controlStack: ControlStack): MongoCont
356
371
  return getControlAdapter().readAllMarkers(asMongoDriver(options.driver));
357
372
  },
358
373
 
359
- async readLedger(options): Promise<readonly LedgerEntryRecord[]> {
360
- return getControlAdapter().readLedger(asMongoDriver(options.driver), options.space);
361
- },
362
-
363
374
  async introspect(options): Promise<MongoSchemaIR> {
364
375
  return getControlAdapter().introspectSchema(asMongoDriver(options.driver));
365
376
  },