@prisma-next/family-mongo 0.6.0-dev.9 → 0.7.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.
- package/dist/control.d.mts +23 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +87 -45
- package/dist/control.mjs.map +1 -1
- package/package.json +18 -17
- package/src/core/control-instance.ts +35 -21
- package/src/core/control-types.ts +23 -0
- package/src/core/mongo-target-descriptor.ts +117 -49
- package/src/exports/control.ts +1 -0
package/dist/control.d.mts
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
import { ControlFamilyDescriptor, ControlFamilyInstance, ControlStack, MigratableTargetDescriptor, OperationPreviewCapable, SchemaViewCapable } from "@prisma-next/framework-components/control";
|
|
2
|
-
import {
|
|
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(
|
|
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
|
package/dist/control.d.mts.map
CHANGED
|
@@ -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":"
|
|
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
|
-
|
|
251
|
-
return readMarker(extractDb$1(options.driver));
|
|
253
|
+
return readMarker(extractDb$1(options.driver), options.space);
|
|
252
254
|
}
|
|
253
255
|
async readAllMarkers(options) {
|
|
254
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
package/dist/control.mjs.map
CHANGED
|
@@ -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":"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"}
|
|
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.
|
|
3
|
+
"version": "0.7.0-dev.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -8,27 +8,28 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"mongodb": "^6.16.0",
|
|
10
10
|
"pathe": "^2.0.3",
|
|
11
|
-
"@prisma-next/adapter-mongo": "0.
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/
|
|
16
|
-
"@prisma-next/framework-components": "0.
|
|
17
|
-
"@prisma-next/
|
|
18
|
-
"@prisma-next/mongo-emitter": "0.
|
|
19
|
-
"@prisma-next/mongo-
|
|
20
|
-
"@prisma-next/mongo
|
|
21
|
-
"@prisma-next/
|
|
22
|
-
"@prisma-next/
|
|
23
|
-
"@prisma-next/utils": "0.
|
|
11
|
+
"@prisma-next/adapter-mongo": "0.7.0-dev.1",
|
|
12
|
+
"@prisma-next/contract": "0.7.0-dev.1",
|
|
13
|
+
"@prisma-next/driver-mongo": "0.7.0-dev.1",
|
|
14
|
+
"@prisma-next/emitter": "0.7.0-dev.1",
|
|
15
|
+
"@prisma-next/errors": "0.7.0-dev.1",
|
|
16
|
+
"@prisma-next/framework-components": "0.7.0-dev.1",
|
|
17
|
+
"@prisma-next/mongo-query-ast": "0.7.0-dev.1",
|
|
18
|
+
"@prisma-next/mongo-emitter": "0.7.0-dev.1",
|
|
19
|
+
"@prisma-next/mongo-schema-ir": "0.7.0-dev.1",
|
|
20
|
+
"@prisma-next/target-mongo": "0.7.0-dev.1",
|
|
21
|
+
"@prisma-next/migration-tools": "0.7.0-dev.1",
|
|
22
|
+
"@prisma-next/mongo-contract": "0.7.0-dev.1",
|
|
23
|
+
"@prisma-next/utils": "0.7.0-dev.1"
|
|
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.
|
|
30
|
-
"@prisma-next/
|
|
31
|
-
"@prisma-next/tsdown": "0.0.0"
|
|
29
|
+
"@prisma-next/mongo-contract-ts": "0.7.0-dev.1",
|
|
30
|
+
"@prisma-next/test-utils": "0.0.1",
|
|
31
|
+
"@prisma-next/tsdown": "0.0.0",
|
|
32
|
+
"@prisma-next/tsconfig": "0.0.0"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
34
35
|
"dist",
|
|
@@ -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
|
|
296
|
-
|
|
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(
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
}
|
package/src/exports/control.ts
CHANGED