@prisma-next/family-mongo 0.4.0-dev.9 → 0.4.2

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