@mikro-orm/core 7.1.0-dev.5 → 7.1.0-dev.50

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 (66) hide show
  1. package/EntityManager.d.ts +87 -12
  2. package/EntityManager.js +252 -40
  3. package/README.md +2 -1
  4. package/connections/Connection.d.ts +67 -1
  5. package/connections/Connection.js +64 -0
  6. package/drivers/IDatabaseDriver.d.ts +45 -7
  7. package/entity/BaseEntity.d.ts +68 -1
  8. package/entity/BaseEntity.js +18 -0
  9. package/entity/Collection.d.ts +6 -3
  10. package/entity/Collection.js +15 -4
  11. package/entity/EntityAssigner.js +8 -0
  12. package/entity/EntityFactory.js +5 -3
  13. package/entity/EntityLoader.d.ts +8 -1
  14. package/entity/EntityLoader.js +103 -30
  15. package/entity/EntityRepository.d.ts +27 -9
  16. package/entity/EntityRepository.js +12 -0
  17. package/entity/Reference.d.ts +42 -1
  18. package/entity/Reference.js +9 -0
  19. package/entity/defineEntity.d.ts +101 -21
  20. package/entity/defineEntity.js +17 -6
  21. package/entity/utils.js +4 -5
  22. package/enums.d.ts +8 -1
  23. package/errors.d.ts +2 -0
  24. package/errors.js +4 -0
  25. package/index.d.ts +2 -2
  26. package/index.js +1 -1
  27. package/metadata/EntitySchema.js +18 -0
  28. package/metadata/MetadataDiscovery.d.ts +12 -0
  29. package/metadata/MetadataDiscovery.js +173 -22
  30. package/metadata/MetadataValidator.d.ts +26 -0
  31. package/metadata/MetadataValidator.js +237 -1
  32. package/metadata/Routine.d.ts +74 -0
  33. package/metadata/Routine.js +140 -0
  34. package/metadata/index.d.ts +1 -0
  35. package/metadata/index.js +1 -0
  36. package/metadata/types.d.ts +78 -9
  37. package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
  38. package/naming-strategy/NamingStrategy.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/platforms/Platform.d.ts +18 -3
  41. package/platforms/Platform.js +58 -6
  42. package/serialization/EntitySerializer.js +4 -3
  43. package/typings.d.ts +399 -22
  44. package/typings.js +43 -12
  45. package/unit-of-work/UnitOfWork.js +43 -5
  46. package/utils/AbstractMigrator.d.ts +20 -5
  47. package/utils/AbstractMigrator.js +263 -28
  48. package/utils/AbstractSchemaGenerator.d.ts +1 -1
  49. package/utils/AbstractSchemaGenerator.js +4 -1
  50. package/utils/Configuration.d.ts +30 -0
  51. package/utils/Configuration.js +45 -0
  52. package/utils/DataloaderUtils.d.ts +10 -1
  53. package/utils/DataloaderUtils.js +78 -0
  54. package/utils/EntityComparator.js +1 -1
  55. package/utils/QueryHelper.d.ts +16 -0
  56. package/utils/QueryHelper.js +15 -0
  57. package/utils/TransactionManager.js +2 -0
  58. package/utils/Utils.js +1 -1
  59. package/utils/fs-utils.d.ts +2 -0
  60. package/utils/fs-utils.js +7 -1
  61. package/utils/index.d.ts +1 -0
  62. package/utils/index.js +1 -0
  63. package/utils/partition-utils.d.ts +17 -0
  64. package/utils/partition-utils.js +79 -0
  65. package/utils/upsert-utils.d.ts +2 -0
  66. package/utils/upsert-utils.js +26 -1
@@ -114,7 +114,12 @@ export class MetadataDiscovery {
114
114
  }
115
115
  else {
116
116
  const name = prop.name;
117
- if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
117
+ // For to-one relations, only swap to the accessor for the documented
118
+ // backing-field convention (a `_`-prefixed property like `_draft` paired
119
+ // with `accessor: 'draft'`). The non-prefixed sibling-helper pattern
120
+ // (`author` + `accessor: 'authorId'`) keeps the property name as the
121
+ // canonical FK column source, so this stays non-breaking.
122
+ if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED || name.startsWith('_')) {
118
123
  prop.name = prop.accessor;
119
124
  }
120
125
  this.initRelation(prop);
@@ -159,6 +164,7 @@ export class MetadataDiscovery {
159
164
  forEachProp((m, p) => this.initGeneratedColumn(m, p));
160
165
  filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
161
166
  filtered.forEach(meta => this.initCheckConstraints(meta));
167
+ filtered.forEach(meta => this.initTriggers(meta));
162
168
  forEachProp((_m, p) => {
163
169
  this.initDefaultValue(p);
164
170
  this.inferTypeFromDefault(p);
@@ -479,17 +485,28 @@ export class MetadataDiscovery {
479
485
  prop.polymorphic = prop2.polymorphic;
480
486
  prop.discriminator = prop2.discriminator;
481
487
  prop.discriminatorColumn = prop2.discriminatorColumn;
482
- prop.discriminatorValue = prop2.discriminatorValue;
488
+ // For a union-target pivot each inverse side sits on one specific target class, so its
489
+ // discriminator value is that class's tableName. For Rails-style, prop2 has a single fixed value.
490
+ prop.discriminatorValue = QueryHelper.isUnionTargetPolymorphic(prop2) ? meta.tableName : prop2.discriminatorValue;
483
491
  }
484
492
  prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
485
- // For polymorphic M:N, use discriminator base name for FK column (e.g., taggable_id instead of post_id)
486
- if (prop.polymorphic && prop.discriminator) {
493
+ // Union-target polymorphic M:N: owner side is fixed (real FK), target side uses discriminator-derived names.
494
+ const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
495
+ if (prop.polymorphic && prop.discriminator && !isUnionTargetMN) {
496
+ // Rails-style: owner side is polymorphic, uses discriminator base name (e.g. taggable_id instead of post_id)
487
497
  prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
488
498
  }
489
499
  else {
490
500
  prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK));
491
501
  }
492
- prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
502
+ if (isUnionTargetMN) {
503
+ // Target side uses discriminator base name (e.g. attachable_id — shared across Image/Video)
504
+ const targetPkCols = Utils.flatten(meta2.primaryKeys.map(pk => meta2.properties[pk].fieldNames));
505
+ prop.inverseJoinColumns ??= targetPkCols.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, targetPkCols.length > 1));
506
+ }
507
+ else {
508
+ prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
509
+ }
493
510
  }
494
511
  isExplicitTableName(meta) {
495
512
  return meta.tableName !== this.#namingStrategy.classToTableName(meta.className);
@@ -556,6 +573,12 @@ export class MetadataDiscovery {
556
573
  }
557
574
  }
558
575
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
576
+ // Fail fast when the platform rejects the metadata, so users see the platform-specific
577
+ // error (e.g. "SqlitePlatform does not support partitioned tables") instead of a
578
+ // downstream core validator message that assumes partitioning is supported.
579
+ if (meta.partitionBy && !this.#platform.supportsPartitionedTables()) {
580
+ this.#platform.validateMetadata(meta);
581
+ }
559
582
  this.#validator.validateEntityDefinition(this.#metadata, meta.class, this.#config.get('discovery'));
560
583
  for (const prop of Object.values(meta.properties)) {
561
584
  this.initNullability(prop);
@@ -582,6 +605,28 @@ export class MetadataDiscovery {
582
605
  prop.pivotEntity = pivotMeta.class;
583
606
  if (prop.inversedBy) {
584
607
  prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
608
+ const targetRoot = prop.targetMeta.root;
609
+ if (targetRoot !== prop.targetMeta && targetRoot.properties[prop.inversedBy]) {
610
+ targetRoot.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
611
+ }
612
+ }
613
+ // Propagate pivotEntity to ALL inverse collections using mappedBy pointing at this
614
+ // owner prop. Covers three cases:
615
+ // - regular inverse (Tag.posts mappedBy Post.tags) — handled by inversedBy above
616
+ // - union-target inverse (Image.posts mappedBy Post.attachments) — on each polymorph target
617
+ // - merged inverse (Tag.owners mappedBy [Post,Video].tags) — union collection on the target
618
+ const inverseCandidates = QueryHelper.isUnionTargetPolymorphic(prop)
619
+ ? prop.polymorphTargets
620
+ : [prop.targetMeta];
621
+ for (const targetMeta of inverseCandidates) {
622
+ for (const inverseProp of Object.values(targetMeta.properties)) {
623
+ if (inverseProp.kind === ReferenceKind.MANY_TO_MANY &&
624
+ inverseProp.mappedBy === prop.name &&
625
+ !inverseProp.pivotEntity) {
626
+ inverseProp.pivotEntity = pivotMeta.class;
627
+ inverseProp.pivotTable = pivotMeta.tableName;
628
+ }
629
+ }
585
630
  }
586
631
  return pivotMeta;
587
632
  });
@@ -721,8 +766,12 @@ export class MetadataDiscovery {
721
766
  }
722
767
  }
723
768
  }
724
- // For polymorphic M:N, create discriminator column and polymorphic FK
725
- if (prop.polymorphic && prop.discriminatorColumn) {
769
+ // Union-target polymorphic M:N: discriminator + target FK share the pivot across multiple target types
770
+ if (prop.discriminatorColumn && QueryHelper.isUnionTargetPolymorphic(prop)) {
771
+ this.defineUnionTargetPolymorphicPivotProperties(pivotMeta2, meta, prop);
772
+ }
773
+ else if (prop.polymorphic && prop.discriminatorColumn) {
774
+ // Rails-style polymorphic M:N: multiple owners share the pivot, single target type
726
775
  this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
727
776
  }
728
777
  else {
@@ -809,6 +858,33 @@ export class MetadataDiscovery {
809
858
  pivotMeta.polymorphicDiscriminatorMap ??= {};
810
859
  pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
811
860
  }
861
+ /**
862
+ * Mirror of definePolymorphicPivotProperties for union-target M:N
863
+ * (e.g. Post.attachments -> Image | Video via shared pivot with a target-side discriminator).
864
+ *
865
+ * Pivot shape:
866
+ * (owner_fk..., discriminator_column, target_fk...)
867
+ * - owner side is a normal M:1 to the single owner entity
868
+ * - target side is a discriminator column + per-target-type virtual M:1 relations
869
+ */
870
+ defineUnionTargetPolymorphicPivotProperties(pivotMeta, meta, prop) {
871
+ const discriminatorColumn = prop.discriminatorColumn;
872
+ const targets = prop.polymorphTargets;
873
+ pivotMeta.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, prop.discriminator, true, false);
874
+ const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.#platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: true, nullable: false });
875
+ this.initFieldName(discriminatorProp);
876
+ pivotMeta.properties[discriminatorColumn] = discriminatorProp;
877
+ const firstTargetColumnTypes = this.getPrimaryKeyColumnTypes(targets[0]);
878
+ pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, firstTargetColumnTypes, [...prop.inverseJoinColumns], { type: targets[0].className, primary: true, nullable: false });
879
+ pivotMeta.polymorphicDiscriminatorMap ??= {};
880
+ for (const targetMeta of targets) {
881
+ const relationName = `${prop.discriminator}_${targetMeta.tableName}`;
882
+ const relation = this.definePolymorphicOwnerRelation(prop, relationName, targetMeta);
883
+ relation.joinColumns = relation.fieldNames = relation.ownColumns = [...prop.inverseJoinColumns];
884
+ pivotMeta.properties[relationName] = relation;
885
+ pivotMeta.polymorphicDiscriminatorMap[targetMeta.tableName] = targetMeta.class;
886
+ }
887
+ }
812
888
  /**
813
889
  * Create a virtual M:1 relation from pivot to a polymorphic owner entity.
814
890
  * This enables single-query join loading for inverse-side polymorphic M:N.
@@ -958,9 +1034,14 @@ export class MetadataDiscovery {
958
1034
  meta.propertyOrder.set(prop.name, (order += 0.01));
959
1035
  });
960
1036
  }
961
- meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
962
- meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
963
- meta.checks = Utils.unique([...base.checks, ...meta.checks]);
1037
+ // TPT children have their own tables that don't contain the parent's columns,
1038
+ // so propagating parent indexes/uniques/checks/triggers would target missing columns.
1039
+ if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
1040
+ meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
1041
+ meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
1042
+ meta.checks = Utils.unique([...base.checks, ...meta.checks]);
1043
+ meta.triggers = Utils.unique([...base.triggers, ...meta.triggers]);
1044
+ }
964
1045
  const pks = Object.values(meta.properties)
965
1046
  .filter(p => p.primary)
966
1047
  .map(p => p.name);
@@ -1045,11 +1126,15 @@ export class MetadataDiscovery {
1045
1126
  prop.discriminatorColumn ??= this.#namingStrategy.discriminatorColumnName(prop.discriminator);
1046
1127
  prop.createForeignKeyConstraint = false;
1047
1128
  const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
1048
- if (isToOne) {
1129
+ const isUnionTargetMN = prop.kind === ReferenceKind.MANY_TO_MANY && Array.isArray(prop.target);
1130
+ if (isToOne || isUnionTargetMN) {
1049
1131
  const types = prop.type.split(/ ?\| ?/);
1050
1132
  prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
1051
1133
  prop.targetMeta = prop.polymorphTargets[0];
1052
1134
  prop.referencedPKs = prop.targetMeta?.primaryKeys;
1135
+ if (isUnionTargetMN && prop.polymorphTargets.length < 2) {
1136
+ throw new MetadataError(`${meta.className}.${prop.name} union-target polymorphic M:N requires at least two target entity types; use a regular M:N relation for a single target.`);
1137
+ }
1053
1138
  }
1054
1139
  if (prop.discriminatorMap) {
1055
1140
  const normalizedMap = {};
@@ -1065,7 +1150,7 @@ export class MetadataDiscovery {
1065
1150
  }
1066
1151
  prop.discriminatorMap = normalizedMap;
1067
1152
  }
1068
- else if (isToOne) {
1153
+ else if (isToOne || isUnionTargetMN) {
1069
1154
  prop.discriminatorMap = {};
1070
1155
  const tableNameToTarget = new Map();
1071
1156
  for (const target of prop.polymorphTargets) {
@@ -1187,6 +1272,19 @@ export class MetadataDiscovery {
1187
1272
  });
1188
1273
  }
1189
1274
  }
1275
+ sameRelationTargetRoot(rootProp, prop) {
1276
+ if (!rootProp || rootProp.kind !== prop.kind) {
1277
+ return false;
1278
+ }
1279
+ if (prop.kind !== ReferenceKind.MANY_TO_ONE &&
1280
+ prop.kind !== ReferenceKind.ONE_TO_ONE &&
1281
+ prop.kind !== ReferenceKind.ONE_TO_MANY &&
1282
+ prop.kind !== ReferenceKind.MANY_TO_MANY) {
1283
+ return false;
1284
+ }
1285
+ const aRoot = this.#metadata.getByClassName(prop.type, false)?.root;
1286
+ return aRoot != null && aRoot === this.#metadata.getByClassName(rootProp.type, false)?.root;
1287
+ }
1190
1288
  initSingleTableInheritance(meta, metadata) {
1191
1289
  if (meta.root !== meta && !meta.__processed) {
1192
1290
  meta.root = metadata.find(m => m.class === meta.root.class);
@@ -1227,12 +1325,24 @@ export class MetadataDiscovery {
1227
1325
  Object.values(meta.properties).forEach(prop => {
1228
1326
  const newProp = { ...prop };
1229
1327
  const rootProp = meta.root.properties[prop.name];
1328
+ // A child that narrows a relation to a subclass of the root's declared
1329
+ // target (same STI hierarchy) shares the FK column with the root; treat
1330
+ // that as matching so the rename branch below doesn't run (which would
1331
+ // crash — `targetMeta` is only populated later, in `initRelation`).
1332
+ const narrowedRelationOverride = rootProp != null && rootProp.type !== prop.type && this.sameRelationTargetRoot(rootProp, prop);
1230
1333
  // stiMerged is set during inlineProperties when a property was merged
1231
1334
  // from multiple polymorphic variants with different types. The flag is
1232
1335
  // cleared implicitly when the first child claims the root property via
1233
1336
  // addProperty below, so subsequent children correctly trigger renaming.
1234
- const typesMatch = rootProp?.type === prop.type || rootProp?.stiMerged === true;
1337
+ const typesMatch = rootProp?.type === prop.type || rootProp?.stiMerged === true || narrowedRelationOverride;
1338
+ // Inverse-side relations (1:m, inverse m:n) have no FK column on this
1339
+ // entity, so the rename branch — which exists to disambiguate physical
1340
+ // columns shared across STI children — doesn't apply. Skipping it also
1341
+ // avoids crashing on `[...rootProp.fieldNames]` since fieldNames is
1342
+ // never populated for inverse-side properties.
1343
+ const isInverseSideRelation = prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner);
1235
1344
  if (rootProp &&
1345
+ !isInverseSideRelation &&
1236
1346
  (!typesMatch ||
1237
1347
  (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
1238
1348
  const name = newProp.name;
@@ -1281,6 +1391,20 @@ export class MetadataDiscovery {
1281
1391
  }
1282
1392
  newProp.nullable = true;
1283
1393
  newProp.inherited = !rootProp;
1394
+ // For narrowed relation overrides, keep the root's declaration intact so
1395
+ // the full target union (e.g. `Food`) is preserved for populates from the
1396
+ // abstract root — otherwise the last child processed would win.
1397
+ if (narrowedRelationOverride) {
1398
+ return;
1399
+ }
1400
+ // STI siblings can declare the same property name on inverse-side relations
1401
+ // pointing to disjoint targets (e.g. `Dog.items → DogItem` and
1402
+ // `Cat.items → CatItem` with no shared base). There's no physical column
1403
+ // to manage at root, and propagating would clobber the first child's
1404
+ // declaration; each child keeps its local typed property instead.
1405
+ if (isInverseSideRelation && rootProp && rootProp.type !== prop.type) {
1406
+ return;
1407
+ }
1284
1408
  meta.root.addProperty(newProp);
1285
1409
  });
1286
1410
  meta.tableName = meta.root.tableName;
@@ -1296,10 +1420,10 @@ export class MetadataDiscovery {
1296
1420
  if (meta.root !== meta) {
1297
1421
  meta.root = metadata.find(m => m.class === meta.root.class);
1298
1422
  }
1299
- const inheritance = meta.inheritance;
1300
- if (inheritance === 'tpt' && meta.root === meta) {
1301
- meta.inheritanceType = 'tpt';
1302
- meta.tptChildren = [];
1423
+ // init the root eagerly so children iterated before the root (e.g. alphabetical glob order) still get linked
1424
+ if (meta.root.inheritance === 'tpt' && meta.root.inheritanceType !== 'tpt') {
1425
+ meta.root.inheritanceType = 'tpt';
1426
+ meta.root.tptChildren ??= [];
1303
1427
  }
1304
1428
  if (meta.root.inheritanceType !== 'tpt') {
1305
1429
  return;
@@ -1458,13 +1582,18 @@ export class MetadataDiscovery {
1458
1582
  });
1459
1583
  }
1460
1584
  createDiscriminatorProperty(meta) {
1461
- meta.addProperty({
1585
+ const prop = {
1462
1586
  name: meta.discriminatorColumn,
1463
1587
  type: 'string',
1464
1588
  enum: true,
1465
1589
  kind: ReferenceKind.SCALAR,
1466
1590
  userDefined: false,
1467
- });
1591
+ };
1592
+ // honor the column-name override set via `discriminatorColumn` when `discriminator` is also given
1593
+ if (meta.discriminatorFieldName) {
1594
+ prop.fieldName = meta.discriminatorFieldName;
1595
+ }
1596
+ meta.addProperty(prop);
1468
1597
  }
1469
1598
  initAutoincrement(meta) {
1470
1599
  const pks = meta.getPrimaryProps();
@@ -1487,7 +1616,7 @@ export class MetadataDiscovery {
1487
1616
  const table = this.createSchemaTable(meta);
1488
1617
  for (const check of meta.checks) {
1489
1618
  const fieldNames = check.property ? meta.properties[check.property].fieldNames : [];
1490
- check.name ??= this.#namingStrategy.indexName(meta.tableName, fieldNames, 'check');
1619
+ check.name ??= this.#platform.getIndexName(meta.tableName, fieldNames, 'check');
1491
1620
  if (check.expression instanceof Function) {
1492
1621
  check.expression = check.expression(columns, table);
1493
1622
  }
@@ -1512,7 +1641,7 @@ export class MetadataDiscovery {
1512
1641
  }
1513
1642
  if (expression) {
1514
1643
  meta.checks.push({
1515
- name: this.#namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
1644
+ name: this.#platform.getIndexName(meta.tableName, prop.fieldNames, 'check'),
1516
1645
  property: prop.name,
1517
1646
  expression,
1518
1647
  });
@@ -1520,6 +1649,27 @@ export class MetadataDiscovery {
1520
1649
  }
1521
1650
  }
1522
1651
  }
1652
+ initTriggers(meta) {
1653
+ if (meta.triggers.length === 0) {
1654
+ return;
1655
+ }
1656
+ const columns = meta.createSchemaColumnMappingObject();
1657
+ const table = this.createSchemaTable(meta);
1658
+ for (const trigger of meta.triggers) {
1659
+ if (trigger.body && trigger.expression) {
1660
+ throw new MetadataError(`Trigger "${trigger.name ?? '(unnamed)'}" on entity ${meta.className} defines both 'body' and 'expression'. Use one or the other.`);
1661
+ }
1662
+ if (!trigger.body && !trigger.expression) {
1663
+ throw new MetadataError(`Trigger "${trigger.name ?? '(unnamed)'}" on entity ${meta.className} must define either 'body' or 'expression'.`);
1664
+ }
1665
+ trigger.name ??= this.#namingStrategy.indexName(meta.tableName, trigger.events, 'trigger');
1666
+ trigger.forEach ??= 'row';
1667
+ if (trigger.body instanceof Function) {
1668
+ trigger.body = trigger.body(columns, table);
1669
+ }
1670
+ }
1671
+ meta.hasTriggers = true;
1672
+ }
1523
1673
  initGeneratedColumn(meta, prop) {
1524
1674
  if (!prop.generated && prop.columnTypes) {
1525
1675
  const match = /(.*) generated always as (.*)/i.exec(prop.columnTypes[0]);
@@ -1667,6 +1817,7 @@ export class MetadataDiscovery {
1667
1817
  prop.customType &&
1668
1818
  !(prop.customType instanceof t.array) &&
1669
1819
  !(prop.customType instanceof t.enumArray) &&
1820
+ !(prop.customType instanceof t.json) &&
1670
1821
  prop.kind === ReferenceKind.SCALAR) {
1671
1822
  const innerType = prop.customType;
1672
1823
  innerType.platform = this.#platform;
@@ -1702,7 +1853,7 @@ export class MetadataDiscovery {
1702
1853
  if (!prop.customType && prop.array && prop.items) {
1703
1854
  prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1704
1855
  }
1705
- const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1856
+ const isArray = prop.array || prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1706
1857
  if (objectEmbeddable && !prop.customType && isArray) {
1707
1858
  prop.customType = new t.json();
1708
1859
  }
@@ -1,4 +1,5 @@
1
1
  import type { EntityMetadata, EntityName } from '../typings.js';
2
+ import type { Routine } from './Routine.js';
2
3
  import { type MetadataDiscoveryOptions } from '../utils/Configuration.js';
3
4
  import type { MetadataStorage } from './MetadataStorage.js';
4
5
  /**
@@ -6,7 +7,32 @@ import type { MetadataStorage } from './MetadataStorage.js';
6
7
  */
7
8
  export declare class MetadataValidator {
8
9
  validateEntityDefinition<T>(metadata: MetadataStorage, name: EntityName<T>, options: MetadataDiscoveryOptions): void;
10
+ validateRoutineDefinition(routine: Routine): void;
9
11
  validateDiscovered(discovered: EntityMetadata[], options: MetadataDiscoveryOptions): void;
12
+ private validatePartitioning;
13
+ /**
14
+ * Find the first partition name whose normalized form (case-folded for unquoted segments,
15
+ * quoted segments preserved) has already been seen. Returns the offending name in its
16
+ * original form for the error message.
17
+ */
18
+ private findDuplicatePartitionName;
19
+ /**
20
+ * Partition names may be bare (`child`), schema-qualified (`schema.child`), or use quoted
21
+ * identifiers (`"my.schema"."child"`). Reject anything with more than one unquoted `.`.
22
+ */
23
+ private hasValidPartitionName;
24
+ private hasPartitionExpression;
25
+ private validatePartitionKeyConstraints;
26
+ /**
27
+ * Returns the list of physical field names that a partition expression references, or
28
+ * `undefined` when the expression is opaque (callback, or raw SQL like `date_trunc('day', x)`
29
+ * that we cannot statically parse). Opaque expressions intentionally bypass the primary-key /
30
+ * unique-constraint coverage checks — users are trusted to ensure the referenced columns are
31
+ * part of the partition key, since PostgreSQL will surface the violation at DDL execution.
32
+ */
33
+ private getPartitionKeyFields;
34
+ private resolvePartitionKeyField;
35
+ private getConstraintFields;
10
36
  private validateReference;
11
37
  private validateTargetKey;
12
38
  /**