@prisma-next/target-mongo 0.4.2 → 0.4.4
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 +24 -7
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +98 -72
- package/dist/control.mjs.map +1 -1
- package/dist/descriptor-meta-D9_5quQi.mjs +14 -0
- package/dist/descriptor-meta-D9_5quQi.mjs.map +1 -0
- package/dist/{migration-factories-Dbk5afMU.mjs → migration-factories-CoNYWrd1.mjs} +3 -1
- package/dist/migration-factories-CoNYWrd1.mjs.map +1 -0
- package/dist/migration.d.mts +7 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +1 -1
- package/dist/{op-factory-call-CVgzmLJh.d.mts → op-factory-call--nK5dk8n.d.mts} +1 -1
- package/dist/{op-factory-call-CVgzmLJh.d.mts.map → op-factory-call--nK5dk8n.d.mts.map} +1 -1
- package/dist/pack.mjs +1 -11
- package/dist/pack.mjs.map +1 -1
- package/dist/runtime.d.mts +20 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +28 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/schema-verify.mjs +1 -1
- package/dist/{verify-mongo-schema-P0TRBJNs.mjs → verify-mongo-schema-Daa7BMJY.mjs} +1 -1
- package/dist/{verify-mongo-schema-P0TRBJNs.mjs.map → verify-mongo-schema-Daa7BMJY.mjs.map} +1 -1
- package/package.json +16 -14
- package/src/core/marker-ledger.ts +90 -20
- package/src/core/migration-factories.ts +8 -0
- package/src/core/mongo-ops-serializer.ts +0 -8
- package/src/core/mongo-planner.ts +8 -2
- package/src/core/mongo-runner.ts +80 -57
- package/src/core/planner-produced-migration.ts +0 -1
- package/src/core/render-typescript.ts +3 -7
- package/src/exports/runtime.ts +38 -0
- package/dist/migration-factories-Dbk5afMU.mjs.map +0 -1
|
@@ -197,13 +197,9 @@ const PlanMetaJson = type({
|
|
|
197
197
|
target: 'string',
|
|
198
198
|
storageHash: 'string',
|
|
199
199
|
lane: 'string',
|
|
200
|
-
paramDescriptors: 'unknown[]',
|
|
201
200
|
'targetFamily?': 'string',
|
|
202
201
|
'profileHash?': 'string',
|
|
203
202
|
'annotations?': 'Record<string, unknown>',
|
|
204
|
-
'refs?': 'Record<string, unknown>',
|
|
205
|
-
'projection?': 'Record<string, string> | string[]',
|
|
206
|
-
'projectionTypes?': 'Record<string, string>',
|
|
207
203
|
});
|
|
208
204
|
|
|
209
205
|
const QueryPlanJson = type({
|
|
@@ -427,13 +423,9 @@ export function deserializeMongoQueryPlan(json: unknown): MongoQueryPlan {
|
|
|
427
423
|
target: m.target,
|
|
428
424
|
storageHash: m.storageHash,
|
|
429
425
|
lane: m.lane,
|
|
430
|
-
paramDescriptors: m.paramDescriptors as PlanMeta['paramDescriptors'],
|
|
431
426
|
...ifDefined('targetFamily', m.targetFamily),
|
|
432
427
|
...ifDefined('profileHash', m.profileHash),
|
|
433
428
|
...ifDefined('annotations', m.annotations),
|
|
434
|
-
...ifDefined('refs', m.refs),
|
|
435
|
-
...ifDefined('projection', m.projection),
|
|
436
|
-
...ifDefined('projectionTypes', m.projectionTypes),
|
|
437
429
|
};
|
|
438
430
|
return { collection: data.collection, command, meta };
|
|
439
431
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
1
2
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
2
3
|
import type {
|
|
3
4
|
MigrationOperationClass,
|
|
@@ -225,7 +226,12 @@ export class MongoMigrationPlanner implements MigrationPlanner<'mongo', 'mongo'>
|
|
|
225
226
|
readonly contract: unknown;
|
|
226
227
|
readonly schema: unknown;
|
|
227
228
|
readonly policy: MigrationOperationPolicy;
|
|
228
|
-
|
|
229
|
+
/**
|
|
230
|
+
* The "from" contract (state the planner assumes the database starts at),
|
|
231
|
+
* or `null` for reconciliation flows. Used to populate `describe().from`
|
|
232
|
+
* on the produced plan as `fromContract?.storage.storageHash ?? null`.
|
|
233
|
+
*/
|
|
234
|
+
readonly fromContract: Contract | null;
|
|
229
235
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'mongo', 'mongo'>>;
|
|
230
236
|
}): MigrationPlannerResult {
|
|
231
237
|
const contract = options.contract as MongoContract;
|
|
@@ -234,7 +240,7 @@ export class MongoMigrationPlanner implements MigrationPlanner<'mongo', 'mongo'>
|
|
|
234
240
|
return {
|
|
235
241
|
kind: 'success',
|
|
236
242
|
plan: new PlannerProducedMongoMigration(result.calls, {
|
|
237
|
-
from: options.
|
|
243
|
+
from: options.fromContract?.storage.storageHash ?? null,
|
|
238
244
|
to: contract.storage.storageHash,
|
|
239
245
|
}),
|
|
240
246
|
};
|
package/src/core/mongo-runner.ts
CHANGED
|
@@ -34,10 +34,15 @@ export interface MarkerOperations {
|
|
|
34
34
|
initMarker(destination: {
|
|
35
35
|
readonly storageHash: string;
|
|
36
36
|
readonly profileHash: string;
|
|
37
|
+
readonly invariants?: readonly string[];
|
|
37
38
|
}): Promise<void>;
|
|
38
39
|
updateMarker(
|
|
39
40
|
expectedFrom: string,
|
|
40
|
-
destination: {
|
|
41
|
+
destination: {
|
|
42
|
+
readonly storageHash: string;
|
|
43
|
+
readonly profileHash: string;
|
|
44
|
+
readonly invariants?: readonly string[];
|
|
45
|
+
},
|
|
41
46
|
): Promise<boolean>;
|
|
42
47
|
writeLedgerEntry(entry: {
|
|
43
48
|
readonly edgeId: string;
|
|
@@ -177,60 +182,82 @@ export class MongoMigrationRunner {
|
|
|
177
182
|
const destination = options.plan.destination;
|
|
178
183
|
const profileHash = options.destinationContract.profileHash ?? destination.storageHash;
|
|
179
184
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
185
|
+
const incomingInvariants = options.plan.providedInvariants ?? [];
|
|
186
|
+
const existingInvariantSet = new Set(existingMarker?.invariants ?? []);
|
|
187
|
+
const incomingIsSubsetOfExisting = incomingInvariants.every((id) =>
|
|
188
|
+
existingInvariantSet.has(id),
|
|
189
|
+
);
|
|
190
|
+
const markerAlreadyAtDestination =
|
|
191
|
+
existingMarker !== null &&
|
|
192
|
+
existingMarker.storageHash === destination.storageHash &&
|
|
193
|
+
existingMarker.profileHash === profileHash;
|
|
194
|
+
|
|
195
|
+
// Skip marker/ledger writes (and schema verification) only when the apply
|
|
196
|
+
// is a true no-op: no operations executed, marker already at destination,
|
|
197
|
+
// and every incoming invariant is already in the stored set.
|
|
198
|
+
//
|
|
199
|
+
// Divergence from the SQL runners (postgres/sqlite): those runners gate
|
|
200
|
+
// the no-op skip on `isSelfEdge` (origin === destination) only, so a
|
|
201
|
+
// non-self-edge `db update` that introspects-as-no-op still writes a
|
|
202
|
+
// ledger entry. Mongo skips even those because the runner has no
|
|
203
|
+
// structural distinction between self-edge and re-apply — invariant-
|
|
204
|
+
// aware routing here does not yet differentiate between the two
|
|
205
|
+
// ledger semantics. If the SQL audit-trail behavior should hold for
|
|
206
|
+
// Mongo too, gate this `isNoOp` on a self-edge check (or, conversely,
|
|
207
|
+
// align the SQL runners to skip non-self-edge no-ops uniformly).
|
|
208
|
+
const isNoOp =
|
|
209
|
+
operationsExecuted === 0 && markerAlreadyAtDestination && incomingIsSubsetOfExisting;
|
|
210
|
+
|
|
211
|
+
if (!isNoOp) {
|
|
212
|
+
const liveSchema = await this.deps.introspectSchema();
|
|
213
|
+
const verifyResult = verifyMongoSchema({
|
|
214
|
+
contract: options.destinationContract,
|
|
215
|
+
schema: liveSchema,
|
|
216
|
+
strict: options.strictVerification ?? true,
|
|
217
|
+
frameworkComponents: options.frameworkComponents,
|
|
218
|
+
...(options.context ? { context: options.context } : {}),
|
|
200
219
|
});
|
|
201
|
-
|
|
220
|
+
if (!verifyResult.ok) {
|
|
221
|
+
return runnerFailure('SCHEMA_VERIFY_FAILED', verifyResult.summary, {
|
|
222
|
+
why: 'The resulting database schema does not satisfy the destination contract.',
|
|
223
|
+
meta: { issues: verifyResult.schema.issues },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
202
226
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
227
|
+
if (existingMarker) {
|
|
228
|
+
const updated = await markerOps.updateMarker(existingMarker.storageHash, {
|
|
229
|
+
storageHash: destination.storageHash,
|
|
230
|
+
profileHash,
|
|
231
|
+
invariants: incomingInvariants,
|
|
232
|
+
});
|
|
233
|
+
if (!updated) {
|
|
234
|
+
return runnerFailure(
|
|
235
|
+
'MARKER_CAS_FAILURE',
|
|
236
|
+
'Marker was modified by another process during migration execution.',
|
|
237
|
+
{
|
|
238
|
+
meta: {
|
|
239
|
+
expectedStorageHash: existingMarker.storageHash,
|
|
240
|
+
destinationStorageHash: destination.storageHash,
|
|
241
|
+
},
|
|
216
242
|
},
|
|
217
|
-
|
|
218
|
-
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
await markerOps.initMarker({
|
|
247
|
+
storageHash: destination.storageHash,
|
|
248
|
+
profileHash,
|
|
249
|
+
invariants: incomingInvariants,
|
|
250
|
+
});
|
|
219
251
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
252
|
+
|
|
253
|
+
const originHash = existingMarker?.storageHash ?? '';
|
|
254
|
+
await markerOps.writeLedgerEntry({
|
|
255
|
+
edgeId: `${originHash}->${destination.storageHash}`,
|
|
256
|
+
from: originHash,
|
|
257
|
+
to: destination.storageHash,
|
|
224
258
|
});
|
|
225
259
|
}
|
|
226
260
|
|
|
227
|
-
const originHash = existingMarker?.storageHash ?? '';
|
|
228
|
-
await markerOps.writeLedgerEntry({
|
|
229
|
-
edgeId: `${originHash}->${destination.storageHash}`,
|
|
230
|
-
from: originHash,
|
|
231
|
-
to: destination.storageHash,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
261
|
return ok({ operationsPlanned: operations.length, operationsExecuted });
|
|
235
262
|
}
|
|
236
263
|
|
|
@@ -271,7 +298,7 @@ export class MongoMigrationRunner {
|
|
|
271
298
|
}
|
|
272
299
|
|
|
273
300
|
for (const plan of op.run) {
|
|
274
|
-
const wireCommand = adapter.lower(plan);
|
|
301
|
+
const wireCommand = await adapter.lower(plan, {});
|
|
275
302
|
for await (const _ of driver.execute(wireCommand)) {
|
|
276
303
|
/* consume */
|
|
277
304
|
}
|
|
@@ -319,7 +346,7 @@ export class MongoMigrationRunner {
|
|
|
319
346
|
},
|
|
320
347
|
);
|
|
321
348
|
}
|
|
322
|
-
const wireCommand = adapter.lower(check.source);
|
|
349
|
+
const wireCommand = await adapter.lower(check.source, {});
|
|
323
350
|
let matchFound = false;
|
|
324
351
|
for await (const row of driver.execute<Record<string, unknown>>(wireCommand)) {
|
|
325
352
|
if (filterEvaluator.evaluate(check.filter, row)) {
|
|
@@ -387,13 +414,9 @@ export class MongoMigrationRunner {
|
|
|
387
414
|
): MigrationRunnerResult | undefined {
|
|
388
415
|
const origin = plan.origin ?? null;
|
|
389
416
|
if (!origin) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
'Database already has a contract marker but the plan has no origin. This would silently overwrite the existing marker.',
|
|
394
|
-
{ meta: { markerStorageHash: marker.storageHash } },
|
|
395
|
-
);
|
|
396
|
-
}
|
|
417
|
+
// No origin assertion on the plan — the caller has done its own
|
|
418
|
+
// correctness check (typically `db update` via live-schema
|
|
419
|
+
// introspection) and does not rely on marker continuity.
|
|
397
420
|
return undefined;
|
|
398
421
|
}
|
|
399
422
|
|
|
@@ -3,9 +3,8 @@ import { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-n
|
|
|
3
3
|
import type { OpFactoryCall } from './op-factory-call';
|
|
4
4
|
|
|
5
5
|
export interface RenderMigrationMeta {
|
|
6
|
-
readonly from: string;
|
|
6
|
+
readonly from: string | null;
|
|
7
7
|
readonly to: string;
|
|
8
|
-
readonly kind?: string;
|
|
9
8
|
readonly labels?: readonly string[];
|
|
10
9
|
}
|
|
11
10
|
|
|
@@ -36,8 +35,8 @@ const BASE_IMPORTS: readonly ImportRequirement[] = [
|
|
|
36
35
|
* `Migration` (i.e. `MongoMigration`) from `@prisma-next/family-mongo`, and
|
|
37
36
|
* implements the abstract `operations` and `describe` members. `meta` is
|
|
38
37
|
* always rendered — `describe()` is part of the `Migration` contract, so
|
|
39
|
-
* even an empty stub must satisfy it; callers pass
|
|
40
|
-
* migration-new scaffold.
|
|
38
|
+
* even an empty stub must satisfy it; callers pass `from: null` for a
|
|
39
|
+
* baseline `migration-new` scaffold (and a real `to` hash either way).
|
|
41
40
|
*
|
|
42
41
|
* The walk is polymorphic: each call node contributes its own
|
|
43
42
|
* `renderTypeScript()` expression and declares its own
|
|
@@ -89,9 +88,6 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
|
|
|
89
88
|
lines.push(' return {');
|
|
90
89
|
lines.push(` from: ${JSON.stringify(meta.from)},`);
|
|
91
90
|
lines.push(` to: ${JSON.stringify(meta.to)},`);
|
|
92
|
-
if (meta.kind) {
|
|
93
|
-
lines.push(` kind: ${JSON.stringify(meta.kind)},`);
|
|
94
|
-
}
|
|
95
91
|
if (meta.labels && meta.labels.length > 0) {
|
|
96
92
|
lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
|
|
97
93
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RuntimeTargetDescriptor,
|
|
3
|
+
RuntimeTargetInstance,
|
|
4
|
+
} from '@prisma-next/framework-components/execution';
|
|
5
|
+
import { createMongoCodecRegistry, type MongoCodecRegistry } from '@prisma-next/mongo-codec';
|
|
6
|
+
import { mongoTargetDescriptorMeta } from '../core/descriptor-meta';
|
|
7
|
+
|
|
8
|
+
export interface MongoRuntimeTargetInstance extends RuntimeTargetInstance<'mongo', 'mongo'> {}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Target-mongo deliberately does NOT import `MongoRuntimeTargetDescriptor`
|
|
12
|
+
* from `@prisma-next/mongo-runtime`. The target package is a control-plane
|
|
13
|
+
* residence and must not pull the Mongo execution-plane package into its
|
|
14
|
+
* dependency closure. The runtime descriptor here is shaped to satisfy the
|
|
15
|
+
* framework's `RuntimeTargetDescriptor` plus the structural
|
|
16
|
+
* `MongoStaticContributions` (`codecs`) that `@prisma-next/mongo-runtime`
|
|
17
|
+
* consumers narrow to at composition time.
|
|
18
|
+
*/
|
|
19
|
+
const mongoRuntimeTargetDescriptor: RuntimeTargetDescriptor<
|
|
20
|
+
'mongo',
|
|
21
|
+
'mongo',
|
|
22
|
+
MongoRuntimeTargetInstance
|
|
23
|
+
> & {
|
|
24
|
+
readonly codecs: () => MongoCodecRegistry;
|
|
25
|
+
} = {
|
|
26
|
+
...mongoTargetDescriptorMeta,
|
|
27
|
+
// The target descriptor itself contributes no codecs — the standard set
|
|
28
|
+
// lives on the adapter descriptor (see `@prisma-next/adapter-mongo/runtime`).
|
|
29
|
+
codecs: () => createMongoCodecRegistry(),
|
|
30
|
+
create(): MongoRuntimeTargetInstance {
|
|
31
|
+
return {
|
|
32
|
+
familyId: 'mongo',
|
|
33
|
+
targetId: 'mongo',
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default mongoRuntimeTargetDescriptor;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"migration-factories-Dbk5afMU.mjs","names":["MATCH_ALL_FILTER: MongoFilterExpr","precheck: readonly MongoDataTransformCheck[]","postcheck: readonly MongoDataTransformCheck[]","postcheckExpect: 'exists' | 'notExists'","run: MongoQueryPlan[]"],"sources":["../src/core/migration-factories.ts"],"sourcesContent":["import type {\n MongoDataTransformCheck,\n MongoDataTransformOperation,\n MongoFilterExpr,\n MongoIndexKey,\n} from '@prisma-next/mongo-query-ast/control';\nimport {\n buildIndexOpId,\n CollModCommand,\n type CollModOptions,\n CreateCollectionCommand,\n type CreateCollectionOptions,\n CreateIndexCommand,\n type CreateIndexOptions,\n DropCollectionCommand,\n DropIndexCommand,\n defaultMongoIndexName,\n keysToKeySpec,\n ListCollectionsCommand,\n ListIndexesCommand,\n MongoAndExpr,\n MongoExistsExpr,\n MongoFieldFilter,\n type MongoMigrationPlanOperation,\n} from '@prisma-next/mongo-query-ast/control';\nimport type { MongoQueryPlan } from '@prisma-next/mongo-query-ast/execution';\nimport type { CollModMeta } from './op-factory-call';\n\ninterface Buildable {\n build(): MongoQueryPlan;\n}\n\nfunction isBuildable(value: unknown): value is Buildable {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'build' in value &&\n typeof (value as { build: unknown }).build === 'function'\n );\n}\n\nfunction resolveQuery(value: MongoQueryPlan | Buildable): MongoQueryPlan {\n return isBuildable(value) ? value.build() : value;\n}\n\n// Every MongoDB document carries `_id`, so `exists('_id')` is equivalent to\n// \"match all\". The filter AST has no identity/always-true expression.\nconst MATCH_ALL_FILTER: MongoFilterExpr = MongoExistsExpr.exists('_id');\n\nexport function dataTransform(\n name: string,\n options: {\n check?: {\n source: () => MongoQueryPlan | Buildable;\n filter?: MongoFilterExpr;\n expect?: 'exists' | 'notExists';\n description?: string;\n };\n run: () => MongoQueryPlan | Buildable;\n },\n): MongoDataTransformOperation {\n let precheck: readonly MongoDataTransformCheck[] = [];\n let postcheck: readonly MongoDataTransformCheck[] = [];\n\n if (options.check) {\n const source = resolveQuery(options.check.source());\n const filter = options.check.filter ?? MATCH_ALL_FILTER;\n const description = options.check.description ?? `Check for data transform: ${name}`;\n const precheckExpect = options.check.expect ?? 'exists';\n const postcheckExpect: 'exists' | 'notExists' =\n precheckExpect === 'exists' ? 'notExists' : 'exists';\n\n precheck = [{ description, source, filter, expect: precheckExpect }];\n postcheck = [{ description, source, filter, expect: postcheckExpect }];\n }\n\n const run: MongoQueryPlan[] = [resolveQuery(options.run())];\n\n return {\n id: `data_transform.${name}`,\n label: `Data transform: ${name}`,\n operationClass: 'data',\n name,\n precheck,\n run,\n postcheck,\n };\n}\n\nfunction formatKeys(keys: ReadonlyArray<MongoIndexKey>): string {\n return keys.map((k) => `${k.field}:${k.direction}`).join(', ');\n}\n\nfunction isTextIndex(keys: ReadonlyArray<MongoIndexKey>): boolean {\n return keys.some((k) => k.direction === 'text');\n}\n\nfunction keyFilter(keys: ReadonlyArray<MongoIndexKey>) {\n return isTextIndex(keys)\n ? MongoFieldFilter.eq('key._fts', 'text')\n : MongoFieldFilter.eq('key', keysToKeySpec(keys));\n}\n\nexport function createIndex(\n collection: string,\n keys: ReadonlyArray<MongoIndexKey>,\n options?: CreateIndexOptions,\n): MongoMigrationPlanOperation {\n const name = defaultMongoIndexName(keys);\n const filter = keyFilter(keys);\n const fullFilter = options?.unique\n ? MongoAndExpr.of([filter, MongoFieldFilter.eq('unique', true)])\n : filter;\n\n return {\n id: buildIndexOpId('create', collection, keys),\n label: `Create index on ${collection} (${formatKeys(keys)})`,\n operationClass: 'additive',\n precheck: [\n {\n description: `index does not already exist on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter,\n expect: 'notExists',\n },\n ],\n execute: [\n {\n description: `create index on ${collection}`,\n command: new CreateIndexCommand(collection, keys, {\n ...options,\n unique: options?.unique ?? undefined,\n name,\n }),\n },\n ],\n postcheck: [\n {\n description: `index exists on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter: fullFilter,\n expect: 'exists',\n },\n ],\n };\n}\n\nexport function dropIndex(\n collection: string,\n keys: ReadonlyArray<MongoIndexKey>,\n): MongoMigrationPlanOperation {\n const indexName = defaultMongoIndexName(keys);\n const filter = keyFilter(keys);\n\n return {\n id: buildIndexOpId('drop', collection, keys),\n label: `Drop index on ${collection} (${formatKeys(keys)})`,\n operationClass: 'destructive',\n precheck: [\n {\n description: `index exists on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter,\n expect: 'exists',\n },\n ],\n execute: [\n {\n description: `drop index on ${collection}`,\n command: new DropIndexCommand(collection, indexName),\n },\n ],\n postcheck: [\n {\n description: `index no longer exists on ${collection}`,\n source: new ListIndexesCommand(collection),\n filter,\n expect: 'notExists',\n },\n ],\n };\n}\n\nexport function createCollection(\n collection: string,\n options?: CreateCollectionOptions,\n): MongoMigrationPlanOperation {\n return {\n id: `collection.${collection}.create`,\n label: `Create collection ${collection}`,\n operationClass: 'additive',\n precheck: [\n {\n description: `collection ${collection} does not exist`,\n source: new ListCollectionsCommand(),\n filter: MongoFieldFilter.eq('name', collection),\n expect: 'notExists',\n },\n ],\n execute: [\n {\n description: `create collection ${collection}`,\n command: new CreateCollectionCommand(collection, options),\n },\n ],\n postcheck: [],\n };\n}\n\nexport function dropCollection(collection: string): MongoMigrationPlanOperation {\n return {\n id: `collection.${collection}.drop`,\n label: `Drop collection ${collection}`,\n operationClass: 'destructive',\n precheck: [],\n execute: [\n {\n description: `drop collection ${collection}`,\n command: new DropCollectionCommand(collection),\n },\n ],\n postcheck: [],\n };\n}\n\nexport function setValidation(\n collection: string,\n schema: Record<string, unknown>,\n options?: { validationLevel?: 'strict' | 'moderate'; validationAction?: 'error' | 'warn' },\n): MongoMigrationPlanOperation {\n return {\n id: `collection.${collection}.setValidation`,\n label: `Set validation on ${collection}`,\n operationClass: 'destructive',\n precheck: [],\n execute: [\n {\n description: `set validation on ${collection}`,\n command: new CollModCommand(collection, {\n validator: { $jsonSchema: schema },\n validationLevel: options?.validationLevel,\n validationAction: options?.validationAction,\n }),\n },\n ],\n postcheck: [],\n };\n}\n\nexport function collMod(\n collection: string,\n options: CollModOptions,\n meta?: CollModMeta,\n): MongoMigrationPlanOperation {\n const hasValidator = options.validator != null && Object.keys(options.validator).length > 0;\n\n return {\n id: meta?.id ?? `collection.${collection}.collMod`,\n label: meta?.label ?? `Modify collection ${collection}`,\n operationClass: meta?.operationClass ?? 'destructive',\n precheck:\n options.validator != null\n ? [\n {\n description: `collection ${collection} exists`,\n source: new ListCollectionsCommand(),\n filter: MongoFieldFilter.eq('name', collection),\n expect: 'exists' as const,\n },\n ]\n : [],\n execute: [\n {\n description: `modify ${collection}`,\n command: new CollModCommand(collection, options),\n },\n ],\n postcheck: hasValidator\n ? [\n {\n description: `validator applied on ${collection}`,\n source: new ListCollectionsCommand(),\n filter: MongoAndExpr.of([\n MongoFieldFilter.eq('name', collection),\n ...(options.validationLevel\n ? [MongoFieldFilter.eq('options.validationLevel', options.validationLevel)]\n : []),\n ...(options.validationAction\n ? [MongoFieldFilter.eq('options.validationAction', options.validationAction)]\n : []),\n ]),\n expect: 'exists' as const,\n },\n ]\n : [],\n };\n}\n\nexport function validatedCollection(\n name: string,\n schema: Record<string, unknown>,\n indexes: ReadonlyArray<{ keys: MongoIndexKey[]; unique?: boolean }>,\n): MongoMigrationPlanOperation[] {\n return [\n createCollection(name, {\n validator: { $jsonSchema: schema },\n validationLevel: 'strict',\n validationAction: 'error',\n }),\n ...indexes.map((idx) => createIndex(name, idx.keys, { unique: idx.unique })),\n ];\n}\n"],"mappings":"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"}
|