@prisma/orm-target-mongo 8.0.0-rc.18-dev.28 → 8.0.0-rc.18-dev.30

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 (54) hide show
  1. package/dist/adapter.mjs +1 -1
  2. package/dist/adapter__control.d.mts +1 -1
  3. package/dist/adapter__control.mjs +1 -1
  4. package/dist/adapter__runtime.mjs +2 -2
  5. package/dist/{codecs-9hW1zPTj-UIJ0Z1th.mjs → codecs-BVrMDhha-DBl_TLS8.mjs} +3 -3
  6. package/dist/{codecs-9hW1zPTj-UIJ0Z1th.mjs.map → codecs-BVrMDhha-DBl_TLS8.mjs.map} +1 -1
  7. package/dist/{control-rl8nrKoA.d.mts → control-DpeYOTlN.d.mts} +8 -4
  8. package/dist/control-DpeYOTlN.d.mts.map +1 -0
  9. package/dist/{control-CDY-DZw5.mjs → control-lAuWLUE_.mjs} +205 -89
  10. package/dist/control-lAuWLUE_.mjs.map +1 -0
  11. package/dist/{data-types-CfdzqGSQ-NIDeT9x3.mjs → data-types-vptiHFHC-LUJwNRIq.mjs} +3 -3
  12. package/dist/{data-types-CfdzqGSQ-NIDeT9x3.mjs.map → data-types-vptiHFHC-LUJwNRIq.mjs.map} +1 -1
  13. package/dist/{descriptor-meta-BcqjYuQn-BguYOxpH.mjs → descriptor-meta-BQBreSNF-CZ9uOPwO.mjs} +15 -7
  14. package/dist/descriptor-meta-BQBreSNF-CZ9uOPwO.mjs.map +1 -0
  15. package/dist/{descriptor-meta-runtime-BtNLF4vW-Dbt6W7RT.mjs → descriptor-meta-runtime-D0YxXi85-CjeTEg7T.mjs} +4 -4
  16. package/dist/{descriptor-meta-runtime-BtNLF4vW-Dbt6W7RT.mjs.map → descriptor-meta-runtime-D0YxXi85-CjeTEg7T.mjs.map} +1 -1
  17. package/dist/driver.mjs +1 -1
  18. package/dist/driver__control.mjs +1 -1
  19. package/dist/driver__control.mjs.map +1 -1
  20. package/dist/{migration-D_hZnT_B.d.mts → migration-DKoUk2CF.d.mts} +2 -2
  21. package/dist/{migration-D_hZnT_B.d.mts.map → migration-DKoUk2CF.d.mts.map} +1 -1
  22. package/dist/{mongo-adapter-Bo6ciURK-DRuYz-F7.mjs → mongo-adapter-Bo6ciURK-Cy0xnkRa.mjs} +2 -2
  23. package/dist/{mongo-adapter-Bo6ciURK-DRuYz-F7.mjs.map → mongo-adapter-Bo6ciURK-Cy0xnkRa.mjs.map} +1 -1
  24. package/dist/{mongo-driver-Bim0d3Y1-DmdzEYr_.mjs → mongo-driver-_5ZHrJDP-LGH7Kem3.mjs} +3 -3
  25. package/dist/{mongo-driver-Bim0d3Y1-DmdzEYr_.mjs.map → mongo-driver-_5ZHrJDP-LGH7Kem3.mjs.map} +1 -1
  26. package/dist/{op-factory-call-BoYr7vvM-CNpt7COE.d.mts → op-factory-call-BoYr7vvM-PXfT4zvm.d.mts} +2 -2
  27. package/dist/{op-factory-call-BoYr7vvM-CNpt7COE.d.mts.map → op-factory-call-BoYr7vvM-PXfT4zvm.d.mts.map} +1 -1
  28. package/dist/pack-BuZ5LBMg.d.mts +230 -0
  29. package/dist/pack-BuZ5LBMg.d.mts.map +1 -0
  30. package/dist/{prisma6-binding-ZruukPaf.mjs → prisma6-binding-DKZXqEL5.mjs} +3 -5
  31. package/dist/prisma6-binding-DKZXqEL5.mjs.map +1 -0
  32. package/dist/{prisma6-binding-DppqC9Jo.d.mts → prisma6-binding-DRM8QnmA.d.mts} +1 -2
  33. package/dist/{prisma6-binding-DppqC9Jo.d.mts.map → prisma6-binding-DRM8QnmA.d.mts.map} +1 -1
  34. package/dist/target.d.mts +5 -5
  35. package/dist/target.mjs +4 -4
  36. package/dist/target__codecs.mjs +1 -1
  37. package/dist/target__control.d.mts +2 -2
  38. package/dist/target__control.mjs +1 -1
  39. package/dist/target__data-types.mjs +1 -1
  40. package/dist/target__migration.d.mts +1 -1
  41. package/dist/target__pack.d.mts +1 -1
  42. package/dist/target__pack.mjs +1 -1
  43. package/dist/target__pack.mjs.map +1 -1
  44. package/dist/target__prisma6-binding.d.mts +1 -1
  45. package/dist/target__prisma6-binding.mjs +1 -1
  46. package/dist/target__runtime.mjs +1 -1
  47. package/dist/target__runtime.mjs.map +1 -1
  48. package/package.json +9 -9
  49. package/dist/control-CDY-DZw5.mjs.map +0 -1
  50. package/dist/control-rl8nrKoA.d.mts.map +0 -1
  51. package/dist/descriptor-meta-BcqjYuQn-BguYOxpH.mjs.map +0 -1
  52. package/dist/pack-DEK1QhCx.d.mts +0 -110
  53. package/dist/pack-DEK1QhCx.d.mts.map +0 -1
  54. package/dist/prisma6-binding-ZruukPaf.mjs.map +0 -1
@@ -1,20 +1,20 @@
1
1
  import { t as mongoTargetError } from "./mongo-target-errors-Btf3ML1w-B5EMYbz1.mjs";
2
- import { t as mongoTargetDescriptorMeta } from "./descriptor-meta-BcqjYuQn-BguYOxpH.mjs";
2
+ import { t as mongoTargetDescriptorMeta } from "./descriptor-meta-BQBreSNF-CZ9uOPwO.mjs";
3
3
  import { a as dropCollection, c as renameField, d as unsetField, n as createCollection, o as dropIndex, r as createIndex, s as renameCollection, t as collMod, u as unrewritableFieldNameReason } from "./migration-factories-jwrORVMV-BGqJk_WT.mjs";
4
4
  import { blindCast } from "@prisma/orm-framework/utils/casts";
5
5
  import { type } from "arktype";
6
6
  import { ifDefined } from "@prisma/orm-framework/utils/defined";
7
7
  import { NamespaceBase, UNBOUND_NAMESPACE_ID, elementCoordinates, freezeNode, hydrateNamespaceEntities } from "@prisma/orm-framework/components/ir";
8
- import { APP_SPACE_ID, VERIFY_CODE_SCHEMA_FAILURE, contractSnapshotJsonSpecifier, contractSnapshotTypesSpecifier, describeMigrationStatement, issueOutcome, modelDisplayName } from "@prisma/orm-framework/components/control";
9
8
  import { MongoSchemaCollection, MongoSchemaIR, canonicalize, deepEqual, indexLookupKey } from "@prisma/orm-family-mongo/schema-ir";
10
9
  import { CollModCommand, CreateCollectionCommand, CreateIndexCommand, DropCollectionCommand, DropIndexCommand, ListCollectionsCommand, ListIndexesCommand, MongoAndExpr, MongoExistsExpr, MongoFieldFilter, MongoNotExpr, MongoOrExpr, RenameCollectionCommand, isMongoDmlOperation } from "@prisma/orm-family-mongo/query-ast/control";
11
10
  import { AggregateCommand, MongoAddFieldsStage, MongoLimitStage, MongoLookupStage, MongoMatchStage, MongoMergeStage, MongoProjectStage, MongoSortStage, RawAggregateCommand, RawDeleteManyCommand, RawDeleteOneCommand, RawFindOneAndDeleteCommand, RawFindOneAndUpdateCommand, RawInsertManyCommand, RawInsertOneCommand, RawUpdateManyCommand, RawUpdateOneCommand } from "@prisma/orm-family-mongo/query-ast/execution";
12
11
  import { canonicalizeSchemasForVerification, contractToMongoSchemaIR, diffMongoSchemas, fieldRewriteOf } from "@prisma/orm-family-mongo/family/control";
13
12
  import { notOk, ok } from "@prisma/orm-framework/utils/result";
14
13
  import { MongoContractSerializerBase, MongoSchemaVerifierBase } from "@prisma/orm-family-mongo/family/ir";
15
- import { MongoStorage } from "@prisma/orm-family-mongo/contract";
14
+ import { MongoCollection, MongoStorage, collectionControlPoliciesOf, collectionControlPolicyOf, collectionRootModelsOf, storedFieldNameOf, storesField, validatorFieldNamesOf, validatorRootShapesOf } from "@prisma/orm-family-mongo/contract";
16
15
  import { composeMongoEntityKinds } from "@prisma/orm-family-mongo/contract/entity-kinds";
17
- import { asNamespaceId, effectiveControlPolicy } from "@prisma/orm-framework/contract/types";
16
+ import { asNamespaceId, governingControlPolicy } from "@prisma/orm-framework/contract/types";
17
+ import { APP_SPACE_ID, VERIFY_CODE_SCHEMA_FAILURE, contractSnapshotJsonSpecifier, contractSnapshotTypesSpecifier, describeMigrationStatement, issueOutcome, modelDisplayName } from "@prisma/orm-framework/components/control";
18
18
  import { TsExpression, jsonToTsSource, renderImports } from "@prisma/orm-framework/ts-render";
19
19
  import { Migration } from "@prisma/orm-toolchain/migration-tools/migration";
20
20
  import { resolveRequirementSpecifiers } from "@prisma/orm-framework/components/emission";
@@ -399,8 +399,28 @@ function variantFilter(contract, coordinate) {
399
399
  const value = Object.entries(baseModel?.variants ?? {}).find(([, variant]) => variant.namespace === coordinate.namespaceId && variant.model === coordinate.model)?.[0];
400
400
  return field === void 0 || value === void 0 ? void 0 : { [field]: value };
401
401
  }
402
+ function declaredCollection(contract, collection) {
403
+ return Object.values(contract.storage.namespaces).map((namespace) => namespace.entries["collection"]?.[collection]).find((entry) => entry instanceof MongoCollection);
404
+ }
405
+ /**
406
+ * The control policy of `collection`. A collection the contract leaves out keeps the policy `fromContract` gave it, when that was not `managed`, so leaving an `external` collection out of the contract does not drop it.
407
+ */
408
+ function collectionControlOf(contract, fromContract, collection) {
409
+ const declared = declaredCollection(contract, collection);
410
+ const original = declared === void 0 && fromContract !== null ? declaredCollection(fromContract, collection) : void 0;
411
+ const policy = collectionControlPolicyOf(collection, collectionControlPoliciesOf(contract));
412
+ if (original === void 0 || fromContract === null) return {
413
+ policy,
414
+ declared: declared?.control
415
+ };
416
+ const originalPolicy = governingControlPolicy({ explicitNodeControlPolicy: original.control }, fromContract.defaultControlPolicy);
417
+ return {
418
+ policy: originalPolicy === "managed" ? policy : originalPolicy,
419
+ declared: original.control
420
+ };
421
+ }
402
422
  function collectionControlPolicy(contract, collection) {
403
- return effectiveControlPolicy(Object.values(contract.storage.namespaces).map((namespace) => namespace?.entries.collection?.[collection]).find((entry) => entry !== void 0)?.control, contract.defaultControlPolicy);
423
+ return collectionControlOf(contract, null, collection).policy;
404
424
  }
405
425
  /** Why MongoDB would refuse a collection name, if it would. */
406
426
  function invalidCollectionNameReason(name) {
@@ -428,6 +448,65 @@ function refused(statement, label, reason, why) {
428
448
  };
429
449
  }
430
450
  const LEAVE_OUT = "Leave out this statement.";
451
+ const SEPARATE_THE_MODELS = "move the models to separate collections, or carry out the change in a dataTransform operation of a migration.ts, with a filter that selects only their documents";
452
+ /**
453
+ * The discriminator field and value a variant's filter selects, or `undefined` for a filter that selects by anything else, or no filter.
454
+ */
455
+ function discriminatorValueOf(filter) {
456
+ const entries = Object.entries(filter ?? {});
457
+ if (entries.length !== 1) return void 0;
458
+ const [entry] = entries;
459
+ if (entry === void 0) return void 0;
460
+ const [field, value] = entry;
461
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? {
462
+ field,
463
+ value
464
+ } : void 0;
465
+ }
466
+ /**
467
+ * The other models stored in `collection`, root models and their variants, whose documents a change of `storedName` with `filter` reaches, and which store the field themselves or through their base. No discriminator tells the documents of several root models apart, so without a filter, or with a base model's filter, every other root model's documents are reached. A variant's filter, one discriminator value, reaches a model only when its documents can hold that value: a variant of another root model with the same discriminator field and value, or a model that declares the discriminator field itself.
468
+ */
469
+ function otherModelsReached(contract, model, collection, filter, storedName) {
470
+ const models = contract.domain.namespaces[model.namespaceId]?.models ?? {};
471
+ const baseOf = (name) => {
472
+ const base = models[name]?.base;
473
+ return base === void 0 ? void 0 : models[base.model];
474
+ };
475
+ const storesOrInherits = (name, field) => {
476
+ const each = models[name];
477
+ const base = baseOf(name);
478
+ return each !== void 0 && storesField(each, field) || base !== void 0 && storesField(base, field);
479
+ };
480
+ const selected = discriminatorValueOf(filter);
481
+ const canHold = (name) => {
482
+ if (selected === void 0) return true;
483
+ const base = baseOf(name);
484
+ if (base?.discriminator?.field === selected.field) return Object.entries(base.variants ?? {}).some(([value, variant]) => variant.model === name && value === selected.value);
485
+ return storesOrInherits(name, selected.field);
486
+ };
487
+ const subjectRoot = modelAt(contract, model)?.base?.model ?? model.model;
488
+ return (collectionRootModelsOf(models)[collection] ?? []).filter((name) => name !== subjectRoot).flatMap((name) => [name, ...Object.values(models[name]?.variants ?? {}).map((variant) => variant.model)]).filter((name) => canHold(name) && storesOrInherits(name, storedName));
489
+ }
490
+ function sharedCollectionWhy(collection, others, change) {
491
+ const names = others.map((name) => JSON.stringify(name)).join(", ");
492
+ return `Collection ${JSON.stringify(collection)} stores several models and no discriminator tells their documents apart, so ${change} of ${names} too.`;
493
+ }
494
+ /**
495
+ * A removal the plan cannot carry out because another root model stored in the same collection still stores the field, and no discriminator selects the documents of the model that loses it.
496
+ */
497
+ function sharedCollectionRemovalRefusals(removals, contract) {
498
+ return removals.flatMap((removal) => {
499
+ const { subject, call } = removal;
500
+ if (subject.kind !== "field") return [];
501
+ const others = otherModelsReached(contract, subject, call.collection, call.filter, call.field);
502
+ if (others.length === 0) return [];
503
+ return [{
504
+ kind: "fieldRemovalRefused",
505
+ summary: `Cannot remove field ${JSON.stringify(call.field)} of model ${JSON.stringify(subject.model)} from collection ${JSON.stringify(call.collection)}: model ${others.map((name) => JSON.stringify(name)).join(", ")} stores it in the same collection`,
506
+ why: `${sharedCollectionWhy(call.collection, others, "removing the field would remove it from the documents")} To keep it in ${others.map((name) => JSON.stringify(name)).join(", ")} documents only, ${SEPARATE_THE_MODELS}.`
507
+ }];
508
+ });
509
+ }
431
510
  const TIME_SERIES_UPDATES = "MongoDB updates the documents of a time-series collection only through its metaField";
432
511
  /**
433
512
  * A removal the plan cannot carry out because its collection is a time-series collection, whose
@@ -465,7 +544,14 @@ function planStatements(input) {
465
544
  renamedCollections.set(outcome.call.from, outcome.call.to);
466
545
  collectionRenames.push(outcome.call);
467
546
  }
468
- if (outcome.kind === "renameField") fieldRenames.push(outcome.call);
547
+ if (outcome.kind === "renameField") {
548
+ const same = fieldRenames.find((call) => sameFieldRename(call, outcome.call));
549
+ if (same !== void 0) {
550
+ callOfStatement.push(same);
551
+ continue;
552
+ }
553
+ fieldRenames.push(outcome.call);
554
+ }
469
555
  callOfStatement.push(outcome.kind === "unchanged" ? void 0 : outcome.call);
470
556
  }
471
557
  return conflicts.length > 0 ? notOk(conflicts) : ok({
@@ -475,6 +561,10 @@ function planStatements(input) {
475
561
  callOfStatement
476
562
  });
477
563
  }
564
+ /** Whether two field renames rewrite the same documents the same way, as when every root model of a shared collection renames a field together. */
565
+ function sameFieldRename(a, b) {
566
+ return a.collection === b.collection && a.from === b.from && a.to === b.to && JSON.stringify(a.filter) === JSON.stringify(b.filter);
567
+ }
478
568
  function renameInSchema(schema, from, to) {
479
569
  return new MongoSchemaIR(schema.collections.map((collection) => collection.name === from ? new MongoSchemaCollection({
480
570
  name: to,
@@ -556,7 +646,12 @@ function planFieldStatement(statement, label, renamedCollections, schema, input)
556
646
  if (schema.collection(collection)?.options?.timeseries !== void 0) return refuse(`collection "${collection}" is a time-series collection`, `${TIME_SERIES_UPDATES}, so a statement cannot move the values of a field in them. Keep the stored name: name the field ${JSON.stringify(statement.from.field)} in the contract (with @map(${JSON.stringify(statement.from.field)}) in PSL), then run this command again without this statement.`);
557
647
  const policyRefusal = controlPolicyRefusal(statement, label, input.contract, collection);
558
648
  if (policyRefusal !== void 0) return policyRefusal;
559
- const filter = variantFilter(input.fromContract, statement.from) ?? rootDocumentsLosing(input.contract, statement.to, statement.from.field)?.filter;
649
+ const variant = variantFilter(input.fromContract, statement.from);
650
+ const fromModel = modelAt(input.fromContract, statement.from);
651
+ const renamedTogether = new Set(input.statements.flatMap((other) => other.entity === "field" && other.from.namespaceId === statement.from.namespaceId && other.from.field === statement.from.field && other.to.field === statement.to.field ? [other.from.model] : []));
652
+ const others = otherModelsReached(input.fromContract, statement.from, originCollection, variant, fromModel === void 0 ? statement.from.field : storedFieldNameOf(fromModel, statement.from.field)).filter((name) => !renamedTogether.has(name));
653
+ if (others.length > 0) return refuse(`model ${others.map((name) => JSON.stringify(name)).join(", ")} stores field ${JSON.stringify(statement.from.field)} in the same collection, ${JSON.stringify(collection)}`, `${sharedCollectionWhy(collection, others, "renaming the field would rename it in the documents")} To change it, ${SEPARATE_THE_MODELS}.`);
654
+ const filter = variant ?? rootDocumentsLosing(input.contract, statement.to, statement.from.field)?.filter;
560
655
  return {
561
656
  kind: "renameField",
562
657
  call: new RenameFieldCall(collection, statement.from.field, statement.to.field, filter)
@@ -664,28 +759,17 @@ function planFieldRemovals(input) {
664
759
  });
665
760
  });
666
761
  }
667
- function isRecord$1(value) {
668
- return typeof value === "object" && value !== null && !Array.isArray(value);
669
- }
670
- function propertyNames(schema) {
671
- const properties = isRecord$1(schema) ? schema["properties"] : void 0;
672
- return isRecord$1(properties) ? Object.keys(properties) : [];
673
- }
674
- function branchPropertyNames(schema) {
675
- const branches = isRecord$1(schema) ? schema["oneOf"] : void 0;
676
- return Array.isArray(branches) ? branches.flatMap(propertyNames) : [];
677
- }
678
762
  /**
679
- * Without an origin contract, a field is known removed when the origin validator declares it at the
680
- * top level and the destination validator declares it nowhere. It is named by its storage name.
763
+ * Without an origin contract, a field is known removed when the origin validator declares it for a
764
+ * root model and the destination validator declares it nowhere. It is named by its storage name.
681
765
  */
682
766
  function planValidatorFieldRemovals(input) {
683
767
  return input.origin.collections.flatMap((originCollection) => {
684
768
  if (collectionControlPolicy(input.contract, originCollection.name) !== "managed") return [];
685
769
  const destination = input.destination.collection(originCollection.name);
686
770
  if (destination?.validator === void 0 || originCollection.validator === void 0) return [];
687
- const kept = /* @__PURE__ */ new Set([...propertyNames(destination.validator?.jsonSchema), ...branchPropertyNames(destination.validator?.jsonSchema)]);
688
- return propertyNames(originCollection.validator.jsonSchema).filter((field) => unrewritableFieldNameReason(field) === void 0 && !kept.has(field)).sort().map((field) => ({
771
+ const kept = validatorFieldNamesOf(destination.validator.jsonSchema);
772
+ return [...new Set(validatorRootShapesOf(originCollection.validator.jsonSchema).flatMap((root) => Object.keys(root.properties)))].filter((field) => unrewritableFieldNameReason(field) === void 0 && !kept.has(field)).sort().map((field) => ({
689
773
  call: new UnsetFieldCall(originCollection.name, field),
690
774
  subject: {
691
775
  kind: "storage",
@@ -852,35 +936,6 @@ var PlannerProducedMongoMigration = class extends Migration {
852
936
  });
853
937
  }
854
938
  };
855
- const SAME_HASH = "the migration would start and end at the same contract hash";
856
- function why(collection) {
857
- return `The contract gives collection ${JSON.stringify(collection)} no validator, so the names of its fields are not part of the contract's storage hash; a contract written in TypeScript has none yet. A migration that only renames or removes a field would start and end at the same hash, and db migrate refuses such a migration. Use prisma db update for this change instead: it carries the change out with the same --rename or --delete and writes no migration. Or write the migration by hand: run prisma migration new --from <hash of the migration the database is at>, and carry the change out in a dataTransform operation of its migration.ts, since db migrate accepts a migration that starts and ends at the same hash when it runs a dataTransform.`;
858
- }
859
- /**
860
- * Refuses, for a migration package, each field rename and field removal of a plan whose contract
861
- * storage hash does not change: the collection has no validator in the contract, so its field
862
- * names are not in the hash, and `db migrate` refuses a migration that starts and ends at the same
863
- * hash. `db update` writes no migration and plans without an origin, so it is not refused.
864
- */
865
- function sameHashFieldChangeRefusals(input) {
866
- if (input.origin === null || input.origin.storageHash !== input.contract.storage.storageHash) return [];
867
- const renames = input.statements.flatMap((statement, index) => {
868
- const call = input.callOfStatement[index];
869
- if (!(call instanceof RenameFieldCall)) return [];
870
- return [{
871
- kind: "statementRefused",
872
- summary: `Cannot ${describeMigrationStatement(statement, input.fromContract, input.contract)}: ${SAME_HASH}`,
873
- why: why(call.collection),
874
- refusedStatement: statement
875
- }];
876
- });
877
- const removals = input.removals.map(({ call }) => ({
878
- kind: "fieldRemovalRefused",
879
- summary: `Cannot remove field ${JSON.stringify(call.field)} from collection ${JSON.stringify(call.collection)}: ${SAME_HASH}`,
880
- why: why(call.collection)
881
- }));
882
- return [...renames, ...removals];
883
- }
884
939
  function validatorsEqual(a, b) {
885
940
  if (!a && !b) return true;
886
941
  if (!a || !b) return false;
@@ -889,8 +944,11 @@ function validatorsEqual(a, b) {
889
944
  function isPlainObject(v) {
890
945
  return typeof v === "object" && v !== null && !Array.isArray(v);
891
946
  }
947
+ /** Each field a root model of the validator declares, with the distinct schemas the root models give it, in canonical form. */
892
948
  function propertiesOf(schema) {
893
- return isPlainObject(schema["properties"]) ? schema["properties"] : {};
949
+ const schemas = /* @__PURE__ */ new Map();
950
+ for (const root of validatorRootShapesOf(schema)) for (const [field, fieldSchema] of Object.entries(root.properties)) schemas.set(field, (schemas.get(field) ?? /* @__PURE__ */ new Set()).add(canonicalize(fieldSchema)));
951
+ return Object.fromEntries([...schemas].map(([field, distinct]) => [field, [...distinct].sort().join("|")]));
894
952
  }
895
953
  /**
896
954
  * A label naming the properties whose schema changed, so a dry run shows what a validator update does: `Update validator on items (changed: meta; added: note)`.
@@ -898,7 +956,7 @@ function propertiesOf(schema) {
898
956
  function validatorUpdateLabel(collName, origin, dest) {
899
957
  const originProps = propertiesOf(origin.jsonSchema);
900
958
  const destProps = propertiesOf(dest.jsonSchema);
901
- const changed = Object.keys(destProps).filter((field) => Object.hasOwn(originProps, field) && canonicalize(originProps[field]) !== canonicalize(destProps[field]));
959
+ const changed = Object.keys(destProps).filter((field) => Object.hasOwn(originProps, field) && originProps[field] !== destProps[field]);
902
960
  const added = Object.keys(destProps).filter((field) => !Object.hasOwn(originProps, field));
903
961
  const removed = Object.keys(originProps).filter((field) => !Object.hasOwn(destProps, field));
904
962
  const parts = [
@@ -1011,6 +1069,72 @@ function modelStoredInDestination(contract, model) {
1011
1069
  const namespace = contract.domain.namespaces[model.namespaceId];
1012
1070
  return namespace !== void 0 && Object.hasOwn(namespace.models, model.model) ? collectionOf(contract, model) : void 0;
1013
1071
  }
1072
+ function callsOf(diff) {
1073
+ return [
1074
+ ...diff.collCreates,
1075
+ ...diff.drops,
1076
+ ...diff.creates,
1077
+ ...diff.validatorOps,
1078
+ ...diff.mutableOptionOps,
1079
+ ...diff.collDrops
1080
+ ];
1081
+ }
1082
+ function planCollectionDiff(collName, originColl, destColl) {
1083
+ const collCreates = [];
1084
+ const drops = [];
1085
+ const creates = [];
1086
+ const validatorOps = [];
1087
+ const mutableOptionOps = [];
1088
+ const collDrops = [];
1089
+ const conflicts = [];
1090
+ if (!originColl) {
1091
+ if (destColl && (collectionHasOptions(destColl) || destColl.indexes.length === 0)) {
1092
+ const opts = collectionHasOptions(destColl) ? schemaCollectionToCreateCollectionOptions(destColl) : void 0;
1093
+ collCreates.push(new CreateCollectionCall(collName, opts));
1094
+ }
1095
+ } else if (!destColl) collDrops.push(new DropCollectionCall(collName));
1096
+ else {
1097
+ const immutableChange = hasImmutableOptionChange(originColl.options, destColl.options);
1098
+ if (immutableChange) conflicts.push({
1099
+ kind: "policy-violation",
1100
+ summary: `Cannot change immutable collection option '${immutableChange}' on ${collName}`,
1101
+ why: `MongoDB does not support modifying the '${immutableChange}' option after collection creation`
1102
+ });
1103
+ const mutableCall = planMutableOptionsDiffCall(collName, originColl.options, destColl.options);
1104
+ if (mutableCall) mutableOptionOps.push(mutableCall);
1105
+ const validatorCall = planValidatorDiffCall(collName, originColl.validator, destColl.validator);
1106
+ if (validatorCall) validatorOps.push(validatorCall);
1107
+ }
1108
+ const originLookup = /* @__PURE__ */ new Map();
1109
+ if (originColl) for (const idx of originColl.indexes) originLookup.set(indexLookupKey(idx), idx);
1110
+ const destLookup = /* @__PURE__ */ new Map();
1111
+ if (destColl) for (const idx of destColl.indexes) destLookup.set(indexLookupKey(idx), idx);
1112
+ for (const [lookupKey, idx] of originLookup) if (!destLookup.has(lookupKey)) drops.push(new DropIndexCall(collName, idx.keys));
1113
+ for (const [lookupKey, idx] of destLookup) if (!originLookup.has(lookupKey)) creates.push(new CreateIndexCall(collName, idx.keys, schemaIndexToCreateIndexOptions(idx)));
1114
+ return {
1115
+ collCreates,
1116
+ drops,
1117
+ creates,
1118
+ validatorOps,
1119
+ mutableOptionOps,
1120
+ collDrops,
1121
+ conflicts
1122
+ };
1123
+ }
1124
+ /**
1125
+ * The warning for a change a collection's control policy left out of the plan, worded as the Postgres planner words a suppressed call.
1126
+ */
1127
+ function suppressedCallWarning(call, collection, control) {
1128
+ const declared = control.declared !== void 0 && control.declared !== control.policy ? ` but declared '${control.declared}'` : "";
1129
+ return {
1130
+ kind: "controlPolicySuppressedCall",
1131
+ summary: `control policy suppressed: ${call.label} — collection "${collection}" has effective control '${control.policy}'${declared}`,
1132
+ meta: {
1133
+ controlPolicy: control.policy,
1134
+ ...ifDefined("declaredControlPolicy", control.declared)
1135
+ }
1136
+ };
1137
+ }
1014
1138
  var MongoMigrationPlanner = class {
1015
1139
  planCalls(options) {
1016
1140
  const contract = blindCast(options.contract);
@@ -1023,34 +1147,23 @@ var MongoMigrationPlanner = class {
1023
1147
  const mutableOptionOps = [];
1024
1148
  const collDrops = [];
1025
1149
  const conflicts = [];
1150
+ const warnings = [];
1026
1151
  const allCollectionNames = /* @__PURE__ */ new Set([...originIR.collectionNames, ...destinationIR.collectionNames]);
1027
1152
  for (const collName of [...allCollectionNames].sort()) {
1028
1153
  const originColl = originIR.collection(collName);
1029
- const destColl = destinationIR.collection(collName);
1030
- if (!originColl) {
1031
- if (destColl && (collectionHasOptions(destColl) || destColl.indexes.length === 0)) {
1032
- const opts = collectionHasOptions(destColl) ? schemaCollectionToCreateCollectionOptions(destColl) : void 0;
1033
- collCreates.push(new CreateCollectionCall(collName, opts));
1034
- }
1035
- } else if (!destColl) collDrops.push(new DropCollectionCall(collName));
1036
- else {
1037
- const immutableChange = hasImmutableOptionChange(originColl.options, destColl.options);
1038
- if (immutableChange) conflicts.push({
1039
- kind: "policy-violation",
1040
- summary: `Cannot change immutable collection option '${immutableChange}' on ${collName}`,
1041
- why: `MongoDB does not support modifying the '${immutableChange}' option after collection creation`
1042
- });
1043
- const mutableCall = planMutableOptionsDiffCall(collName, originColl.options, destColl.options);
1044
- if (mutableCall) mutableOptionOps.push(mutableCall);
1045
- const validatorCall = planValidatorDiffCall(collName, originColl.validator, destColl.validator);
1046
- if (validatorCall) validatorOps.push(validatorCall);
1154
+ const planned = planCollectionDiff(collName, originColl, destinationIR.collection(collName));
1155
+ const control = collectionControlOf(contract, options.fromContract, collName);
1156
+ if (!plansChangesTo(control.policy, originColl !== void 0)) {
1157
+ warnings.push(...callsOf(planned).map((call) => suppressedCallWarning(call, collName, control)));
1158
+ continue;
1047
1159
  }
1048
- const originLookup = /* @__PURE__ */ new Map();
1049
- if (originColl) for (const idx of originColl.indexes) originLookup.set(indexLookupKey(idx), idx);
1050
- const destLookup = /* @__PURE__ */ new Map();
1051
- if (destColl) for (const idx of destColl.indexes) destLookup.set(indexLookupKey(idx), idx);
1052
- for (const [lookupKey, idx] of originLookup) if (!destLookup.has(lookupKey)) drops.push(new DropIndexCall(collName, idx.keys));
1053
- for (const [lookupKey, idx] of destLookup) if (!originLookup.has(lookupKey)) creates.push(new CreateIndexCall(collName, idx.keys, schemaIndexToCreateIndexOptions(idx)));
1160
+ collCreates.push(...planned.collCreates);
1161
+ drops.push(...planned.drops);
1162
+ creates.push(...planned.creates);
1163
+ validatorOps.push(...planned.validatorOps);
1164
+ mutableOptionOps.push(...planned.mutableOptionOps);
1165
+ collDrops.push(...planned.collDrops);
1166
+ conflicts.push(...planned.conflicts);
1054
1167
  }
1055
1168
  if (conflicts.length > 0) return {
1056
1169
  kind: "failure",
@@ -1078,7 +1191,8 @@ var MongoMigrationPlanner = class {
1078
1191
  };
1079
1192
  return {
1080
1193
  kind: "success",
1081
- calls: allCalls
1194
+ calls: allCalls,
1195
+ warnings
1082
1196
  };
1083
1197
  }
1084
1198
  plan(options) {
@@ -1115,14 +1229,7 @@ var MongoMigrationPlanner = class {
1115
1229
  collectionAfterRenames: (collection) => renamedTo.get(collection) ?? collection,
1116
1230
  keptCollections: new Set(schema.collectionNames.filter((name) => destinationIR.collection(name) !== void 0))
1117
1231
  });
1118
- const removalRefusals = [...timeSeriesRemovalRefusals(removals, schema), ...sameHashFieldChangeRefusals({
1119
- origin: options.origin,
1120
- fromContract: fromContract ?? contract,
1121
- contract,
1122
- statements: renames,
1123
- callOfStatement,
1124
- removals
1125
- })];
1232
+ const removalRefusals = [...timeSeriesRemovalRefusals(removals, schema), ...sharedCollectionRemovalRefusals(removals, contract)];
1126
1233
  if (removalRefusals.length > 0) return {
1127
1234
  kind: "failure",
1128
1235
  conflicts: removalRefusals
@@ -1136,7 +1243,7 @@ var MongoMigrationPlanner = class {
1136
1243
  }
1137
1244
  });
1138
1245
  if (result.kind === "failure") return result;
1139
- const { calls } = result;
1246
+ const { calls, warnings } = result;
1140
1247
  const fieldLoss = removals.map((removal) => ({
1141
1248
  operationIndex: calls.indexOf(removal.call),
1142
1249
  subject: removal.subject,
@@ -1161,7 +1268,8 @@ var MongoMigrationPlanner = class {
1161
1268
  destination: destinationIR
1162
1269
  }), ...fieldLoss].sort((a, b) => a.operationIndex - b.operationIndex),
1163
1270
  accessWidening: [],
1164
- temporaryValues: []
1271
+ temporaryValues: [],
1272
+ ...warnings.length > 0 ? { warnings } : {}
1165
1273
  };
1166
1274
  }
1167
1275
  /**
@@ -1180,6 +1288,14 @@ var MongoMigrationPlanner = class {
1180
1288
  }, context.snapshotsImportPath);
1181
1289
  }
1182
1290
  };
1291
+ function plansChangesTo(policy, collectionExists) {
1292
+ switch (policy) {
1293
+ case "managed": return true;
1294
+ case "tolerated": return !collectionExists;
1295
+ case "external":
1296
+ case "observed": return false;
1297
+ }
1298
+ }
1183
1299
  function planValidatorDiffCall(collName, originValidator, destValidator) {
1184
1300
  if (validatorsEqual(originValidator, destValidator)) return void 0;
1185
1301
  if (destValidator) return new CollModCall(collName, {
@@ -2244,4 +2360,4 @@ const mongoTargetDescriptor = {
2244
2360
  //#endregion
2245
2361
  export { schemaIndexToCreateIndexOptions as C, schemaCollectionToCreateCollectionOptions as S, deserializeMongoOp as _, DropIndexCall as a, renderCallsToTypeScript as b, MongoMigrationRunner as c, MongoTargetSchemaVerifier as d, MongoTargetUnboundDatabase as f, UnsetFieldCall as g, RenameFieldCall as h, DropCollectionCall as i, MongoTargetContractSerializer as l, RenameCollectionCall as m, CreateCollectionCall as n, FilterEvaluator as o, PlannerProducedMongoMigration as p, CreateIndexCall as r, MongoMigrationPlanner as s, CollModCall as t, MongoTargetDatabase as u, deserializeMongoOps as v, serializeMongoOps as w, renderOps as x, mongoTargetDescriptor as y };
2246
2362
 
2247
- //# sourceMappingURL=control-CDY-DZw5.mjs.map
2363
+ //# sourceMappingURL=control-lAuWLUE_.mjs.map