@prisma-next/family-mongo 0.7.0-dev.7 → 0.8.0-dev.1

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.
Files changed (37) hide show
  1. package/README.md +22 -9
  2. package/dist/control-adapter.d.mts +75 -0
  3. package/dist/control-adapter.d.mts.map +1 -0
  4. package/dist/control-adapter.mjs +1 -0
  5. package/dist/control.d.mts +60 -16
  6. package/dist/control.d.mts.map +1 -1
  7. package/dist/control.mjs +257 -276
  8. package/dist/control.mjs.map +1 -1
  9. package/dist/ir.d.mts +131 -0
  10. package/dist/ir.d.mts.map +1 -0
  11. package/dist/ir.mjs +54 -0
  12. package/dist/ir.mjs.map +1 -0
  13. package/dist/mongo-contract-serializer-Co3EaTVj.mjs +98 -0
  14. package/dist/mongo-contract-serializer-Co3EaTVj.mjs.map +1 -0
  15. package/dist/schema-verify.d.mts +22 -2
  16. package/dist/schema-verify.d.mts.map +1 -0
  17. package/dist/schema-verify.mjs +1 -1
  18. package/dist/verify-mongo-schema-BL7t9YTB.mjs +592 -0
  19. package/dist/verify-mongo-schema-BL7t9YTB.mjs.map +1 -0
  20. package/package.json +18 -20
  21. package/src/core/contract-to-schema.ts +84 -0
  22. package/src/core/control-adapter.ts +97 -0
  23. package/src/core/control-instance.ts +275 -272
  24. package/src/core/control-target-descriptor.ts +26 -0
  25. package/src/core/control-types.ts +6 -4
  26. package/src/core/ir/mongo-contract-serializer-base.ts +124 -0
  27. package/src/core/ir/mongo-contract-serializer.ts +18 -0
  28. package/src/core/ir/mongo-schema-verifier-base.ts +87 -0
  29. package/src/core/operation-preview.ts +131 -0
  30. package/src/core/schema-diff.ts +402 -0
  31. package/src/core/schema-verify/canonicalize-introspection.ts +389 -0
  32. package/src/core/schema-verify/verify-mongo-schema.ts +60 -0
  33. package/src/exports/control-adapter.ts +1 -0
  34. package/src/exports/control.ts +8 -1
  35. package/src/exports/ir.ts +3 -0
  36. package/src/exports/schema-verify.ts +4 -2
  37. package/src/core/mongo-target-descriptor.ts +0 -180
@@ -1,4 +1,3 @@
1
- import { introspectSchema } from '@prisma-next/adapter-mongo/control';
2
1
  import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
3
2
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
4
3
  import type {
@@ -22,291 +21,67 @@ import {
22
21
  } from '@prisma-next/framework-components/control';
23
22
  import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
24
23
  import type { MongoContract } from '@prisma-next/mongo-contract';
25
- import { validateMongoContract } from '@prisma-next/mongo-contract';
26
24
  import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
27
- import {
28
- formatMongoOperations,
29
- initMarker,
30
- readAllMarkers,
31
- readMarker,
32
- updateMarker,
33
- } from '@prisma-next/target-mongo/control';
34
- import { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';
35
25
  import { ifDefined } from '@prisma-next/utils/defined';
36
- import type { Db } from 'mongodb';
26
+ import type { MongoControlAdapter, MongoControlAdapterDescriptor } from './control-adapter';
37
27
  import type { MongoControlExtensionDescriptor } from './control-types';
28
+ import { MongoContractSerializer } from './ir/mongo-contract-serializer';
29
+ import { mongoOperationsToPreview } from './operation-preview';
38
30
  import { mongoSchemaToView } from './schema-to-view';
31
+ import { verifyMongoSchema } from './schema-verify/verify-mongo-schema';
39
32
 
40
33
  export interface MongoControlFamilyInstance
41
34
  extends ControlFamilyInstance<'mongo', MongoSchemaIR>,
42
35
  SchemaViewCapable<MongoSchemaIR>,
43
36
  OperationPreviewCapable {
37
+ /**
38
+ * Validates the JSON contract envelope structurally and returns it
39
+ * cast to the framework `Contract` shape. The per-target serializer
40
+ * (held on the Mongo target descriptor) does the class-form wrap for
41
+ * downstream consumers; the family only needs the validated data.
42
+ */
44
43
  validateContract(contractJson: unknown): Contract;
45
44
  }
46
45
 
47
- function extractDb(driver: ControlDriverInstance<'mongo', string>): Db {
48
- const mongoDriver = driver as ControlDriverInstance<'mongo', string> & { db?: Db };
49
- if (!mongoDriver.db) {
50
- throw new Error(
51
- 'Mongo control driver does not expose a db property. ' +
52
- 'Use createMongoControlDriver() from @prisma-next/adapter-mongo/control.',
53
- );
54
- }
55
- return mongoDriver.db;
46
+ function deserializeMongoContract(contractJson: unknown): MongoContract {
47
+ // Structural validation only the per-target serializer wraps the
48
+ // result in a class-form `MongoTargetContract` for downstream
49
+ // consumers (CLI, runner). The family-instance methods only read
50
+ // hash/target fields off the validated shape, so the unwrapped
51
+ // `MongoContract` is sufficient here and avoids a family→target
52
+ // runtime dep.
53
+ return new MongoContractSerializer().deserializeContract(contractJson);
56
54
  }
57
55
 
58
- class MongoFamilyInstance implements MongoControlFamilyInstance {
59
- readonly familyId = 'mongo' as const;
60
-
61
- validateContract(contractJson: unknown): Contract {
62
- const validated = validateMongoContract<MongoContract>(contractJson);
63
- // MongoContract and Contract share structure but are typed independently;
64
- // validateMongoContract guarantees the shape, so the double cast is safe.
65
- return validated.contract as unknown as Contract;
66
- }
67
-
68
- async verify(options: {
69
- readonly driver: ControlDriverInstance<'mongo', string>;
70
- readonly contract: unknown;
71
- readonly expectedTargetId: string;
72
- readonly contractPath: string;
73
- readonly configPath?: string;
74
- }): Promise<VerifyDatabaseResult> {
75
- const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;
76
- const startTime = Date.now();
77
-
78
- const validated = validateMongoContract<MongoContract>(rawContract);
79
- const contract = validated.contract;
80
-
81
- const contractStorageHash = contract.storage.storageHash;
82
- const contractProfileHash = contract.profileHash;
83
- const contractTarget = contract.target;
84
-
85
- const baseOpts = {
86
- contractStorageHash,
87
- contractProfileHash,
88
- expectedTargetId,
89
- contractPath,
90
- ...ifDefined('configPath', configPath),
91
- };
92
-
93
- if (contractTarget !== expectedTargetId) {
94
- return buildVerifyResult({
95
- ...baseOpts,
96
- ok: false,
97
- code: VERIFY_CODE_TARGET_MISMATCH,
98
- summary: 'Target mismatch',
99
- actualTargetId: contractTarget,
100
- totalTime: Date.now() - startTime,
101
- });
102
- }
103
-
104
- const db = extractDb(driver);
105
- const marker = await readMarker(db, APP_SPACE_ID);
106
-
107
- if (!marker) {
108
- return buildVerifyResult({
109
- ...baseOpts,
110
- ok: false,
111
- code: VERIFY_CODE_MARKER_MISSING,
112
- summary: 'Marker missing',
113
- totalTime: Date.now() - startTime,
114
- });
115
- }
116
-
117
- if (marker.storageHash !== contractStorageHash) {
118
- return buildVerifyResult({
119
- ...baseOpts,
120
- ok: false,
121
- code: VERIFY_CODE_HASH_MISMATCH,
122
- summary: 'Hash mismatch',
123
- marker,
124
- totalTime: Date.now() - startTime,
125
- });
126
- }
127
-
128
- if (contractProfileHash && marker.profileHash !== contractProfileHash) {
129
- return buildVerifyResult({
130
- ...baseOpts,
131
- ok: false,
132
- code: VERIFY_CODE_HASH_MISMATCH,
133
- summary: 'Hash mismatch',
134
- marker,
135
- totalTime: Date.now() - startTime,
136
- });
137
- }
138
-
139
- return buildVerifyResult({
140
- ...baseOpts,
141
- ok: true,
142
- summary: 'Database matches contract',
143
- marker,
144
- totalTime: Date.now() - startTime,
145
- });
146
- }
147
-
148
- async schemaVerify(options: {
149
- readonly driver: ControlDriverInstance<'mongo', string>;
150
- readonly contract: unknown;
151
- readonly strict: boolean;
152
- readonly contractPath: string;
153
- readonly configPath?: string;
154
- readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;
155
- }): Promise<VerifyDatabaseSchemaResult> {
156
- const { driver, contract: rawContract, strict, contractPath, configPath } = options;
157
-
158
- const validated = validateMongoContract<MongoContract>(rawContract);
159
- const contract = validated.contract;
160
-
161
- const db = extractDb(driver);
162
- const liveIR = await introspectSchema(db);
163
-
164
- return verifyMongoSchema({
165
- contract,
166
- schema: liveIR,
167
- strict,
168
- frameworkComponents: options.frameworkComponents,
169
- context: {
170
- contractPath,
171
- ...ifDefined('configPath', configPath),
172
- },
173
- });
174
- }
175
-
176
- schemaVerifyAgainstSchema(options: {
177
- readonly contract: unknown;
178
- readonly schema: MongoSchemaIR;
179
- readonly strict: boolean;
180
- readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;
181
- }): VerifyDatabaseSchemaResult {
182
- const validated = validateMongoContract<MongoContract>(options.contract);
183
- return verifyMongoSchema({
184
- contract: validated.contract,
185
- schema: options.schema,
186
- strict: options.strict,
187
- frameworkComponents: options.frameworkComponents,
188
- });
189
- }
190
-
191
- async sign(options: {
192
- readonly driver: ControlDriverInstance<'mongo', string>;
193
- readonly contract: unknown;
194
- readonly contractPath: string;
195
- readonly configPath?: string;
196
- }): Promise<SignDatabaseResult> {
197
- const { driver, contract: rawContract, contractPath, configPath } = options;
198
- const startTime = Date.now();
199
-
200
- const validated = validateMongoContract<MongoContract>(rawContract);
201
- const contract = validated.contract;
202
-
203
- const contractStorageHash = contract.storage.storageHash;
204
- const contractProfileHash = contract.profileHash;
205
-
206
- const db = extractDb(driver);
207
-
208
- const existingMarker = await readMarker(db, APP_SPACE_ID);
209
-
210
- let markerCreated = false;
211
- let markerUpdated = false;
212
- let previousHashes: { storageHash?: string; profileHash?: string } | undefined;
213
-
214
- if (!existingMarker) {
215
- await initMarker(db, APP_SPACE_ID, {
216
- storageHash: contractStorageHash,
217
- profileHash: contractProfileHash,
218
- });
219
- markerCreated = true;
220
- } else {
221
- const storageHashMatches = existingMarker.storageHash === contractStorageHash;
222
- const profileHashMatches = existingMarker.profileHash === contractProfileHash;
223
-
224
- if (!storageHashMatches || !profileHashMatches) {
225
- previousHashes = {
226
- storageHash: existingMarker.storageHash,
227
- profileHash: existingMarker.profileHash,
228
- };
229
- const updated = await updateMarker(db, APP_SPACE_ID, existingMarker.storageHash, {
230
- storageHash: contractStorageHash,
231
- profileHash: contractProfileHash,
232
- });
233
- if (!updated) {
234
- throw new Error('CAS conflict: marker was modified by another process during sign');
235
- }
236
- markerUpdated = true;
237
- }
238
- }
239
-
240
- let summary: string;
241
- if (markerCreated) {
242
- summary = 'Database signed (marker created)';
243
- } else if (markerUpdated) {
244
- summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? 'unknown'})`;
245
- } else {
246
- summary = 'Database already signed with this contract';
247
- }
248
-
249
- return {
250
- ok: true,
251
- summary,
252
- contract: {
253
- storageHash: contractStorageHash,
254
- profileHash: contractProfileHash,
255
- },
256
- target: {
257
- expected: contract.target,
258
- actual: contract.target,
259
- },
260
- marker: {
261
- created: markerCreated,
262
- updated: markerUpdated,
263
- ...ifDefined('previous', previousHashes),
264
- },
265
- meta: {
266
- contractPath,
267
- ...ifDefined('configPath', configPath),
268
- },
269
- timings: {
270
- total: Date.now() - startTime,
271
- },
272
- };
273
- }
274
-
275
- async readMarker(options: {
276
- readonly driver: ControlDriverInstance<'mongo', string>;
277
- readonly space: string;
278
- }): Promise<ContractMarkerRecord | null> {
279
- const db = extractDb(options.driver);
280
- return readMarker(db, options.space);
281
- }
282
-
283
- async readAllMarkers(options: {
284
- readonly driver: ControlDriverInstance<'mongo', string>;
285
- }): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
286
- const db = extractDb(options.driver);
287
- return readAllMarkers(db);
288
- }
289
-
290
- async introspect(options: {
291
- readonly driver: ControlDriverInstance<'mongo', string>;
292
- readonly contract?: unknown;
293
- }): Promise<MongoSchemaIR> {
294
- const db = extractDb(options.driver);
295
- return introspectSchema(db);
296
- }
297
-
298
- toSchemaView(schema: MongoSchemaIR): CoreSchemaView {
299
- return mongoSchemaToView(schema);
300
- }
56
+ /**
57
+ * Family-method contract input. By the time control-plane methods
58
+ * (`verify`, `verifySchema`, `sign`, …) are invoked through the CLI
59
+ * control client (`client.ts`), the input has already been threaded
60
+ * through `familyInstance.validateContract`. The value is therefore a
61
+ * class-form `MongoTargetContract` (or a structurally-equivalent
62
+ * envelope post-deserialization) and must NOT be re-fed through
63
+ * structural validation (arktype rejects extra keys like `namespaces`).
64
+ *
65
+ * The parameter type on the framework SPI is `unknown` for variance
66
+ * reasons (so the family can express its own contract type without
67
+ * leaking it to the framework). This helper recovers the validated
68
+ * shape with a single narrow cast.
69
+ */
70
+ function asValidatedMongoContract(contract: unknown): MongoContract {
71
+ return contract as MongoContract;
72
+ }
301
73
 
302
- toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
303
- return {
304
- statements: formatMongoOperations(operations).map((text) => ({
305
- text,
306
- language: 'mongodb-shell',
307
- })),
308
- };
309
- }
74
+ function isMongoControlAdapter(value: unknown): value is MongoControlAdapter<'mongo'> {
75
+ return (
76
+ typeof value === 'object' &&
77
+ value !== null &&
78
+ 'readMarker' in value &&
79
+ typeof (value as { readMarker: unknown }).readMarker === 'function' &&
80
+ 'readAllMarkers' in value &&
81
+ typeof (value as { readAllMarkers: unknown }).readAllMarkers === 'function' &&
82
+ 'introspectSchema' in value &&
83
+ typeof (value as { introspectSchema: unknown }).introspectSchema === 'function'
84
+ );
310
85
  }
311
86
 
312
87
  function buildVerifyResult(opts: {
@@ -373,5 +148,233 @@ export function createMongoFamilyInstance(controlStack: ControlStack): MongoCont
373
148
  });
374
149
  }
375
150
  }
376
- return new MongoFamilyInstance();
151
+
152
+ // Mongo dispatch surface. Every wire-level operation routes through
153
+ // the adapter resolved from the control stack; the family carries no
154
+ // direct imports of target/adapter/driver internals. Mirrors the SQL
155
+ // family's `getControlAdapter()` helper.
156
+ const adapter = controlStack.adapter as MongoControlAdapterDescriptor<'mongo'> | undefined;
157
+ const getControlAdapter = (): MongoControlAdapter<'mongo'> => {
158
+ if (!adapter) {
159
+ throw new Error('Mongo family requires an adapter descriptor in ControlStack');
160
+ }
161
+ const controlAdapter = adapter.create(controlStack as ControlStack<'mongo', 'mongo'>);
162
+ if (!isMongoControlAdapter(controlAdapter)) {
163
+ throw new Error(
164
+ 'Adapter does not implement MongoControlAdapter (missing readMarker, readAllMarkers, or introspectSchema)',
165
+ );
166
+ }
167
+ return controlAdapter;
168
+ };
169
+
170
+ // The family-level driver type is `ControlDriverInstance<'mongo', string>`,
171
+ // but the SPI methods are typed against `<'mongo', 'mongo'>`. Today's only
172
+ // Mongo target is `'mongo'`, so the runtime values are identical; the cast
173
+ // satisfies the structural type-system mismatch on `targetId`.
174
+ const asMongoDriver = (
175
+ driver: ControlDriverInstance<'mongo', string>,
176
+ ): ControlDriverInstance<'mongo', 'mongo'> => driver as ControlDriverInstance<'mongo', 'mongo'>;
177
+
178
+ return {
179
+ familyId: 'mongo' as const,
180
+
181
+ validateContract(contractJson: unknown): Contract {
182
+ // The deserialized class form (MongoTargetContract, owned by
183
+ // target-mongo) and the framework Contract are structurally
184
+ // compatible — same fields, just a class instance on the storage
185
+ // envelope. The cast preserves the framework signature.
186
+ return deserializeMongoContract(contractJson) as unknown as Contract;
187
+ },
188
+
189
+ async verify(options): Promise<VerifyDatabaseResult> {
190
+ const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;
191
+ const startTime = Date.now();
192
+
193
+ const contract = asValidatedMongoContract(rawContract);
194
+
195
+ const contractStorageHash = contract.storage.storageHash;
196
+ const contractProfileHash = contract.profileHash;
197
+ const contractTarget = contract.target;
198
+
199
+ const baseOpts = {
200
+ contractStorageHash,
201
+ contractProfileHash,
202
+ expectedTargetId,
203
+ contractPath,
204
+ ...ifDefined('configPath', configPath),
205
+ };
206
+
207
+ if (contractTarget !== expectedTargetId) {
208
+ return buildVerifyResult({
209
+ ...baseOpts,
210
+ ok: false,
211
+ code: VERIFY_CODE_TARGET_MISMATCH,
212
+ summary: 'Target mismatch',
213
+ actualTargetId: contractTarget,
214
+ totalTime: Date.now() - startTime,
215
+ });
216
+ }
217
+
218
+ const marker = await getControlAdapter().readMarker(asMongoDriver(driver), APP_SPACE_ID);
219
+
220
+ if (!marker) {
221
+ return buildVerifyResult({
222
+ ...baseOpts,
223
+ ok: false,
224
+ code: VERIFY_CODE_MARKER_MISSING,
225
+ summary: 'Marker missing',
226
+ totalTime: Date.now() - startTime,
227
+ });
228
+ }
229
+
230
+ if (marker.storageHash !== contractStorageHash) {
231
+ return buildVerifyResult({
232
+ ...baseOpts,
233
+ ok: false,
234
+ code: VERIFY_CODE_HASH_MISMATCH,
235
+ summary: 'Hash mismatch',
236
+ marker,
237
+ totalTime: Date.now() - startTime,
238
+ });
239
+ }
240
+
241
+ if (contractProfileHash && marker.profileHash !== contractProfileHash) {
242
+ return buildVerifyResult({
243
+ ...baseOpts,
244
+ ok: false,
245
+ code: VERIFY_CODE_HASH_MISMATCH,
246
+ summary: 'Hash mismatch',
247
+ marker,
248
+ totalTime: Date.now() - startTime,
249
+ });
250
+ }
251
+
252
+ return buildVerifyResult({
253
+ ...baseOpts,
254
+ ok: true,
255
+ summary: 'Database matches contract',
256
+ marker,
257
+ totalTime: Date.now() - startTime,
258
+ });
259
+ },
260
+
261
+ verifySchema(options: {
262
+ readonly contract: unknown;
263
+ readonly schema: MongoSchemaIR;
264
+ readonly strict: boolean;
265
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;
266
+ }): VerifyDatabaseSchemaResult {
267
+ const contract = asValidatedMongoContract(options.contract);
268
+ return verifyMongoSchema({
269
+ contract,
270
+ schema: options.schema,
271
+ strict: options.strict,
272
+ frameworkComponents: options.frameworkComponents,
273
+ });
274
+ },
275
+
276
+ async sign(options): Promise<SignDatabaseResult> {
277
+ const { driver, contract: rawContract, contractPath, configPath } = options;
278
+ const startTime = Date.now();
279
+
280
+ const contract = asValidatedMongoContract(rawContract);
281
+
282
+ const contractStorageHash = contract.storage.storageHash;
283
+ const contractProfileHash = contract.profileHash;
284
+
285
+ const controlAdapter = getControlAdapter();
286
+ const mongoDriver = asMongoDriver(driver);
287
+
288
+ const existingMarker = await controlAdapter.readMarker(mongoDriver, APP_SPACE_ID);
289
+
290
+ let markerCreated = false;
291
+ let markerUpdated = false;
292
+ let previousHashes: { storageHash?: string; profileHash?: string } | undefined;
293
+
294
+ if (!existingMarker) {
295
+ await controlAdapter.initMarker(mongoDriver, APP_SPACE_ID, {
296
+ storageHash: contractStorageHash,
297
+ profileHash: contractProfileHash,
298
+ });
299
+ markerCreated = true;
300
+ } else {
301
+ const storageHashMatches = existingMarker.storageHash === contractStorageHash;
302
+ const profileHashMatches = existingMarker.profileHash === contractProfileHash;
303
+
304
+ if (!storageHashMatches || !profileHashMatches) {
305
+ previousHashes = {
306
+ storageHash: existingMarker.storageHash,
307
+ profileHash: existingMarker.profileHash,
308
+ };
309
+ const updated = await controlAdapter.updateMarker(
310
+ mongoDriver,
311
+ APP_SPACE_ID,
312
+ existingMarker.storageHash,
313
+ {
314
+ storageHash: contractStorageHash,
315
+ profileHash: contractProfileHash,
316
+ },
317
+ );
318
+ if (!updated) {
319
+ throw new Error('CAS conflict: marker was modified by another process during sign');
320
+ }
321
+ markerUpdated = true;
322
+ }
323
+ }
324
+
325
+ let summary: string;
326
+ if (markerCreated) {
327
+ summary = 'Database signed (marker created)';
328
+ } else if (markerUpdated) {
329
+ summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? 'unknown'})`;
330
+ } else {
331
+ summary = 'Database already signed with this contract';
332
+ }
333
+
334
+ return {
335
+ ok: true,
336
+ summary,
337
+ contract: {
338
+ storageHash: contractStorageHash,
339
+ profileHash: contractProfileHash,
340
+ },
341
+ target: {
342
+ expected: contract.target,
343
+ actual: contract.target,
344
+ },
345
+ marker: {
346
+ created: markerCreated,
347
+ updated: markerUpdated,
348
+ ...ifDefined('previous', previousHashes),
349
+ },
350
+ meta: {
351
+ contractPath,
352
+ ...ifDefined('configPath', configPath),
353
+ },
354
+ timings: {
355
+ total: Date.now() - startTime,
356
+ },
357
+ };
358
+ },
359
+
360
+ async readMarker(options): Promise<ContractMarkerRecord | null> {
361
+ return getControlAdapter().readMarker(asMongoDriver(options.driver), options.space);
362
+ },
363
+
364
+ async readAllMarkers(options): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
365
+ return getControlAdapter().readAllMarkers(asMongoDriver(options.driver));
366
+ },
367
+
368
+ async introspect(options): Promise<MongoSchemaIR> {
369
+ return getControlAdapter().introspectSchema(asMongoDriver(options.driver));
370
+ },
371
+
372
+ toSchemaView(schema: MongoSchemaIR): CoreSchemaView {
373
+ return mongoSchemaToView(schema);
374
+ },
375
+
376
+ toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
377
+ return mongoOperationsToPreview(operations);
378
+ },
379
+ };
377
380
  }
@@ -0,0 +1,26 @@
1
+ import type {
2
+ ContractSerializer,
3
+ MigratableTargetDescriptor,
4
+ SchemaVerifier,
5
+ } from '@prisma-next/framework-components/control';
6
+ import type { MongoContract } from '@prisma-next/mongo-contract';
7
+ import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
8
+ import type { MongoControlFamilyInstance } from './control-instance';
9
+
10
+ /**
11
+ * Mongo target control descriptor type. Extends the framework's
12
+ * `MigratableTargetDescriptor` with two named SPI properties next to
13
+ * the existing `migrations` capability:
14
+ *
15
+ * - `contractSerializer` — JSON to class boundary for Mongo contracts.
16
+ * - `schemaVerifier` — per-target verifier walking the family contract
17
+ * against `MongoSchemaIR`.
18
+ *
19
+ * The descriptor itself is the aggregator; no extra `Target<TContract,
20
+ * TSchema>` interface is introduced.
21
+ */
22
+ export interface MongoControlTargetDescriptor<TContract extends MongoContract = MongoContract>
23
+ extends MigratableTargetDescriptor<'mongo', 'mongo', MongoControlFamilyInstance> {
24
+ readonly contractSerializer: ContractSerializer<TContract>;
25
+ readonly schemaVerifier: SchemaVerifier<TContract, MongoSchemaIR>;
26
+ }
@@ -2,7 +2,7 @@ import type {
2
2
  ContractSpace,
3
3
  ControlExtensionDescriptor,
4
4
  } from '@prisma-next/framework-components/control';
5
- import type { MongoContract, MongoStorage } from '@prisma-next/mongo-contract';
5
+ import type { MongoContract, MongoStorageShape } from '@prisma-next/mongo-contract';
6
6
 
7
7
  /**
8
8
  * Mongo-family extension descriptor.
@@ -14,10 +14,12 @@ import type { MongoContract, MongoStorage } from '@prisma-next/mongo-contract';
14
14
  * The shape comes from `@prisma-next/framework-components/control`
15
15
  * (`ContractSpace`) — contract-space identity is a framework concept,
16
16
  * not a Mongo-specific one. The Mongo family specialises the generic
17
- * to `MongoContract<MongoStorage>` so descriptor authors continue to
18
- * see a typed contract value. Mirrors `SqlControlExtensionDescriptor`.
17
+ * to `MongoContract<MongoStorageShape>` so descriptor authors see a
18
+ * typed contract value over the raw-JSON envelope shape; the runtime
19
+ * in-memory class `MongoStorage` structurally satisfies the shape.
20
+ * Mirrors `SqlControlExtensionDescriptor`.
19
21
  */
20
22
  export interface MongoControlExtensionDescriptor
21
23
  extends ControlExtensionDescriptor<'mongo', 'mongo'> {
22
- readonly contractSpace?: ContractSpace<MongoContract<MongoStorage>>;
24
+ readonly contractSpace?: ContractSpace<MongoContract<MongoStorageShape>>;
23
25
  }