@mikro-orm/core 7.0.0-dev.23 → 7.0.0-dev.231

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 (209) hide show
  1. package/EntityManager.d.ts +91 -59
  2. package/EntityManager.js +303 -251
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +2 -0
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +17 -8
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +12 -5
  13. package/connections/Connection.js +21 -12
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +118 -35
  16. package/drivers/IDatabaseDriver.d.ts +42 -19
  17. package/entity/BaseEntity.d.ts +61 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +101 -29
  20. package/entity/Collection.js +436 -104
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +7 -1
  24. package/entity/EntityFactory.js +83 -54
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +48 -15
  27. package/entity/EntityLoader.d.ts +7 -6
  28. package/entity/EntityLoader.js +221 -93
  29. package/entity/EntityRepository.d.ts +27 -7
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/PolymorphicRef.d.ts +12 -0
  32. package/entity/PolymorphicRef.js +18 -0
  33. package/entity/Reference.d.ts +1 -5
  34. package/entity/Reference.js +21 -12
  35. package/entity/WrappedEntity.d.ts +0 -5
  36. package/entity/WrappedEntity.js +2 -7
  37. package/entity/defineEntity.d.ts +380 -310
  38. package/entity/defineEntity.js +124 -273
  39. package/entity/index.d.ts +2 -2
  40. package/entity/index.js +2 -2
  41. package/entity/utils.js +1 -1
  42. package/entity/validators.d.ts +11 -0
  43. package/entity/validators.js +65 -0
  44. package/enums.d.ts +8 -6
  45. package/enums.js +2 -1
  46. package/errors.d.ts +20 -10
  47. package/errors.js +55 -23
  48. package/events/EventManager.d.ts +2 -1
  49. package/events/EventManager.js +19 -11
  50. package/hydration/Hydrator.js +1 -2
  51. package/hydration/ObjectHydrator.d.ts +4 -4
  52. package/hydration/ObjectHydrator.js +87 -35
  53. package/index.d.ts +2 -2
  54. package/index.js +1 -2
  55. package/logging/DefaultLogger.d.ts +1 -1
  56. package/logging/DefaultLogger.js +1 -0
  57. package/logging/SimpleLogger.d.ts +1 -1
  58. package/logging/colors.d.ts +1 -1
  59. package/logging/colors.js +7 -6
  60. package/logging/index.d.ts +1 -0
  61. package/logging/index.js +1 -0
  62. package/logging/inspect.d.ts +2 -0
  63. package/logging/inspect.js +11 -0
  64. package/metadata/EntitySchema.d.ts +47 -23
  65. package/metadata/EntitySchema.js +92 -33
  66. package/metadata/MetadataDiscovery.d.ts +64 -9
  67. package/metadata/MetadataDiscovery.js +778 -325
  68. package/metadata/MetadataProvider.d.ts +11 -2
  69. package/metadata/MetadataProvider.js +46 -2
  70. package/metadata/MetadataStorage.d.ts +13 -11
  71. package/metadata/MetadataStorage.js +70 -37
  72. package/metadata/MetadataValidator.d.ts +32 -9
  73. package/metadata/MetadataValidator.js +196 -41
  74. package/metadata/discover-entities.d.ts +5 -0
  75. package/metadata/discover-entities.js +40 -0
  76. package/metadata/index.d.ts +1 -1
  77. package/metadata/index.js +1 -1
  78. package/metadata/types.d.ts +526 -0
  79. package/metadata/types.js +1 -0
  80. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  81. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  82. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  83. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  84. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/MongoNamingStrategy.js +6 -6
  86. package/naming-strategy/NamingStrategy.d.ts +28 -4
  87. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  89. package/not-supported.d.ts +2 -0
  90. package/not-supported.js +4 -0
  91. package/package.json +19 -11
  92. package/platforms/ExceptionConverter.js +1 -1
  93. package/platforms/Platform.d.ts +7 -14
  94. package/platforms/Platform.js +20 -43
  95. package/serialization/EntitySerializer.d.ts +5 -0
  96. package/serialization/EntitySerializer.js +47 -27
  97. package/serialization/EntityTransformer.js +28 -18
  98. package/serialization/SerializationContext.d.ts +6 -6
  99. package/serialization/SerializationContext.js +3 -3
  100. package/types/ArrayType.d.ts +1 -1
  101. package/types/ArrayType.js +2 -3
  102. package/types/BigIntType.d.ts +8 -6
  103. package/types/BigIntType.js +1 -1
  104. package/types/BlobType.d.ts +0 -1
  105. package/types/BlobType.js +0 -3
  106. package/types/BooleanType.d.ts +1 -0
  107. package/types/BooleanType.js +3 -0
  108. package/types/DecimalType.d.ts +6 -4
  109. package/types/DecimalType.js +2 -2
  110. package/types/DoubleType.js +1 -1
  111. package/types/EnumArrayType.js +1 -2
  112. package/types/JsonType.d.ts +1 -1
  113. package/types/JsonType.js +7 -2
  114. package/types/TinyIntType.js +1 -1
  115. package/types/Type.d.ts +2 -4
  116. package/types/Type.js +3 -3
  117. package/types/Uint8ArrayType.d.ts +0 -1
  118. package/types/Uint8ArrayType.js +1 -4
  119. package/types/index.d.ts +1 -1
  120. package/typings.d.ts +381 -171
  121. package/typings.js +97 -44
  122. package/unit-of-work/ChangeSet.d.ts +4 -6
  123. package/unit-of-work/ChangeSet.js +4 -5
  124. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  125. package/unit-of-work/ChangeSetComputer.js +35 -14
  126. package/unit-of-work/ChangeSetPersister.d.ts +7 -3
  127. package/unit-of-work/ChangeSetPersister.js +83 -25
  128. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  129. package/unit-of-work/CommitOrderCalculator.js +13 -13
  130. package/unit-of-work/IdentityMap.d.ts +12 -0
  131. package/unit-of-work/IdentityMap.js +39 -1
  132. package/unit-of-work/UnitOfWork.d.ts +27 -3
  133. package/unit-of-work/UnitOfWork.js +258 -92
  134. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  135. package/utils/AbstractSchemaGenerator.js +28 -17
  136. package/utils/AsyncContext.d.ts +6 -0
  137. package/utils/AsyncContext.js +42 -0
  138. package/utils/Configuration.d.ts +795 -209
  139. package/utils/Configuration.js +150 -192
  140. package/utils/ConfigurationLoader.d.ts +1 -54
  141. package/utils/ConfigurationLoader.js +1 -352
  142. package/utils/Cursor.d.ts +0 -3
  143. package/utils/Cursor.js +24 -11
  144. package/utils/DataloaderUtils.d.ts +10 -5
  145. package/utils/DataloaderUtils.js +29 -12
  146. package/utils/EntityComparator.d.ts +16 -9
  147. package/utils/EntityComparator.js +158 -58
  148. package/utils/QueryHelper.d.ts +18 -6
  149. package/utils/QueryHelper.js +76 -23
  150. package/utils/RawQueryFragment.d.ts +28 -34
  151. package/utils/RawQueryFragment.js +35 -71
  152. package/utils/RequestContext.js +2 -2
  153. package/utils/TransactionContext.js +2 -2
  154. package/utils/TransactionManager.js +28 -4
  155. package/utils/Utils.d.ts +14 -127
  156. package/utils/Utils.js +85 -397
  157. package/utils/clone.js +8 -23
  158. package/utils/env-vars.d.ts +7 -0
  159. package/utils/env-vars.js +97 -0
  160. package/utils/fs-utils.d.ts +33 -0
  161. package/utils/fs-utils.js +192 -0
  162. package/utils/index.d.ts +1 -1
  163. package/utils/index.js +1 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +46 -3
  166. package/decorators/Check.d.ts +0 -3
  167. package/decorators/Check.js +0 -13
  168. package/decorators/CreateRequestContext.d.ts +0 -3
  169. package/decorators/CreateRequestContext.js +0 -32
  170. package/decorators/Embeddable.d.ts +0 -8
  171. package/decorators/Embeddable.js +0 -11
  172. package/decorators/Embedded.d.ts +0 -12
  173. package/decorators/Embedded.js +0 -18
  174. package/decorators/Entity.d.ts +0 -33
  175. package/decorators/Entity.js +0 -12
  176. package/decorators/Enum.d.ts +0 -9
  177. package/decorators/Enum.js +0 -16
  178. package/decorators/Filter.d.ts +0 -2
  179. package/decorators/Filter.js +0 -8
  180. package/decorators/Formula.d.ts +0 -4
  181. package/decorators/Formula.js +0 -15
  182. package/decorators/Indexed.d.ts +0 -19
  183. package/decorators/Indexed.js +0 -20
  184. package/decorators/ManyToMany.d.ts +0 -42
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -34
  187. package/decorators/ManyToOne.js +0 -14
  188. package/decorators/OneToMany.d.ts +0 -28
  189. package/decorators/OneToMany.js +0 -17
  190. package/decorators/OneToOne.d.ts +0 -28
  191. package/decorators/OneToOne.js +0 -7
  192. package/decorators/PrimaryKey.d.ts +0 -8
  193. package/decorators/PrimaryKey.js +0 -20
  194. package/decorators/Property.d.ts +0 -250
  195. package/decorators/Property.js +0 -32
  196. package/decorators/Transactional.d.ts +0 -14
  197. package/decorators/Transactional.js +0 -28
  198. package/decorators/hooks.d.ts +0 -16
  199. package/decorators/hooks.js +0 -47
  200. package/decorators/index.d.ts +0 -17
  201. package/decorators/index.js +0 -17
  202. package/entity/ArrayCollection.d.ts +0 -118
  203. package/entity/ArrayCollection.js +0 -407
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  207. package/metadata/ReflectMetadataProvider.js +0 -44
  208. package/utils/resolveContextProvider.d.ts +0 -10
  209. package/utils/resolveContextProvider.js +0 -28
@@ -1,22 +1,22 @@
1
- import { basename, extname } from 'node:path';
2
- import { glob } from 'tinyglobby';
3
1
  import { EntityMetadata, } from '../typings.js';
4
- import { Utils } from '../utils/Utils.js';
2
+ import { compareArrays, Utils } from '../utils/Utils.js';
3
+ import { QueryHelper } from '../utils/QueryHelper.js';
5
4
  import { MetadataValidator } from './MetadataValidator.js';
5
+ import { MetadataProvider } from './MetadataProvider.js';
6
6
  import { MetadataStorage } from './MetadataStorage.js';
7
7
  import { EntitySchema } from './EntitySchema.js';
8
8
  import { Cascade, ReferenceKind } from '../enums.js';
9
9
  import { MetadataError } from '../errors.js';
10
- import { ArrayType, BigIntType, BlobType, DateType, DecimalType, DoubleType, EnumArrayType, IntervalType, JsonType, t, Type, Uint8ArrayType, UnknownType, } from '../types/index.js';
10
+ import { t, Type } from '../types/index.js';
11
11
  import { colors } from '../logging/colors.js';
12
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
12
+ import { raw, Raw } from '../utils/RawQueryFragment.js';
13
+ import { BaseEntity } from '../entity/BaseEntity.js';
13
14
  export class MetadataDiscovery {
14
15
  metadata;
15
16
  platform;
16
17
  config;
17
18
  namingStrategy;
18
19
  metadataProvider;
19
- cache;
20
20
  logger;
21
21
  schemaHelper;
22
22
  validator = new MetadataValidator();
@@ -27,13 +27,14 @@ export class MetadataDiscovery {
27
27
  this.config = config;
28
28
  this.namingStrategy = this.config.getNamingStrategy();
29
29
  this.metadataProvider = this.config.getMetadataProvider();
30
- this.cache = this.config.getMetadataCacheAdapter();
31
30
  this.logger = this.config.getLogger();
32
31
  this.schemaHelper = this.platform.getSchemaHelper();
33
32
  }
34
33
  async discover(preferTs = true) {
34
+ this.discovered.length = 0;
35
35
  const startTime = Date.now();
36
- this.logger.log('discovery', `ORM entity discovery started, using ${colors.cyan(this.metadataProvider.constructor.name)}`);
36
+ const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
37
+ this.logger.log('discovery', `ORM entity discovery started${suffix}`);
37
38
  await this.findEntities(preferTs);
38
39
  for (const meta of this.discovered) {
39
40
  /* v8 ignore next */
@@ -47,10 +48,13 @@ export class MetadataDiscovery {
47
48
  await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
48
49
  return storage;
49
50
  }
50
- discoverSync(preferTs = true) {
51
+ discoverSync() {
52
+ this.discovered.length = 0;
51
53
  const startTime = Date.now();
52
- this.logger.log('discovery', `ORM entity discovery started, using ${colors.cyan(this.metadataProvider.constructor.name)} in sync mode`);
53
- this.findEntities(preferTs, true);
54
+ const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
55
+ this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
56
+ const refs = this.config.get('entities');
57
+ this.discoverReferences(refs);
54
58
  for (const meta of this.discovered) {
55
59
  /* v8 ignore next */
56
60
  void this.config.get('discovery').onMetadata?.(meta, this.platform);
@@ -70,82 +74,120 @@ export class MetadataDiscovery {
70
74
  .sort((a, b) => b.root.name.localeCompare(a.root.name))
71
75
  .forEach(meta => {
72
76
  this.platform.validateMetadata(meta);
73
- discovered.set(meta.className, meta);
77
+ discovered.set(meta.class, meta);
74
78
  });
79
+ for (const meta of discovered) {
80
+ meta.root = discovered.get(meta.root.class);
81
+ if (meta.inheritanceType === 'tpt') {
82
+ this.computeTPTOwnProps(meta);
83
+ }
84
+ }
75
85
  return discovered;
76
86
  }
87
+ initAccessors(meta) {
88
+ for (const prop of Object.values(meta.properties)) {
89
+ if (!prop.accessor || meta.properties[prop.accessor]) {
90
+ continue;
91
+ }
92
+ const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
93
+ if (desc?.get || desc?.set) {
94
+ this.initFieldName(prop);
95
+ const accessor = prop.name;
96
+ prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
97
+ if (prop.accessor === true) {
98
+ prop.getter = prop.setter = true;
99
+ }
100
+ else {
101
+ prop.getter = prop.setter = false;
102
+ }
103
+ prop.accessor = accessor;
104
+ prop.serializedName ??= accessor;
105
+ Utils.renameKey(meta.properties, accessor, prop.name);
106
+ }
107
+ else {
108
+ const name = prop.name;
109
+ prop.name = prop.accessor;
110
+ this.initFieldName(prop);
111
+ prop.serializedName ??= prop.accessor;
112
+ prop.name = name;
113
+ }
114
+ }
115
+ }
77
116
  processDiscoveredEntities(discovered) {
78
117
  for (const meta of discovered) {
79
118
  let i = 1;
80
119
  Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
81
120
  Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
121
+ this.initAccessors(meta);
82
122
  }
83
123
  // ignore base entities (not annotated with @Entity)
84
124
  const filtered = discovered.filter(meta => meta.root.name);
85
125
  // sort so we discover entities first to get around issues with nested embeddables
86
126
  filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
87
127
  filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
128
+ filtered.forEach(meta => this.initTPTRelationships(meta, filtered));
88
129
  filtered.forEach(meta => this.defineBaseEntityProperties(meta));
89
- filtered.forEach(meta => this.metadata.set(meta.className, EntitySchema.fromMetadata(meta).init().meta));
130
+ filtered.forEach(meta => {
131
+ const newMeta = EntitySchema.fromMetadata(meta).init().meta;
132
+ return this.metadata.set(newMeta.class, newMeta);
133
+ });
90
134
  filtered.forEach(meta => this.initAutoincrement(meta));
91
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initEmbeddables(meta, prop)));
92
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initFactoryField(meta, prop)));
93
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initFieldName(prop)));
94
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initVersionProperty(meta, prop)));
95
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initCustomType(meta, prop)));
96
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initGeneratedColumn(meta, prop)));
135
+ const forEachProp = (cb) => {
136
+ filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
137
+ };
138
+ forEachProp((m, p) => this.initFactoryField(m, p));
139
+ forEachProp((m, p) => this.initPolymorphicRelation(m, p, filtered));
140
+ forEachProp((_m, p) => this.initRelation(p));
141
+ forEachProp((m, p) => this.initEmbeddables(m, p));
142
+ forEachProp((_m, p) => this.initFieldName(p));
143
+ filtered.forEach(meta => this.finalizeTPTInheritance(meta, filtered));
144
+ filtered.forEach(meta => this.computeTPTOwnProps(meta));
145
+ forEachProp((m, p) => this.initVersionProperty(m, p));
146
+ forEachProp((m, p) => this.initCustomType(m, p));
147
+ forEachProp((m, p) => this.initGeneratedColumn(m, p));
97
148
  filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
98
149
  filtered.forEach(meta => this.initCheckConstraints(meta));
99
- for (const meta of filtered) {
100
- for (const prop of Object.values(meta.properties)) {
101
- this.initDefaultValue(prop);
102
- this.inferTypeFromDefault(prop);
103
- this.initColumnType(prop);
104
- // change tracking on scalars is used only for "auto" flushMode
105
- if (this.config.get('flushMode') !== 'auto' && [ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind)) {
106
- prop.trackChanges = false;
107
- }
108
- }
109
- }
110
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initIndexes(meta, prop)));
150
+ forEachProp((_m, p) => {
151
+ this.initDefaultValue(p);
152
+ this.inferTypeFromDefault(p);
153
+ this.initRelation(p);
154
+ this.initColumnType(p);
155
+ });
156
+ forEachProp((m, p) => this.initIndexes(m, p));
111
157
  filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
112
- filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
113
158
  for (const meta of filtered) {
114
159
  discovered.push(...this.processEntity(meta));
115
160
  }
116
161
  discovered.forEach(meta => meta.sync(true));
117
- const combinedCachePath = this.cache.combine?.();
118
- // override the path in the options, so we can log it from the CLI in `cache:generate` command
119
- if (combinedCachePath) {
120
- this.config.get('metadataCache').combined = combinedCachePath;
121
- }
162
+ this.metadataProvider.combineCache();
122
163
  return discovered.map(meta => {
123
- meta = this.metadata.get(meta.className);
164
+ meta = this.metadata.get(meta.class);
124
165
  meta.sync(true);
166
+ this.findReferencingProperties(meta, filtered);
167
+ if (meta.inheritanceType === 'tpt') {
168
+ this.computeTPTOwnProps(meta);
169
+ }
125
170
  return meta;
126
171
  });
127
172
  }
128
- findEntities(preferTs, sync = false) {
129
- this.discovered.length = 0;
130
- const options = this.config.get('discovery');
131
- const key = (preferTs && this.config.get('preferTs', Utils.detectTypeScriptSupport()) && this.config.get('entitiesTs').length > 0) ? 'entitiesTs' : 'entities';
132
- const paths = this.config.get(key).filter(item => Utils.isString(item));
133
- const refs = this.config.get(key).filter(item => !Utils.isString(item));
173
+ async findEntities(preferTs) {
174
+ const { entities, entitiesTs, baseDir } = this.config.getAll();
175
+ const targets = (preferTs && entitiesTs.length > 0) ? entitiesTs : entities;
176
+ const processed = [];
177
+ const paths = [];
178
+ for (const entity of targets) {
179
+ if (typeof entity === 'string') {
180
+ paths.push(entity);
181
+ }
182
+ else {
183
+ processed.push(entity);
184
+ }
185
+ }
134
186
  if (paths.length > 0) {
135
- if (sync || options.requireEntitiesArray) {
136
- throw new Error(`[requireEntitiesArray] Explicit list of entities is required, please use the 'entities' option.`);
137
- }
138
- return this.discoverDirectories(paths).then(() => {
139
- this.discoverReferences(refs);
140
- this.discoverMissingTargets();
141
- this.validator.validateDiscovered(this.discovered, options);
142
- return this.discovered;
143
- });
187
+ const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
188
+ processed.push(...await discoverEntities(paths, { baseDir }));
144
189
  }
145
- this.discoverReferences(refs);
146
- this.discoverMissingTargets();
147
- this.validator.validateDiscovered(this.discovered, options);
148
- return this.discovered;
190
+ return this.discoverReferences(processed);
149
191
  }
150
192
  discoverMissingTargets() {
151
193
  const unwrap = (type) => type
@@ -154,17 +196,22 @@ export class MetadataDiscovery {
154
196
  .replace(/\((.*)\)/, '$1'); // unwrap union types
155
197
  const missing = [];
156
198
  this.discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
157
- if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity && !this.discovered.find(m => m.className === Utils.className(prop.pivotEntity))) {
158
- const target = typeof prop.pivotEntity === 'function'
159
- ? prop.pivotEntity()
160
- : prop.pivotEntity;
161
- missing.push(target);
162
- }
163
- if (prop.kind !== ReferenceKind.SCALAR && !unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
164
- const target = typeof prop.entity === 'function'
199
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
200
+ const pivotEntity = prop.pivotEntity;
201
+ const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
202
+ ? pivotEntity()
203
+ : pivotEntity;
204
+ if (!this.discovered.find(m => m.className === Utils.className(target))) {
205
+ missing.push(target);
206
+ }
207
+ }
208
+ if (prop.kind !== ReferenceKind.SCALAR) {
209
+ const target = typeof prop.entity === 'function' && !prop.entity.prototype
165
210
  ? prop.entity()
166
211
  : prop.type;
167
- missing.push(...Utils.asArray(target));
212
+ if (!unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
213
+ missing.push(...Utils.asArray(target));
214
+ }
168
215
  }
169
216
  }));
170
217
  if (missing.length > 0) {
@@ -173,123 +220,107 @@ export class MetadataDiscovery {
173
220
  }
174
221
  tryDiscoverTargets(targets) {
175
222
  for (const target of targets) {
176
- if (typeof target === 'function' && target.name && !this.metadata.has(target.name)) {
177
- this.discoverReferences([target]);
178
- this.discoverMissingTargets();
179
- }
180
- }
181
- }
182
- async discoverDirectories(paths) {
183
- paths = paths.map(path => Utils.normalizePath(path));
184
- const files = await glob(paths, { cwd: Utils.normalizePath(this.config.get('baseDir')) });
185
- this.logger.log('discovery', `- processing ${colors.cyan('' + files.length)} files`);
186
- const found = [];
187
- for (const filepath of files) {
188
- const filename = basename(filepath);
189
- if (!filename.match(/\.[cm]?[jt]s$/) ||
190
- filename.endsWith('.js.map') ||
191
- filename.match(/\.d\.[cm]?ts/) ||
192
- filename.startsWith('.') ||
193
- filename.match(/index\.[cm]?[jt]s$/)) {
194
- this.logger.log('discovery', `- ignoring file ${filename}`);
195
- continue;
196
- }
197
- const name = this.namingStrategy.getClassName(filename);
198
- const path = Utils.normalizePath(this.config.get('baseDir'), filepath);
199
- const targets = await this.getEntityClassOrSchema(path, name);
200
- for (const target of targets) {
201
- if (!(target instanceof Function) && !(target instanceof EntitySchema)) {
202
- this.logger.log('discovery', `- ignoring file ${filename}`);
203
- continue;
223
+ const schema = target instanceof EntitySchema ? target : undefined;
224
+ const isDiscoverable = typeof target === 'function' || schema;
225
+ if (isDiscoverable && target.name) {
226
+ // Get the actual class for EntitySchema, or use target directly for classes
227
+ const targetClass = schema ? schema.meta.class : target;
228
+ if (!this.metadata.has(targetClass)) {
229
+ this.discoverReferences([target], false);
230
+ this.discoverMissingTargets();
204
231
  }
205
- const entity = this.prepare(target);
206
- const schema = this.getSchema(entity, path);
207
- const meta = schema.init().meta;
208
- this.metadata.set(meta.className, meta);
209
- found.push([schema, path]);
210
232
  }
211
233
  }
212
- for (const [schema, path] of found) {
213
- this.discoverEntity(schema, path);
214
- }
215
234
  }
216
- discoverReferences(refs) {
235
+ discoverReferences(refs, validate = true) {
217
236
  const found = [];
218
237
  for (const entity of refs) {
219
- const schema = this.getSchema(this.prepare(entity));
238
+ if (typeof entity === 'string') {
239
+ throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
240
+ }
241
+ const schema = this.getSchema(entity);
220
242
  const meta = schema.init().meta;
221
- this.metadata.set(meta.className, meta);
243
+ this.metadata.set(meta.class, meta);
222
244
  found.push(schema);
223
245
  }
224
246
  // discover parents (base entities) automatically
225
247
  for (const meta of this.metadata) {
226
248
  let parent = meta.extends;
227
- if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.className)) {
228
- this.discoverReferences([parent]);
249
+ if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
250
+ this.discoverReferences([parent], false);
229
251
  }
230
- /* v8 ignore next 3 */
252
+ if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
253
+ this.discoverReferences([parent], false);
254
+ }
255
+ /* v8 ignore next */
231
256
  if (!meta.class) {
232
257
  continue;
233
258
  }
234
259
  parent = Object.getPrototypeOf(meta.class);
235
- if (parent.name !== '' && !this.metadata.has(parent.name)) {
236
- this.discoverReferences([parent]);
260
+ // Skip if parent is the auto-generated base class for the same entity (from setClass usage)
261
+ if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
262
+ this.discoverReferences([parent], false);
237
263
  }
238
264
  }
239
265
  for (const schema of found) {
240
266
  this.discoverEntity(schema);
241
267
  }
268
+ this.discoverMissingTargets();
269
+ if (validate) {
270
+ this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
271
+ }
242
272
  return this.discovered.filter(meta => found.find(m => m.name === meta.className));
243
273
  }
244
- reset(className) {
245
- const exists = this.discovered.findIndex(m => m.className === className);
274
+ reset(entityName) {
275
+ const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
246
276
  if (exists !== -1) {
247
- this.metadata.reset(this.discovered[exists].className);
277
+ this.metadata.reset(this.discovered[exists].class);
248
278
  this.discovered.splice(exists, 1);
249
279
  }
250
280
  }
251
- prepare(entity) {
252
- /* v8 ignore next 3 */
253
- if ('schema' in entity && entity.schema instanceof EntitySchema) {
254
- return entity.schema;
255
- }
281
+ getSchema(entity) {
256
282
  if (EntitySchema.REGISTRY.has(entity)) {
257
- return EntitySchema.REGISTRY.get(entity);
283
+ entity = EntitySchema.REGISTRY.get(entity);
258
284
  }
259
- return entity;
260
- }
261
- getSchema(entity, filepath) {
262
285
  if (entity instanceof EntitySchema) {
263
- if (filepath) {
264
- // initialize global metadata for given entity
265
- MetadataStorage.getMetadata(entity.meta.className, filepath);
266
- }
267
286
  const meta = Utils.copy(entity.meta, false);
268
287
  return EntitySchema.fromMetadata(meta);
269
288
  }
270
289
  const path = entity[MetadataStorage.PATH_SYMBOL];
271
290
  if (path) {
272
291
  const meta = Utils.copy(MetadataStorage.getMetadata(entity.name, path), false);
273
- meta.path = Utils.relativePath(path, this.config.get('baseDir'));
274
- this.metadata.set(entity.name, meta);
292
+ meta.path = path;
293
+ this.metadata.set(entity, meta);
275
294
  }
276
- const exists = this.metadata.has(entity.name);
277
- const meta = this.metadata.get(entity.name, true);
295
+ const exists = this.metadata.has(entity);
296
+ const meta = this.metadata.get(entity, true);
278
297
  meta.abstract ??= !(exists && meta.name);
279
298
  const schema = EntitySchema.fromMetadata(meta);
280
299
  schema.setClass(entity);
281
300
  return schema;
282
301
  }
283
- discoverEntity(schema, path) {
284
- this.logger.log('discovery', `- processing entity ${colors.cyan(schema.meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
302
+ getRootEntity(meta) {
303
+ const base = meta.extends && this.metadata.find(meta.extends);
304
+ if (!base || base === meta) { // make sure we do not fall into infinite loop
305
+ return meta;
306
+ }
307
+ const root = this.getRootEntity(base);
308
+ // For STI or TPT, use the root entity.
309
+ // Check both `inheritanceType` (set during discovery) and raw `inheritance` option (set before discovery).
310
+ if (root.discriminatorColumn || root.inheritanceType || root.inheritance === 'tpt') {
311
+ return root;
312
+ }
313
+ return meta;
314
+ }
315
+ discoverEntity(schema) {
285
316
  const meta = schema.meta;
286
- const root = Utils.getRootEntity(this.metadata, meta);
287
- schema.meta.path = Utils.relativePath(path || meta.path, this.config.get('baseDir'));
288
- const cache = this.metadataProvider.useCache() && meta.path && this.cache.get(meta.className + extname(meta.path));
317
+ const path = meta.path;
318
+ this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
319
+ const root = this.getRootEntity(meta);
320
+ schema.meta.path = meta.path;
321
+ const cache = this.metadataProvider.getCachedMetadata(meta, root);
289
322
  if (cache) {
290
323
  this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
291
- this.metadataProvider.loadFromCache(meta, cache);
292
- meta.root = root;
293
324
  this.discovered.push(meta);
294
325
  return;
295
326
  }
@@ -298,50 +329,18 @@ export class MetadataDiscovery {
298
329
  this.inferDefaultValue(meta, prop);
299
330
  }
300
331
  // if the definition is using EntitySchema we still want it to go through the metadata provider to validate no types are missing
301
- this.metadataProvider.loadEntityMetadata(meta, meta.className);
302
- if (!meta.collection && meta.name) {
332
+ this.metadataProvider.loadEntityMetadata(meta);
333
+ if (!meta.tableName && meta.name) {
303
334
  const entityName = root.discriminatorColumn ? root.name : meta.name;
304
- meta.collection = this.namingStrategy.classToTableName(entityName);
335
+ meta.tableName = this.namingStrategy.classToTableName(entityName);
305
336
  }
306
- delete meta.root; // to allow caching (as root can contain cycles)
307
- this.saveToCache(meta);
337
+ this.metadataProvider.saveToCache(meta);
308
338
  meta.root = root;
309
339
  this.discovered.push(meta);
310
340
  }
311
- saveToCache(meta) {
312
- if (!this.metadataProvider.useCache()) {
313
- return;
314
- }
315
- const copy = Utils.copy(meta, false);
316
- for (const prop of copy.props) {
317
- if (Type.isMappedType(prop.type)) {
318
- Reflect.deleteProperty(prop, 'type');
319
- Reflect.deleteProperty(prop, 'customType');
320
- }
321
- if (prop.default) {
322
- const raw = RawQueryFragment.getKnownFragment(prop.default);
323
- if (raw) {
324
- prop.defaultRaw ??= this.platform.formatQuery(raw.sql, raw.params);
325
- Reflect.deleteProperty(prop, 'default');
326
- }
327
- }
328
- Reflect.deleteProperty(prop, 'targetMeta');
329
- }
330
- [
331
- 'prototype', 'props', 'referencingProperties', 'propertyOrder', 'relations',
332
- 'concurrencyCheckKeys', 'checks',
333
- ].forEach(key => delete copy[key]);
334
- // base entity without properties might not have path, but nothing to cache there
335
- if (meta.path) {
336
- this.cache.set(meta.className + extname(meta.path), copy, meta.path);
337
- }
338
- }
339
341
  initNullability(prop) {
340
- if (prop.kind === ReferenceKind.MANY_TO_ONE) {
341
- return Utils.defaultValue(prop, 'nullable', prop.optional || prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));
342
- }
343
342
  if (prop.kind === ReferenceKind.ONE_TO_ONE) {
344
- return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner || prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));
343
+ return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
345
344
  }
346
345
  return Utils.defaultValue(prop, 'nullable', prop.optional);
347
346
  }
@@ -365,6 +364,12 @@ export class MetadataDiscovery {
365
364
  if (!prop.joinColumns || !prop.columnTypes || prop.ownColumns || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
366
365
  continue;
367
366
  }
367
+ // For polymorphic relations, ownColumns should include all fieldNames
368
+ // (discriminator + ID columns) since they are all owned by this relation
369
+ if (prop.polymorphic) {
370
+ prop.ownColumns = prop.fieldNames;
371
+ continue;
372
+ }
368
373
  if (prop.joinColumns.length > 1) {
369
374
  prop.ownColumns = prop.joinColumns.filter(col => {
370
375
  return !meta.props.find(p => p.name !== prop.name && (!p.fieldNames || p.fieldNames.includes(col)));
@@ -376,7 +381,7 @@ export class MetadataDiscovery {
376
381
  if (prop.joinColumns.length !== prop.columnTypes.length) {
377
382
  prop.columnTypes = prop.joinColumns.flatMap(field => {
378
383
  const matched = meta.props.find(p => p.fieldNames?.includes(field));
379
- /* v8 ignore next 3 */
384
+ /* v8 ignore next */
380
385
  if (!matched) {
381
386
  throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
382
387
  }
@@ -395,30 +400,30 @@ export class MetadataDiscovery {
395
400
  if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
396
401
  prop.fieldNames = [this.namingStrategy.propertyToColumnName(prop.name, object)];
397
402
  }
398
- else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
403
+ else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
399
404
  prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
400
405
  }
401
406
  else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
402
407
  prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
403
408
  }
404
409
  }
405
- initManyToOneFieldName(prop, name) {
406
- const meta2 = this.metadata.get(prop.type);
410
+ initManyToOneFieldName(prop, name, tableName) {
411
+ const meta2 = prop.targetMeta;
407
412
  const ret = [];
408
413
  for (const primaryKey of meta2.primaryKeys) {
409
414
  this.initFieldName(meta2.properties[primaryKey]);
410
415
  for (const fieldName of meta2.properties[primaryKey].fieldNames) {
411
- ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
416
+ ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
412
417
  }
413
418
  }
414
419
  return ret;
415
420
  }
416
421
  initManyToManyFieldName(prop, name) {
417
- const meta2 = this.metadata.get(prop.type);
422
+ const meta2 = prop.targetMeta;
418
423
  return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
419
424
  }
420
425
  initManyToManyFields(meta, prop) {
421
- const meta2 = this.metadata.get(prop.type);
426
+ const meta2 = prop.targetMeta;
422
427
  Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
423
428
  const pivotMeta = this.metadata.find(prop.pivotEntity);
424
429
  const props = Object.values(pivotMeta?.properties ?? {});
@@ -439,35 +444,77 @@ export class MetadataDiscovery {
439
444
  prop.inverseJoinColumns ??= second.fieldNames;
440
445
  }
441
446
  if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
442
- prop.pivotTable = this.namingStrategy.joinTableName(meta.tableName, meta2.tableName, prop.name);
447
+ prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
443
448
  }
444
449
  if (prop.mappedBy) {
445
450
  const prop2 = meta2.properties[prop.mappedBy];
446
451
  this.initManyToManyFields(meta2, prop2);
447
452
  prop.pivotTable = prop2.pivotTable;
448
- prop.pivotEntity = prop2.pivotEntity ?? prop2.pivotTable;
453
+ prop.pivotEntity = prop2.pivotEntity;
449
454
  prop.fixedOrder = prop2.fixedOrder;
450
455
  prop.fixedOrderColumn = prop2.fixedOrderColumn;
451
456
  prop.joinColumns = prop2.inverseJoinColumns;
452
457
  prop.inverseJoinColumns = prop2.joinColumns;
458
+ prop.polymorphic = prop2.polymorphic;
459
+ prop.discriminator = prop2.discriminator;
460
+ prop.discriminatorColumn = prop2.discriminatorColumn;
461
+ prop.discriminatorValue = prop2.discriminatorValue;
453
462
  }
454
463
  prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
455
- prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK));
456
- prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
464
+ // For polymorphic M:N, use discriminator base name for FK column (e.g., taggable_id instead of post_id)
465
+ if (prop.polymorphic && prop.discriminator) {
466
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
467
+ }
468
+ else {
469
+ const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
470
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
471
+ }
472
+ const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
473
+ prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
474
+ }
475
+ isExplicitTableName(meta) {
476
+ return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
457
477
  }
458
478
  initManyToOneFields(prop) {
459
- const meta2 = this.metadata.get(prop.type);
460
- const fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
461
- Utils.defaultValue(prop, 'referencedTableName', meta2.collection);
479
+ if (prop.polymorphic && prop.polymorphTargets) {
480
+ const fieldNames1 = prop.targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
481
+ const idColumns = fieldNames1.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, fieldNames1.length > 1));
482
+ prop.fieldNames ??= [prop.discriminatorColumn, ...idColumns];
483
+ prop.joinColumns ??= idColumns;
484
+ prop.referencedColumnNames ??= fieldNames1;
485
+ prop.referencedTableName ??= prop.targetMeta.tableName;
486
+ return;
487
+ }
488
+ const meta2 = prop.targetMeta;
489
+ let fieldNames;
490
+ // If targetKey is specified, use that property's field names instead of PKs
491
+ if (prop.targetKey) {
492
+ const targetProp = meta2.properties[prop.targetKey];
493
+ fieldNames = targetProp.fieldNames;
494
+ }
495
+ else {
496
+ fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
497
+ }
498
+ Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
462
499
  if (!prop.joinColumns) {
463
500
  prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
464
501
  }
465
502
  if (!prop.referencedColumnNames) {
466
503
  prop.referencedColumnNames = fieldNames;
467
504
  }
505
+ // Relations to composite PK targets need cascade update by default,
506
+ // since composite PKs are more likely to have mutable components
507
+ if (meta2.compositePK) {
508
+ prop.updateRule ??= 'cascade';
509
+ }
510
+ // Nullable relations default to 'set null' on delete - when the referenced
511
+ // entity is deleted, set the FK to null rather than failing
512
+ if (prop.nullable) {
513
+ prop.deleteRule ??= 'set null';
514
+ }
468
515
  }
469
516
  initOneToManyFields(prop) {
470
- const meta2 = this.metadata.get(prop.type);
517
+ const meta2 = prop.targetMeta;
471
518
  if (!prop.joinColumns) {
472
519
  prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
473
520
  }
@@ -480,12 +527,17 @@ export class MetadataDiscovery {
480
527
  const pks = Object.values(meta.properties).filter(prop => prop.primary);
481
528
  meta.primaryKeys = pks.map(prop => prop.name);
482
529
  meta.compositePK = pks.length > 1;
483
- // FK used as PK, we need to cascade
484
- if (pks.length === 1 && pks[0].kind !== ReferenceKind.SCALAR) {
485
- pks[0].deleteRule ??= 'cascade';
530
+ // FK used as PK, we need to cascade - applies to both single FK-as-PK
531
+ // and composite PKs where all PKs are FKs (e.g., pivot-like entities)
532
+ const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
533
+ if (fkPks.length > 0 && fkPks.length === pks.length) {
534
+ for (const pk of fkPks) {
535
+ pk.deleteRule ??= 'cascade';
536
+ pk.updateRule ??= 'cascade';
537
+ }
486
538
  }
487
539
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
488
- this.validator.validateEntityDefinition(this.metadata, meta.className, this.config.get('discovery'));
540
+ this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
489
541
  for (const prop of Object.values(meta.properties)) {
490
542
  this.initNullability(prop);
491
543
  this.applyNamingStrategy(meta, prop);
@@ -498,15 +550,21 @@ export class MetadataDiscovery {
498
550
  }
499
551
  this.initOwnColumns(meta);
500
552
  meta.simplePK = pks.length === 1 && pks[0].kind === ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
501
- meta.serializedPrimaryKey = this.platform.getSerializedPrimaryKeyField(meta.primaryKeys[0]);
502
- const serializedPKProp = meta.properties[meta.serializedPrimaryKey];
503
- if (serializedPKProp && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
504
- serializedPKProp.persist = false;
553
+ meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
554
+ if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
555
+ meta.properties[meta.serializedPrimaryKey].persist ??= false;
505
556
  }
506
557
  if (this.platform.usesPivotTable()) {
507
558
  return Object.values(meta.properties)
508
559
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
509
- .map(prop => this.definePivotTableEntity(meta, prop));
560
+ .map(prop => {
561
+ const pivotMeta = this.definePivotTableEntity(meta, prop);
562
+ prop.pivotEntity = pivotMeta.class;
563
+ if (prop.inversedBy) {
564
+ prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
565
+ }
566
+ return pivotMeta;
567
+ });
510
568
  }
511
569
  return [];
512
570
  }
@@ -523,8 +581,11 @@ export class MetadataDiscovery {
523
581
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
524
582
  const value = prop[type];
525
583
  if (value instanceof Function) {
526
- const meta2 = this.metadata.get(prop.type);
584
+ const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
527
585
  prop[type] = value(meta2.properties)?.name;
586
+ if (type === 'pivotEntity' && value) {
587
+ prop[type] = value(meta2.properties);
588
+ }
528
589
  if (prop[type] == null) {
529
590
  throw MetadataError.fromWrongReference(meta, prop, type);
530
591
  }
@@ -540,9 +601,9 @@ export class MetadataDiscovery {
540
601
  }
541
602
  else if (fks.length >= 2) {
542
603
  [first, second] = fks;
543
- /* v8 ignore next 3 */
544
604
  }
545
605
  else {
606
+ /* v8 ignore next */
546
607
  return [];
547
608
  }
548
609
  // wrong FK order, first FK needs to point to the owning side
@@ -556,7 +617,9 @@ export class MetadataDiscovery {
556
617
  return [first, second];
557
618
  }
558
619
  definePivotTableEntity(meta, prop) {
559
- const pivotMeta = this.metadata.find(prop.pivotEntity);
620
+ const pivotMeta = prop.pivotEntity
621
+ ? this.metadata.find(prop.pivotEntity)
622
+ : this.metadata.getByClassName(prop.pivotTable, false);
560
623
  // ensure inverse side exists so we can join it when populating via pivot tables
561
624
  if (!prop.inversedBy && prop.targetMeta) {
562
625
  const inverseName = `${meta.className}_${prop.name}__inverse`;
@@ -565,6 +628,8 @@ export class MetadataDiscovery {
565
628
  name: inverseName,
566
629
  kind: ReferenceKind.MANY_TO_MANY,
567
630
  type: meta.className,
631
+ target: meta.class,
632
+ targetMeta: meta,
568
633
  mappedBy: prop.name,
569
634
  pivotEntity: prop.pivotEntity,
570
635
  pivotTable: prop.pivotTable,
@@ -573,55 +638,190 @@ export class MetadataDiscovery {
573
638
  };
574
639
  this.applyNamingStrategy(prop.targetMeta, inverseProp);
575
640
  this.initCustomType(prop.targetMeta, inverseProp);
576
- this.initRelation(inverseProp);
577
641
  prop.targetMeta.properties[inverseName] = inverseProp;
578
642
  }
579
643
  if (pivotMeta) {
644
+ prop.pivotEntity = pivotMeta.class;
580
645
  this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
646
+ if (prop.polymorphic && prop.discriminatorValue) {
647
+ pivotMeta.polymorphicDiscriminatorMap ??= {};
648
+ pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
649
+ // For composite PK entities sharing a polymorphic pivot table,
650
+ // we need to add columns for each entity type's PKs
651
+ this.addPolymorphicPivotColumns(pivotMeta, meta, prop);
652
+ // Add virtual M:1 relation for this polymorphic owner (for join loading)
653
+ const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
654
+ if (!pivotMeta.properties[ownerRelationName]) {
655
+ pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
656
+ }
657
+ }
581
658
  return pivotMeta;
582
659
  }
583
- const exists = this.metadata.find(prop.pivotTable);
584
- if (exists) {
585
- prop.pivotEntity = exists.className;
586
- return exists;
587
- }
588
660
  let tableName = prop.pivotTable;
589
661
  let schemaName;
590
662
  if (prop.pivotTable.includes('.')) {
591
663
  [schemaName, tableName] = prop.pivotTable.split('.');
592
664
  }
593
665
  schemaName ??= meta.schema;
594
- const targetType = prop.targetMeta.className;
595
- const data = new EntityMetadata({
666
+ const targetMeta = prop.targetMeta;
667
+ const targetType = targetMeta.className;
668
+ const pivotMeta2 = new EntityMetadata({
596
669
  name: prop.pivotTable,
597
670
  className: prop.pivotTable,
598
671
  collection: tableName,
599
672
  schema: schemaName,
600
673
  pivotTable: true,
601
674
  });
602
- prop.pivotEntity = data.className;
675
+ prop.pivotEntity = pivotMeta2.class;
603
676
  if (prop.fixedOrder) {
604
- const primaryProp = this.defineFixedOrderProperty(prop, targetType);
605
- data.properties[primaryProp.name] = primaryProp;
677
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
678
+ pivotMeta2.properties[primaryProp.name] = primaryProp;
606
679
  }
607
680
  else {
608
- data.compositePK = true;
681
+ pivotMeta2.compositePK = true;
609
682
  }
610
683
  // handle self-referenced m:n with same default field names
611
684
  if (meta.className === targetType && prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
612
- prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(meta.className + '_1', name, meta.compositePK));
613
- prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(meta.className + '_2', name, meta.compositePK));
685
+ // use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
686
+ const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
687
+ prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
688
+ prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
614
689
  if (prop.inversedBy) {
615
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
690
+ const prop2 = targetMeta.properties[prop.inversedBy];
616
691
  prop2.inverseJoinColumns = prop.joinColumns;
617
692
  prop2.joinColumns = prop.inverseJoinColumns;
618
693
  }
694
+ // propagate updated joinColumns to all child entities that inherit this property (STI)
695
+ for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
696
+ const childProp = childMeta.properties[prop.name];
697
+ if (childProp) {
698
+ childProp.joinColumns = prop.joinColumns;
699
+ childProp.inverseJoinColumns = prop.inverseJoinColumns;
700
+ }
701
+ }
619
702
  }
620
- data.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.className, targetType + '_inverse', true, meta.className === targetType);
621
- data.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetType, meta.name + '_owner', false, meta.className === targetType);
622
- return this.metadata.set(data.className, EntitySchema.fromMetadata(data).init().meta);
703
+ // For polymorphic M:N, create discriminator column and polymorphic FK
704
+ if (prop.polymorphic && prop.discriminatorColumn) {
705
+ this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
706
+ }
707
+ else {
708
+ pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
709
+ pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
710
+ }
711
+ return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
623
712
  }
624
- defineFixedOrderProperty(prop, targetType) {
713
+ /**
714
+ * Create a scalar property for a pivot table column.
715
+ */
716
+ createPivotScalarProperty(name, columnTypes, fieldNames = [name], options = {}) {
717
+ return {
718
+ name,
719
+ fieldNames,
720
+ columnTypes,
721
+ type: options.type ?? 'number',
722
+ kind: ReferenceKind.SCALAR,
723
+ primary: options.primary ?? false,
724
+ nullable: options.nullable ?? true,
725
+ ...(options.persist !== undefined && { persist: options.persist }),
726
+ };
727
+ }
728
+ /**
729
+ * Get column types for an entity's primary keys, initializing them if needed.
730
+ */
731
+ getPrimaryKeyColumnTypes(meta) {
732
+ const columnTypes = [];
733
+ for (const pk of meta.primaryKeys) {
734
+ const pkProp = meta.properties[pk];
735
+ this.initCustomType(meta, pkProp);
736
+ this.initColumnType(pkProp);
737
+ columnTypes.push(...pkProp.columnTypes);
738
+ }
739
+ return columnTypes;
740
+ }
741
+ /**
742
+ * Add missing FK columns for a polymorphic entity to an existing pivot table.
743
+ */
744
+ addPolymorphicPivotColumns(pivotMeta, meta, prop) {
745
+ const existingFieldNames = new Set(Object.values(pivotMeta.properties).flatMap(p => p.fieldNames ?? []));
746
+ const columnTypes = this.getPrimaryKeyColumnTypes(meta);
747
+ for (let i = 0; i < prop.joinColumns.length; i++) {
748
+ const joinColumn = prop.joinColumns[i];
749
+ if (!existingFieldNames.has(joinColumn)) {
750
+ pivotMeta.properties[joinColumn] = this.createPivotScalarProperty(joinColumn, [columnTypes[i]]);
751
+ }
752
+ }
753
+ }
754
+ /**
755
+ * Define properties for a polymorphic pivot table.
756
+ */
757
+ definePolymorphicPivotProperties(pivotMeta, meta, prop, targetMeta) {
758
+ const discriminatorColumn = prop.discriminatorColumn;
759
+ const isCompositePK = meta.compositePK;
760
+ // For composite PK polymorphic M:N, we need fixedOrder (auto-increment PK)
761
+ if (isCompositePK && !prop.fixedOrder) {
762
+ prop.fixedOrder = true;
763
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
764
+ pivotMeta.properties[primaryProp.name] = primaryProp;
765
+ pivotMeta.compositePK = false;
766
+ }
767
+ const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: !isCompositePK, nullable: false });
768
+ this.initFieldName(discriminatorProp);
769
+ pivotMeta.properties[discriminatorColumn] = discriminatorProp;
770
+ const columnTypes = this.getPrimaryKeyColumnTypes(meta);
771
+ if (isCompositePK) {
772
+ // Create separate properties for each PK column (nullable for other entity types)
773
+ for (let i = 0; i < prop.joinColumns.length; i++) {
774
+ pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [columnTypes[i]]);
775
+ }
776
+ // Virtual property combining all columns (for compatibility)
777
+ pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, persist: false });
778
+ }
779
+ else {
780
+ pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, primary: true, nullable: false });
781
+ }
782
+ pivotMeta.properties[targetMeta.className + '_inverse'] = this.definePivotProperty(prop, targetMeta.className + '_inverse', targetMeta.class, prop.discriminator, false, false);
783
+ // Create virtual M:1 relation to the polymorphic owner for single-query join loading
784
+ const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
785
+ pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
786
+ pivotMeta.polymorphicDiscriminatorMap ??= {};
787
+ pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
788
+ }
789
+ /**
790
+ * Create a virtual M:1 relation from pivot to a polymorphic owner entity.
791
+ * This enables single-query join loading for inverse-side polymorphic M:N.
792
+ */
793
+ definePolymorphicOwnerRelation(prop, name, targetMeta) {
794
+ const ret = {
795
+ name,
796
+ type: targetMeta.className,
797
+ target: targetMeta.class,
798
+ kind: ReferenceKind.MANY_TO_ONE,
799
+ nullable: true,
800
+ owner: true,
801
+ primary: false,
802
+ createForeignKeyConstraint: false,
803
+ persist: false,
804
+ index: false,
805
+ };
806
+ ret.targetMeta = targetMeta;
807
+ ret.fieldNames = ret.joinColumns = ret.ownColumns = [...prop.joinColumns];
808
+ ret.referencedColumnNames = [];
809
+ ret.inverseJoinColumns = [];
810
+ for (const primaryKey of targetMeta.primaryKeys) {
811
+ const pkProp = targetMeta.properties[primaryKey];
812
+ ret.referencedColumnNames.push(...pkProp.fieldNames);
813
+ ret.inverseJoinColumns.push(...pkProp.fieldNames);
814
+ ret.length = pkProp.length;
815
+ ret.precision = pkProp.precision;
816
+ ret.scale = pkProp.scale;
817
+ }
818
+ const schema = targetMeta.schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
819
+ ret.referencedTableName = schema && schema !== '*' ? schema + '.' + targetMeta.tableName : targetMeta.tableName;
820
+ this.initColumnType(ret);
821
+ this.initRelation(ret);
822
+ return ret;
823
+ }
824
+ defineFixedOrderProperty(prop, targetMeta) {
625
825
  const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
626
826
  const primaryProp = {
627
827
  name: pk,
@@ -635,7 +835,7 @@ export class MetadataDiscovery {
635
835
  this.initColumnType(primaryProp);
636
836
  prop.fixedOrderColumn = pk;
637
837
  if (prop.inversedBy) {
638
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
838
+ const prop2 = targetMeta.properties[prop.inversedBy];
639
839
  prop2.fixedOrder = true;
640
840
  prop2.fixedOrderColumn = pk;
641
841
  }
@@ -644,7 +844,8 @@ export class MetadataDiscovery {
644
844
  definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
645
845
  const ret = {
646
846
  name,
647
- type,
847
+ type: Utils.className(type),
848
+ target: type,
648
849
  kind: ReferenceKind.MANY_TO_ONE,
649
850
  cascade: [Cascade.ALL],
650
851
  fixedOrder: prop.fixedOrder,
@@ -656,10 +857,9 @@ export class MetadataDiscovery {
656
857
  deleteRule: prop.deleteRule,
657
858
  createForeignKeyConstraint: prop.createForeignKeyConstraint,
658
859
  };
659
- if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
660
- ret.updateRule ??= 'no action';
661
- ret.deleteRule ??= 'no action';
662
- }
860
+ const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
861
+ ret.updateRule ??= defaultRule;
862
+ ret.deleteRule ??= defaultRule;
663
863
  const meta = this.metadata.get(type);
664
864
  ret.targetMeta = meta;
665
865
  ret.joinColumns = [];
@@ -702,7 +902,7 @@ export class MetadataDiscovery {
702
902
  Object.values(meta.properties)
703
903
  .filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
704
904
  .forEach(prop => {
705
- const meta2 = this.metadata.get(prop.type);
905
+ const meta2 = prop.targetMeta;
706
906
  const prop2 = meta2.properties[prop.mappedBy];
707
907
  if (prop2 && !prop2.inversedBy) {
708
908
  prop2.inversedBy = prop.name;
@@ -710,7 +910,7 @@ export class MetadataDiscovery {
710
910
  });
711
911
  }
712
912
  defineBaseEntityProperties(meta) {
713
- const base = meta.extends && this.metadata.get(Utils.className(meta.extends));
913
+ const base = meta.extends && this.metadata.get(meta.extends);
714
914
  if (!base || base === meta) { // make sure we do not fall into infinite loop
715
915
  return 0;
716
916
  }
@@ -741,12 +941,9 @@ export class MetadataDiscovery {
741
941
  Utils.keys(base.hooks).forEach(type => {
742
942
  meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
743
943
  });
744
- if (meta.constructorParams.length === 0 && base.constructorParams.length > 0) {
944
+ if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
745
945
  meta.constructorParams = [...base.constructorParams];
746
946
  }
747
- if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
748
- meta.toJsonParams = [...base.toJsonParams];
749
- }
750
947
  return order;
751
948
  }
752
949
  initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
@@ -803,6 +1000,54 @@ export class MetadataDiscovery {
803
1000
  polymorphs.forEach(meta => meta.root = embeddable);
804
1001
  }
805
1002
  }
1003
+ initPolymorphicRelation(meta, prop, discovered) {
1004
+ if (!prop.discriminator && !prop.discriminatorColumn && !prop.discriminatorMap && !Array.isArray(prop.target)) {
1005
+ return;
1006
+ }
1007
+ prop.polymorphic = true;
1008
+ prop.discriminator ??= prop.name;
1009
+ prop.discriminatorColumn ??= this.namingStrategy.discriminatorColumnName(prop.discriminator);
1010
+ prop.createForeignKeyConstraint = false;
1011
+ const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
1012
+ if (isToOne) {
1013
+ const types = prop.type.split(/ ?\| ?/);
1014
+ prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
1015
+ prop.targetMeta = prop.polymorphTargets[0];
1016
+ prop.referencedPKs = prop.targetMeta?.primaryKeys;
1017
+ }
1018
+ if (prop.discriminatorMap) {
1019
+ const normalizedMap = {};
1020
+ for (const [key, value] of Object.entries(prop.discriminatorMap)) {
1021
+ const targetMeta = this.metadata.getByClassName(value, false);
1022
+ if (!targetMeta) {
1023
+ throw MetadataError.fromUnknownEntity(value, `${meta.className}.${prop.name} discriminatorMap`);
1024
+ }
1025
+ normalizedMap[key] = targetMeta.class;
1026
+ if (targetMeta.class === meta.class) {
1027
+ prop.discriminatorValue = key;
1028
+ }
1029
+ }
1030
+ prop.discriminatorMap = normalizedMap;
1031
+ }
1032
+ else if (isToOne) {
1033
+ prop.discriminatorMap = {};
1034
+ const tableNameToTarget = new Map();
1035
+ for (const target of prop.polymorphTargets) {
1036
+ const existing = tableNameToTarget.get(target.tableName);
1037
+ if (existing) {
1038
+ throw MetadataError.incompatiblePolymorphicTargets(meta, prop, existing, target, `both use table '${target.tableName}'. Use separate properties instead of a single polymorphic relation.`);
1039
+ }
1040
+ tableNameToTarget.set(target.tableName, target);
1041
+ prop.discriminatorMap[target.tableName] = target.class;
1042
+ }
1043
+ }
1044
+ else {
1045
+ prop.discriminatorValue ??= meta.tableName;
1046
+ if (!prop.discriminatorMap) {
1047
+ prop.discriminatorMap = { [prop.discriminatorValue]: meta.class };
1048
+ }
1049
+ }
1050
+ }
806
1051
  initEmbeddables(meta, embeddedProp, visited = new Set()) {
807
1052
  if (embeddedProp.kind !== ReferenceKind.EMBEDDED || visited.has(embeddedProp)) {
808
1053
  return;
@@ -838,7 +1083,7 @@ export class MetadataDiscovery {
838
1083
  const glue = object ? '~' : '_';
839
1084
  for (const prop of Object.values(embeddable.properties)) {
840
1085
  const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
841
- meta.properties[name] = Utils.copy(prop, false);
1086
+ meta.properties[name] = Utils.copy(prop);
842
1087
  meta.properties[name].name = name;
843
1088
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
844
1089
  meta.propertyOrder.set(name, (order += 0.01));
@@ -875,10 +1120,12 @@ export class MetadataDiscovery {
875
1120
  path = [embeddedProp.fieldNames[0]];
876
1121
  }
877
1122
  this.initFieldName(prop, true);
1123
+ this.initRelation(prop);
878
1124
  path.push(prop.fieldNames[0]);
879
1125
  meta.properties[name].fieldNames = prop.fieldNames;
880
1126
  meta.properties[name].embeddedPath = path;
881
- const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), prop.runtimeType ?? prop.type, true));
1127
+ const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
1128
+ const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
882
1129
  meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
883
1130
  meta.properties[name].persist = false; // only virtual as we store the whole object
884
1131
  meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
@@ -906,7 +1153,7 @@ export class MetadataDiscovery {
906
1153
  }
907
1154
  initSingleTableInheritance(meta, metadata) {
908
1155
  if (meta.root !== meta && !meta.__processed) {
909
- meta.root = metadata.find(m => m.className === meta.root.className);
1156
+ meta.root = metadata.find(m => m.class === meta.root.class);
910
1157
  meta.root.__processed = true;
911
1158
  }
912
1159
  else {
@@ -915,17 +1162,24 @@ export class MetadataDiscovery {
915
1162
  if (!meta.root.discriminatorColumn) {
916
1163
  return;
917
1164
  }
918
- if (!meta.root.discriminatorMap) {
1165
+ meta.root.inheritanceType = 'sti';
1166
+ if (meta.root.discriminatorMap) {
1167
+ const map = meta.root.discriminatorMap;
1168
+ Object.keys(map)
1169
+ .filter(key => typeof map[key] === 'string')
1170
+ .forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
1171
+ }
1172
+ else {
919
1173
  meta.root.discriminatorMap = {};
920
1174
  const children = metadata
921
- .filter(m => m.root.className === meta.root.className && !m.abstract)
1175
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
922
1176
  .sort((a, b) => a.className.localeCompare(b.className));
923
1177
  for (const m of children) {
924
1178
  const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
925
- meta.root.discriminatorMap[name] = m.className;
1179
+ meta.root.discriminatorMap[name] = m.class;
926
1180
  }
927
1181
  }
928
- meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, className]) => className === meta.className)?.[0];
1182
+ meta.discriminatorValue = QueryHelper.findDiscriminatorValue(meta.root.discriminatorMap, meta.class);
929
1183
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
930
1184
  this.createDiscriminatorProperty(meta.root);
931
1185
  }
@@ -934,32 +1188,219 @@ export class MetadataDiscovery {
934
1188
  if (meta.root === meta) {
935
1189
  return;
936
1190
  }
937
- let i = 1;
938
1191
  Object.values(meta.properties).forEach(prop => {
939
1192
  const newProp = { ...prop };
940
- if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
1193
+ const rootProp = meta.root.properties[prop.name];
1194
+ if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
941
1195
  const name = newProp.name;
942
1196
  this.initFieldName(newProp, newProp.object);
943
- newProp.name = name + '_' + (i++);
1197
+ newProp.renamedFrom = name;
1198
+ newProp.name = `${name}_${meta._id}`;
944
1199
  meta.root.addProperty(newProp);
1200
+ this.initFieldName(prop, prop.object);
1201
+ // Track all field variants and map discriminator values to field names
1202
+ if (!rootProp.stiFieldNames) {
1203
+ this.initFieldName(rootProp, rootProp.object);
1204
+ rootProp.stiFieldNames = [...rootProp.fieldNames];
1205
+ rootProp.stiFieldNameMap = {};
1206
+ // Find which discriminator owns the original fieldNames
1207
+ for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
1208
+ const childMeta = this.metadata.find(childClass);
1209
+ if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
1210
+ rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
1211
+ break;
1212
+ }
1213
+ }
1214
+ }
1215
+ rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
1216
+ rootProp.stiFieldNames.push(...prop.fieldNames);
945
1217
  newProp.nullable = true;
946
1218
  newProp.name = name;
947
1219
  newProp.hydrate = false;
948
1220
  newProp.inherited = true;
949
1221
  return;
950
1222
  }
951
- if (prop.enum && prop.items && meta.root.properties[prop.name]?.items) {
952
- newProp.items = Utils.unique([...meta.root.properties[prop.name].items, ...prop.items]);
1223
+ if (prop.enum && prop.items && rootProp?.items) {
1224
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
953
1225
  }
954
1226
  newProp.nullable = true;
955
- newProp.inherited = true;
1227
+ newProp.inherited = !rootProp;
956
1228
  meta.root.addProperty(newProp);
957
1229
  });
958
- meta.collection = meta.root.collection;
1230
+ meta.tableName = meta.root.tableName;
959
1231
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
960
1232
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
961
1233
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
962
1234
  }
1235
+ /**
1236
+ * First pass of TPT initialization: sets up hierarchy relationships
1237
+ * (inheritanceType, tptParent, tptChildren) before properties have fieldNames.
1238
+ */
1239
+ initTPTRelationships(meta, metadata) {
1240
+ if (meta.root !== meta) {
1241
+ meta.root = metadata.find(m => m.class === meta.root.class);
1242
+ }
1243
+ const inheritance = meta.inheritance;
1244
+ if (inheritance === 'tpt' && meta.root === meta) {
1245
+ meta.inheritanceType = 'tpt';
1246
+ meta.tptChildren = [];
1247
+ }
1248
+ if (meta.root.inheritanceType !== 'tpt') {
1249
+ return;
1250
+ }
1251
+ const parent = this.getTPTParent(meta, metadata);
1252
+ if (parent) {
1253
+ meta.tptParent = parent;
1254
+ meta.inheritanceType = 'tpt';
1255
+ parent.tptChildren ??= [];
1256
+ if (!parent.tptChildren.includes(meta)) {
1257
+ parent.tptChildren.push(meta);
1258
+ }
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Second pass of TPT initialization: re-resolves metadata references after fieldNames
1263
+ * are set, syncs to registry metadata, and sets up discriminators.
1264
+ */
1265
+ finalizeTPTInheritance(meta, metadata) {
1266
+ if (meta.inheritanceType !== 'tpt') {
1267
+ return;
1268
+ }
1269
+ if (meta.tptParent) {
1270
+ meta.tptParent = metadata.find(m => m.class === meta.tptParent.class) ?? meta.tptParent;
1271
+ }
1272
+ if (meta.tptChildren) {
1273
+ meta.tptChildren = meta.tptChildren.map(child => metadata.find(m => m.class === child.class) ?? child);
1274
+ }
1275
+ const registryMeta = this.metadata.get(meta.class);
1276
+ if (registryMeta && registryMeta !== meta) {
1277
+ registryMeta.inheritanceType = meta.inheritanceType;
1278
+ registryMeta.tptParent = meta.tptParent ? this.metadata.get(meta.tptParent.class) : undefined;
1279
+ registryMeta.tptChildren = meta.tptChildren?.map(child => this.metadata.get(child.class));
1280
+ }
1281
+ this.initTPTDiscriminator(meta, metadata);
1282
+ }
1283
+ /**
1284
+ * Initialize TPT discriminator map and virtual discriminator property.
1285
+ * Unlike STI where the discriminator is a persisted column, TPT discriminator is computed
1286
+ * at query time using CASE WHEN expressions based on which child table has data.
1287
+ */
1288
+ initTPTDiscriminator(meta, metadata) {
1289
+ const allDescendants = this.collectAllTPTDescendants(meta, metadata);
1290
+ allDescendants.sort((a, b) => this.getTPTDepth(b) - this.getTPTDepth(a));
1291
+ meta.allTPTDescendants = allDescendants;
1292
+ if (meta.root !== meta) {
1293
+ return;
1294
+ }
1295
+ meta.root.discriminatorMap = {};
1296
+ for (const m of allDescendants) {
1297
+ const name = this.namingStrategy.classToTableName(m.className);
1298
+ meta.root.discriminatorMap[name] = m.class;
1299
+ m.discriminatorValue = name;
1300
+ }
1301
+ if (!meta.abstract) {
1302
+ const name = this.namingStrategy.classToTableName(meta.className);
1303
+ meta.root.discriminatorMap[name] = meta.class;
1304
+ meta.discriminatorValue = name;
1305
+ }
1306
+ // Virtual discriminator property - computed at query time via CASE WHEN, not persisted
1307
+ const discriminatorColumn = '__tpt_type';
1308
+ if (!meta.root.properties[discriminatorColumn]) {
1309
+ meta.root.addProperty({
1310
+ name: discriminatorColumn,
1311
+ type: 'string',
1312
+ kind: ReferenceKind.SCALAR,
1313
+ persist: false,
1314
+ userDefined: false,
1315
+ hidden: true,
1316
+ });
1317
+ }
1318
+ meta.root.tptDiscriminatorColumn = discriminatorColumn;
1319
+ }
1320
+ /**
1321
+ * Recursively collect all TPT descendants (children, grandchildren, etc.)
1322
+ */
1323
+ collectAllTPTDescendants(meta, metadata) {
1324
+ const descendants = [];
1325
+ const collect = (parent) => {
1326
+ for (const child of parent.tptChildren ?? []) {
1327
+ const resolved = metadata.find(m => m.class === child.class) ?? child;
1328
+ if (!resolved.abstract) {
1329
+ descendants.push(resolved);
1330
+ }
1331
+ collect(resolved);
1332
+ }
1333
+ };
1334
+ collect(meta);
1335
+ return descendants;
1336
+ }
1337
+ /**
1338
+ * Computes ownProps for TPT entities - only properties defined in THIS entity,
1339
+ * not inherited from parent. Also creates synthetic join properties for parent/child relationships.
1340
+ *
1341
+ * Called multiple times during discovery as metadata is progressively built.
1342
+ * Each pass overwrites earlier results to reflect the final state of properties.
1343
+ */
1344
+ computeTPTOwnProps(meta) {
1345
+ if (meta.inheritanceType !== 'tpt') {
1346
+ return;
1347
+ }
1348
+ const belongsToTable = (prop) => prop.persist !== false || prop.primary;
1349
+ // Use meta.properties (object) since meta.props (array) may not be populated yet
1350
+ const allProps = Object.values(meta.properties);
1351
+ if (!meta.tptParent) {
1352
+ meta.ownProps = allProps.filter(belongsToTable);
1353
+ return;
1354
+ }
1355
+ const parentPropNames = new Set(Object.values(meta.tptParent.properties).map(p => p.name));
1356
+ meta.ownProps = allProps.filter(prop => !parentPropNames.has(prop.name) && belongsToTable(prop));
1357
+ // Create synthetic join properties for the parent-child relationship
1358
+ const childFieldNames = meta.getPrimaryProps().flatMap(p => p.fieldNames);
1359
+ const parentFieldNames = meta.tptParent.getPrimaryProps().flatMap(p => p.fieldNames);
1360
+ meta.tptParentProp = {
1361
+ name: '[tpt:parent]',
1362
+ kind: ReferenceKind.MANY_TO_ONE,
1363
+ targetMeta: meta.tptParent,
1364
+ fieldNames: childFieldNames,
1365
+ referencedColumnNames: parentFieldNames,
1366
+ persist: false,
1367
+ };
1368
+ meta.tptInverseProp = {
1369
+ name: '[tpt:child]',
1370
+ kind: ReferenceKind.ONE_TO_ONE,
1371
+ owner: true,
1372
+ targetMeta: meta,
1373
+ fieldNames: parentFieldNames,
1374
+ joinColumns: parentFieldNames,
1375
+ referencedColumnNames: childFieldNames,
1376
+ persist: false,
1377
+ };
1378
+ }
1379
+ /** Returns the depth of a TPT entity in its hierarchy (0 for root). */
1380
+ getTPTDepth(meta) {
1381
+ let depth = 0;
1382
+ let current = meta;
1383
+ while (current.tptParent) {
1384
+ depth++;
1385
+ current = current.tptParent;
1386
+ }
1387
+ return depth;
1388
+ }
1389
+ /**
1390
+ * Find the direct TPT parent entity for the given entity.
1391
+ */
1392
+ getTPTParent(meta, metadata) {
1393
+ if (meta.root === meta) {
1394
+ return undefined;
1395
+ }
1396
+ return metadata.find(m => {
1397
+ const ext = meta.extends;
1398
+ if (ext instanceof EntitySchema) {
1399
+ return m.class === ext.meta.class || m.className === ext.meta.className;
1400
+ }
1401
+ return m.class === ext || m.className === Utils.className(ext);
1402
+ });
1403
+ }
963
1404
  createDiscriminatorProperty(meta) {
964
1405
  meta.addProperty({
965
1406
  name: meta.discriminatorColumn,
@@ -976,18 +1417,28 @@ export class MetadataDiscovery {
976
1417
  pks[0].autoincrement ??= true;
977
1418
  }
978
1419
  }
1420
+ createSchemaTable(meta) {
1421
+ const qualifiedName = meta.schema ? `${meta.schema}.${meta.tableName}` : meta.tableName;
1422
+ return {
1423
+ name: meta.tableName,
1424
+ schema: meta.schema,
1425
+ qualifiedName,
1426
+ toString: () => qualifiedName,
1427
+ };
1428
+ }
979
1429
  initCheckConstraints(meta) {
980
- const map = meta.createColumnMappingObject();
1430
+ const columns = meta.createSchemaColumnMappingObject();
1431
+ const table = this.createSchemaTable(meta);
981
1432
  for (const check of meta.checks) {
982
- const columns = check.property ? meta.properties[check.property].fieldNames : [];
983
- check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
1433
+ const fieldNames = check.property ? meta.properties[check.property].fieldNames : [];
1434
+ check.name ??= this.namingStrategy.indexName(meta.tableName, fieldNames, 'check');
984
1435
  if (check.expression instanceof Function) {
985
- check.expression = check.expression(map);
1436
+ check.expression = check.expression(columns, table);
986
1437
  }
987
1438
  }
988
1439
  if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
989
1440
  for (const prop of meta.props) {
990
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => Utils.isString(item))) {
1441
+ if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
991
1442
  this.initFieldName(prop);
992
1443
  meta.checks.push({
993
1444
  name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
@@ -1012,16 +1463,16 @@ export class MetadataDiscovery {
1012
1463
  }
1013
1464
  return;
1014
1465
  }
1015
- const map = meta.createColumnMappingObject();
1016
1466
  if (prop.generated instanceof Function) {
1017
- prop.generated = prop.generated(map);
1467
+ const columns = meta.createSchemaColumnMappingObject();
1468
+ prop.generated = prop.generated(columns, this.createSchemaTable(meta));
1018
1469
  }
1019
1470
  }
1020
1471
  getDefaultVersionValue(meta, prop) {
1021
1472
  if (typeof prop.defaultRaw !== 'undefined') {
1022
1473
  return prop.defaultRaw;
1023
1474
  }
1024
- /* v8 ignore next 3 */
1475
+ /* v8 ignore next */
1025
1476
  if (prop.default != null) {
1026
1477
  return '' + this.platform.quoteVersionValue(prop.default, prop);
1027
1478
  }
@@ -1034,10 +1485,6 @@ export class MetadataDiscovery {
1034
1485
  return '1';
1035
1486
  }
1036
1487
  inferDefaultValue(meta, prop) {
1037
- /* v8 ignore next 3 */
1038
- if (!meta.class) {
1039
- return;
1040
- }
1041
1488
  try {
1042
1489
  // try to create two entity instances to detect the value is stable
1043
1490
  const now = Date.now();
@@ -1065,7 +1512,7 @@ export class MetadataDiscovery {
1065
1512
  return;
1066
1513
  }
1067
1514
  let val = prop.default;
1068
- const raw = RawQueryFragment.getKnownFragment(val);
1515
+ const raw = Raw.getKnownFragment(val);
1069
1516
  if (raw) {
1070
1517
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1071
1518
  return;
@@ -1112,51 +1559,61 @@ export class MetadataDiscovery {
1112
1559
  }
1113
1560
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1114
1561
  if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
1115
- prop.customType = new prop.type();
1116
- prop.type = prop.customType.constructor.name;
1562
+ // if the type is an ORM defined mapped type without `ensureComparable: true`,
1563
+ // we use just the type name, to have more performant hydration code
1564
+ const type = Utils.keys(t).find(type => {
1565
+ return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
1566
+ });
1567
+ if (type) {
1568
+ prop.type = type === 'datetime' ? 'Date' : type;
1569
+ }
1570
+ else {
1571
+ prop.customType = new prop.type();
1572
+ prop.type = prop.customType.constructor.name;
1573
+ }
1117
1574
  }
1118
1575
  if (simple) {
1119
1576
  return;
1120
1577
  }
1121
1578
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1122
- prop.customType = new JsonType();
1579
+ prop.customType = new t.json();
1123
1580
  }
1124
1581
  if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1125
- prop.customType = new JsonType();
1582
+ prop.customType = new t.json();
1126
1583
  }
1127
1584
  if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1128
- prop.customType = new JsonType();
1585
+ prop.customType = new t.json();
1129
1586
  }
1130
1587
  if (!prop.customType && prop.array && prop.items) {
1131
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1588
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1132
1589
  }
1133
1590
  const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1134
1591
  if (objectEmbeddable && !prop.customType && isArray) {
1135
- prop.customType = new JsonType();
1592
+ prop.customType = new t.json();
1136
1593
  }
1137
1594
  // for number arrays we make sure to convert the items to numbers
1138
1595
  if (!prop.customType && prop.type === 'number[]') {
1139
- prop.customType = new ArrayType(i => +i);
1596
+ prop.customType = new t.array(i => +i);
1140
1597
  }
1141
1598
  // `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
1142
1599
  if (!prop.customType && isArray) {
1143
- prop.customType = new ArrayType();
1600
+ prop.customType = new t.array();
1144
1601
  }
1145
1602
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1146
- prop.customType = new BlobType();
1603
+ prop.customType = new t.blob();
1147
1604
  }
1148
1605
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1149
- prop.customType = new Uint8ArrayType();
1606
+ prop.customType = new t.uint8array();
1150
1607
  }
1151
1608
  const mappedType = this.getMappedType(prop);
1152
1609
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1153
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1610
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1154
1611
  .filter(type => mappedType instanceof type)
1155
- .forEach(type => prop.customType = new type());
1612
+ .forEach((type) => prop.customType = new type());
1156
1613
  }
1157
1614
  if (prop.customType && !prop.columnTypes) {
1158
1615
  const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1159
- if (prop.customType.compareAsType() === 'any' && ![JsonType].some(t => prop.customType instanceof t)) {
1616
+ if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
1160
1617
  prop.runtimeType ??= mappedType.runtimeType;
1161
1618
  }
1162
1619
  else {
@@ -1176,16 +1633,16 @@ export class MetadataDiscovery {
1176
1633
  prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1177
1634
  prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1178
1635
  prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1179
- if (prop.customType instanceof BigIntType && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1636
+ if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1180
1637
  prop.customType.mode = prop.runtimeType.toLowerCase();
1181
1638
  }
1182
1639
  }
1183
1640
  if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1184
1641
  prop.type = prop.customType.name;
1185
1642
  }
1186
- if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && this.metadata.get(prop.type).compositePK) {
1643
+ if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.polymorphic && prop.targetMeta.compositePK) {
1187
1644
  prop.customTypes = [];
1188
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1645
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1189
1646
  if (pk.customType) {
1190
1647
  prop.customTypes.push(pk.customType);
1191
1648
  prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
@@ -1197,7 +1654,7 @@ export class MetadataDiscovery {
1197
1654
  }
1198
1655
  }
1199
1656
  }
1200
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1657
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1201
1658
  if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1202
1659
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1203
1660
  }
@@ -1212,22 +1669,36 @@ export class MetadataDiscovery {
1212
1669
  }
1213
1670
  }
1214
1671
  initRelation(prop) {
1215
- if (prop.kind === ReferenceKind.SCALAR) {
1672
+ if (prop.kind === ReferenceKind.SCALAR || prop.polymorphTargets) {
1216
1673
  return;
1217
1674
  }
1218
- const meta2 = this.discovered.find(m => m.className === prop.type);
1219
- prop.referencedPKs = meta2.primaryKeys;
1675
+ // when the target is a polymorphic embedded entity, `prop.target` is an array of classes, we need to get the metadata by the type name instead
1676
+ const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
1677
+ // If targetKey is specified, use that property instead of PKs for referencedPKs
1678
+ prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
1220
1679
  prop.targetMeta = meta2;
1680
+ if (meta2.view) {
1681
+ prop.createForeignKeyConstraint = false;
1682
+ }
1683
+ // Auto-generate formula for persist: false relations, but only for single-column FKs
1684
+ // Composite FK relations need standard JOIN conditions, not formula-based
1221
1685
  if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
1222
- prop.formula = a => `${a}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1686
+ this.initFieldName(prop);
1687
+ if (prop.fieldNames?.length === 1) {
1688
+ prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1689
+ }
1223
1690
  }
1224
1691
  }
1225
1692
  initColumnType(prop) {
1226
1693
  this.initUnsigned(prop);
1227
- this.metadata.find(prop.type)?.getPrimaryProps().map(pk => {
1228
- prop.length ??= pk.length;
1229
- prop.precision ??= pk.precision;
1230
- prop.scale ??= pk.scale;
1694
+ // Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
1695
+ const targetProps = prop.targetMeta
1696
+ ? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
1697
+ : [];
1698
+ targetProps.map(targetProp => {
1699
+ prop.length ??= targetProp.length;
1700
+ prop.precision ??= targetProp.precision;
1701
+ prop.scale ??= targetProp.scale;
1231
1702
  });
1232
1703
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1233
1704
  delete prop.type;
@@ -1240,8 +1711,7 @@ export class MetadataDiscovery {
1240
1711
  if (prop.kind === ReferenceKind.SCALAR) {
1241
1712
  const mappedType = this.getMappedType(prop);
1242
1713
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1243
- if (mappedType instanceof UnknownType
1244
- && !prop.columnTypes
1714
+ if (mappedType instanceof t.unknown
1245
1715
  // it could be a runtime type from reflect-metadata
1246
1716
  && !SCALAR_TYPES.includes(prop.type)
1247
1717
  // or it might be inferred via ts-morph to some generic type alias
@@ -1254,23 +1724,31 @@ export class MetadataDiscovery {
1254
1724
  }
1255
1725
  return;
1256
1726
  }
1257
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1727
+ /* v8 ignore next */
1728
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1258
1729
  prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1259
1730
  return;
1260
1731
  }
1261
- const targetMeta = this.metadata.get(prop.type);
1732
+ const targetMeta = prop.targetMeta;
1262
1733
  prop.columnTypes = [];
1263
- for (const pk of targetMeta.getPrimaryProps()) {
1264
- this.initCustomType(targetMeta, pk);
1265
- this.initColumnType(pk);
1266
- const mappedType = this.getMappedType(pk);
1267
- let columnTypes = pk.columnTypes;
1268
- if (pk.autoincrement) {
1269
- columnTypes = [mappedType.getColumnType({ ...pk, autoincrement: false }, this.platform)];
1734
+ // Use targetKey property if specified, otherwise use primary key properties
1735
+ const referencedProps = prop.targetKey
1736
+ ? [targetMeta.properties[prop.targetKey]]
1737
+ : targetMeta.getPrimaryProps();
1738
+ if (prop.polymorphic && prop.polymorphTargets) {
1739
+ prop.columnTypes.push(this.platform.getVarcharTypeDeclarationSQL(prop));
1740
+ }
1741
+ for (const referencedProp of referencedProps) {
1742
+ this.initCustomType(targetMeta, referencedProp);
1743
+ this.initColumnType(referencedProp);
1744
+ const mappedType = this.getMappedType(referencedProp);
1745
+ let columnTypes = referencedProp.columnTypes;
1746
+ if (referencedProp.autoincrement) {
1747
+ columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
1270
1748
  }
1271
1749
  prop.columnTypes.push(...columnTypes);
1272
- if (!targetMeta.compositePK) {
1273
- prop.customType = pk.customType;
1750
+ if (!targetMeta.compositePK || prop.targetKey) {
1751
+ prop.customType = referencedProp.customType;
1274
1752
  }
1275
1753
  }
1276
1754
  }
@@ -1284,7 +1762,7 @@ export class MetadataDiscovery {
1284
1762
  t = 'enum';
1285
1763
  }
1286
1764
  else if (prop.enum) {
1287
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1765
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1288
1766
  }
1289
1767
  if (t === 'Date') {
1290
1768
  t = 'datetime';
@@ -1308,8 +1786,7 @@ export class MetadataDiscovery {
1308
1786
  return;
1309
1787
  }
1310
1788
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1311
- const meta2 = this.metadata.get(prop.type);
1312
- prop.unsigned = meta2.getPrimaryProps().some(pk => {
1789
+ prop.unsigned = prop.targetMeta.getPrimaryProps().some(pk => {
1313
1790
  this.initUnsigned(pk);
1314
1791
  return pk.unsigned;
1315
1792
  });
@@ -1323,30 +1800,6 @@ export class MetadataDiscovery {
1323
1800
  prop.index ??= true;
1324
1801
  }
1325
1802
  }
1326
- async getEntityClassOrSchema(path, name) {
1327
- const exports = await Utils.dynamicImport(path);
1328
- const targets = Object.values(exports)
1329
- .filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
1330
- // ignore class implementations that are linked from an EntitySchema
1331
- for (const item of targets) {
1332
- if (item instanceof EntitySchema) {
1333
- targets.forEach((item2, idx) => {
1334
- if (item.meta.class === item2) {
1335
- targets.splice(idx, 1);
1336
- }
1337
- });
1338
- }
1339
- }
1340
- if (targets.length > 0) {
1341
- return targets;
1342
- }
1343
- const target = exports.default ?? exports[name];
1344
- /* v8 ignore next 3 */
1345
- if (!target) {
1346
- throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
1347
- }
1348
- return [target];
1349
- }
1350
1803
  shouldForceConstructorUsage(meta) {
1351
1804
  const forceConstructor = this.config.get('forceEntityConstructor');
1352
1805
  if (Array.isArray(forceConstructor)) {