@mikro-orm/core 7.0.0-dev.17 → 7.0.0-dev.171

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 (211) hide show
  1. package/EntityManager.d.ts +99 -57
  2. package/EntityManager.js +300 -274
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +103 -143
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +8 -7
  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 +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +80 -35
  16. package/drivers/IDatabaseDriver.d.ts +44 -17
  17. package/entity/BaseEntity.d.ts +2 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +94 -29
  20. package/entity/Collection.js +434 -97
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +13 -1
  24. package/entity/EntityFactory.js +84 -53
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +35 -15
  27. package/entity/EntityLoader.d.ts +6 -6
  28. package/entity/EntityLoader.js +109 -72
  29. package/entity/EntityRepository.d.ts +24 -4
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/Reference.d.ts +6 -5
  32. package/entity/Reference.js +34 -9
  33. package/entity/WrappedEntity.d.ts +2 -7
  34. package/entity/WrappedEntity.js +3 -8
  35. package/entity/defineEntity.d.ts +580 -0
  36. package/entity/defineEntity.js +533 -0
  37. package/entity/index.d.ts +3 -2
  38. package/entity/index.js +3 -2
  39. package/entity/utils.d.ts +7 -0
  40. package/entity/utils.js +16 -4
  41. package/entity/validators.d.ts +11 -0
  42. package/entity/validators.js +65 -0
  43. package/enums.d.ts +21 -5
  44. package/enums.js +15 -1
  45. package/errors.d.ts +22 -9
  46. package/errors.js +56 -21
  47. package/events/EventManager.d.ts +2 -1
  48. package/events/EventManager.js +19 -11
  49. package/hydration/Hydrator.js +1 -2
  50. package/hydration/ObjectHydrator.d.ts +4 -4
  51. package/hydration/ObjectHydrator.js +52 -33
  52. package/index.d.ts +2 -2
  53. package/index.js +1 -2
  54. package/logging/DefaultLogger.d.ts +1 -1
  55. package/logging/DefaultLogger.js +1 -0
  56. package/logging/SimpleLogger.d.ts +1 -1
  57. package/logging/colors.d.ts +1 -1
  58. package/logging/colors.js +7 -6
  59. package/logging/index.d.ts +1 -0
  60. package/logging/index.js +1 -0
  61. package/logging/inspect.d.ts +2 -0
  62. package/logging/inspect.js +11 -0
  63. package/metadata/EntitySchema.d.ts +18 -22
  64. package/metadata/EntitySchema.js +59 -34
  65. package/metadata/MetadataDiscovery.d.ts +6 -10
  66. package/metadata/MetadataDiscovery.js +369 -326
  67. package/metadata/MetadataProvider.d.ts +11 -2
  68. package/metadata/MetadataProvider.js +46 -2
  69. package/metadata/MetadataStorage.d.ts +13 -11
  70. package/metadata/MetadataStorage.js +70 -37
  71. package/metadata/MetadataValidator.d.ts +17 -9
  72. package/metadata/MetadataValidator.js +93 -39
  73. package/metadata/discover-entities.d.ts +5 -0
  74. package/metadata/discover-entities.js +40 -0
  75. package/metadata/index.d.ts +1 -1
  76. package/metadata/index.js +1 -1
  77. package/metadata/types.d.ts +490 -0
  78. package/metadata/types.js +1 -0
  79. package/naming-strategy/AbstractNamingStrategy.d.ts +8 -4
  80. package/naming-strategy/AbstractNamingStrategy.js +8 -2
  81. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  82. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  83. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  84. package/naming-strategy/MongoNamingStrategy.js +6 -6
  85. package/naming-strategy/NamingStrategy.d.ts +14 -4
  86. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  87. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  88. package/not-supported.d.ts +2 -0
  89. package/not-supported.js +4 -0
  90. package/package.json +18 -11
  91. package/platforms/ExceptionConverter.js +1 -1
  92. package/platforms/Platform.d.ts +6 -13
  93. package/platforms/Platform.js +17 -43
  94. package/serialization/EntitySerializer.d.ts +5 -0
  95. package/serialization/EntitySerializer.js +47 -27
  96. package/serialization/EntityTransformer.js +28 -18
  97. package/serialization/SerializationContext.d.ts +6 -6
  98. package/serialization/SerializationContext.js +16 -13
  99. package/types/ArrayType.d.ts +1 -1
  100. package/types/ArrayType.js +2 -3
  101. package/types/BigIntType.d.ts +8 -6
  102. package/types/BigIntType.js +1 -1
  103. package/types/BlobType.d.ts +0 -1
  104. package/types/BlobType.js +0 -3
  105. package/types/BooleanType.d.ts +2 -1
  106. package/types/BooleanType.js +3 -0
  107. package/types/DecimalType.d.ts +6 -4
  108. package/types/DecimalType.js +3 -3
  109. package/types/DoubleType.js +2 -2
  110. package/types/EnumArrayType.js +1 -2
  111. package/types/JsonType.d.ts +1 -1
  112. package/types/JsonType.js +7 -2
  113. package/types/TinyIntType.js +1 -1
  114. package/types/Type.d.ts +2 -4
  115. package/types/Type.js +3 -3
  116. package/types/Uint8ArrayType.d.ts +0 -1
  117. package/types/Uint8ArrayType.js +1 -4
  118. package/types/index.d.ts +1 -1
  119. package/typings.d.ts +185 -116
  120. package/typings.js +59 -44
  121. package/unit-of-work/ChangeSet.d.ts +2 -6
  122. package/unit-of-work/ChangeSet.js +4 -5
  123. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  124. package/unit-of-work/ChangeSetComputer.js +26 -13
  125. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  126. package/unit-of-work/ChangeSetPersister.js +70 -34
  127. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  128. package/unit-of-work/CommitOrderCalculator.js +13 -13
  129. package/unit-of-work/IdentityMap.d.ts +12 -0
  130. package/unit-of-work/IdentityMap.js +39 -1
  131. package/unit-of-work/UnitOfWork.d.ts +23 -3
  132. package/unit-of-work/UnitOfWork.js +175 -98
  133. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  134. package/utils/AbstractSchemaGenerator.js +18 -16
  135. package/utils/AsyncContext.d.ts +6 -0
  136. package/utils/AsyncContext.js +42 -0
  137. package/utils/Configuration.d.ts +764 -207
  138. package/utils/Configuration.js +146 -190
  139. package/utils/ConfigurationLoader.d.ts +1 -54
  140. package/utils/ConfigurationLoader.js +1 -352
  141. package/utils/Cursor.d.ts +0 -3
  142. package/utils/Cursor.js +27 -11
  143. package/utils/DataloaderUtils.d.ts +15 -5
  144. package/utils/DataloaderUtils.js +64 -30
  145. package/utils/EntityComparator.d.ts +13 -9
  146. package/utils/EntityComparator.js +101 -42
  147. package/utils/QueryHelper.d.ts +14 -6
  148. package/utils/QueryHelper.js +87 -25
  149. package/utils/RawQueryFragment.d.ts +48 -25
  150. package/utils/RawQueryFragment.js +66 -70
  151. package/utils/RequestContext.js +2 -2
  152. package/utils/TransactionContext.js +2 -2
  153. package/utils/TransactionManager.d.ts +65 -0
  154. package/utils/TransactionManager.js +223 -0
  155. package/utils/Utils.d.ts +13 -126
  156. package/utils/Utils.js +100 -391
  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 +32 -0
  161. package/utils/fs-utils.js +178 -0
  162. package/utils/index.d.ts +2 -1
  163. package/utils/index.js +2 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +55 -4
  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 -18
  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 -40
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -32
  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 -26
  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 -13
  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 -116
  203. package/entity/ArrayCollection.js +0 -402
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/exports.d.ts +0 -24
  207. package/exports.js +0 -23
  208. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  209. package/metadata/ReflectMetadataProvider.js +0 -44
  210. package/utils/resolveContextProvider.d.ts +0 -10
  211. package/utils/resolveContextProvider.js +0 -28
@@ -1,22 +1,21 @@
1
- import { basename, extname } from 'node:path';
2
- import globby from 'globby';
3
1
  import { EntityMetadata, } from '../typings.js';
4
- import { Utils } from '../utils/Utils.js';
2
+ import { compareArrays, Utils } from '../utils/Utils.js';
5
3
  import { MetadataValidator } from './MetadataValidator.js';
4
+ import { MetadataProvider } from './MetadataProvider.js';
6
5
  import { MetadataStorage } from './MetadataStorage.js';
7
6
  import { EntitySchema } from './EntitySchema.js';
8
7
  import { Cascade, ReferenceKind } from '../enums.js';
9
8
  import { MetadataError } from '../errors.js';
10
- import { ArrayType, BigIntType, BlobType, DateType, DecimalType, DoubleType, EnumArrayType, IntervalType, JsonType, t, Type, Uint8ArrayType, UnknownType, } from '../types/index.js';
9
+ import { t, Type } from '../types/index.js';
11
10
  import { colors } from '../logging/colors.js';
12
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
11
+ import { raw, Raw } from '../utils/RawQueryFragment.js';
12
+ import { BaseEntity } from '../entity/BaseEntity.js';
13
13
  export class MetadataDiscovery {
14
14
  metadata;
15
15
  platform;
16
16
  config;
17
17
  namingStrategy;
18
18
  metadataProvider;
19
- cache;
20
19
  logger;
21
20
  schemaHelper;
22
21
  validator = new MetadataValidator();
@@ -27,13 +26,14 @@ export class MetadataDiscovery {
27
26
  this.config = config;
28
27
  this.namingStrategy = this.config.getNamingStrategy();
29
28
  this.metadataProvider = this.config.getMetadataProvider();
30
- this.cache = this.config.getMetadataCacheAdapter();
31
29
  this.logger = this.config.getLogger();
32
30
  this.schemaHelper = this.platform.getSchemaHelper();
33
31
  }
34
32
  async discover(preferTs = true) {
33
+ this.discovered.length = 0;
35
34
  const startTime = Date.now();
36
- this.logger.log('discovery', `ORM entity discovery started, using ${colors.cyan(this.metadataProvider.constructor.name)}`);
35
+ const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
36
+ this.logger.log('discovery', `ORM entity discovery started${suffix}`);
37
37
  await this.findEntities(preferTs);
38
38
  for (const meta of this.discovered) {
39
39
  /* v8 ignore next */
@@ -47,10 +47,13 @@ export class MetadataDiscovery {
47
47
  await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
48
48
  return storage;
49
49
  }
50
- discoverSync(preferTs = true) {
50
+ discoverSync() {
51
+ this.discovered.length = 0;
51
52
  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);
53
+ const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
54
+ this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
55
+ const refs = this.config.get('entities');
56
+ this.discoverReferences(refs);
54
57
  for (const meta of this.discovered) {
55
58
  /* v8 ignore next */
56
59
  void this.config.get('discovery').onMetadata?.(meta, this.platform);
@@ -70,15 +73,48 @@ export class MetadataDiscovery {
70
73
  .sort((a, b) => b.root.name.localeCompare(a.root.name))
71
74
  .forEach(meta => {
72
75
  this.platform.validateMetadata(meta);
73
- discovered.set(meta.className, meta);
76
+ discovered.set(meta.class, meta);
74
77
  });
78
+ for (const meta of discovered) {
79
+ meta.root = discovered.get(meta.root.class);
80
+ }
75
81
  return discovered;
76
82
  }
83
+ initAccessors(meta) {
84
+ for (const prop of Object.values(meta.properties)) {
85
+ if (!prop.accessor || meta.properties[prop.accessor]) {
86
+ continue;
87
+ }
88
+ const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
89
+ if (desc?.get || desc?.set) {
90
+ this.initFieldName(prop);
91
+ const accessor = prop.name;
92
+ prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
93
+ if (prop.accessor === true) {
94
+ prop.getter = prop.setter = true;
95
+ }
96
+ else {
97
+ prop.getter = prop.setter = false;
98
+ }
99
+ prop.accessor = accessor;
100
+ prop.serializedName ??= accessor;
101
+ Utils.renameKey(meta.properties, accessor, prop.name);
102
+ }
103
+ else {
104
+ const name = prop.name;
105
+ prop.name = prop.accessor;
106
+ this.initFieldName(prop);
107
+ prop.serializedName ??= prop.accessor;
108
+ prop.name = name;
109
+ }
110
+ }
111
+ }
77
112
  processDiscoveredEntities(discovered) {
78
113
  for (const meta of discovered) {
79
114
  let i = 1;
80
115
  Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
81
116
  Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
117
+ this.initAccessors(meta);
82
118
  }
83
119
  // ignore base entities (not annotated with @Entity)
84
120
  const filtered = discovered.filter(meta => meta.root.name);
@@ -86,66 +122,61 @@ export class MetadataDiscovery {
86
122
  filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
87
123
  filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
88
124
  filtered.forEach(meta => this.defineBaseEntityProperties(meta));
89
- filtered.forEach(meta => this.metadata.set(meta.className, EntitySchema.fromMetadata(meta).init().meta));
125
+ filtered.forEach(meta => {
126
+ const newMeta = EntitySchema.fromMetadata(meta).init().meta;
127
+ return this.metadata.set(newMeta.class, newMeta);
128
+ });
90
129
  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)));
130
+ const forEachProp = (cb) => {
131
+ filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
132
+ };
133
+ forEachProp((m, p) => this.initFactoryField(m, p));
134
+ forEachProp((_m, p) => this.initRelation(p));
135
+ forEachProp((m, p) => this.initEmbeddables(m, p));
136
+ forEachProp((_m, p) => this.initFieldName(p));
137
+ forEachProp((m, p) => this.initVersionProperty(m, p));
138
+ forEachProp((m, p) => this.initCustomType(m, p));
139
+ forEachProp((m, p) => this.initGeneratedColumn(m, p));
97
140
  filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
98
141
  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)));
142
+ forEachProp((_m, p) => {
143
+ this.initDefaultValue(p);
144
+ this.inferTypeFromDefault(p);
145
+ this.initRelation(p);
146
+ this.initColumnType(p);
147
+ });
148
+ forEachProp((m, p) => this.initIndexes(m, p));
111
149
  filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
112
- filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
113
150
  for (const meta of filtered) {
114
151
  discovered.push(...this.processEntity(meta));
115
152
  }
116
153
  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
- }
154
+ this.metadataProvider.combineCache();
122
155
  return discovered.map(meta => {
123
- meta = this.metadata.get(meta.className);
156
+ meta = this.metadata.get(meta.class);
124
157
  meta.sync(true);
158
+ this.findReferencingProperties(meta, filtered);
125
159
  return meta;
126
160
  });
127
161
  }
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));
134
- 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.`);
162
+ async findEntities(preferTs) {
163
+ const { entities, entitiesTs, baseDir } = this.config.getAll();
164
+ const targets = (preferTs && entitiesTs.length > 0) ? entitiesTs : entities;
165
+ const processed = [];
166
+ const paths = [];
167
+ for (const entity of targets) {
168
+ if (typeof entity === 'string') {
169
+ paths.push(entity);
170
+ }
171
+ else {
172
+ processed.push(entity);
137
173
  }
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
- });
144
174
  }
145
- this.discoverReferences(refs);
146
- this.discoverMissingTargets();
147
- this.validator.validateDiscovered(this.discovered, options);
148
- return this.discovered;
175
+ if (paths.length > 0) {
176
+ const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
177
+ processed.push(...await discoverEntities(paths, { baseDir }));
178
+ }
179
+ return this.discoverReferences(processed);
149
180
  }
150
181
  discoverMissingTargets() {
151
182
  const unwrap = (type) => type
@@ -154,17 +185,22 @@ export class MetadataDiscovery {
154
185
  .replace(/\((.*)\)/, '$1'); // unwrap union types
155
186
  const missing = [];
156
187
  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);
188
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
189
+ const pivotEntity = prop.pivotEntity;
190
+ const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
191
+ ? pivotEntity()
192
+ : pivotEntity;
193
+ if (!this.discovered.find(m => m.className === Utils.className(target))) {
194
+ missing.push(target);
195
+ }
162
196
  }
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'
197
+ if (prop.kind !== ReferenceKind.SCALAR) {
198
+ const target = typeof prop.entity === 'function' && !prop.entity.prototype
165
199
  ? prop.entity()
166
200
  : prop.type;
167
- missing.push(...Utils.asArray(target));
201
+ if (!unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
202
+ missing.push(...Utils.asArray(target));
203
+ }
168
204
  }
169
205
  }));
170
206
  if (missing.length > 0) {
@@ -173,123 +209,100 @@ export class MetadataDiscovery {
173
209
  }
174
210
  tryDiscoverTargets(targets) {
175
211
  for (const target of targets) {
176
- if (typeof target === 'function' && target.name && !this.metadata.has(target.name)) {
177
- this.discoverReferences([target]);
212
+ const isDiscoverable = typeof target === 'function' || target instanceof EntitySchema;
213
+ if (isDiscoverable && target.name && !this.metadata.has(target)) {
214
+ this.discoverReferences([target], false);
178
215
  this.discoverMissingTargets();
179
216
  }
180
217
  }
181
218
  }
182
- async discoverDirectories(paths) {
183
- paths = paths.map(path => Utils.normalizePath(path));
184
- const files = await globby(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;
204
- }
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
- }
211
- }
212
- for (const [schema, path] of found) {
213
- this.discoverEntity(schema, path);
214
- }
215
- }
216
- discoverReferences(refs) {
219
+ discoverReferences(refs, validate = true) {
217
220
  const found = [];
218
221
  for (const entity of refs) {
219
- const schema = this.getSchema(this.prepare(entity));
222
+ if (typeof entity === 'string') {
223
+ throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
224
+ }
225
+ const schema = this.getSchema(entity);
220
226
  const meta = schema.init().meta;
221
- this.metadata.set(meta.className, meta);
227
+ this.metadata.set(meta.class, meta);
222
228
  found.push(schema);
223
229
  }
224
230
  // discover parents (base entities) automatically
225
231
  for (const meta of this.metadata) {
226
232
  let parent = meta.extends;
227
- if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.className)) {
228
- this.discoverReferences([parent]);
233
+ if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
234
+ this.discoverReferences([parent], false);
235
+ }
236
+ if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
237
+ this.discoverReferences([parent], false);
229
238
  }
230
- /* v8 ignore next 3 */
239
+ /* v8 ignore next */
231
240
  if (!meta.class) {
232
241
  continue;
233
242
  }
234
243
  parent = Object.getPrototypeOf(meta.class);
235
- if (parent.name !== '' && !this.metadata.has(parent.name)) {
236
- this.discoverReferences([parent]);
244
+ // Skip if parent is the auto-generated base class for the same entity (from setClass usage)
245
+ if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
246
+ this.discoverReferences([parent], false);
237
247
  }
238
248
  }
239
249
  for (const schema of found) {
240
250
  this.discoverEntity(schema);
241
251
  }
252
+ this.discoverMissingTargets();
253
+ if (validate) {
254
+ this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
255
+ }
242
256
  return this.discovered.filter(meta => found.find(m => m.name === meta.className));
243
257
  }
244
- reset(className) {
245
- const exists = this.discovered.findIndex(m => m.className === className);
258
+ reset(entityName) {
259
+ const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
246
260
  if (exists !== -1) {
247
- this.metadata.reset(this.discovered[exists].className);
261
+ this.metadata.reset(this.discovered[exists].class);
248
262
  this.discovered.splice(exists, 1);
249
263
  }
250
264
  }
251
- prepare(entity) {
252
- /* v8 ignore next 3 */
253
- if ('schema' in entity && entity.schema instanceof EntitySchema) {
254
- return entity.schema;
255
- }
265
+ getSchema(entity) {
256
266
  if (EntitySchema.REGISTRY.has(entity)) {
257
- return EntitySchema.REGISTRY.get(entity);
267
+ entity = EntitySchema.REGISTRY.get(entity);
258
268
  }
259
- return entity;
260
- }
261
- getSchema(entity, filepath) {
262
269
  if (entity instanceof EntitySchema) {
263
- if (filepath) {
264
- // initialize global metadata for given entity
265
- MetadataStorage.getMetadata(entity.meta.className, filepath);
266
- }
267
270
  const meta = Utils.copy(entity.meta, false);
268
271
  return EntitySchema.fromMetadata(meta);
269
272
  }
270
273
  const path = entity[MetadataStorage.PATH_SYMBOL];
271
274
  if (path) {
272
275
  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);
276
+ meta.path = path;
277
+ this.metadata.set(entity, meta);
275
278
  }
276
- const exists = this.metadata.has(entity.name);
277
- const meta = this.metadata.get(entity.name, true);
279
+ const exists = this.metadata.has(entity);
280
+ const meta = this.metadata.get(entity, true);
278
281
  meta.abstract ??= !(exists && meta.name);
279
282
  const schema = EntitySchema.fromMetadata(meta);
280
283
  schema.setClass(entity);
281
284
  return schema;
282
285
  }
283
- discoverEntity(schema, path) {
284
- this.logger.log('discovery', `- processing entity ${colors.cyan(schema.meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
286
+ getRootEntity(meta) {
287
+ const base = meta.extends && this.metadata.find(meta.extends);
288
+ if (!base || base === meta) { // make sure we do not fall into infinite loop
289
+ return meta;
290
+ }
291
+ const root = this.getRootEntity(base);
292
+ if (root.discriminatorColumn) {
293
+ return root;
294
+ }
295
+ return meta;
296
+ }
297
+ discoverEntity(schema) {
285
298
  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));
299
+ const path = meta.path;
300
+ this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
301
+ const root = this.getRootEntity(meta);
302
+ schema.meta.path = meta.path;
303
+ const cache = this.metadataProvider.getCachedMetadata(meta, root);
289
304
  if (cache) {
290
305
  this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
291
- this.metadataProvider.loadFromCache(meta, cache);
292
- meta.root = root;
293
306
  this.discovered.push(meta);
294
307
  return;
295
308
  }
@@ -298,50 +311,18 @@ export class MetadataDiscovery {
298
311
  this.inferDefaultValue(meta, prop);
299
312
  }
300
313
  // 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) {
314
+ this.metadataProvider.loadEntityMetadata(meta);
315
+ if (!meta.tableName && meta.name) {
303
316
  const entityName = root.discriminatorColumn ? root.name : meta.name;
304
- meta.collection = this.namingStrategy.classToTableName(entityName);
317
+ meta.tableName = this.namingStrategy.classToTableName(entityName);
305
318
  }
306
- delete meta.root; // to allow caching (as root can contain cycles)
307
- this.saveToCache(meta);
319
+ this.metadataProvider.saveToCache(meta);
308
320
  meta.root = root;
309
321
  this.discovered.push(meta);
310
322
  }
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
323
  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
324
  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));
325
+ return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
345
326
  }
346
327
  return Utils.defaultValue(prop, 'nullable', prop.optional);
347
328
  }
@@ -376,7 +357,7 @@ export class MetadataDiscovery {
376
357
  if (prop.joinColumns.length !== prop.columnTypes.length) {
377
358
  prop.columnTypes = prop.joinColumns.flatMap(field => {
378
359
  const matched = meta.props.find(p => p.fieldNames?.includes(field));
379
- /* v8 ignore next 3 */
360
+ /* v8 ignore next */
380
361
  if (!matched) {
381
362
  throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
382
363
  }
@@ -403,7 +384,7 @@ export class MetadataDiscovery {
403
384
  }
404
385
  }
405
386
  initManyToOneFieldName(prop, name) {
406
- const meta2 = this.metadata.get(prop.type);
387
+ const meta2 = prop.targetMeta;
407
388
  const ret = [];
408
389
  for (const primaryKey of meta2.primaryKeys) {
409
390
  this.initFieldName(meta2.properties[primaryKey]);
@@ -414,11 +395,11 @@ export class MetadataDiscovery {
414
395
  return ret;
415
396
  }
416
397
  initManyToManyFieldName(prop, name) {
417
- const meta2 = this.metadata.get(prop.type);
398
+ const meta2 = prop.targetMeta;
418
399
  return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
419
400
  }
420
401
  initManyToManyFields(meta, prop) {
421
- const meta2 = this.metadata.get(prop.type);
402
+ const meta2 = prop.targetMeta;
422
403
  Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
423
404
  const pivotMeta = this.metadata.find(prop.pivotEntity);
424
405
  const props = Object.values(pivotMeta?.properties ?? {});
@@ -439,35 +420,53 @@ export class MetadataDiscovery {
439
420
  prop.inverseJoinColumns ??= second.fieldNames;
440
421
  }
441
422
  if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
442
- prop.pivotTable = this.namingStrategy.joinTableName(meta.tableName, meta2.tableName, prop.name);
423
+ prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
443
424
  }
444
425
  if (prop.mappedBy) {
445
426
  const prop2 = meta2.properties[prop.mappedBy];
446
427
  this.initManyToManyFields(meta2, prop2);
447
428
  prop.pivotTable = prop2.pivotTable;
448
- prop.pivotEntity = prop2.pivotEntity ?? prop2.pivotTable;
429
+ prop.pivotEntity = prop2.pivotEntity;
449
430
  prop.fixedOrder = prop2.fixedOrder;
450
431
  prop.fixedOrderColumn = prop2.fixedOrderColumn;
451
432
  prop.joinColumns = prop2.inverseJoinColumns;
452
433
  prop.inverseJoinColumns = prop2.joinColumns;
453
434
  }
454
435
  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));
436
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, meta.root.tableName));
456
437
  prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
457
438
  }
458
439
  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);
440
+ const meta2 = prop.targetMeta;
441
+ let fieldNames;
442
+ // If targetKey is specified, use that property's field names instead of PKs
443
+ if (prop.targetKey) {
444
+ const targetProp = meta2.properties[prop.targetKey];
445
+ fieldNames = targetProp.fieldNames;
446
+ }
447
+ else {
448
+ fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
449
+ }
450
+ Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
462
451
  if (!prop.joinColumns) {
463
452
  prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
464
453
  }
465
454
  if (!prop.referencedColumnNames) {
466
455
  prop.referencedColumnNames = fieldNames;
467
456
  }
457
+ // Relations to composite PK targets need cascade update by default,
458
+ // since composite PKs are more likely to have mutable components
459
+ if (meta2.compositePK) {
460
+ prop.updateRule ??= 'cascade';
461
+ }
462
+ // Nullable relations default to 'set null' on delete - when the referenced
463
+ // entity is deleted, set the FK to null rather than failing
464
+ if (prop.nullable) {
465
+ prop.deleteRule ??= 'set null';
466
+ }
468
467
  }
469
468
  initOneToManyFields(prop) {
470
- const meta2 = this.metadata.get(prop.type);
469
+ const meta2 = prop.targetMeta;
471
470
  if (!prop.joinColumns) {
472
471
  prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
473
472
  }
@@ -480,12 +479,17 @@ export class MetadataDiscovery {
480
479
  const pks = Object.values(meta.properties).filter(prop => prop.primary);
481
480
  meta.primaryKeys = pks.map(prop => prop.name);
482
481
  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';
482
+ // FK used as PK, we need to cascade - applies to both single FK-as-PK
483
+ // and composite PKs where all PKs are FKs (e.g., pivot-like entities)
484
+ const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
485
+ if (fkPks.length > 0 && fkPks.length === pks.length) {
486
+ for (const pk of fkPks) {
487
+ pk.deleteRule ??= 'cascade';
488
+ pk.updateRule ??= 'cascade';
489
+ }
486
490
  }
487
491
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
488
- this.validator.validateEntityDefinition(this.metadata, meta.className, this.config.get('discovery'));
492
+ this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
489
493
  for (const prop of Object.values(meta.properties)) {
490
494
  this.initNullability(prop);
491
495
  this.applyNamingStrategy(meta, prop);
@@ -498,15 +502,21 @@ export class MetadataDiscovery {
498
502
  }
499
503
  this.initOwnColumns(meta);
500
504
  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;
505
+ meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
506
+ if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
507
+ meta.properties[meta.serializedPrimaryKey].persist ??= false;
505
508
  }
506
509
  if (this.platform.usesPivotTable()) {
507
510
  return Object.values(meta.properties)
508
511
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
509
- .map(prop => this.definePivotTableEntity(meta, prop));
512
+ .map(prop => {
513
+ const pivotMeta = this.definePivotTableEntity(meta, prop);
514
+ prop.pivotEntity = pivotMeta.class;
515
+ if (prop.inversedBy) {
516
+ prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
517
+ }
518
+ return pivotMeta;
519
+ });
510
520
  }
511
521
  return [];
512
522
  }
@@ -523,8 +533,11 @@ export class MetadataDiscovery {
523
533
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
524
534
  const value = prop[type];
525
535
  if (value instanceof Function) {
526
- const meta2 = this.metadata.get(prop.type);
536
+ const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
527
537
  prop[type] = value(meta2.properties)?.name;
538
+ if (type === 'pivotEntity' && value) {
539
+ prop[type] = value(meta2.properties);
540
+ }
528
541
  if (prop[type] == null) {
529
542
  throw MetadataError.fromWrongReference(meta, prop, type);
530
543
  }
@@ -540,9 +553,9 @@ export class MetadataDiscovery {
540
553
  }
541
554
  else if (fks.length >= 2) {
542
555
  [first, second] = fks;
543
- /* v8 ignore next 3 */
544
556
  }
545
557
  else {
558
+ /* v8 ignore next */
546
559
  return [];
547
560
  }
548
561
  // wrong FK order, first FK needs to point to the owning side
@@ -556,7 +569,9 @@ export class MetadataDiscovery {
556
569
  return [first, second];
557
570
  }
558
571
  definePivotTableEntity(meta, prop) {
559
- const pivotMeta = this.metadata.find(prop.pivotEntity);
572
+ const pivotMeta = prop.pivotEntity
573
+ ? this.metadata.find(prop.pivotEntity)
574
+ : this.metadata.getByClassName(prop.pivotTable, false);
560
575
  // ensure inverse side exists so we can join it when populating via pivot tables
561
576
  if (!prop.inversedBy && prop.targetMeta) {
562
577
  const inverseName = `${meta.className}_${prop.name}__inverse`;
@@ -565,6 +580,8 @@ export class MetadataDiscovery {
565
580
  name: inverseName,
566
581
  kind: ReferenceKind.MANY_TO_MANY,
567
582
  type: meta.className,
583
+ target: meta.class,
584
+ targetMeta: meta,
568
585
  mappedBy: prop.name,
569
586
  pivotEntity: prop.pivotEntity,
570
587
  pivotTable: prop.pivotTable,
@@ -573,55 +590,51 @@ export class MetadataDiscovery {
573
590
  };
574
591
  this.applyNamingStrategy(prop.targetMeta, inverseProp);
575
592
  this.initCustomType(prop.targetMeta, inverseProp);
576
- this.initRelation(inverseProp);
577
593
  prop.targetMeta.properties[inverseName] = inverseProp;
578
594
  }
579
595
  if (pivotMeta) {
596
+ prop.pivotEntity = pivotMeta.class;
580
597
  this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
581
598
  return pivotMeta;
582
599
  }
583
- const exists = this.metadata.find(prop.pivotTable);
584
- if (exists) {
585
- prop.pivotEntity = exists.className;
586
- return exists;
587
- }
588
600
  let tableName = prop.pivotTable;
589
601
  let schemaName;
590
602
  if (prop.pivotTable.includes('.')) {
591
603
  [schemaName, tableName] = prop.pivotTable.split('.');
592
604
  }
593
605
  schemaName ??= meta.schema;
594
- const targetType = prop.targetMeta.className;
595
- const data = new EntityMetadata({
606
+ const targetMeta = prop.targetMeta;
607
+ const targetType = targetMeta.className;
608
+ const pivotMeta2 = new EntityMetadata({
596
609
  name: prop.pivotTable,
597
610
  className: prop.pivotTable,
598
611
  collection: tableName,
599
612
  schema: schemaName,
600
613
  pivotTable: true,
601
614
  });
602
- prop.pivotEntity = data.className;
615
+ prop.pivotEntity = pivotMeta2.class;
603
616
  if (prop.fixedOrder) {
604
- const primaryProp = this.defineFixedOrderProperty(prop, targetType);
605
- data.properties[primaryProp.name] = primaryProp;
617
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
618
+ pivotMeta2.properties[primaryProp.name] = primaryProp;
606
619
  }
607
620
  else {
608
- data.compositePK = true;
621
+ pivotMeta2.compositePK = true;
609
622
  }
610
623
  // handle self-referenced m:n with same default field names
611
624
  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));
625
+ prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(meta.tableName + '_1', name, meta.compositePK));
626
+ prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(meta.tableName + '_2', name, meta.compositePK));
614
627
  if (prop.inversedBy) {
615
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
628
+ const prop2 = targetMeta.properties[prop.inversedBy];
616
629
  prop2.inverseJoinColumns = prop.joinColumns;
617
630
  prop2.joinColumns = prop.inverseJoinColumns;
618
631
  }
619
632
  }
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, data);
633
+ pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
634
+ pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
635
+ return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
623
636
  }
624
- defineFixedOrderProperty(prop, targetType) {
637
+ defineFixedOrderProperty(prop, targetMeta) {
625
638
  const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
626
639
  const primaryProp = {
627
640
  name: pk,
@@ -635,7 +648,7 @@ export class MetadataDiscovery {
635
648
  this.initColumnType(primaryProp);
636
649
  prop.fixedOrderColumn = pk;
637
650
  if (prop.inversedBy) {
638
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
651
+ const prop2 = targetMeta.properties[prop.inversedBy];
639
652
  prop2.fixedOrder = true;
640
653
  prop2.fixedOrderColumn = pk;
641
654
  }
@@ -644,7 +657,8 @@ export class MetadataDiscovery {
644
657
  definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
645
658
  const ret = {
646
659
  name,
647
- type,
660
+ type: Utils.className(type),
661
+ target: type,
648
662
  kind: ReferenceKind.MANY_TO_ONE,
649
663
  cascade: [Cascade.ALL],
650
664
  fixedOrder: prop.fixedOrder,
@@ -654,11 +668,11 @@ export class MetadataDiscovery {
654
668
  autoincrement: false,
655
669
  updateRule: prop.updateRule,
656
670
  deleteRule: prop.deleteRule,
671
+ createForeignKeyConstraint: prop.createForeignKeyConstraint,
657
672
  };
658
- if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
659
- ret.updateRule ??= 'no action';
660
- ret.deleteRule ??= 'no action';
661
- }
673
+ const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
674
+ ret.updateRule ??= defaultRule;
675
+ ret.deleteRule ??= defaultRule;
662
676
  const meta = this.metadata.get(type);
663
677
  ret.targetMeta = meta;
664
678
  ret.joinColumns = [];
@@ -701,7 +715,7 @@ export class MetadataDiscovery {
701
715
  Object.values(meta.properties)
702
716
  .filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
703
717
  .forEach(prop => {
704
- const meta2 = this.metadata.get(prop.type);
718
+ const meta2 = prop.targetMeta;
705
719
  const prop2 = meta2.properties[prop.mappedBy];
706
720
  if (prop2 && !prop2.inversedBy) {
707
721
  prop2.inversedBy = prop.name;
@@ -709,7 +723,7 @@ export class MetadataDiscovery {
709
723
  });
710
724
  }
711
725
  defineBaseEntityProperties(meta) {
712
- const base = meta.extends && this.metadata.get(Utils.className(meta.extends));
726
+ const base = meta.extends && this.metadata.get(meta.extends);
713
727
  if (!base || base === meta) { // make sure we do not fall into infinite loop
714
728
  return 0;
715
729
  }
@@ -740,12 +754,9 @@ export class MetadataDiscovery {
740
754
  Utils.keys(base.hooks).forEach(type => {
741
755
  meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
742
756
  });
743
- if (meta.constructorParams.length === 0 && base.constructorParams.length > 0) {
757
+ if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
744
758
  meta.constructorParams = [...base.constructorParams];
745
759
  }
746
- if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
747
- meta.toJsonParams = [...base.toJsonParams];
748
- }
749
760
  return order;
750
761
  }
751
762
  initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
@@ -821,22 +832,30 @@ export class MetadataDiscovery {
821
832
  }
822
833
  return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
823
834
  };
835
+ const isParentArray = (prop) => {
836
+ if (prop.array) {
837
+ return true;
838
+ }
839
+ return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
840
+ };
824
841
  const rootProperty = getRootProperty(embeddedProp);
825
842
  const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
826
843
  const object = isParentObject(embeddedProp);
844
+ const array = isParentArray(embeddedProp);
827
845
  this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
828
846
  // the prefix of the parent cannot be a boolean; it already passed here
829
847
  const prefix = this.getPrefix(embeddedProp, parentProperty);
830
848
  const glue = object ? '~' : '_';
831
849
  for (const prop of Object.values(embeddable.properties)) {
832
850
  const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
833
- meta.properties[name] = Utils.copy(prop, false);
851
+ meta.properties[name] = Utils.copy(prop);
834
852
  meta.properties[name].name = name;
835
853
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
836
854
  meta.propertyOrder.set(name, (order += 0.01));
837
855
  embeddedProp.embeddedProps[prop.name] = meta.properties[name];
838
856
  meta.properties[name].persist ??= embeddedProp.persist;
839
- if (embeddedProp.nullable) {
857
+ const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
858
+ if (embeddedProp.nullable || refInArray) {
840
859
  meta.properties[name].nullable = true;
841
860
  }
842
861
  if (meta.properties[name].fieldNames) {
@@ -866,14 +885,17 @@ export class MetadataDiscovery {
866
885
  path = [embeddedProp.fieldNames[0]];
867
886
  }
868
887
  this.initFieldName(prop, true);
888
+ this.initRelation(prop);
869
889
  path.push(prop.fieldNames[0]);
870
890
  meta.properties[name].fieldNames = prop.fieldNames;
871
891
  meta.properties[name].embeddedPath = path;
872
- const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), prop.runtimeType ?? prop.type, true));
892
+ const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
893
+ const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
873
894
  meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
874
895
  meta.properties[name].persist = false; // only virtual as we store the whole object
875
896
  meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
876
897
  meta.properties[name].object = true;
898
+ this.initCustomType(meta, meta.properties[name], false, true);
877
899
  }
878
900
  this.initEmbeddables(meta, meta.properties[name], visited);
879
901
  }
@@ -896,7 +918,7 @@ export class MetadataDiscovery {
896
918
  }
897
919
  initSingleTableInheritance(meta, metadata) {
898
920
  if (meta.root !== meta && !meta.__processed) {
899
- meta.root = metadata.find(m => m.className === meta.root.className);
921
+ meta.root = metadata.find(m => m.class === meta.root.class);
900
922
  meta.root.__processed = true;
901
923
  }
902
924
  else {
@@ -905,17 +927,23 @@ export class MetadataDiscovery {
905
927
  if (!meta.root.discriminatorColumn) {
906
928
  return;
907
929
  }
908
- if (!meta.root.discriminatorMap) {
930
+ if (meta.root.discriminatorMap) {
931
+ const map = meta.root.discriminatorMap;
932
+ Object.keys(map)
933
+ .filter(key => typeof map[key] === 'string')
934
+ .forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
935
+ }
936
+ else {
909
937
  meta.root.discriminatorMap = {};
910
938
  const children = metadata
911
- .filter(m => m.root.className === meta.root.className && !m.abstract)
939
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
912
940
  .sort((a, b) => a.className.localeCompare(b.className));
913
941
  for (const m of children) {
914
942
  const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
915
- meta.root.discriminatorMap[name] = m.className;
943
+ meta.root.discriminatorMap[name] = m.class;
916
944
  }
917
945
  }
918
- meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, className]) => className === meta.className)?.[0];
946
+ meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
919
947
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
920
948
  this.createDiscriminatorProperty(meta.root);
921
949
  }
@@ -927,25 +955,44 @@ export class MetadataDiscovery {
927
955
  let i = 1;
928
956
  Object.values(meta.properties).forEach(prop => {
929
957
  const newProp = { ...prop };
930
- if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
958
+ const rootProp = meta.root.properties[prop.name];
959
+ if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
931
960
  const name = newProp.name;
932
961
  this.initFieldName(newProp, newProp.object);
962
+ newProp.renamedFrom = name;
933
963
  newProp.name = name + '_' + (i++);
934
964
  meta.root.addProperty(newProp);
965
+ this.initFieldName(prop, prop.object);
966
+ // Track all field variants and map discriminator values to field names
967
+ if (!rootProp.stiFieldNames) {
968
+ this.initFieldName(rootProp, rootProp.object);
969
+ rootProp.stiFieldNames = [...rootProp.fieldNames];
970
+ rootProp.stiFieldNameMap = {};
971
+ // Find which discriminator owns the original fieldNames
972
+ for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
973
+ const childMeta = this.metadata.find(childClass);
974
+ if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
975
+ rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
976
+ break;
977
+ }
978
+ }
979
+ }
980
+ rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
981
+ rootProp.stiFieldNames.push(...prop.fieldNames);
935
982
  newProp.nullable = true;
936
983
  newProp.name = name;
937
984
  newProp.hydrate = false;
938
985
  newProp.inherited = true;
939
986
  return;
940
987
  }
941
- if (prop.enum && prop.items && meta.root.properties[prop.name]?.items) {
942
- newProp.items = Utils.unique([...meta.root.properties[prop.name].items, ...prop.items]);
988
+ if (prop.enum && prop.items && rootProp?.items) {
989
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
943
990
  }
944
991
  newProp.nullable = true;
945
- newProp.inherited = true;
992
+ newProp.inherited = !rootProp;
946
993
  meta.root.addProperty(newProp);
947
994
  });
948
- meta.collection = meta.root.collection;
995
+ meta.tableName = meta.root.tableName;
949
996
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
950
997
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
951
998
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
@@ -967,7 +1014,7 @@ export class MetadataDiscovery {
967
1014
  }
968
1015
  }
969
1016
  initCheckConstraints(meta) {
970
- const map = this.createColumnMappingObject(meta);
1017
+ const map = meta.createColumnMappingObject();
971
1018
  for (const check of meta.checks) {
972
1019
  const columns = check.property ? meta.properties[check.property].fieldNames : [];
973
1020
  check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
@@ -977,7 +1024,7 @@ export class MetadataDiscovery {
977
1024
  }
978
1025
  if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
979
1026
  for (const prop of meta.props) {
980
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => Utils.isString(item))) {
1027
+ if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
981
1028
  this.initFieldName(prop);
982
1029
  meta.checks.push({
983
1030
  name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
@@ -1002,38 +1049,28 @@ export class MetadataDiscovery {
1002
1049
  }
1003
1050
  return;
1004
1051
  }
1005
- const map = this.createColumnMappingObject(meta);
1052
+ const map = meta.createColumnMappingObject();
1006
1053
  if (prop.generated instanceof Function) {
1007
1054
  prop.generated = prop.generated(map);
1008
1055
  }
1009
1056
  }
1010
- createColumnMappingObject(meta) {
1011
- return Object.values(meta.properties).reduce((o, prop) => {
1012
- if (prop.fieldNames) {
1013
- o[prop.name] = prop.fieldNames[0];
1014
- }
1015
- return o;
1016
- }, {});
1017
- }
1018
- getDefaultVersionValue(prop) {
1057
+ getDefaultVersionValue(meta, prop) {
1019
1058
  if (typeof prop.defaultRaw !== 'undefined') {
1020
1059
  return prop.defaultRaw;
1021
1060
  }
1022
- /* v8 ignore next 3 */
1061
+ /* v8 ignore next */
1023
1062
  if (prop.default != null) {
1024
1063
  return '' + this.platform.quoteVersionValue(prop.default, prop);
1025
1064
  }
1026
- if (prop.type.toLowerCase() === 'date') {
1065
+ this.initCustomType(meta, prop, true);
1066
+ const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
1067
+ if (type === 'Date') {
1027
1068
  prop.length ??= this.platform.getDefaultVersionLength();
1028
1069
  return this.platform.getCurrentTimestampSQL(prop.length);
1029
1070
  }
1030
1071
  return '1';
1031
1072
  }
1032
1073
  inferDefaultValue(meta, prop) {
1033
- /* v8 ignore next 3 */
1034
- if (!meta.class) {
1035
- return;
1036
- }
1037
1074
  try {
1038
1075
  // try to create two entity instances to detect the value is stable
1039
1076
  const now = Date.now();
@@ -1061,12 +1098,12 @@ export class MetadataDiscovery {
1061
1098
  return;
1062
1099
  }
1063
1100
  let val = prop.default;
1064
- const raw = RawQueryFragment.getKnownFragment(val);
1101
+ const raw = Raw.getKnownFragment(val);
1065
1102
  if (raw) {
1066
1103
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1067
1104
  return;
1068
1105
  }
1069
- if (prop.customType instanceof ArrayType && Array.isArray(prop.default)) {
1106
+ if (Array.isArray(prop.default) && prop.customType) {
1070
1107
  val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1071
1108
  }
1072
1109
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
@@ -1094,13 +1131,13 @@ export class MetadataDiscovery {
1094
1131
  if (prop.version) {
1095
1132
  this.initDefaultValue(prop);
1096
1133
  meta.versionProperty = prop.name;
1097
- prop.defaultRaw = this.getDefaultVersionValue(prop);
1134
+ prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
1098
1135
  }
1099
1136
  if (prop.concurrencyCheck && !prop.primary) {
1100
1137
  meta.concurrencyCheckKeys.add(prop.name);
1101
1138
  }
1102
1139
  }
1103
- initCustomType(meta, prop) {
1140
+ initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
1104
1141
  // `prop.type` might be actually instance of custom type class
1105
1142
  if (Type.isMappedType(prop.type) && !prop.customType) {
1106
1143
  prop.customType = prop.type;
@@ -1108,41 +1145,61 @@ export class MetadataDiscovery {
1108
1145
  }
1109
1146
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1110
1147
  if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
1111
- prop.customType = new prop.type();
1112
- prop.type = prop.customType.constructor.name;
1148
+ // if the type is an ORM defined mapped type without `ensureComparable: true`,
1149
+ // we use just the type name, to have more performant hydration code
1150
+ const type = Utils.keys(t).find(type => {
1151
+ return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
1152
+ });
1153
+ if (type) {
1154
+ prop.type = type === 'datetime' ? 'Date' : type;
1155
+ }
1156
+ else {
1157
+ prop.customType = new prop.type();
1158
+ prop.type = prop.customType.constructor.name;
1159
+ }
1160
+ }
1161
+ if (simple) {
1162
+ return;
1113
1163
  }
1114
1164
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1115
- prop.customType = new JsonType();
1165
+ prop.customType = new t.json();
1116
1166
  }
1117
1167
  if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1118
- prop.customType = new JsonType();
1168
+ prop.customType = new t.json();
1169
+ }
1170
+ if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1171
+ prop.customType = new t.json();
1119
1172
  }
1120
1173
  if (!prop.customType && prop.array && prop.items) {
1121
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1174
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1175
+ }
1176
+ const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1177
+ if (objectEmbeddable && !prop.customType && isArray) {
1178
+ prop.customType = new t.json();
1122
1179
  }
1123
1180
  // for number arrays we make sure to convert the items to numbers
1124
1181
  if (!prop.customType && prop.type === 'number[]') {
1125
- prop.customType = new ArrayType(i => +i);
1182
+ prop.customType = new t.array(i => +i);
1126
1183
  }
1127
1184
  // `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
1128
- if (!prop.customType && (prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]'))) {
1129
- prop.customType = new ArrayType();
1185
+ if (!prop.customType && isArray) {
1186
+ prop.customType = new t.array();
1130
1187
  }
1131
1188
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1132
- prop.customType = new BlobType();
1189
+ prop.customType = new t.blob();
1133
1190
  }
1134
1191
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1135
- prop.customType = new Uint8ArrayType();
1192
+ prop.customType = new t.uint8array();
1136
1193
  }
1137
1194
  const mappedType = this.getMappedType(prop);
1138
1195
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1139
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1196
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1140
1197
  .filter(type => mappedType instanceof type)
1141
- .forEach(type => prop.customType = new type());
1198
+ .forEach((type) => prop.customType = new type());
1142
1199
  }
1143
1200
  if (prop.customType && !prop.columnTypes) {
1144
1201
  const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1145
- if (prop.customType.compareAsType() === 'any' && ![JsonType].some(t => prop.customType instanceof t)) {
1202
+ if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
1146
1203
  prop.runtimeType ??= mappedType.runtimeType;
1147
1204
  }
1148
1205
  else {
@@ -1162,16 +1219,16 @@ export class MetadataDiscovery {
1162
1219
  prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1163
1220
  prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1164
1221
  prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1165
- if (prop.customType instanceof BigIntType && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1222
+ if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1166
1223
  prop.customType.mode = prop.runtimeType.toLowerCase();
1167
1224
  }
1168
1225
  }
1169
- if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !prop.type?.toString().endsWith('[]')) {
1226
+ if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1170
1227
  prop.type = prop.customType.name;
1171
1228
  }
1172
- if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && this.metadata.get(prop.type).compositePK) {
1229
+ if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
1173
1230
  prop.customTypes = [];
1174
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1231
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1175
1232
  if (pk.customType) {
1176
1233
  prop.customTypes.push(pk.customType);
1177
1234
  prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
@@ -1183,7 +1240,7 @@ export class MetadataDiscovery {
1183
1240
  }
1184
1241
  }
1185
1242
  }
1186
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1243
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1187
1244
  if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1188
1245
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1189
1246
  }
@@ -1201,19 +1258,25 @@ export class MetadataDiscovery {
1201
1258
  if (prop.kind === ReferenceKind.SCALAR) {
1202
1259
  return;
1203
1260
  }
1204
- const meta2 = this.discovered.find(m => m.className === prop.type);
1205
- prop.referencedPKs = meta2.primaryKeys;
1261
+ // 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
1262
+ const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
1263
+ // If targetKey is specified, use that property instead of PKs for referencedPKs
1264
+ prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
1206
1265
  prop.targetMeta = meta2;
1207
1266
  if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
1208
- prop.formula = a => `${a}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1267
+ prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1209
1268
  }
1210
1269
  }
1211
1270
  initColumnType(prop) {
1212
1271
  this.initUnsigned(prop);
1213
- this.metadata.find(prop.type)?.getPrimaryProps().map(pk => {
1214
- prop.length ??= pk.length;
1215
- prop.precision ??= pk.precision;
1216
- prop.scale ??= pk.scale;
1272
+ // Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
1273
+ const targetProps = prop.targetMeta
1274
+ ? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
1275
+ : [];
1276
+ targetProps.map(targetProp => {
1277
+ prop.length ??= targetProp.length;
1278
+ prop.precision ??= targetProp.precision;
1279
+ prop.scale ??= targetProp.scale;
1217
1280
  });
1218
1281
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1219
1282
  delete prop.type;
@@ -1226,8 +1289,7 @@ export class MetadataDiscovery {
1226
1289
  if (prop.kind === ReferenceKind.SCALAR) {
1227
1290
  const mappedType = this.getMappedType(prop);
1228
1291
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1229
- if (mappedType instanceof UnknownType
1230
- && !prop.columnTypes
1292
+ if (mappedType instanceof t.unknown
1231
1293
  // it could be a runtime type from reflect-metadata
1232
1294
  && !SCALAR_TYPES.includes(prop.type)
1233
1295
  // or it might be inferred via ts-morph to some generic type alias
@@ -1240,23 +1302,28 @@ export class MetadataDiscovery {
1240
1302
  }
1241
1303
  return;
1242
1304
  }
1243
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1305
+ /* v8 ignore next */
1306
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1244
1307
  prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1245
1308
  return;
1246
1309
  }
1247
- const targetMeta = this.metadata.get(prop.type);
1310
+ const targetMeta = prop.targetMeta;
1248
1311
  prop.columnTypes = [];
1249
- for (const pk of targetMeta.getPrimaryProps()) {
1250
- this.initCustomType(targetMeta, pk);
1251
- this.initColumnType(pk);
1252
- const mappedType = this.getMappedType(pk);
1253
- let columnTypes = pk.columnTypes;
1254
- if (pk.autoincrement) {
1255
- columnTypes = [mappedType.getColumnType({ ...pk, autoincrement: false }, this.platform)];
1312
+ // Use targetKey property if specified, otherwise use primary key properties
1313
+ const referencedProps = prop.targetKey
1314
+ ? [targetMeta.properties[prop.targetKey]]
1315
+ : targetMeta.getPrimaryProps();
1316
+ for (const referencedProp of referencedProps) {
1317
+ this.initCustomType(targetMeta, referencedProp);
1318
+ this.initColumnType(referencedProp);
1319
+ const mappedType = this.getMappedType(referencedProp);
1320
+ let columnTypes = referencedProp.columnTypes;
1321
+ if (referencedProp.autoincrement) {
1322
+ columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
1256
1323
  }
1257
1324
  prop.columnTypes.push(...columnTypes);
1258
- if (!targetMeta.compositePK) {
1259
- prop.customType = pk.customType;
1325
+ if (!targetMeta.compositePK || prop.targetKey) {
1326
+ prop.customType = referencedProp.customType;
1260
1327
  }
1261
1328
  }
1262
1329
  }
@@ -1270,7 +1337,7 @@ export class MetadataDiscovery {
1270
1337
  t = 'enum';
1271
1338
  }
1272
1339
  else if (prop.enum) {
1273
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1340
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1274
1341
  }
1275
1342
  if (t === 'Date') {
1276
1343
  t = 'datetime';
@@ -1294,7 +1361,7 @@ export class MetadataDiscovery {
1294
1361
  return;
1295
1362
  }
1296
1363
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1297
- const meta2 = this.metadata.get(prop.type);
1364
+ const meta2 = prop.targetMeta;
1298
1365
  prop.unsigned = meta2.getPrimaryProps().some(pk => {
1299
1366
  this.initUnsigned(pk);
1300
1367
  return pk.unsigned;
@@ -1309,30 +1376,6 @@ export class MetadataDiscovery {
1309
1376
  prop.index ??= true;
1310
1377
  }
1311
1378
  }
1312
- async getEntityClassOrSchema(path, name) {
1313
- const exports = await Utils.dynamicImport(path);
1314
- const targets = Object.values(exports)
1315
- .filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
1316
- // ignore class implementations that are linked from an EntitySchema
1317
- for (const item of targets) {
1318
- if (item instanceof EntitySchema) {
1319
- targets.forEach((item2, idx) => {
1320
- if (item.meta.class === item2) {
1321
- targets.splice(idx, 1);
1322
- }
1323
- });
1324
- }
1325
- }
1326
- if (targets.length > 0) {
1327
- return targets;
1328
- }
1329
- const target = exports.default ?? exports[name];
1330
- /* v8 ignore next 3 */
1331
- if (!target) {
1332
- throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
1333
- }
1334
- return [target];
1335
- }
1336
1379
  shouldForceConstructorUsage(meta) {
1337
1380
  const forceConstructor = this.config.get('forceEntityConstructor');
1338
1381
  if (Array.isArray(forceConstructor)) {