@prisma-next/family-mongo 0.6.0-dev.12 → 0.6.0-dev.14

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,16 +1,35 @@
1
- import { ControlFamilyDescriptor, ControlFamilyInstance, ControlStack, MigratableTargetDescriptor, OperationPreviewCapable, SchemaViewCapable } from "@prisma-next/framework-components/control";
2
- import { Contract } from "@prisma-next/contract/types";
1
+ import { ContractSpace, ControlExtensionDescriptor, ControlFamilyDescriptor, ControlFamilyInstance, ControlStack, MigratableTargetDescriptor, OperationPreviewCapable, SchemaViewCapable } from "@prisma-next/framework-components/control";
2
+ import { MongoContract, MongoStorage } from "@prisma-next/mongo-contract";
3
3
  import { MongoSchemaIR } from "@prisma-next/mongo-schema-ir";
4
+ import { Contract } from "@prisma-next/contract/types";
4
5
 
5
6
  //#region src/core/control-instance.d.ts
6
7
  interface MongoControlFamilyInstance extends ControlFamilyInstance<'mongo', MongoSchemaIR>, SchemaViewCapable<MongoSchemaIR>, OperationPreviewCapable {
7
8
  validateContract(contractJson: unknown): Contract;
8
9
  }
9
- declare function createMongoFamilyInstance(_controlStack: ControlStack): MongoControlFamilyInstance;
10
+ declare function createMongoFamilyInstance(controlStack: ControlStack): MongoControlFamilyInstance;
10
11
  //#endregion
11
12
  //#region src/core/control-descriptor.d.ts
12
13
  declare const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>;
13
14
  //#endregion
15
+ //#region src/core/control-types.d.ts
16
+ /**
17
+ * Mongo-family extension descriptor.
18
+ *
19
+ * Extensions that contribute schema opt into the per-space planner /
20
+ * runner / verifier by setting `contractSpace`. Extensions without it
21
+ * are codec-only or query-ops-only — today's behaviour preserved.
22
+ *
23
+ * The shape comes from `@prisma-next/framework-components/control`
24
+ * (`ContractSpace`) — contract-space identity is a framework concept,
25
+ * not a Mongo-specific one. The Mongo family specialises the generic
26
+ * to `MongoContract<MongoStorage>` so descriptor authors continue to
27
+ * see a typed contract value. Mirrors `SqlControlExtensionDescriptor`.
28
+ */
29
+ interface MongoControlExtensionDescriptor extends ControlExtensionDescriptor<'mongo', 'mongo'> {
30
+ readonly contractSpace?: ContractSpace<MongoContract<MongoStorage>>;
31
+ }
32
+ //#endregion
14
33
  //#region src/core/mongo-target-descriptor.d.ts
15
34
  /**
16
35
  * `migration.ts` default-exports a `Migration` subclass whose `operations`
@@ -24,5 +43,5 @@ declare const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoContr
24
43
  */
25
44
  declare const mongoTargetDescriptor: MigratableTargetDescriptor<'mongo', 'mongo', MongoControlFamilyInstance>;
26
45
  //#endregion
27
- export { type MongoControlFamilyInstance, createMongoFamilyInstance, mongoFamilyDescriptor, mongoTargetDescriptor };
46
+ export { type MongoControlExtensionDescriptor, type MongoControlFamilyInstance, createMongoFamilyInstance, mongoFamilyDescriptor, mongoTargetDescriptor };
28
47
  //# sourceMappingURL=control.d.mts.map
@@ -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"],"mappings":";;;;;UAoCiB,0BAAA,SACP,qBAAA,UAA+B,aAAA,GACrC,iBAAA,CAAkB,aAAA,GAClB,uBAAA;EACF,gBAAA,CAAiB,YAAA,YAAwB,QAAA;AAAA;AAAA,iBAgU3B,yBAAA,CAA0B,aAAA,EAAe,YAAA,GAAe,0BAAA;;;cCjV3D,qBAAA,EAAuB,uBAAA,UAAiC,0BAAA;;;;;;ADarE;;;;;;;cELa,qBAAA,EAAuB,0BAAA,mBAGlC,0BAAA"}
1
+ {"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/control-types.ts","../src/core/mongo-target-descriptor.ts"],"mappings":";;;;;;UAuCiB,0BAAA,SACP,qBAAA,UAA+B,aAAA,GACrC,iBAAA,CAAkB,aAAA,GAClB,uBAAA;EACF,gBAAA,CAAiB,YAAA,YAAwB,QAAA;AAAA;AAAA,iBAmT3B,yBAAA,CAA0B,YAAA,EAAc,YAAA,GAAe,0BAAA;;;cCvU1D,qBAAA,EAAuB,uBAAA,UAAiC,0BAAA;;;;;;;ADgBrE;;;;;;;;;UEpBiB,+BAAA,SACP,0BAAA;EAAA,SACC,aAAA,GAAgB,aAAA,CAAc,aAAA,CAAc,YAAA;AAAA;;;;;;;AFkBvD;;;;;;cGEa,qBAAA,EAAuB,0BAAA,mBAGlC,0BAAA"}
package/dist/control.mjs CHANGED
@@ -1,11 +1,14 @@
1
1
  import { mongoEmission } from "@prisma-next/mongo-emitter";
2
2
  import { createMongoRunnerDeps, extractDb, introspectSchema } from "@prisma-next/adapter-mongo/control";
3
3
  import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH } from "@prisma-next/framework-components/control";
4
+ import { assertDescriptorSelfConsistency } from "@prisma-next/migration-tools/spaces";
4
5
  import { validateMongoContract } from "@prisma-next/mongo-contract";
5
- import { MongoMigrationPlanner, MongoMigrationRunner, contractToMongoSchemaIR, formatMongoOperations, initMarker, readMarker, updateMarker } from "@prisma-next/target-mongo/control";
6
+ import { MongoMigrationPlanner, MongoMigrationRunner, contractToMongoSchemaIR, formatMongoOperations, initMarker, readAllMarkers, readMarker, updateMarker } from "@prisma-next/target-mongo/control";
6
7
  import { verifyMongoSchema } from "@prisma-next/target-mongo/schema-verify";
7
8
  import { ifDefined } from "@prisma-next/utils/defined";
8
9
  import { MongoDriverImpl } from "@prisma-next/driver-mongo";
10
+ import { projectSchemaToSpace } from "@prisma-next/migration-tools/aggregate";
11
+ import { MongoSchemaIR } from "@prisma-next/mongo-schema-ir";
9
12
  import mongoTargetDescriptorMeta from "@prisma-next/target-mongo/pack";
10
13
  import { notOk, ok } from "@prisma-next/utils/result";
11
14
  //#region src/core/schema-to-view.ts
@@ -133,7 +136,7 @@ var MongoFamilyInstance = class {
133
136
  actualTargetId: contractTarget,
134
137
  totalTime: Date.now() - startTime
135
138
  });
136
- const marker = await readMarker(extractDb$1(driver));
139
+ const marker = await readMarker(extractDb$1(driver), APP_SPACE_ID);
137
140
  if (!marker) return buildVerifyResult({
138
141
  ...baseOpts,
139
142
  ok: false,
@@ -194,12 +197,12 @@ var MongoFamilyInstance = class {
194
197
  const contractStorageHash = contract.storage.storageHash;
195
198
  const contractProfileHash = contract.profileHash;
196
199
  const db = extractDb$1(driver);
197
- const existingMarker = await readMarker(db);
200
+ const existingMarker = await readMarker(db, APP_SPACE_ID);
198
201
  let markerCreated = false;
199
202
  let markerUpdated = false;
200
203
  let previousHashes;
201
204
  if (!existingMarker) {
202
- await initMarker(db, {
205
+ await initMarker(db, APP_SPACE_ID, {
203
206
  storageHash: contractStorageHash,
204
207
  profileHash: contractProfileHash
205
208
  });
@@ -212,7 +215,7 @@ var MongoFamilyInstance = class {
212
215
  storageHash: existingMarker.storageHash,
213
216
  profileHash: existingMarker.profileHash
214
217
  };
215
- if (!await updateMarker(db, existingMarker.storageHash, {
218
+ if (!await updateMarker(db, APP_SPACE_ID, existingMarker.storageHash, {
216
219
  storageHash: contractStorageHash,
217
220
  profileHash: contractProfileHash
218
221
  })) throw new Error("CAS conflict: marker was modified by another process during sign");
@@ -247,16 +250,10 @@ var MongoFamilyInstance = class {
247
250
  };
248
251
  }
249
252
  async readMarker(options) {
250
- if (options.space !== APP_SPACE_ID) throw new Error(`Mongo target does not yet support per-space contract markers. readMarker was called with space="${options.space}", but only "${APP_SPACE_ID}" is supported. Per-space marker support is tracked separately for Mongo and is not part of the SQL-family contract-spaces work.`);
251
- return readMarker(extractDb$1(options.driver));
253
+ return readMarker(extractDb$1(options.driver), options.space);
252
254
  }
253
255
  async readAllMarkers(options) {
254
- const appMarker = await this.readMarker({
255
- ...options,
256
- space: APP_SPACE_ID
257
- });
258
- if (appMarker === null) return /* @__PURE__ */ new Map();
259
- return new Map([[APP_SPACE_ID, appMarker]]);
256
+ return readAllMarkers(extractDb$1(options.driver));
260
257
  }
261
258
  async introspect(options) {
262
259
  return introspectSchema(extractDb$1(options.driver));
@@ -295,7 +292,18 @@ function buildVerifyResult(opts) {
295
292
  timings: { total: opts.totalTime }
296
293
  };
297
294
  }
298
- function createMongoFamilyInstance(_controlStack) {
295
+ function createMongoFamilyInstance(controlStack) {
296
+ const extensions = controlStack.extensionPacks ?? [];
297
+ for (const extension of extensions) if (extension.contractSpace) {
298
+ const { contractJson, headRef } = extension.contractSpace;
299
+ assertDescriptorSelfConsistency({
300
+ extensionId: extension.id,
301
+ target: contractJson.target,
302
+ targetFamily: contractJson.targetFamily,
303
+ storage: contractJson.storage,
304
+ headRefHash: headRef.hash
305
+ });
306
+ }
299
307
  return new MongoFamilyInstance();
300
308
  }
301
309
  //#endregion
@@ -331,42 +339,46 @@ const mongoTargetDescriptor = {
331
339
  },
332
340
  createRunner(family) {
333
341
  let cachedDeps;
334
- const runner = {
342
+ const runMongo = async (driver, runnerOptions) => {
343
+ cachedDeps ??= createMongoRunnerDeps(driver, MongoDriverImpl.fromDb(extractDb(driver)), family);
344
+ return new MongoMigrationRunner(cachedDeps).execute({
345
+ ...runnerOptions,
346
+ destinationContract: runnerOptions.destinationContract
347
+ });
348
+ };
349
+ return {
335
350
  async execute(options) {
336
- cachedDeps ??= createMongoRunnerDeps(options.driver, MongoDriverImpl.fromDb(extractDb(options.driver)), family);
337
- const { driver: _, ...runnerOptions } = options;
338
- return new MongoMigrationRunner(cachedDeps).execute({
339
- ...runnerOptions,
340
- destinationContract: runnerOptions.destinationContract
341
- });
351
+ const { driver, ...runnerOptions } = options;
352
+ return runMongo(driver, runnerOptions);
342
353
  },
343
354
  async executeAcrossSpaces({ driver, perSpaceOptions }) {
344
- if (perSpaceOptions.length !== 1) return notOk({
345
- code: "MONGO_MULTI_SPACE_UNSUPPORTED",
346
- summary: `Mongo target supports a single contract space; received ${perSpaceOptions.length}`,
347
- failingSpace: perSpaceOptions[0]?.space ?? "<unknown>"
348
- });
349
- const only = perSpaceOptions[0];
350
- if (!only) return notOk({
351
- code: "MONGO_MULTI_SPACE_UNSUPPORTED",
352
- summary: "Mongo executeAcrossSpaces called with no per-space plans",
353
- failingSpace: "<unknown>"
354
- });
355
- const result = await runner.execute({
356
- ...only,
357
- driver
358
- });
359
- if (!result.ok) return notOk({
360
- ...result.failure,
361
- failingSpace: only.space
362
- });
363
- return ok({ perSpaceResults: [{
364
- space: only.space,
365
- value: result.value
366
- }] });
355
+ const members = perSpaceOptions.map(toSpaceMember);
356
+ const perSpaceResults = [];
357
+ for (let i = 0; i < perSpaceOptions.length; i++) {
358
+ const spaceOptions = perSpaceOptions[i];
359
+ if (!spaceOptions) continue;
360
+ const member = members[i];
361
+ if (!member) continue;
362
+ const others = members.filter((_, j) => j !== i);
363
+ const projectSchema = (schema) => {
364
+ return new MongoSchemaIR(projectSchemaToSpace(schema, member, others).collections);
365
+ };
366
+ const result = await runMongo(driver, {
367
+ ...spaceOptions,
368
+ projectSchema
369
+ });
370
+ if (!result.ok) return notOk({
371
+ ...result.failure,
372
+ failingSpace: spaceOptions.space
373
+ });
374
+ perSpaceResults.push({
375
+ space: spaceOptions.space,
376
+ value: result.value
377
+ });
378
+ }
379
+ return ok({ perSpaceResults });
367
380
  }
368
381
  };
369
- return runner;
370
382
  },
371
383
  contractToSchema(contract) {
372
384
  return contractToMongoSchemaIR(contract);
@@ -379,6 +391,36 @@ const mongoTargetDescriptor = {
379
391
  };
380
392
  }
381
393
  };
394
+ /**
395
+ * Synthesise the minimum {@link projectSchemaToSpace}-compatible
396
+ * `ContractSpaceMember` shape from a per-space option entry. The
397
+ * projector only reads `spaceId` and `contract.storage`; the rest of
398
+ * `ContractSpaceMember` (head ref invariants, hydrated migration
399
+ * graph) is irrelevant at runner time and stubbed with sentinels.
400
+ *
401
+ * The `as unknown as ContractSpaceMember` cast is the load-bearing bit
402
+ * — the projector duck-types its members so a sentinel-shaped graph
403
+ * never gets read, but the framework type carries a richer shape.
404
+ */
405
+ function toSpaceMember(opts) {
406
+ return {
407
+ spaceId: opts.space,
408
+ contract: opts.destinationContract,
409
+ headRef: {
410
+ hash: "",
411
+ invariants: []
412
+ },
413
+ migrations: {
414
+ graph: {
415
+ nodes: /* @__PURE__ */ new Set(),
416
+ forwardChain: /* @__PURE__ */ new Map(),
417
+ reverseChain: /* @__PURE__ */ new Map(),
418
+ migrationByHash: /* @__PURE__ */ new Map()
419
+ },
420
+ packagesByMigrationHash: /* @__PURE__ */ new Map()
421
+ }
422
+ };
423
+ }
382
424
  //#endregion
383
425
  export { createMongoFamilyInstance, mongoFamilyDescriptor, mongoTargetDescriptor };
384
426
 
@@ -1 +1 @@
1
- {"version":3,"file":"control.mjs","names":["extractDb"],"sources":["../src/core/schema-to-view.ts","../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/mongo-target-descriptor.ts"],"sourcesContent":["import type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport { SchemaTreeNode } from '@prisma-next/framework-components/control';\nimport type { MongoSchemaCollection, MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport function mongoSchemaToView(schema: MongoSchemaIR): CoreSchemaView {\n const collectionNodes = schema.collections.map((collection) =>\n collectionToSchemaNode(collection.name, collection),\n );\n\n return {\n root: new SchemaTreeNode({\n kind: 'root',\n id: 'mongo-schema',\n label: 'database',\n ...ifDefined('children', collectionNodes.length > 0 ? collectionNodes : undefined),\n }),\n };\n}\n\nfunction collectionToSchemaNode(name: string, collection: MongoSchemaCollection): SchemaTreeNode {\n const children: SchemaTreeNode[] = [];\n\n for (const index of collection.indexes) {\n const keysSummary = index.keys\n .map((k) => {\n if (k.direction === 1) return k.field;\n if (k.direction === -1) return `${k.field} desc`;\n return `${k.field} ${k.direction}`;\n })\n .join(', ');\n const prefix = index.unique ? 'unique index' : 'index';\n const options: string[] = [];\n if (index.sparse) options.push('sparse');\n if (index.expireAfterSeconds != null) options.push(`ttl: ${index.expireAfterSeconds}s`);\n if (index.partialFilterExpression) options.push('partial');\n const optsSuffix = options.length > 0 ? ` (${options.join(', ')})` : '';\n\n children.push(\n new SchemaTreeNode({\n kind: 'index',\n id: `index-${name}-${index.keys.map((k) => `${k.field}_${k.direction}`).join('_')}`,\n label: `${prefix} (${keysSummary})${optsSuffix}`,\n meta: {\n keys: index.keys,\n unique: index.unique,\n ...ifDefined('sparse', index.sparse || undefined),\n ...ifDefined('expireAfterSeconds', index.expireAfterSeconds ?? undefined),\n ...ifDefined('partialFilterExpression', index.partialFilterExpression ?? undefined),\n },\n }),\n );\n }\n\n if (collection.validator) {\n const validatorChildren: SchemaTreeNode[] = [];\n const jsonSchema = collection.validator.jsonSchema as Record<string, unknown>;\n const properties = jsonSchema['properties'] as\n | Record<string, Record<string, unknown>>\n | undefined;\n const required = new Set((jsonSchema['required'] as string[] | undefined) ?? []);\n\n if (properties) {\n for (const [propName, propDef] of Object.entries(properties)) {\n const bsonType = (propDef['bsonType'] as string) ?? 'unknown';\n const suffix = required.has(propName) ? ' (required)' : '';\n validatorChildren.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `field-${name}-${propName}`,\n label: `${propName}: ${bsonType}${suffix}`,\n }),\n );\n }\n }\n\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `validator-${name}`,\n label: `validator (level: ${collection.validator.validationLevel}, action: ${collection.validator.validationAction})`,\n meta: {\n validationLevel: collection.validator.validationLevel,\n validationAction: collection.validator.validationAction,\n jsonSchema: collection.validator.jsonSchema,\n },\n ...ifDefined('children', validatorChildren.length > 0 ? validatorChildren : undefined),\n }),\n );\n }\n\n if (collection.options) {\n const opts = collection.options;\n const optLabels: string[] = [];\n if (opts.capped) optLabels.push('capped');\n if (opts.timeseries) optLabels.push('timeseries');\n if (opts.collation) optLabels.push('collation');\n if (opts.changeStreamPreAndPostImages) optLabels.push('changeStreamPreAndPostImages');\n if (opts.clusteredIndex) optLabels.push('clusteredIndex');\n\n if (optLabels.length > 0) {\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `options-${name}`,\n label: `options (${optLabels.join(', ')})`,\n meta: {\n ...ifDefined('capped', opts.capped ?? undefined),\n ...ifDefined('timeseries', opts.timeseries ?? undefined),\n ...ifDefined('collation', opts.collation ?? undefined),\n ...ifDefined(\n 'changeStreamPreAndPostImages',\n opts.changeStreamPreAndPostImages ?? undefined,\n ),\n ...ifDefined('clusteredIndex', opts.clusteredIndex ?? undefined),\n },\n }),\n );\n }\n }\n\n return new SchemaTreeNode({\n kind: 'collection',\n id: `collection-${name}`,\n label: `collection ${name}`,\n ...ifDefined('children', children.length > 0 ? children : undefined),\n });\n}\n","import { introspectSchema } from '@prisma-next/adapter-mongo/control';\nimport type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlStack,\n CoreSchemaView,\n MigrationPlanOperation,\n OperationPreview,\n OperationPreviewCapable,\n SchemaViewCapable,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport {\n APP_SPACE_ID,\n VERIFY_CODE_HASH_MISMATCH,\n VERIFY_CODE_MARKER_MISSING,\n VERIFY_CODE_TARGET_MISMATCH,\n} from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport { validateMongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport {\n formatMongoOperations,\n initMarker,\n readMarker,\n updateMarker,\n} from '@prisma-next/target-mongo/control';\nimport { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { Db } from 'mongodb';\nimport { mongoSchemaToView } from './schema-to-view';\n\nexport interface MongoControlFamilyInstance\n extends ControlFamilyInstance<'mongo', MongoSchemaIR>,\n SchemaViewCapable<MongoSchemaIR>,\n OperationPreviewCapable {\n validateContract(contractJson: unknown): Contract;\n}\n\nfunction extractDb(driver: ControlDriverInstance<'mongo', string>): Db {\n const mongoDriver = driver as ControlDriverInstance<'mongo', string> & { db?: Db };\n if (!mongoDriver.db) {\n throw new Error(\n 'Mongo control driver does not expose a db property. ' +\n 'Use createMongoControlDriver() from @prisma-next/adapter-mongo/control.',\n );\n }\n return mongoDriver.db;\n}\n\nclass MongoFamilyInstance implements MongoControlFamilyInstance {\n readonly familyId = 'mongo' as const;\n\n validateContract(contractJson: unknown): Contract {\n const validated = validateMongoContract<MongoContract>(contractJson);\n // MongoContract and Contract share structure but are typed independently;\n // validateMongoContract guarantees the shape, so the double cast is safe.\n return validated.contract as unknown as Contract;\n }\n\n async verify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly expectedTargetId: string;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<VerifyDatabaseResult> {\n const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n const contractTarget = contract.target;\n\n const baseOpts = {\n contractStorageHash,\n contractProfileHash,\n expectedTargetId,\n contractPath,\n ...ifDefined('configPath', configPath),\n };\n\n if (contractTarget !== expectedTargetId) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_TARGET_MISMATCH,\n summary: 'Target mismatch',\n actualTargetId: contractTarget,\n totalTime: Date.now() - startTime,\n });\n }\n\n const db = extractDb(driver);\n const marker = await readMarker(db);\n\n if (!marker) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_MARKER_MISSING,\n summary: 'Marker missing',\n totalTime: Date.now() - startTime,\n });\n }\n\n if (marker.storageHash !== contractStorageHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n if (contractProfileHash && marker.profileHash !== contractProfileHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n return buildVerifyResult({\n ...baseOpts,\n ok: true,\n summary: 'Database matches contract',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n async schemaVerify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly strict: boolean;\n readonly contractPath: string;\n readonly configPath?: string;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;\n }): Promise<VerifyDatabaseSchemaResult> {\n const { driver, contract: rawContract, strict, contractPath, configPath } = options;\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const db = extractDb(driver);\n const liveIR = await introspectSchema(db);\n\n return verifyMongoSchema({\n contract,\n schema: liveIR,\n strict,\n frameworkComponents: options.frameworkComponents,\n context: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n });\n }\n\n schemaVerifyAgainstSchema(options: {\n readonly contract: unknown;\n readonly schema: MongoSchemaIR;\n readonly strict: boolean;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;\n }): VerifyDatabaseSchemaResult {\n const validated = validateMongoContract<MongoContract>(options.contract);\n return verifyMongoSchema({\n contract: validated.contract,\n schema: options.schema,\n strict: options.strict,\n frameworkComponents: options.frameworkComponents,\n });\n }\n\n async sign(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<SignDatabaseResult> {\n const { driver, contract: rawContract, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n\n const db = extractDb(driver);\n\n const existingMarker = await readMarker(db);\n\n let markerCreated = false;\n let markerUpdated = false;\n let previousHashes: { storageHash?: string; profileHash?: string } | undefined;\n\n if (!existingMarker) {\n await initMarker(db, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n markerCreated = true;\n } else {\n const storageHashMatches = existingMarker.storageHash === contractStorageHash;\n const profileHashMatches = existingMarker.profileHash === contractProfileHash;\n\n if (!storageHashMatches || !profileHashMatches) {\n previousHashes = {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n };\n const updated = await updateMarker(db, existingMarker.storageHash, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n if (!updated) {\n throw new Error('CAS conflict: marker was modified by another process during sign');\n }\n markerUpdated = true;\n }\n }\n\n let summary: string;\n if (markerCreated) {\n summary = 'Database signed (marker created)';\n } else if (markerUpdated) {\n summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? 'unknown'})`;\n } else {\n summary = 'Database already signed with this contract';\n }\n\n return {\n ok: true,\n summary,\n contract: {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n },\n target: {\n expected: contract.target,\n actual: contract.target,\n },\n marker: {\n created: markerCreated,\n updated: markerUpdated,\n ...ifDefined('previous', previousHashes),\n },\n meta: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n timings: {\n total: Date.now() - startTime,\n },\n };\n }\n\n async readMarker(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly space: string;\n }): Promise<ContractMarkerRecord | null> {\n if (options.space !== APP_SPACE_ID) {\n throw new Error(\n 'Mongo target does not yet support per-space contract markers. ' +\n `readMarker was called with space=\"${options.space}\", but only \"${APP_SPACE_ID}\" is supported. ` +\n 'Per-space marker support is tracked separately for Mongo and is not part of the SQL-family contract-spaces work.',\n );\n }\n const db = extractDb(options.driver);\n return readMarker(db);\n }\n\n // Mongo does not yet participate in the per-space mechanism — the\n // `space` column was introduced in the SQL family's marker only.\n // The bridge: surface the single app marker keyed by APP_SPACE_ID so\n // the per-space verifier sees a coherent input shape; per-space mongo\n // support is a future extension.\n async readAllMarkers(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n }): Promise<ReadonlyMap<string, ContractMarkerRecord>> {\n const appMarker = await this.readMarker({ ...options, space: APP_SPACE_ID });\n if (appMarker === null) return new Map();\n return new Map([[APP_SPACE_ID, appMarker]]);\n }\n\n async introspect(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract?: unknown;\n }): Promise<MongoSchemaIR> {\n const db = extractDb(options.driver);\n return introspectSchema(db);\n }\n\n toSchemaView(schema: MongoSchemaIR): CoreSchemaView {\n return mongoSchemaToView(schema);\n }\n\n toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {\n return {\n statements: formatMongoOperations(operations).map((text) => ({\n text,\n language: 'mongodb-shell',\n })),\n };\n }\n}\n\nfunction buildVerifyResult(opts: {\n ok: boolean;\n code?: string;\n summary: string;\n contractStorageHash: string;\n contractProfileHash?: string;\n marker?: ContractMarkerRecord;\n expectedTargetId: string;\n actualTargetId?: string;\n contractPath: string;\n configPath?: string;\n totalTime: number;\n}): VerifyDatabaseResult {\n return {\n ok: opts.ok,\n ...ifDefined('code', opts.code),\n summary: opts.summary,\n contract: {\n storageHash: opts.contractStorageHash,\n ...ifDefined('profileHash', opts.contractProfileHash),\n },\n ...ifDefined(\n 'marker',\n opts.marker\n ? { storageHash: opts.marker.storageHash, profileHash: opts.marker.profileHash }\n : undefined,\n ),\n target: {\n expected: opts.expectedTargetId,\n ...ifDefined('actual', opts.actualTargetId),\n },\n meta: {\n contractPath: opts.contractPath,\n ...ifDefined('configPath', opts.configPath),\n },\n timings: { total: opts.totalTime },\n };\n}\n\nexport function createMongoFamilyInstance(_controlStack: ControlStack): MongoControlFamilyInstance {\n return new MongoFamilyInstance();\n}\n","import type {\n ControlFamilyDescriptor,\n ControlStack,\n} from '@prisma-next/framework-components/control';\nimport { mongoEmission } from '@prisma-next/mongo-emitter';\nimport { createMongoFamilyInstance, type MongoControlFamilyInstance } from './control-instance';\n\nclass MongoFamilyDescriptor\n implements ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>\n{\n readonly kind = 'family' as const;\n readonly id = 'mongo';\n readonly familyId = 'mongo' as const;\n readonly version = '0.0.1';\n readonly emission = mongoEmission;\n\n create<TTargetId extends string>(\n stack: ControlStack<'mongo', TTargetId>,\n ): MongoControlFamilyInstance {\n return createMongoFamilyInstance(stack);\n }\n}\n\nexport const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance> =\n new MongoFamilyDescriptor();\n","import { createMongoRunnerDeps, extractDb } from '@prisma-next/adapter-mongo/control';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { MongoDriverImpl } from '@prisma-next/driver-mongo';\nimport type {\n MigratableTargetDescriptor,\n MigrationRunner,\n MultiSpaceCapableRunner,\n MultiSpaceRunnerFailure,\n MultiSpaceRunnerResult,\n} from '@prisma-next/framework-components/control';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport {\n contractToMongoSchemaIR,\n MongoMigrationPlanner,\n MongoMigrationRunner,\n type MongoRunnerDependencies,\n} from '@prisma-next/target-mongo/control';\nimport mongoTargetDescriptorMeta from '@prisma-next/target-mongo/pack';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type { MongoControlFamilyInstance } from './control-instance';\n\n/**\n * `migration.ts` default-exports a `Migration` subclass whose `operations`\n * getter returns the ordered list of operations and whose `describe()`\n * returns the manifest identity metadata. `MongoMigrationPlanner.plan()`\n * returns a `MigrationPlanWithAuthoringSurface` that knows how to render\n * itself back to such a file; `MongoMigrationPlanner.emptyMigration()`\n * returns the same shape for `migration new`. Users run the scaffolded\n * `migration.ts` directly (via `node migration.ts`) to self-emit\n * `ops.json` and attest the `migrationHash`.\n */\nexport const mongoTargetDescriptor: MigratableTargetDescriptor<\n 'mongo',\n 'mongo',\n MongoControlFamilyInstance\n> = {\n ...mongoTargetDescriptorMeta,\n migrations: {\n createPlanner(_family: MongoControlFamilyInstance) {\n return new MongoMigrationPlanner();\n },\n createRunner(family: MongoControlFamilyInstance) {\n // Deps are bound to the first driver passed to execute() and cached for\n // subsequent calls. Callers must not change the driver between calls.\n let cachedDeps: MongoRunnerDependencies | undefined;\n const runner: MigrationRunner<'mongo', 'mongo'> & MultiSpaceCapableRunner<'mongo', 'mongo'> =\n {\n async execute(options) {\n cachedDeps ??= createMongoRunnerDeps(\n options.driver,\n MongoDriverImpl.fromDb(extractDb(options.driver)),\n family,\n );\n const { driver: _, ...runnerOptions } = options;\n // The framework `MigrationRunner` interface types `destinationContract`\n // as `unknown`; the Mongo runner narrows to `MongoContract`. Validation\n // happens upstream — `migration apply` calls\n // `familyInstance.validateContract(migration.toContract)` before\n // routing the contract here (see\n // `packages/1-framework/3-tooling/cli/src/control-api/operations/migration-apply.ts`),\n // so this cast simply preserves the framework signature without\n // weakening the runner's typed surface or duplicating validation.\n return new MongoMigrationRunner(cachedDeps).execute({\n ...runnerOptions,\n destinationContract: runnerOptions.destinationContract as MongoContract,\n });\n },\n // Mongo per-space is a non-goal per the extension-contract-spaces project\n // spec (TML-2397): no Mongo extension contract spaces exist. The aggregate\n // is always single-member for Mongo, so `executeAcrossSpaces` is a\n // degenerate shim that asserts length === 1 and delegates to `execute`.\n // The shim exists so `applyAggregate` (the shared CLI primitive driving\n // `db init` / `db update` / `migration apply`) routes through Mongo\n // identically to the SQL family.\n async executeAcrossSpaces({ driver, perSpaceOptions }): Promise<MultiSpaceRunnerResult> {\n if (perSpaceOptions.length !== 1) {\n return notOk<MultiSpaceRunnerFailure>({\n code: 'MONGO_MULTI_SPACE_UNSUPPORTED',\n summary: `Mongo target supports a single contract space; received ${perSpaceOptions.length}`,\n failingSpace: perSpaceOptions[0]?.space ?? '<unknown>',\n });\n }\n const only = perSpaceOptions[0];\n if (!only) {\n return notOk<MultiSpaceRunnerFailure>({\n code: 'MONGO_MULTI_SPACE_UNSUPPORTED',\n summary: 'Mongo executeAcrossSpaces called with no per-space plans',\n failingSpace: '<unknown>',\n });\n }\n const result = await runner.execute({ ...only, driver });\n if (!result.ok) {\n return notOk<MultiSpaceRunnerFailure>({\n ...result.failure,\n failingSpace: only.space,\n });\n }\n return ok({\n perSpaceResults: [{ space: only.space, value: result.value }],\n });\n },\n };\n return runner;\n },\n contractToSchema(contract: Contract | null) {\n return contractToMongoSchemaIR(contract as MongoContract | null);\n },\n },\n create() {\n return { familyId: 'mongo' as const, targetId: 'mongo' as const };\n },\n};\n"],"mappings":";;;;;;;;;;;AAKA,SAAgB,kBAAkB,QAAuC;CACvE,MAAM,kBAAkB,OAAO,YAAY,KAAK,eAC9C,uBAAuB,WAAW,MAAM,WAAW,CACpD;CAED,OAAO,EACL,MAAM,IAAI,eAAe;EACvB,MAAM;EACN,IAAI;EACJ,OAAO;EACP,GAAG,UAAU,YAAY,gBAAgB,SAAS,IAAI,kBAAkB,KAAA,EAAU;EACnF,CAAC,EACH;;AAGH,SAAS,uBAAuB,MAAc,YAAmD;CAC/F,MAAM,WAA6B,EAAE;CAErC,KAAK,MAAM,SAAS,WAAW,SAAS;EACtC,MAAM,cAAc,MAAM,KACvB,KAAK,MAAM;GACV,IAAI,EAAE,cAAc,GAAG,OAAO,EAAE;GAChC,IAAI,EAAE,cAAc,IAAI,OAAO,GAAG,EAAE,MAAM;GAC1C,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE;IACvB,CACD,KAAK,KAAK;EACb,MAAM,SAAS,MAAM,SAAS,iBAAiB;EAC/C,MAAM,UAAoB,EAAE;EAC5B,IAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS;EACxC,IAAI,MAAM,sBAAsB,MAAM,QAAQ,KAAK,QAAQ,MAAM,mBAAmB,GAAG;EACvF,IAAI,MAAM,yBAAyB,QAAQ,KAAK,UAAU;EAC1D,MAAM,aAAa,QAAQ,SAAS,IAAI,KAAK,QAAQ,KAAK,KAAK,CAAC,KAAK;EAErE,SAAS,KACP,IAAI,eAAe;GACjB,MAAM;GACN,IAAI,SAAS,KAAK,GAAG,MAAM,KAAK,KAAK,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,YAAY,CAAC,KAAK,IAAI;GACjF,OAAO,GAAG,OAAO,IAAI,YAAY,GAAG;GACpC,MAAM;IACJ,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd,GAAG,UAAU,UAAU,MAAM,UAAU,KAAA,EAAU;IACjD,GAAG,UAAU,sBAAsB,MAAM,sBAAsB,KAAA,EAAU;IACzE,GAAG,UAAU,2BAA2B,MAAM,2BAA2B,KAAA,EAAU;IACpF;GACF,CAAC,CACH;;CAGH,IAAI,WAAW,WAAW;EACxB,MAAM,oBAAsC,EAAE;EAC9C,MAAM,aAAa,WAAW,UAAU;EACxC,MAAM,aAAa,WAAW;EAG9B,MAAM,WAAW,IAAI,IAAK,WAAW,eAAwC,EAAE,CAAC;EAEhF,IAAI,YACF,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,WAAW,EAAE;GAC5D,MAAM,WAAY,QAAQ,eAA0B;GACpD,MAAM,SAAS,SAAS,IAAI,SAAS,GAAG,gBAAgB;GACxD,kBAAkB,KAChB,IAAI,eAAe;IACjB,MAAM;IACN,IAAI,SAAS,KAAK,GAAG;IACrB,OAAO,GAAG,SAAS,IAAI,WAAW;IACnC,CAAC,CACH;;EAIL,SAAS,KACP,IAAI,eAAe;GACjB,MAAM;GACN,IAAI,aAAa;GACjB,OAAO,qBAAqB,WAAW,UAAU,gBAAgB,YAAY,WAAW,UAAU,iBAAiB;GACnH,MAAM;IACJ,iBAAiB,WAAW,UAAU;IACtC,kBAAkB,WAAW,UAAU;IACvC,YAAY,WAAW,UAAU;IAClC;GACD,GAAG,UAAU,YAAY,kBAAkB,SAAS,IAAI,oBAAoB,KAAA,EAAU;GACvF,CAAC,CACH;;CAGH,IAAI,WAAW,SAAS;EACtB,MAAM,OAAO,WAAW;EACxB,MAAM,YAAsB,EAAE;EAC9B,IAAI,KAAK,QAAQ,UAAU,KAAK,SAAS;EACzC,IAAI,KAAK,YAAY,UAAU,KAAK,aAAa;EACjD,IAAI,KAAK,WAAW,UAAU,KAAK,YAAY;EAC/C,IAAI,KAAK,8BAA8B,UAAU,KAAK,+BAA+B;EACrF,IAAI,KAAK,gBAAgB,UAAU,KAAK,iBAAiB;EAEzD,IAAI,UAAU,SAAS,GACrB,SAAS,KACP,IAAI,eAAe;GACjB,MAAM;GACN,IAAI,WAAW;GACf,OAAO,YAAY,UAAU,KAAK,KAAK,CAAC;GACxC,MAAM;IACJ,GAAG,UAAU,UAAU,KAAK,UAAU,KAAA,EAAU;IAChD,GAAG,UAAU,cAAc,KAAK,cAAc,KAAA,EAAU;IACxD,GAAG,UAAU,aAAa,KAAK,aAAa,KAAA,EAAU;IACtD,GAAG,UACD,gCACA,KAAK,gCAAgC,KAAA,EACtC;IACD,GAAG,UAAU,kBAAkB,KAAK,kBAAkB,KAAA,EAAU;IACjE;GACF,CAAC,CACH;;CAIL,OAAO,IAAI,eAAe;EACxB,MAAM;EACN,IAAI,cAAc;EAClB,OAAO,cAAc;EACrB,GAAG,UAAU,YAAY,SAAS,SAAS,IAAI,WAAW,KAAA,EAAU;EACrE,CAAC;;;;ACnFJ,SAASA,YAAU,QAAoD;CACrE,MAAM,cAAc;CACpB,IAAI,CAAC,YAAY,IACf,MAAM,IAAI,MACR,8HAED;CAEH,OAAO,YAAY;;AAGrB,IAAM,sBAAN,MAAgE;CAC9D,WAAoB;CAEpB,iBAAiB,cAAiC;EAIhD,OAHkB,sBAAqC,aAGvC,CAAC;;CAGnB,MAAM,OAAO,SAMqB;EAChC,MAAM,EAAE,QAAQ,UAAU,aAAa,kBAAkB,cAAc,eAAe;EACtF,MAAM,YAAY,KAAK,KAAK;EAG5B,MAAM,WADY,sBAAqC,YAC7B,CAAC;EAE3B,MAAM,sBAAsB,SAAS,QAAQ;EAC7C,MAAM,sBAAsB,SAAS;EACrC,MAAM,iBAAiB,SAAS;EAEhC,MAAM,WAAW;GACf;GACA;GACA;GACA;GACA,GAAG,UAAU,cAAc,WAAW;GACvC;EAED,IAAI,mBAAmB,kBACrB,OAAO,kBAAkB;GACvB,GAAG;GACH,IAAI;GACJ,MAAM;GACN,SAAS;GACT,gBAAgB;GAChB,WAAW,KAAK,KAAK,GAAG;GACzB,CAAC;EAIJ,MAAM,SAAS,MAAM,WADVA,YAAU,OACa,CAAC;EAEnC,IAAI,CAAC,QACH,OAAO,kBAAkB;GACvB,GAAG;GACH,IAAI;GACJ,MAAM;GACN,SAAS;GACT,WAAW,KAAK,KAAK,GAAG;GACzB,CAAC;EAGJ,IAAI,OAAO,gBAAgB,qBACzB,OAAO,kBAAkB;GACvB,GAAG;GACH,IAAI;GACJ,MAAM;GACN,SAAS;GACT;GACA,WAAW,KAAK,KAAK,GAAG;GACzB,CAAC;EAGJ,IAAI,uBAAuB,OAAO,gBAAgB,qBAChD,OAAO,kBAAkB;GACvB,GAAG;GACH,IAAI;GACJ,MAAM;GACN,SAAS;GACT;GACA,WAAW,KAAK,KAAK,GAAG;GACzB,CAAC;EAGJ,OAAO,kBAAkB;GACvB,GAAG;GACH,IAAI;GACJ,SAAS;GACT;GACA,WAAW,KAAK,KAAK,GAAG;GACzB,CAAC;;CAGJ,MAAM,aAAa,SAOqB;EACtC,MAAM,EAAE,QAAQ,UAAU,aAAa,QAAQ,cAAc,eAAe;EAG5E,MAAM,WADY,sBAAqC,YAC7B,CAAC;EAK3B,OAAO,kBAAkB;GACvB;GACA,QAAQ,MAJW,iBADVA,YAAU,OACmB,CAAC;GAKvC;GACA,qBAAqB,QAAQ;GAC7B,SAAS;IACP;IACA,GAAG,UAAU,cAAc,WAAW;IACvC;GACF,CAAC;;CAGJ,0BAA0B,SAKK;EAE7B,OAAO,kBAAkB;GACvB,UAFgB,sBAAqC,QAAQ,SAE1C,CAAC;GACpB,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,qBAAqB,QAAQ;GAC9B,CAAC;;CAGJ,MAAM,KAAK,SAKqB;EAC9B,MAAM,EAAE,QAAQ,UAAU,aAAa,cAAc,eAAe;EACpE,MAAM,YAAY,KAAK,KAAK;EAG5B,MAAM,WADY,sBAAqC,YAC7B,CAAC;EAE3B,MAAM,sBAAsB,SAAS,QAAQ;EAC7C,MAAM,sBAAsB,SAAS;EAErC,MAAM,KAAKA,YAAU,OAAO;EAE5B,MAAM,iBAAiB,MAAM,WAAW,GAAG;EAE3C,IAAI,gBAAgB;EACpB,IAAI,gBAAgB;EACpB,IAAI;EAEJ,IAAI,CAAC,gBAAgB;GACnB,MAAM,WAAW,IAAI;IACnB,aAAa;IACb,aAAa;IACd,CAAC;GACF,gBAAgB;SACX;GACL,MAAM,qBAAqB,eAAe,gBAAgB;GAC1D,MAAM,qBAAqB,eAAe,gBAAgB;GAE1D,IAAI,CAAC,sBAAsB,CAAC,oBAAoB;IAC9C,iBAAiB;KACf,aAAa,eAAe;KAC5B,aAAa,eAAe;KAC7B;IAKD,IAAI,CAAC,MAJiB,aAAa,IAAI,eAAe,aAAa;KACjE,aAAa;KACb,aAAa;KACd,CAAC,EAEA,MAAM,IAAI,MAAM,mEAAmE;IAErF,gBAAgB;;;EAIpB,IAAI;EACJ,IAAI,eACF,UAAU;OACL,IAAI,eACT,UAAU,wCAAwC,gBAAgB,eAAe,UAAU;OAE3F,UAAU;EAGZ,OAAO;GACL,IAAI;GACJ;GACA,UAAU;IACR,aAAa;IACb,aAAa;IACd;GACD,QAAQ;IACN,UAAU,SAAS;IACnB,QAAQ,SAAS;IAClB;GACD,QAAQ;IACN,SAAS;IACT,SAAS;IACT,GAAG,UAAU,YAAY,eAAe;IACzC;GACD,MAAM;IACJ;IACA,GAAG,UAAU,cAAc,WAAW;IACvC;GACD,SAAS,EACP,OAAO,KAAK,KAAK,GAAG,WACrB;GACF;;CAGH,MAAM,WAAW,SAGwB;EACvC,IAAI,QAAQ,UAAU,cACpB,MAAM,IAAI,MACR,mGACuC,QAAQ,MAAM,eAAe,aAAa,kIAElF;EAGH,OAAO,WADIA,YAAU,QAAQ,OACT,CAAC;;CAQvB,MAAM,eAAe,SAEkC;EACrD,MAAM,YAAY,MAAM,KAAK,WAAW;GAAE,GAAG;GAAS,OAAO;GAAc,CAAC;EAC5E,IAAI,cAAc,MAAM,uBAAO,IAAI,KAAK;EACxC,OAAO,IAAI,IAAI,CAAC,CAAC,cAAc,UAAU,CAAC,CAAC;;CAG7C,MAAM,WAAW,SAGU;EAEzB,OAAO,iBADIA,YAAU,QAAQ,OACH,CAAC;;CAG7B,aAAa,QAAuC;EAClD,OAAO,kBAAkB,OAAO;;CAGlC,mBAAmB,YAAiE;EAClF,OAAO,EACL,YAAY,sBAAsB,WAAW,CAAC,KAAK,UAAU;GAC3D;GACA,UAAU;GACX,EAAE,EACJ;;;AAIL,SAAS,kBAAkB,MAYF;CACvB,OAAO;EACL,IAAI,KAAK;EACT,GAAG,UAAU,QAAQ,KAAK,KAAK;EAC/B,SAAS,KAAK;EACd,UAAU;GACR,aAAa,KAAK;GAClB,GAAG,UAAU,eAAe,KAAK,oBAAoB;GACtD;EACD,GAAG,UACD,UACA,KAAK,SACD;GAAE,aAAa,KAAK,OAAO;GAAa,aAAa,KAAK,OAAO;GAAa,GAC9E,KAAA,EACL;EACD,QAAQ;GACN,UAAU,KAAK;GACf,GAAG,UAAU,UAAU,KAAK,eAAe;GAC5C;EACD,MAAM;GACJ,cAAc,KAAK;GACnB,GAAG,UAAU,cAAc,KAAK,WAAW;GAC5C;EACD,SAAS,EAAE,OAAO,KAAK,WAAW;EACnC;;AAGH,SAAgB,0BAA0B,eAAyD;CACjG,OAAO,IAAI,qBAAqB;;;;AClWlC,IAAM,wBAAN,MAEA;CACE,OAAgB;CAChB,KAAc;CACd,WAAoB;CACpB,UAAmB;CACnB,WAAoB;CAEpB,OACE,OAC4B;EAC5B,OAAO,0BAA0B,MAAM;;;AAI3C,MAAa,wBACX,IAAI,uBAAuB;;;;;;;;;;;;;ACO7B,MAAa,wBAIT;CACF,GAAG;CACH,YAAY;EACV,cAAc,SAAqC;GACjD,OAAO,IAAI,uBAAuB;;EAEpC,aAAa,QAAoC;GAG/C,IAAI;GACJ,MAAM,SACJ;IACE,MAAM,QAAQ,SAAS;KACrB,eAAe,sBACb,QAAQ,QACR,gBAAgB,OAAO,UAAU,QAAQ,OAAO,CAAC,EACjD,OACD;KACD,MAAM,EAAE,QAAQ,GAAG,GAAG,kBAAkB;KASxC,OAAO,IAAI,qBAAqB,WAAW,CAAC,QAAQ;MAClD,GAAG;MACH,qBAAqB,cAAc;MACpC,CAAC;;IASJ,MAAM,oBAAoB,EAAE,QAAQ,mBAAoD;KACtF,IAAI,gBAAgB,WAAW,GAC7B,OAAO,MAA+B;MACpC,MAAM;MACN,SAAS,2DAA2D,gBAAgB;MACpF,cAAc,gBAAgB,IAAI,SAAS;MAC5C,CAAC;KAEJ,MAAM,OAAO,gBAAgB;KAC7B,IAAI,CAAC,MACH,OAAO,MAA+B;MACpC,MAAM;MACN,SAAS;MACT,cAAc;MACf,CAAC;KAEJ,MAAM,SAAS,MAAM,OAAO,QAAQ;MAAE,GAAG;MAAM;MAAQ,CAAC;KACxD,IAAI,CAAC,OAAO,IACV,OAAO,MAA+B;MACpC,GAAG,OAAO;MACV,cAAc,KAAK;MACpB,CAAC;KAEJ,OAAO,GAAG,EACR,iBAAiB,CAAC;MAAE,OAAO,KAAK;MAAO,OAAO,OAAO;MAAO,CAAC,EAC9D,CAAC;;IAEL;GACH,OAAO;;EAET,iBAAiB,UAA2B;GAC1C,OAAO,wBAAwB,SAAiC;;EAEnE;CACD,SAAS;EACP,OAAO;GAAE,UAAU;GAAkB,UAAU;GAAkB;;CAEpE"}
1
+ {"version":3,"file":"control.mjs","names":["extractDb"],"sources":["../src/core/schema-to-view.ts","../src/core/control-instance.ts","../src/core/control-descriptor.ts","../src/core/mongo-target-descriptor.ts"],"sourcesContent":["import type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport { SchemaTreeNode } from '@prisma-next/framework-components/control';\nimport type { MongoSchemaCollection, MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nexport function mongoSchemaToView(schema: MongoSchemaIR): CoreSchemaView {\n const collectionNodes = schema.collections.map((collection) =>\n collectionToSchemaNode(collection.name, collection),\n );\n\n return {\n root: new SchemaTreeNode({\n kind: 'root',\n id: 'mongo-schema',\n label: 'database',\n ...ifDefined('children', collectionNodes.length > 0 ? collectionNodes : undefined),\n }),\n };\n}\n\nfunction collectionToSchemaNode(name: string, collection: MongoSchemaCollection): SchemaTreeNode {\n const children: SchemaTreeNode[] = [];\n\n for (const index of collection.indexes) {\n const keysSummary = index.keys\n .map((k) => {\n if (k.direction === 1) return k.field;\n if (k.direction === -1) return `${k.field} desc`;\n return `${k.field} ${k.direction}`;\n })\n .join(', ');\n const prefix = index.unique ? 'unique index' : 'index';\n const options: string[] = [];\n if (index.sparse) options.push('sparse');\n if (index.expireAfterSeconds != null) options.push(`ttl: ${index.expireAfterSeconds}s`);\n if (index.partialFilterExpression) options.push('partial');\n const optsSuffix = options.length > 0 ? ` (${options.join(', ')})` : '';\n\n children.push(\n new SchemaTreeNode({\n kind: 'index',\n id: `index-${name}-${index.keys.map((k) => `${k.field}_${k.direction}`).join('_')}`,\n label: `${prefix} (${keysSummary})${optsSuffix}`,\n meta: {\n keys: index.keys,\n unique: index.unique,\n ...ifDefined('sparse', index.sparse || undefined),\n ...ifDefined('expireAfterSeconds', index.expireAfterSeconds ?? undefined),\n ...ifDefined('partialFilterExpression', index.partialFilterExpression ?? undefined),\n },\n }),\n );\n }\n\n if (collection.validator) {\n const validatorChildren: SchemaTreeNode[] = [];\n const jsonSchema = collection.validator.jsonSchema as Record<string, unknown>;\n const properties = jsonSchema['properties'] as\n | Record<string, Record<string, unknown>>\n | undefined;\n const required = new Set((jsonSchema['required'] as string[] | undefined) ?? []);\n\n if (properties) {\n for (const [propName, propDef] of Object.entries(properties)) {\n const bsonType = (propDef['bsonType'] as string) ?? 'unknown';\n const suffix = required.has(propName) ? ' (required)' : '';\n validatorChildren.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `field-${name}-${propName}`,\n label: `${propName}: ${bsonType}${suffix}`,\n }),\n );\n }\n }\n\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `validator-${name}`,\n label: `validator (level: ${collection.validator.validationLevel}, action: ${collection.validator.validationAction})`,\n meta: {\n validationLevel: collection.validator.validationLevel,\n validationAction: collection.validator.validationAction,\n jsonSchema: collection.validator.jsonSchema,\n },\n ...ifDefined('children', validatorChildren.length > 0 ? validatorChildren : undefined),\n }),\n );\n }\n\n if (collection.options) {\n const opts = collection.options;\n const optLabels: string[] = [];\n if (opts.capped) optLabels.push('capped');\n if (opts.timeseries) optLabels.push('timeseries');\n if (opts.collation) optLabels.push('collation');\n if (opts.changeStreamPreAndPostImages) optLabels.push('changeStreamPreAndPostImages');\n if (opts.clusteredIndex) optLabels.push('clusteredIndex');\n\n if (optLabels.length > 0) {\n children.push(\n new SchemaTreeNode({\n kind: 'field',\n id: `options-${name}`,\n label: `options (${optLabels.join(', ')})`,\n meta: {\n ...ifDefined('capped', opts.capped ?? undefined),\n ...ifDefined('timeseries', opts.timeseries ?? undefined),\n ...ifDefined('collation', opts.collation ?? undefined),\n ...ifDefined(\n 'changeStreamPreAndPostImages',\n opts.changeStreamPreAndPostImages ?? undefined,\n ),\n ...ifDefined('clusteredIndex', opts.clusteredIndex ?? undefined),\n },\n }),\n );\n }\n }\n\n return new SchemaTreeNode({\n kind: 'collection',\n id: `collection-${name}`,\n label: `collection ${name}`,\n ...ifDefined('children', children.length > 0 ? children : undefined),\n });\n}\n","import { introspectSchema } from '@prisma-next/adapter-mongo/control';\nimport type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlStack,\n CoreSchemaView,\n MigrationPlanOperation,\n OperationPreview,\n OperationPreviewCapable,\n SchemaViewCapable,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport {\n APP_SPACE_ID,\n VERIFY_CODE_HASH_MISMATCH,\n VERIFY_CODE_MARKER_MISSING,\n VERIFY_CODE_TARGET_MISMATCH,\n} from '@prisma-next/framework-components/control';\nimport { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport { validateMongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport {\n formatMongoOperations,\n initMarker,\n readAllMarkers,\n readMarker,\n updateMarker,\n} from '@prisma-next/target-mongo/control';\nimport { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { Db } from 'mongodb';\nimport type { MongoControlExtensionDescriptor } from './control-types';\nimport { mongoSchemaToView } from './schema-to-view';\n\nexport interface MongoControlFamilyInstance\n extends ControlFamilyInstance<'mongo', MongoSchemaIR>,\n SchemaViewCapable<MongoSchemaIR>,\n OperationPreviewCapable {\n validateContract(contractJson: unknown): Contract;\n}\n\nfunction extractDb(driver: ControlDriverInstance<'mongo', string>): Db {\n const mongoDriver = driver as ControlDriverInstance<'mongo', string> & { db?: Db };\n if (!mongoDriver.db) {\n throw new Error(\n 'Mongo control driver does not expose a db property. ' +\n 'Use createMongoControlDriver() from @prisma-next/adapter-mongo/control.',\n );\n }\n return mongoDriver.db;\n}\n\nclass MongoFamilyInstance implements MongoControlFamilyInstance {\n readonly familyId = 'mongo' as const;\n\n validateContract(contractJson: unknown): Contract {\n const validated = validateMongoContract<MongoContract>(contractJson);\n // MongoContract and Contract share structure but are typed independently;\n // validateMongoContract guarantees the shape, so the double cast is safe.\n return validated.contract as unknown as Contract;\n }\n\n async verify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly expectedTargetId: string;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<VerifyDatabaseResult> {\n const { driver, contract: rawContract, expectedTargetId, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n const contractTarget = contract.target;\n\n const baseOpts = {\n contractStorageHash,\n contractProfileHash,\n expectedTargetId,\n contractPath,\n ...ifDefined('configPath', configPath),\n };\n\n if (contractTarget !== expectedTargetId) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_TARGET_MISMATCH,\n summary: 'Target mismatch',\n actualTargetId: contractTarget,\n totalTime: Date.now() - startTime,\n });\n }\n\n const db = extractDb(driver);\n const marker = await readMarker(db, APP_SPACE_ID);\n\n if (!marker) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_MARKER_MISSING,\n summary: 'Marker missing',\n totalTime: Date.now() - startTime,\n });\n }\n\n if (marker.storageHash !== contractStorageHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n if (contractProfileHash && marker.profileHash !== contractProfileHash) {\n return buildVerifyResult({\n ...baseOpts,\n ok: false,\n code: VERIFY_CODE_HASH_MISMATCH,\n summary: 'Hash mismatch',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n return buildVerifyResult({\n ...baseOpts,\n ok: true,\n summary: 'Database matches contract',\n marker,\n totalTime: Date.now() - startTime,\n });\n }\n\n async schemaVerify(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly strict: boolean;\n readonly contractPath: string;\n readonly configPath?: string;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;\n }): Promise<VerifyDatabaseSchemaResult> {\n const { driver, contract: rawContract, strict, contractPath, configPath } = options;\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const db = extractDb(driver);\n const liveIR = await introspectSchema(db);\n\n return verifyMongoSchema({\n contract,\n schema: liveIR,\n strict,\n frameworkComponents: options.frameworkComponents,\n context: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n });\n }\n\n schemaVerifyAgainstSchema(options: {\n readonly contract: unknown;\n readonly schema: MongoSchemaIR;\n readonly strict: boolean;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', string>>;\n }): VerifyDatabaseSchemaResult {\n const validated = validateMongoContract<MongoContract>(options.contract);\n return verifyMongoSchema({\n contract: validated.contract,\n schema: options.schema,\n strict: options.strict,\n frameworkComponents: options.frameworkComponents,\n });\n }\n\n async sign(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract: unknown;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<SignDatabaseResult> {\n const { driver, contract: rawContract, contractPath, configPath } = options;\n const startTime = Date.now();\n\n const validated = validateMongoContract<MongoContract>(rawContract);\n const contract = validated.contract;\n\n const contractStorageHash = contract.storage.storageHash;\n const contractProfileHash = contract.profileHash;\n\n const db = extractDb(driver);\n\n const existingMarker = await readMarker(db, APP_SPACE_ID);\n\n let markerCreated = false;\n let markerUpdated = false;\n let previousHashes: { storageHash?: string; profileHash?: string } | undefined;\n\n if (!existingMarker) {\n await initMarker(db, APP_SPACE_ID, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n markerCreated = true;\n } else {\n const storageHashMatches = existingMarker.storageHash === contractStorageHash;\n const profileHashMatches = existingMarker.profileHash === contractProfileHash;\n\n if (!storageHashMatches || !profileHashMatches) {\n previousHashes = {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n };\n const updated = await updateMarker(db, APP_SPACE_ID, existingMarker.storageHash, {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n });\n if (!updated) {\n throw new Error('CAS conflict: marker was modified by another process during sign');\n }\n markerUpdated = true;\n }\n }\n\n let summary: string;\n if (markerCreated) {\n summary = 'Database signed (marker created)';\n } else if (markerUpdated) {\n summary = `Database signed (marker updated from ${previousHashes?.storageHash ?? 'unknown'})`;\n } else {\n summary = 'Database already signed with this contract';\n }\n\n return {\n ok: true,\n summary,\n contract: {\n storageHash: contractStorageHash,\n profileHash: contractProfileHash,\n },\n target: {\n expected: contract.target,\n actual: contract.target,\n },\n marker: {\n created: markerCreated,\n updated: markerUpdated,\n ...ifDefined('previous', previousHashes),\n },\n meta: {\n contractPath,\n ...ifDefined('configPath', configPath),\n },\n timings: {\n total: Date.now() - startTime,\n },\n };\n }\n\n async readMarker(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly space: string;\n }): Promise<ContractMarkerRecord | null> {\n const db = extractDb(options.driver);\n return readMarker(db, options.space);\n }\n\n async readAllMarkers(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n }): Promise<ReadonlyMap<string, ContractMarkerRecord>> {\n const db = extractDb(options.driver);\n return readAllMarkers(db);\n }\n\n async introspect(options: {\n readonly driver: ControlDriverInstance<'mongo', string>;\n readonly contract?: unknown;\n }): Promise<MongoSchemaIR> {\n const db = extractDb(options.driver);\n return introspectSchema(db);\n }\n\n toSchemaView(schema: MongoSchemaIR): CoreSchemaView {\n return mongoSchemaToView(schema);\n }\n\n toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {\n return {\n statements: formatMongoOperations(operations).map((text) => ({\n text,\n language: 'mongodb-shell',\n })),\n };\n }\n}\n\nfunction buildVerifyResult(opts: {\n ok: boolean;\n code?: string;\n summary: string;\n contractStorageHash: string;\n contractProfileHash?: string;\n marker?: ContractMarkerRecord;\n expectedTargetId: string;\n actualTargetId?: string;\n contractPath: string;\n configPath?: string;\n totalTime: number;\n}): VerifyDatabaseResult {\n return {\n ok: opts.ok,\n ...ifDefined('code', opts.code),\n summary: opts.summary,\n contract: {\n storageHash: opts.contractStorageHash,\n ...ifDefined('profileHash', opts.contractProfileHash),\n },\n ...ifDefined(\n 'marker',\n opts.marker\n ? { storageHash: opts.marker.storageHash, profileHash: opts.marker.profileHash }\n : undefined,\n ),\n target: {\n expected: opts.expectedTargetId,\n ...ifDefined('actual', opts.actualTargetId),\n },\n meta: {\n contractPath: opts.contractPath,\n ...ifDefined('configPath', opts.configPath),\n },\n timings: { total: opts.totalTime },\n };\n}\n\nexport function createMongoFamilyInstance(controlStack: ControlStack): MongoControlFamilyInstance {\n // Descriptor self-consistency check.\n // Each extension that exposes a `contractSpace` must publish a\n // `headRef.hash` that matches the canonical hash recomputed from its\n // `contractJson`. A stale value would silently corrupt every downstream\n // boundary that trusts `headRef.hash` as the canonical identity (drift\n // detection, on-disk artefact emission, runner marker writes). Failing\n // fast at descriptor-load time turns \"extension author shipped an\n // inconsistent descriptor\" into an explicit, actionable error\n // (`MIGRATION.DESCRIPTOR_HEAD_HASH_MISMATCH`) rather than a confusing\n // mismatch surfacing several layers downstream. Mirrors the SQL family.\n const extensions = (controlStack.extensionPacks ??\n []) as readonly MongoControlExtensionDescriptor[];\n for (const extension of extensions) {\n if (extension.contractSpace) {\n const { contractJson, headRef } = extension.contractSpace;\n assertDescriptorSelfConsistency({\n extensionId: extension.id,\n target: contractJson.target,\n targetFamily: contractJson.targetFamily,\n storage: contractJson.storage,\n headRefHash: headRef.hash,\n });\n }\n }\n return new MongoFamilyInstance();\n}\n","import type {\n ControlFamilyDescriptor,\n ControlStack,\n} from '@prisma-next/framework-components/control';\nimport { mongoEmission } from '@prisma-next/mongo-emitter';\nimport { createMongoFamilyInstance, type MongoControlFamilyInstance } from './control-instance';\n\nclass MongoFamilyDescriptor\n implements ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance>\n{\n readonly kind = 'family' as const;\n readonly id = 'mongo';\n readonly familyId = 'mongo' as const;\n readonly version = '0.0.1';\n readonly emission = mongoEmission;\n\n create<TTargetId extends string>(\n stack: ControlStack<'mongo', TTargetId>,\n ): MongoControlFamilyInstance {\n return createMongoFamilyInstance(stack);\n }\n}\n\nexport const mongoFamilyDescriptor: ControlFamilyDescriptor<'mongo', MongoControlFamilyInstance> =\n new MongoFamilyDescriptor();\n","import { createMongoRunnerDeps, extractDb } from '@prisma-next/adapter-mongo/control';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { MongoDriverImpl } from '@prisma-next/driver-mongo';\nimport type {\n MigratableTargetDescriptor,\n MigrationRunner,\n MigrationRunnerResult,\n MigrationRunnerSuccessValue,\n MultiSpaceCapableRunner,\n MultiSpaceRunnerFailure,\n MultiSpaceRunnerPerSpaceOptions,\n MultiSpaceRunnerResult,\n} from '@prisma-next/framework-components/control';\nimport {\n type ContractSpaceMember,\n projectSchemaToSpace,\n} from '@prisma-next/migration-tools/aggregate';\nimport type { MongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoSchemaCollection } from '@prisma-next/mongo-schema-ir';\nimport { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport {\n contractToMongoSchemaIR,\n MongoMigrationPlanner,\n MongoMigrationRunner,\n type MongoMigrationRunnerExecuteOptions,\n type MongoRunnerDependencies,\n} from '@prisma-next/target-mongo/control';\nimport mongoTargetDescriptorMeta from '@prisma-next/target-mongo/pack';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type { MongoControlFamilyInstance } from './control-instance';\n\n/**\n * `migration.ts` default-exports a `Migration` subclass whose `operations`\n * getter returns the ordered list of operations and whose `describe()`\n * returns the manifest identity metadata. `MongoMigrationPlanner.plan()`\n * returns a `MigrationPlanWithAuthoringSurface` that knows how to render\n * itself back to such a file; `MongoMigrationPlanner.emptyMigration()`\n * returns the same shape for `migration new`. Users run the scaffolded\n * `migration.ts` directly (via `node migration.ts`) to self-emit\n * `ops.json` and attest the `migrationHash`.\n */\nexport const mongoTargetDescriptor: MigratableTargetDescriptor<\n 'mongo',\n 'mongo',\n MongoControlFamilyInstance\n> = {\n ...mongoTargetDescriptorMeta,\n migrations: {\n createPlanner(_family: MongoControlFamilyInstance) {\n return new MongoMigrationPlanner();\n },\n createRunner(family: MongoControlFamilyInstance) {\n // Deps are bound to the first driver passed to execute() and cached for\n // subsequent calls. Callers must not change the driver between calls.\n let cachedDeps: MongoRunnerDependencies | undefined;\n\n const runMongo = async (\n driver: Parameters<MigrationRunner<'mongo', 'mongo'>['execute']>[0]['driver'],\n runnerOptions: Omit<MongoMigrationRunnerExecuteOptions, 'destinationContract'> & {\n readonly destinationContract: unknown;\n },\n ): Promise<MigrationRunnerResult> => {\n cachedDeps ??= createMongoRunnerDeps(\n driver,\n MongoDriverImpl.fromDb(extractDb(driver)),\n family,\n );\n // The framework `MigrationRunner` interface types `destinationContract`\n // as `unknown`; the Mongo runner narrows to `MongoContract`. Validation\n // happens upstream — `migration apply` calls\n // `familyInstance.validateContract(migration.toContract)` before\n // routing the contract here, so this cast preserves the framework\n // signature without weakening the runner's typed surface.\n return new MongoMigrationRunner(cachedDeps).execute({\n ...runnerOptions,\n destinationContract: runnerOptions.destinationContract as MongoContract,\n });\n };\n\n const runner: MigrationRunner<'mongo', 'mongo'> & MultiSpaceCapableRunner<'mongo', 'mongo'> =\n {\n async execute(options) {\n const { driver, ...runnerOptions } = options;\n return runMongo(driver, runnerOptions);\n },\n // Mongo cannot wrap DDL ops in a session transaction (createCollection,\n // createIndex, collMod, setValidation all bypass transactions even on\n // replica sets), so the cross-space envelope is *resumable* rather than\n // transactional. Per-space-internal verify-gated marker atomicity\n // already lives in `runner.execute`: ops apply, schema is introspected\n // and verified, and the marker advances only on verify-pass. This loop\n // composes that guarantee across spaces — earlier-advanced markers are\n // not rolled back when a later space fails. Re-running reads each\n // marker, finds spaces 1..N−1 at-head (no-op skip), retries N onward.\n //\n // Per-space verify is sliced via `projectSchemaToSpace`: the live DB\n // holds collections owned by sibling spaces, but each space's verify\n // only sees the slice that space's contract actually claims. Without\n // the projection an aggregate of two spaces could not pass strict\n // verify (every other-space collection would look like an extra).\n //\n // See `docs/architecture docs/subsystems/10. MongoDB Family.md` §\n // Contract spaces and ADR 212 — Contract spaces.\n async executeAcrossSpaces({ driver, perSpaceOptions }): Promise<MultiSpaceRunnerResult> {\n const members = perSpaceOptions.map(toSpaceMember);\n const perSpaceResults: Array<{\n space: string;\n value: MigrationRunnerSuccessValue;\n }> = [];\n for (let i = 0; i < perSpaceOptions.length; i++) {\n const spaceOptions = perSpaceOptions[i];\n if (!spaceOptions) continue;\n const member = members[i];\n if (!member) continue;\n const others = members.filter((_, j) => j !== i);\n const projectSchema = (schema: MongoSchemaIR): MongoSchemaIR => {\n // `projectSchemaToSpace` returns a plain object\n // `{...schemaIR, collections: prunedArray}` (not a\n // `MongoSchemaIR` instance), so the descriptor rewraps\n // the pruned collections into a fresh `MongoSchemaIR`\n // before handing it to `verifyMongoSchema` (which\n // depends on the class's `collectionNames` /\n // `collection(name)` accessors).\n const projected = projectSchemaToSpace(schema, member, others) as {\n readonly collections: ReadonlyArray<MongoSchemaCollection>;\n };\n return new MongoSchemaIR(projected.collections);\n };\n const result = await runMongo(driver, { ...spaceOptions, projectSchema });\n if (!result.ok) {\n return notOk<MultiSpaceRunnerFailure>({\n ...result.failure,\n failingSpace: spaceOptions.space,\n });\n }\n perSpaceResults.push({ space: spaceOptions.space, value: result.value });\n }\n return ok({ perSpaceResults });\n },\n };\n return runner;\n },\n contractToSchema(contract: Contract | null) {\n return contractToMongoSchemaIR(contract as MongoContract | null);\n },\n },\n create() {\n return { familyId: 'mongo' as const, targetId: 'mongo' as const };\n },\n};\n\n/**\n * Synthesise the minimum {@link projectSchemaToSpace}-compatible\n * `ContractSpaceMember` shape from a per-space option entry. The\n * projector only reads `spaceId` and `contract.storage`; the rest of\n * `ContractSpaceMember` (head ref invariants, hydrated migration\n * graph) is irrelevant at runner time and stubbed with sentinels.\n *\n * The `as unknown as ContractSpaceMember` cast is the load-bearing bit\n * — the projector duck-types its members so a sentinel-shaped graph\n * never gets read, but the framework type carries a richer shape.\n */\nfunction toSpaceMember(\n opts: MultiSpaceRunnerPerSpaceOptions<'mongo', 'mongo'>,\n): ContractSpaceMember {\n return {\n spaceId: opts.space,\n contract: opts.destinationContract as Contract,\n headRef: { hash: '', invariants: [] },\n migrations: {\n graph: {\n nodes: new Set<string>(),\n forwardChain: new Map(),\n reverseChain: new Map(),\n migrationByHash: new Map(),\n },\n packagesByMigrationHash: new Map(),\n },\n } as unknown as ContractSpaceMember;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/family-mongo",
3
- "version": "0.6.0-dev.12",
3
+ "version": "0.6.0-dev.14",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -8,25 +8,26 @@
8
8
  "dependencies": {
9
9
  "mongodb": "^6.16.0",
10
10
  "pathe": "^2.0.3",
11
- "@prisma-next/adapter-mongo": "0.6.0-dev.12",
12
- "@prisma-next/contract": "0.6.0-dev.12",
13
- "@prisma-next/emitter": "0.6.0-dev.12",
14
- "@prisma-next/driver-mongo": "0.6.0-dev.12",
15
- "@prisma-next/errors": "0.6.0-dev.12",
16
- "@prisma-next/mongo-emitter": "0.6.0-dev.12",
17
- "@prisma-next/framework-components": "0.6.0-dev.12",
18
- "@prisma-next/mongo-contract": "0.6.0-dev.12",
19
- "@prisma-next/migration-tools": "0.6.0-dev.12",
20
- "@prisma-next/mongo-query-ast": "0.6.0-dev.12",
21
- "@prisma-next/mongo-schema-ir": "0.6.0-dev.12",
22
- "@prisma-next/target-mongo": "0.6.0-dev.12",
23
- "@prisma-next/utils": "0.6.0-dev.12"
11
+ "@prisma-next/adapter-mongo": "0.6.0-dev.14",
12
+ "@prisma-next/contract": "0.6.0-dev.14",
13
+ "@prisma-next/emitter": "0.6.0-dev.14",
14
+ "@prisma-next/errors": "0.6.0-dev.14",
15
+ "@prisma-next/migration-tools": "0.6.0-dev.14",
16
+ "@prisma-next/framework-components": "0.6.0-dev.14",
17
+ "@prisma-next/driver-mongo": "0.6.0-dev.14",
18
+ "@prisma-next/mongo-schema-ir": "0.6.0-dev.14",
19
+ "@prisma-next/mongo-query-ast": "0.6.0-dev.14",
20
+ "@prisma-next/mongo-emitter": "0.6.0-dev.14",
21
+ "@prisma-next/mongo-contract": "0.6.0-dev.14",
22
+ "@prisma-next/target-mongo": "0.6.0-dev.14",
23
+ "@prisma-next/utils": "0.6.0-dev.14"
24
24
  },
25
25
  "devDependencies": {
26
26
  "tsdown": "0.22.0",
27
27
  "typescript": "5.9.3",
28
28
  "vitest": "4.1.5",
29
- "@prisma-next/mongo-contract-ts": "0.6.0-dev.12",
29
+ "@prisma-next/test-utils": "0.0.1",
30
+ "@prisma-next/mongo-contract-ts": "0.6.0-dev.14",
30
31
  "@prisma-next/tsdown": "0.0.0",
31
32
  "@prisma-next/tsconfig": "0.0.0"
32
33
  },
@@ -20,18 +20,21 @@ import {
20
20
  VERIFY_CODE_MARKER_MISSING,
21
21
  VERIFY_CODE_TARGET_MISMATCH,
22
22
  } from '@prisma-next/framework-components/control';
23
+ import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
23
24
  import type { MongoContract } from '@prisma-next/mongo-contract';
24
25
  import { validateMongoContract } from '@prisma-next/mongo-contract';
25
26
  import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
26
27
  import {
27
28
  formatMongoOperations,
28
29
  initMarker,
30
+ readAllMarkers,
29
31
  readMarker,
30
32
  updateMarker,
31
33
  } from '@prisma-next/target-mongo/control';
32
34
  import { verifyMongoSchema } from '@prisma-next/target-mongo/schema-verify';
33
35
  import { ifDefined } from '@prisma-next/utils/defined';
34
36
  import type { Db } from 'mongodb';
37
+ import type { MongoControlExtensionDescriptor } from './control-types';
35
38
  import { mongoSchemaToView } from './schema-to-view';
36
39
 
37
40
  export interface MongoControlFamilyInstance
@@ -99,7 +102,7 @@ class MongoFamilyInstance implements MongoControlFamilyInstance {
99
102
  }
100
103
 
101
104
  const db = extractDb(driver);
102
- const marker = await readMarker(db);
105
+ const marker = await readMarker(db, APP_SPACE_ID);
103
106
 
104
107
  if (!marker) {
105
108
  return buildVerifyResult({
@@ -202,14 +205,14 @@ class MongoFamilyInstance implements MongoControlFamilyInstance {
202
205
 
203
206
  const db = extractDb(driver);
204
207
 
205
- const existingMarker = await readMarker(db);
208
+ const existingMarker = await readMarker(db, APP_SPACE_ID);
206
209
 
207
210
  let markerCreated = false;
208
211
  let markerUpdated = false;
209
212
  let previousHashes: { storageHash?: string; profileHash?: string } | undefined;
210
213
 
211
214
  if (!existingMarker) {
212
- await initMarker(db, {
215
+ await initMarker(db, APP_SPACE_ID, {
213
216
  storageHash: contractStorageHash,
214
217
  profileHash: contractProfileHash,
215
218
  });
@@ -223,7 +226,7 @@ class MongoFamilyInstance implements MongoControlFamilyInstance {
223
226
  storageHash: existingMarker.storageHash,
224
227
  profileHash: existingMarker.profileHash,
225
228
  };
226
- const updated = await updateMarker(db, existingMarker.storageHash, {
229
+ const updated = await updateMarker(db, APP_SPACE_ID, existingMarker.storageHash, {
227
230
  storageHash: contractStorageHash,
228
231
  profileHash: contractProfileHash,
229
232
  });
@@ -273,28 +276,15 @@ class MongoFamilyInstance implements MongoControlFamilyInstance {
273
276
  readonly driver: ControlDriverInstance<'mongo', string>;
274
277
  readonly space: string;
275
278
  }): Promise<ContractMarkerRecord | null> {
276
- if (options.space !== APP_SPACE_ID) {
277
- throw new Error(
278
- 'Mongo target does not yet support per-space contract markers. ' +
279
- `readMarker was called with space="${options.space}", but only "${APP_SPACE_ID}" is supported. ` +
280
- 'Per-space marker support is tracked separately for Mongo and is not part of the SQL-family contract-spaces work.',
281
- );
282
- }
283
279
  const db = extractDb(options.driver);
284
- return readMarker(db);
280
+ return readMarker(db, options.space);
285
281
  }
286
282
 
287
- // Mongo does not yet participate in the per-space mechanism — the
288
- // `space` column was introduced in the SQL family's marker only.
289
- // The bridge: surface the single app marker keyed by APP_SPACE_ID so
290
- // the per-space verifier sees a coherent input shape; per-space mongo
291
- // support is a future extension.
292
283
  async readAllMarkers(options: {
293
284
  readonly driver: ControlDriverInstance<'mongo', string>;
294
285
  }): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
295
- const appMarker = await this.readMarker({ ...options, space: APP_SPACE_ID });
296
- if (appMarker === null) return new Map();
297
- return new Map([[APP_SPACE_ID, appMarker]]);
286
+ const db = extractDb(options.driver);
287
+ return readAllMarkers(db);
298
288
  }
299
289
 
300
290
  async introspect(options: {
@@ -358,6 +348,30 @@ function buildVerifyResult(opts: {
358
348
  };
359
349
  }
360
350
 
361
- export function createMongoFamilyInstance(_controlStack: ControlStack): MongoControlFamilyInstance {
351
+ export function createMongoFamilyInstance(controlStack: ControlStack): MongoControlFamilyInstance {
352
+ // Descriptor self-consistency check.
353
+ // Each extension that exposes a `contractSpace` must publish a
354
+ // `headRef.hash` that matches the canonical hash recomputed from its
355
+ // `contractJson`. A stale value would silently corrupt every downstream
356
+ // boundary that trusts `headRef.hash` as the canonical identity (drift
357
+ // detection, on-disk artefact emission, runner marker writes). Failing
358
+ // fast at descriptor-load time turns "extension author shipped an
359
+ // inconsistent descriptor" into an explicit, actionable error
360
+ // (`MIGRATION.DESCRIPTOR_HEAD_HASH_MISMATCH`) rather than a confusing
361
+ // mismatch surfacing several layers downstream. Mirrors the SQL family.
362
+ const extensions = (controlStack.extensionPacks ??
363
+ []) as readonly MongoControlExtensionDescriptor[];
364
+ for (const extension of extensions) {
365
+ if (extension.contractSpace) {
366
+ const { contractJson, headRef } = extension.contractSpace;
367
+ assertDescriptorSelfConsistency({
368
+ extensionId: extension.id,
369
+ target: contractJson.target,
370
+ targetFamily: contractJson.targetFamily,
371
+ storage: contractJson.storage,
372
+ headRefHash: headRef.hash,
373
+ });
374
+ }
375
+ }
362
376
  return new MongoFamilyInstance();
363
377
  }
@@ -0,0 +1,23 @@
1
+ import type {
2
+ ContractSpace,
3
+ ControlExtensionDescriptor,
4
+ } from '@prisma-next/framework-components/control';
5
+ import type { MongoContract, MongoStorage } from '@prisma-next/mongo-contract';
6
+
7
+ /**
8
+ * Mongo-family extension descriptor.
9
+ *
10
+ * Extensions that contribute schema opt into the per-space planner /
11
+ * runner / verifier by setting `contractSpace`. Extensions without it
12
+ * are codec-only or query-ops-only — today's behaviour preserved.
13
+ *
14
+ * The shape comes from `@prisma-next/framework-components/control`
15
+ * (`ContractSpace`) — contract-space identity is a framework concept,
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`.
19
+ */
20
+ export interface MongoControlExtensionDescriptor
21
+ extends ControlExtensionDescriptor<'mongo', 'mongo'> {
22
+ readonly contractSpace?: ContractSpace<MongoContract<MongoStorage>>;
23
+ }
@@ -4,15 +4,25 @@ import { MongoDriverImpl } from '@prisma-next/driver-mongo';
4
4
  import type {
5
5
  MigratableTargetDescriptor,
6
6
  MigrationRunner,
7
+ MigrationRunnerResult,
8
+ MigrationRunnerSuccessValue,
7
9
  MultiSpaceCapableRunner,
8
10
  MultiSpaceRunnerFailure,
11
+ MultiSpaceRunnerPerSpaceOptions,
9
12
  MultiSpaceRunnerResult,
10
13
  } from '@prisma-next/framework-components/control';
14
+ import {
15
+ type ContractSpaceMember,
16
+ projectSchemaToSpace,
17
+ } from '@prisma-next/migration-tools/aggregate';
11
18
  import type { MongoContract } from '@prisma-next/mongo-contract';
19
+ import type { MongoSchemaCollection } from '@prisma-next/mongo-schema-ir';
20
+ import { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
12
21
  import {
13
22
  contractToMongoSchemaIR,
14
23
  MongoMigrationPlanner,
15
24
  MongoMigrationRunner,
25
+ type MongoMigrationRunnerExecuteOptions,
16
26
  type MongoRunnerDependencies,
17
27
  } from '@prisma-next/target-mongo/control';
18
28
  import mongoTargetDescriptorMeta from '@prisma-next/target-mongo/pack';
@@ -43,61 +53,89 @@ export const mongoTargetDescriptor: MigratableTargetDescriptor<
43
53
  // Deps are bound to the first driver passed to execute() and cached for
44
54
  // subsequent calls. Callers must not change the driver between calls.
45
55
  let cachedDeps: MongoRunnerDependencies | undefined;
56
+
57
+ const runMongo = async (
58
+ driver: Parameters<MigrationRunner<'mongo', 'mongo'>['execute']>[0]['driver'],
59
+ runnerOptions: Omit<MongoMigrationRunnerExecuteOptions, 'destinationContract'> & {
60
+ readonly destinationContract: unknown;
61
+ },
62
+ ): Promise<MigrationRunnerResult> => {
63
+ cachedDeps ??= createMongoRunnerDeps(
64
+ driver,
65
+ MongoDriverImpl.fromDb(extractDb(driver)),
66
+ family,
67
+ );
68
+ // The framework `MigrationRunner` interface types `destinationContract`
69
+ // as `unknown`; the Mongo runner narrows to `MongoContract`. Validation
70
+ // happens upstream — `migration apply` calls
71
+ // `familyInstance.validateContract(migration.toContract)` before
72
+ // routing the contract here, so this cast preserves the framework
73
+ // signature without weakening the runner's typed surface.
74
+ return new MongoMigrationRunner(cachedDeps).execute({
75
+ ...runnerOptions,
76
+ destinationContract: runnerOptions.destinationContract as MongoContract,
77
+ });
78
+ };
79
+
46
80
  const runner: MigrationRunner<'mongo', 'mongo'> & MultiSpaceCapableRunner<'mongo', 'mongo'> =
47
81
  {
48
82
  async execute(options) {
49
- cachedDeps ??= createMongoRunnerDeps(
50
- options.driver,
51
- MongoDriverImpl.fromDb(extractDb(options.driver)),
52
- family,
53
- );
54
- const { driver: _, ...runnerOptions } = options;
55
- // The framework `MigrationRunner` interface types `destinationContract`
56
- // as `unknown`; the Mongo runner narrows to `MongoContract`. Validation
57
- // happens upstream — `migration apply` calls
58
- // `familyInstance.validateContract(migration.toContract)` before
59
- // routing the contract here (see
60
- // `packages/1-framework/3-tooling/cli/src/control-api/operations/migration-apply.ts`),
61
- // so this cast simply preserves the framework signature without
62
- // weakening the runner's typed surface or duplicating validation.
63
- return new MongoMigrationRunner(cachedDeps).execute({
64
- ...runnerOptions,
65
- destinationContract: runnerOptions.destinationContract as MongoContract,
66
- });
83
+ const { driver, ...runnerOptions } = options;
84
+ return runMongo(driver, runnerOptions);
67
85
  },
68
- // Mongo per-space is a non-goal per the extension-contract-spaces project
69
- // spec (TML-2397): no Mongo extension contract spaces exist. The aggregate
70
- // is always single-member for Mongo, so `executeAcrossSpaces` is a
71
- // degenerate shim that asserts length === 1 and delegates to `execute`.
72
- // The shim exists so `applyAggregate` (the shared CLI primitive driving
73
- // `db init` / `db update` / `migration apply`) routes through Mongo
74
- // identically to the SQL family.
86
+ // Mongo cannot wrap DDL ops in a session transaction (createCollection,
87
+ // createIndex, collMod, setValidation all bypass transactions even on
88
+ // replica sets), so the cross-space envelope is *resumable* rather than
89
+ // transactional. Per-space-internal verify-gated marker atomicity
90
+ // already lives in `runner.execute`: ops apply, schema is introspected
91
+ // and verified, and the marker advances only on verify-pass. This loop
92
+ // composes that guarantee across spaces — earlier-advanced markers are
93
+ // not rolled back when a later space fails. Re-running reads each
94
+ // marker, finds spaces 1..N−1 at-head (no-op skip), retries N onward.
95
+ //
96
+ // Per-space verify is sliced via `projectSchemaToSpace`: the live DB
97
+ // holds collections owned by sibling spaces, but each space's verify
98
+ // only sees the slice that space's contract actually claims. Without
99
+ // the projection an aggregate of two spaces could not pass strict
100
+ // verify (every other-space collection would look like an extra).
101
+ //
102
+ // See `docs/architecture docs/subsystems/10. MongoDB Family.md` §
103
+ // Contract spaces and ADR 212 — Contract spaces.
75
104
  async executeAcrossSpaces({ driver, perSpaceOptions }): Promise<MultiSpaceRunnerResult> {
76
- if (perSpaceOptions.length !== 1) {
77
- return notOk<MultiSpaceRunnerFailure>({
78
- code: 'MONGO_MULTI_SPACE_UNSUPPORTED',
79
- summary: `Mongo target supports a single contract space; received ${perSpaceOptions.length}`,
80
- failingSpace: perSpaceOptions[0]?.space ?? '<unknown>',
81
- });
82
- }
83
- const only = perSpaceOptions[0];
84
- if (!only) {
85
- return notOk<MultiSpaceRunnerFailure>({
86
- code: 'MONGO_MULTI_SPACE_UNSUPPORTED',
87
- summary: 'Mongo executeAcrossSpaces called with no per-space plans',
88
- failingSpace: '<unknown>',
89
- });
90
- }
91
- const result = await runner.execute({ ...only, driver });
92
- if (!result.ok) {
93
- return notOk<MultiSpaceRunnerFailure>({
94
- ...result.failure,
95
- failingSpace: only.space,
96
- });
105
+ const members = perSpaceOptions.map(toSpaceMember);
106
+ const perSpaceResults: Array<{
107
+ space: string;
108
+ value: MigrationRunnerSuccessValue;
109
+ }> = [];
110
+ for (let i = 0; i < perSpaceOptions.length; i++) {
111
+ const spaceOptions = perSpaceOptions[i];
112
+ if (!spaceOptions) continue;
113
+ const member = members[i];
114
+ if (!member) continue;
115
+ const others = members.filter((_, j) => j !== i);
116
+ const projectSchema = (schema: MongoSchemaIR): MongoSchemaIR => {
117
+ // `projectSchemaToSpace` returns a plain object
118
+ // `{...schemaIR, collections: prunedArray}` (not a
119
+ // `MongoSchemaIR` instance), so the descriptor rewraps
120
+ // the pruned collections into a fresh `MongoSchemaIR`
121
+ // before handing it to `verifyMongoSchema` (which
122
+ // depends on the class's `collectionNames` /
123
+ // `collection(name)` accessors).
124
+ const projected = projectSchemaToSpace(schema, member, others) as {
125
+ readonly collections: ReadonlyArray<MongoSchemaCollection>;
126
+ };
127
+ return new MongoSchemaIR(projected.collections);
128
+ };
129
+ const result = await runMongo(driver, { ...spaceOptions, projectSchema });
130
+ if (!result.ok) {
131
+ return notOk<MultiSpaceRunnerFailure>({
132
+ ...result.failure,
133
+ failingSpace: spaceOptions.space,
134
+ });
135
+ }
136
+ perSpaceResults.push({ space: spaceOptions.space, value: result.value });
97
137
  }
98
- return ok({
99
- perSpaceResults: [{ space: only.space, value: result.value }],
100
- });
138
+ return ok({ perSpaceResults });
101
139
  },
102
140
  };
103
141
  return runner;
@@ -110,3 +148,33 @@ export const mongoTargetDescriptor: MigratableTargetDescriptor<
110
148
  return { familyId: 'mongo' as const, targetId: 'mongo' as const };
111
149
  },
112
150
  };
151
+
152
+ /**
153
+ * Synthesise the minimum {@link projectSchemaToSpace}-compatible
154
+ * `ContractSpaceMember` shape from a per-space option entry. The
155
+ * projector only reads `spaceId` and `contract.storage`; the rest of
156
+ * `ContractSpaceMember` (head ref invariants, hydrated migration
157
+ * graph) is irrelevant at runner time and stubbed with sentinels.
158
+ *
159
+ * The `as unknown as ContractSpaceMember` cast is the load-bearing bit
160
+ * — the projector duck-types its members so a sentinel-shaped graph
161
+ * never gets read, but the framework type carries a richer shape.
162
+ */
163
+ function toSpaceMember(
164
+ opts: MultiSpaceRunnerPerSpaceOptions<'mongo', 'mongo'>,
165
+ ): ContractSpaceMember {
166
+ return {
167
+ spaceId: opts.space,
168
+ contract: opts.destinationContract as Contract,
169
+ headRef: { hash: '', invariants: [] },
170
+ migrations: {
171
+ graph: {
172
+ nodes: new Set<string>(),
173
+ forwardChain: new Map(),
174
+ reverseChain: new Map(),
175
+ migrationByHash: new Map(),
176
+ },
177
+ packagesByMigrationHash: new Map(),
178
+ },
179
+ } as unknown as ContractSpaceMember;
180
+ }
@@ -3,4 +3,5 @@ export {
3
3
  createMongoFamilyInstance,
4
4
  type MongoControlFamilyInstance,
5
5
  } from '../core/control-instance';
6
+ export type { MongoControlExtensionDescriptor } from '../core/control-types';
6
7
  export { mongoTargetDescriptor } from '../core/mongo-target-descriptor';