@prisma-next/target-mongo 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/control.d.mts +24 -7
  2. package/dist/control.d.mts.map +1 -1
  3. package/dist/control.mjs +98 -72
  4. package/dist/control.mjs.map +1 -1
  5. package/dist/descriptor-meta-D9_5quQi.mjs +14 -0
  6. package/dist/descriptor-meta-D9_5quQi.mjs.map +1 -0
  7. package/dist/{migration-factories-Dbk5afMU.mjs → migration-factories-CoNYWrd1.mjs} +3 -1
  8. package/dist/migration-factories-CoNYWrd1.mjs.map +1 -0
  9. package/dist/migration.d.mts +7 -1
  10. package/dist/migration.d.mts.map +1 -1
  11. package/dist/migration.mjs +1 -1
  12. package/dist/{op-factory-call-CVgzmLJh.d.mts → op-factory-call--nK5dk8n.d.mts} +1 -1
  13. package/dist/{op-factory-call-CVgzmLJh.d.mts.map → op-factory-call--nK5dk8n.d.mts.map} +1 -1
  14. package/dist/pack.mjs +1 -11
  15. package/dist/pack.mjs.map +1 -1
  16. package/dist/runtime.d.mts +20 -0
  17. package/dist/runtime.d.mts.map +1 -0
  18. package/dist/runtime.mjs +28 -0
  19. package/dist/runtime.mjs.map +1 -0
  20. package/dist/schema-verify.mjs +1 -1
  21. package/dist/{verify-mongo-schema-P0TRBJNs.mjs → verify-mongo-schema-Daa7BMJY.mjs} +1 -1
  22. package/dist/{verify-mongo-schema-P0TRBJNs.mjs.map → verify-mongo-schema-Daa7BMJY.mjs.map} +1 -1
  23. package/package.json +16 -14
  24. package/src/core/marker-ledger.ts +90 -20
  25. package/src/core/migration-factories.ts +8 -0
  26. package/src/core/mongo-ops-serializer.ts +0 -8
  27. package/src/core/mongo-planner.ts +8 -2
  28. package/src/core/mongo-runner.ts +80 -57
  29. package/src/core/planner-produced-migration.ts +0 -1
  30. package/src/core/render-typescript.ts +3 -7
  31. package/src/exports/runtime.ts +38 -0
  32. 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
- readonly fromHash: string;
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.fromHash,
243
+ from: options.fromContract?.storage.storageHash ?? null,
238
244
  to: contract.storage.storageHash,
239
245
  }),
240
246
  };
@@ -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: { readonly storageHash: string; readonly profileHash: string },
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
- if (
181
- operationsExecuted === 0 &&
182
- existingMarker?.storageHash === destination.storageHash &&
183
- existingMarker.profileHash === profileHash
184
- ) {
185
- return ok({ operationsPlanned: operations.length, operationsExecuted });
186
- }
187
-
188
- const liveSchema = await this.deps.introspectSchema();
189
- const verifyResult = verifyMongoSchema({
190
- contract: options.destinationContract,
191
- schema: liveSchema,
192
- strict: options.strictVerification ?? true,
193
- frameworkComponents: options.frameworkComponents,
194
- ...(options.context ? { context: options.context } : {}),
195
- });
196
- if (!verifyResult.ok) {
197
- return runnerFailure('SCHEMA_VERIFY_FAILED', verifyResult.summary, {
198
- why: 'The resulting database schema does not satisfy the destination contract.',
199
- meta: { issues: verifyResult.schema.issues },
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
- if (existingMarker) {
204
- const updated = await markerOps.updateMarker(existingMarker.storageHash, {
205
- storageHash: destination.storageHash,
206
- profileHash,
207
- });
208
- if (!updated) {
209
- return runnerFailure(
210
- 'MARKER_CAS_FAILURE',
211
- 'Marker was modified by another process during migration execution.',
212
- {
213
- meta: {
214
- expectedStorageHash: existingMarker.storageHash,
215
- destinationStorageHash: destination.storageHash,
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
- } else {
221
- await markerOps.initMarker({
222
- storageHash: destination.storageHash,
223
- profileHash,
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
- if (marker) {
391
- return runnerFailure(
392
- 'MARKER_ORIGIN_MISMATCH',
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
 
@@ -47,7 +47,6 @@ export class PlannerProducedMongoMigration
47
47
  return renderCallsToTypeScript(this.calls, {
48
48
  from: this.meta.from,
49
49
  to: this.meta.to,
50
- ...ifDefined('kind', this.meta.kind),
51
50
  ...ifDefined('labels', this.meta.labels),
52
51
  });
53
52
  }
@@ -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 empty strings for a
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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"}