@mikro-orm/core 7.0.0-dev.16 → 7.0.0-dev.161

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 +292 -262
  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 +43 -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 +21 -9
  46. package/errors.js +53 -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 +348 -319
  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 +12 -9
  72. package/metadata/MetadataValidator.js +69 -38
  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 +484 -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 +174 -114
  120. package/typings.js +55 -42
  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 +754 -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,26 +420,34 @@ 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
  }
@@ -467,7 +456,7 @@ export class MetadataDiscovery {
467
456
  }
468
457
  }
469
458
  initOneToManyFields(prop) {
470
- const meta2 = this.metadata.get(prop.type);
459
+ const meta2 = prop.targetMeta;
471
460
  if (!prop.joinColumns) {
472
461
  prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
473
462
  }
@@ -485,7 +474,7 @@ export class MetadataDiscovery {
485
474
  pks[0].deleteRule ??= 'cascade';
486
475
  }
487
476
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
488
- this.validator.validateEntityDefinition(this.metadata, meta.className, this.config.get('discovery'));
477
+ this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
489
478
  for (const prop of Object.values(meta.properties)) {
490
479
  this.initNullability(prop);
491
480
  this.applyNamingStrategy(meta, prop);
@@ -498,15 +487,21 @@ export class MetadataDiscovery {
498
487
  }
499
488
  this.initOwnColumns(meta);
500
489
  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;
490
+ meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
491
+ if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
492
+ meta.properties[meta.serializedPrimaryKey].persist ??= false;
505
493
  }
506
494
  if (this.platform.usesPivotTable()) {
507
495
  return Object.values(meta.properties)
508
496
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
509
- .map(prop => this.definePivotTableEntity(meta, prop));
497
+ .map(prop => {
498
+ const pivotMeta = this.definePivotTableEntity(meta, prop);
499
+ prop.pivotEntity = pivotMeta.class;
500
+ if (prop.inversedBy) {
501
+ prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
502
+ }
503
+ return pivotMeta;
504
+ });
510
505
  }
511
506
  return [];
512
507
  }
@@ -523,8 +518,11 @@ export class MetadataDiscovery {
523
518
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
524
519
  const value = prop[type];
525
520
  if (value instanceof Function) {
526
- const meta2 = this.metadata.get(prop.type);
521
+ const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
527
522
  prop[type] = value(meta2.properties)?.name;
523
+ if (type === 'pivotEntity' && value) {
524
+ prop[type] = value(meta2.properties);
525
+ }
528
526
  if (prop[type] == null) {
529
527
  throw MetadataError.fromWrongReference(meta, prop, type);
530
528
  }
@@ -540,9 +538,9 @@ export class MetadataDiscovery {
540
538
  }
541
539
  else if (fks.length >= 2) {
542
540
  [first, second] = fks;
543
- /* v8 ignore next 3 */
544
541
  }
545
542
  else {
543
+ /* v8 ignore next */
546
544
  return [];
547
545
  }
548
546
  // wrong FK order, first FK needs to point to the owning side
@@ -556,7 +554,9 @@ export class MetadataDiscovery {
556
554
  return [first, second];
557
555
  }
558
556
  definePivotTableEntity(meta, prop) {
559
- const pivotMeta = this.metadata.find(prop.pivotEntity);
557
+ const pivotMeta = prop.pivotEntity
558
+ ? this.metadata.find(prop.pivotEntity)
559
+ : this.metadata.getByClassName(prop.pivotTable, false);
560
560
  // ensure inverse side exists so we can join it when populating via pivot tables
561
561
  if (!prop.inversedBy && prop.targetMeta) {
562
562
  const inverseName = `${meta.className}_${prop.name}__inverse`;
@@ -565,6 +565,8 @@ export class MetadataDiscovery {
565
565
  name: inverseName,
566
566
  kind: ReferenceKind.MANY_TO_MANY,
567
567
  type: meta.className,
568
+ target: meta.class,
569
+ targetMeta: meta,
568
570
  mappedBy: prop.name,
569
571
  pivotEntity: prop.pivotEntity,
570
572
  pivotTable: prop.pivotTable,
@@ -573,55 +575,51 @@ export class MetadataDiscovery {
573
575
  };
574
576
  this.applyNamingStrategy(prop.targetMeta, inverseProp);
575
577
  this.initCustomType(prop.targetMeta, inverseProp);
576
- this.initRelation(inverseProp);
577
578
  prop.targetMeta.properties[inverseName] = inverseProp;
578
579
  }
579
580
  if (pivotMeta) {
581
+ prop.pivotEntity = pivotMeta.class;
580
582
  this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
581
583
  return pivotMeta;
582
584
  }
583
- const exists = this.metadata.find(prop.pivotTable);
584
- if (exists) {
585
- prop.pivotEntity = exists.className;
586
- return exists;
587
- }
588
585
  let tableName = prop.pivotTable;
589
586
  let schemaName;
590
587
  if (prop.pivotTable.includes('.')) {
591
588
  [schemaName, tableName] = prop.pivotTable.split('.');
592
589
  }
593
590
  schemaName ??= meta.schema;
594
- const targetType = prop.targetMeta.className;
595
- const data = new EntityMetadata({
591
+ const targetMeta = prop.targetMeta;
592
+ const targetType = targetMeta.className;
593
+ const pivotMeta2 = new EntityMetadata({
596
594
  name: prop.pivotTable,
597
595
  className: prop.pivotTable,
598
596
  collection: tableName,
599
597
  schema: schemaName,
600
598
  pivotTable: true,
601
599
  });
602
- prop.pivotEntity = data.className;
600
+ prop.pivotEntity = pivotMeta2.class;
603
601
  if (prop.fixedOrder) {
604
- const primaryProp = this.defineFixedOrderProperty(prop, targetType);
605
- data.properties[primaryProp.name] = primaryProp;
602
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
603
+ pivotMeta2.properties[primaryProp.name] = primaryProp;
606
604
  }
607
605
  else {
608
- data.compositePK = true;
606
+ pivotMeta2.compositePK = true;
609
607
  }
610
608
  // handle self-referenced m:n with same default field names
611
609
  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));
610
+ prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(meta.tableName + '_1', name, meta.compositePK));
611
+ prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(meta.tableName + '_2', name, meta.compositePK));
614
612
  if (prop.inversedBy) {
615
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
613
+ const prop2 = targetMeta.properties[prop.inversedBy];
616
614
  prop2.inverseJoinColumns = prop.joinColumns;
617
615
  prop2.joinColumns = prop.inverseJoinColumns;
618
616
  }
619
617
  }
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);
618
+ pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
619
+ pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
620
+ return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
623
621
  }
624
- defineFixedOrderProperty(prop, targetType) {
622
+ defineFixedOrderProperty(prop, targetMeta) {
625
623
  const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
626
624
  const primaryProp = {
627
625
  name: pk,
@@ -635,7 +633,7 @@ export class MetadataDiscovery {
635
633
  this.initColumnType(primaryProp);
636
634
  prop.fixedOrderColumn = pk;
637
635
  if (prop.inversedBy) {
638
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
636
+ const prop2 = targetMeta.properties[prop.inversedBy];
639
637
  prop2.fixedOrder = true;
640
638
  prop2.fixedOrderColumn = pk;
641
639
  }
@@ -644,7 +642,8 @@ export class MetadataDiscovery {
644
642
  definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
645
643
  const ret = {
646
644
  name,
647
- type,
645
+ type: Utils.className(type),
646
+ target: type,
648
647
  kind: ReferenceKind.MANY_TO_ONE,
649
648
  cascade: [Cascade.ALL],
650
649
  fixedOrder: prop.fixedOrder,
@@ -654,6 +653,7 @@ export class MetadataDiscovery {
654
653
  autoincrement: false,
655
654
  updateRule: prop.updateRule,
656
655
  deleteRule: prop.deleteRule,
656
+ createForeignKeyConstraint: prop.createForeignKeyConstraint,
657
657
  };
658
658
  if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
659
659
  ret.updateRule ??= 'no action';
@@ -701,7 +701,7 @@ export class MetadataDiscovery {
701
701
  Object.values(meta.properties)
702
702
  .filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
703
703
  .forEach(prop => {
704
- const meta2 = this.metadata.get(prop.type);
704
+ const meta2 = prop.targetMeta;
705
705
  const prop2 = meta2.properties[prop.mappedBy];
706
706
  if (prop2 && !prop2.inversedBy) {
707
707
  prop2.inversedBy = prop.name;
@@ -709,7 +709,7 @@ export class MetadataDiscovery {
709
709
  });
710
710
  }
711
711
  defineBaseEntityProperties(meta) {
712
- const base = meta.extends && this.metadata.get(Utils.className(meta.extends));
712
+ const base = meta.extends && this.metadata.get(meta.extends);
713
713
  if (!base || base === meta) { // make sure we do not fall into infinite loop
714
714
  return 0;
715
715
  }
@@ -740,12 +740,9 @@ export class MetadataDiscovery {
740
740
  Utils.keys(base.hooks).forEach(type => {
741
741
  meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
742
742
  });
743
- if (meta.constructorParams.length === 0 && base.constructorParams.length > 0) {
743
+ if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
744
744
  meta.constructorParams = [...base.constructorParams];
745
745
  }
746
- if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
747
- meta.toJsonParams = [...base.toJsonParams];
748
- }
749
746
  return order;
750
747
  }
751
748
  initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
@@ -821,22 +818,30 @@ export class MetadataDiscovery {
821
818
  }
822
819
  return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
823
820
  };
821
+ const isParentArray = (prop) => {
822
+ if (prop.array) {
823
+ return true;
824
+ }
825
+ return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
826
+ };
824
827
  const rootProperty = getRootProperty(embeddedProp);
825
828
  const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
826
829
  const object = isParentObject(embeddedProp);
830
+ const array = isParentArray(embeddedProp);
827
831
  this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
828
832
  // the prefix of the parent cannot be a boolean; it already passed here
829
833
  const prefix = this.getPrefix(embeddedProp, parentProperty);
830
834
  const glue = object ? '~' : '_';
831
835
  for (const prop of Object.values(embeddable.properties)) {
832
836
  const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
833
- meta.properties[name] = Utils.copy(prop, false);
837
+ meta.properties[name] = Utils.copy(prop);
834
838
  meta.properties[name].name = name;
835
839
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
836
840
  meta.propertyOrder.set(name, (order += 0.01));
837
841
  embeddedProp.embeddedProps[prop.name] = meta.properties[name];
838
842
  meta.properties[name].persist ??= embeddedProp.persist;
839
- if (embeddedProp.nullable) {
843
+ const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
844
+ if (embeddedProp.nullable || refInArray) {
840
845
  meta.properties[name].nullable = true;
841
846
  }
842
847
  if (meta.properties[name].fieldNames) {
@@ -866,14 +871,17 @@ export class MetadataDiscovery {
866
871
  path = [embeddedProp.fieldNames[0]];
867
872
  }
868
873
  this.initFieldName(prop, true);
874
+ this.initRelation(prop);
869
875
  path.push(prop.fieldNames[0]);
870
876
  meta.properties[name].fieldNames = prop.fieldNames;
871
877
  meta.properties[name].embeddedPath = path;
872
- const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), prop.runtimeType ?? prop.type, true));
878
+ const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
879
+ const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
873
880
  meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
874
881
  meta.properties[name].persist = false; // only virtual as we store the whole object
875
882
  meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
876
883
  meta.properties[name].object = true;
884
+ this.initCustomType(meta, meta.properties[name], false, true);
877
885
  }
878
886
  this.initEmbeddables(meta, meta.properties[name], visited);
879
887
  }
@@ -896,7 +904,7 @@ export class MetadataDiscovery {
896
904
  }
897
905
  initSingleTableInheritance(meta, metadata) {
898
906
  if (meta.root !== meta && !meta.__processed) {
899
- meta.root = metadata.find(m => m.className === meta.root.className);
907
+ meta.root = metadata.find(m => m.class === meta.root.class);
900
908
  meta.root.__processed = true;
901
909
  }
902
910
  else {
@@ -905,17 +913,23 @@ export class MetadataDiscovery {
905
913
  if (!meta.root.discriminatorColumn) {
906
914
  return;
907
915
  }
908
- if (!meta.root.discriminatorMap) {
916
+ if (meta.root.discriminatorMap) {
917
+ const map = meta.root.discriminatorMap;
918
+ Object.keys(map)
919
+ .filter(key => typeof map[key] === 'string')
920
+ .forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
921
+ }
922
+ else {
909
923
  meta.root.discriminatorMap = {};
910
924
  const children = metadata
911
- .filter(m => m.root.className === meta.root.className && !m.abstract)
925
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
912
926
  .sort((a, b) => a.className.localeCompare(b.className));
913
927
  for (const m of children) {
914
928
  const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
915
- meta.root.discriminatorMap[name] = m.className;
929
+ meta.root.discriminatorMap[name] = m.class;
916
930
  }
917
931
  }
918
- meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, className]) => className === meta.className)?.[0];
932
+ meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
919
933
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
920
934
  this.createDiscriminatorProperty(meta.root);
921
935
  }
@@ -927,25 +941,44 @@ export class MetadataDiscovery {
927
941
  let i = 1;
928
942
  Object.values(meta.properties).forEach(prop => {
929
943
  const newProp = { ...prop };
930
- if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
944
+ const rootProp = meta.root.properties[prop.name];
945
+ if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
931
946
  const name = newProp.name;
932
947
  this.initFieldName(newProp, newProp.object);
948
+ newProp.renamedFrom = name;
933
949
  newProp.name = name + '_' + (i++);
934
950
  meta.root.addProperty(newProp);
951
+ // Track all field variants and map discriminator values to field names
952
+ if (!rootProp.stiFieldNames) {
953
+ this.initFieldName(prop, prop.object);
954
+ this.initFieldName(rootProp, rootProp.object);
955
+ rootProp.stiFieldNames = [...rootProp.fieldNames];
956
+ rootProp.stiFieldNameMap = {};
957
+ // Find which discriminator owns the original fieldNames
958
+ for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
959
+ const childMeta = this.metadata.find(childClass);
960
+ if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
961
+ rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
962
+ break;
963
+ }
964
+ }
965
+ }
966
+ rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
967
+ rootProp.stiFieldNames.push(...prop.fieldNames);
935
968
  newProp.nullable = true;
936
969
  newProp.name = name;
937
970
  newProp.hydrate = false;
938
971
  newProp.inherited = true;
939
972
  return;
940
973
  }
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]);
974
+ if (prop.enum && prop.items && rootProp?.items) {
975
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
943
976
  }
944
977
  newProp.nullable = true;
945
- newProp.inherited = true;
978
+ newProp.inherited = !rootProp;
946
979
  meta.root.addProperty(newProp);
947
980
  });
948
- meta.collection = meta.root.collection;
981
+ meta.tableName = meta.root.tableName;
949
982
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
950
983
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
951
984
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
@@ -967,7 +1000,7 @@ export class MetadataDiscovery {
967
1000
  }
968
1001
  }
969
1002
  initCheckConstraints(meta) {
970
- const map = this.createColumnMappingObject(meta);
1003
+ const map = meta.createColumnMappingObject();
971
1004
  for (const check of meta.checks) {
972
1005
  const columns = check.property ? meta.properties[check.property].fieldNames : [];
973
1006
  check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
@@ -977,7 +1010,7 @@ export class MetadataDiscovery {
977
1010
  }
978
1011
  if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
979
1012
  for (const prop of meta.props) {
980
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => Utils.isString(item))) {
1013
+ if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
981
1014
  this.initFieldName(prop);
982
1015
  meta.checks.push({
983
1016
  name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
@@ -1002,38 +1035,28 @@ export class MetadataDiscovery {
1002
1035
  }
1003
1036
  return;
1004
1037
  }
1005
- const map = this.createColumnMappingObject(meta);
1038
+ const map = meta.createColumnMappingObject();
1006
1039
  if (prop.generated instanceof Function) {
1007
1040
  prop.generated = prop.generated(map);
1008
1041
  }
1009
1042
  }
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) {
1043
+ getDefaultVersionValue(meta, prop) {
1019
1044
  if (typeof prop.defaultRaw !== 'undefined') {
1020
1045
  return prop.defaultRaw;
1021
1046
  }
1022
- /* v8 ignore next 3 */
1047
+ /* v8 ignore next */
1023
1048
  if (prop.default != null) {
1024
1049
  return '' + this.platform.quoteVersionValue(prop.default, prop);
1025
1050
  }
1026
- if (prop.type.toLowerCase() === 'date') {
1051
+ this.initCustomType(meta, prop, true);
1052
+ const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
1053
+ if (type === 'Date') {
1027
1054
  prop.length ??= this.platform.getDefaultVersionLength();
1028
1055
  return this.platform.getCurrentTimestampSQL(prop.length);
1029
1056
  }
1030
1057
  return '1';
1031
1058
  }
1032
1059
  inferDefaultValue(meta, prop) {
1033
- /* v8 ignore next 3 */
1034
- if (!meta.class) {
1035
- return;
1036
- }
1037
1060
  try {
1038
1061
  // try to create two entity instances to detect the value is stable
1039
1062
  const now = Date.now();
@@ -1061,12 +1084,12 @@ export class MetadataDiscovery {
1061
1084
  return;
1062
1085
  }
1063
1086
  let val = prop.default;
1064
- const raw = RawQueryFragment.getKnownFragment(val);
1087
+ const raw = Raw.getKnownFragment(val);
1065
1088
  if (raw) {
1066
1089
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1067
1090
  return;
1068
1091
  }
1069
- if (prop.customType instanceof ArrayType && Array.isArray(prop.default)) {
1092
+ if (Array.isArray(prop.default) && prop.customType) {
1070
1093
  val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1071
1094
  }
1072
1095
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
@@ -1094,13 +1117,13 @@ export class MetadataDiscovery {
1094
1117
  if (prop.version) {
1095
1118
  this.initDefaultValue(prop);
1096
1119
  meta.versionProperty = prop.name;
1097
- prop.defaultRaw = this.getDefaultVersionValue(prop);
1120
+ prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
1098
1121
  }
1099
1122
  if (prop.concurrencyCheck && !prop.primary) {
1100
1123
  meta.concurrencyCheckKeys.add(prop.name);
1101
1124
  }
1102
1125
  }
1103
- initCustomType(meta, prop) {
1126
+ initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
1104
1127
  // `prop.type` might be actually instance of custom type class
1105
1128
  if (Type.isMappedType(prop.type) && !prop.customType) {
1106
1129
  prop.customType = prop.type;
@@ -1108,41 +1131,61 @@ export class MetadataDiscovery {
1108
1131
  }
1109
1132
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1110
1133
  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;
1134
+ // if the type is an ORM defined mapped type without `ensureComparable: true`,
1135
+ // we use just the type name, to have more performant hydration code
1136
+ const type = Utils.keys(t).find(type => {
1137
+ return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
1138
+ });
1139
+ if (type) {
1140
+ prop.type = type === 'datetime' ? 'Date' : type;
1141
+ }
1142
+ else {
1143
+ prop.customType = new prop.type();
1144
+ prop.type = prop.customType.constructor.name;
1145
+ }
1146
+ }
1147
+ if (simple) {
1148
+ return;
1113
1149
  }
1114
1150
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1115
- prop.customType = new JsonType();
1151
+ prop.customType = new t.json();
1116
1152
  }
1117
1153
  if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1118
- prop.customType = new JsonType();
1154
+ prop.customType = new t.json();
1155
+ }
1156
+ if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1157
+ prop.customType = new t.json();
1119
1158
  }
1120
1159
  if (!prop.customType && prop.array && prop.items) {
1121
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1160
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1161
+ }
1162
+ const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1163
+ if (objectEmbeddable && !prop.customType && isArray) {
1164
+ prop.customType = new t.json();
1122
1165
  }
1123
1166
  // for number arrays we make sure to convert the items to numbers
1124
1167
  if (!prop.customType && prop.type === 'number[]') {
1125
- prop.customType = new ArrayType(i => +i);
1168
+ prop.customType = new t.array(i => +i);
1126
1169
  }
1127
1170
  // `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();
1171
+ if (!prop.customType && isArray) {
1172
+ prop.customType = new t.array();
1130
1173
  }
1131
1174
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1132
- prop.customType = new BlobType();
1175
+ prop.customType = new t.blob();
1133
1176
  }
1134
1177
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1135
- prop.customType = new Uint8ArrayType();
1178
+ prop.customType = new t.uint8array();
1136
1179
  }
1137
1180
  const mappedType = this.getMappedType(prop);
1138
1181
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1139
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1182
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1140
1183
  .filter(type => mappedType instanceof type)
1141
- .forEach(type => prop.customType = new type());
1184
+ .forEach((type) => prop.customType = new type());
1142
1185
  }
1143
1186
  if (prop.customType && !prop.columnTypes) {
1144
1187
  const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1145
- if (prop.customType.compareAsType() === 'any' && ![JsonType].some(t => prop.customType instanceof t)) {
1188
+ if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
1146
1189
  prop.runtimeType ??= mappedType.runtimeType;
1147
1190
  }
1148
1191
  else {
@@ -1162,16 +1205,16 @@ export class MetadataDiscovery {
1162
1205
  prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1163
1206
  prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1164
1207
  prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1165
- if (prop.customType instanceof BigIntType && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1208
+ if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1166
1209
  prop.customType.mode = prop.runtimeType.toLowerCase();
1167
1210
  }
1168
1211
  }
1169
- if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !prop.type?.toString().endsWith('[]')) {
1212
+ if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1170
1213
  prop.type = prop.customType.name;
1171
1214
  }
1172
- if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && this.metadata.get(prop.type).compositePK) {
1215
+ if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
1173
1216
  prop.customTypes = [];
1174
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1217
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1175
1218
  if (pk.customType) {
1176
1219
  prop.customTypes.push(pk.customType);
1177
1220
  prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
@@ -1183,7 +1226,7 @@ export class MetadataDiscovery {
1183
1226
  }
1184
1227
  }
1185
1228
  }
1186
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1229
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1187
1230
  if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1188
1231
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1189
1232
  }
@@ -1201,19 +1244,25 @@ export class MetadataDiscovery {
1201
1244
  if (prop.kind === ReferenceKind.SCALAR) {
1202
1245
  return;
1203
1246
  }
1204
- const meta2 = this.discovered.find(m => m.className === prop.type);
1205
- prop.referencedPKs = meta2.primaryKeys;
1247
+ // 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
1248
+ const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
1249
+ // If targetKey is specified, use that property instead of PKs for referencedPKs
1250
+ prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
1206
1251
  prop.targetMeta = meta2;
1207
1252
  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])}`;
1253
+ prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1209
1254
  }
1210
1255
  }
1211
1256
  initColumnType(prop) {
1212
1257
  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;
1258
+ // Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
1259
+ const targetProps = prop.targetMeta
1260
+ ? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
1261
+ : [];
1262
+ targetProps.map(targetProp => {
1263
+ prop.length ??= targetProp.length;
1264
+ prop.precision ??= targetProp.precision;
1265
+ prop.scale ??= targetProp.scale;
1217
1266
  });
1218
1267
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1219
1268
  delete prop.type;
@@ -1226,8 +1275,7 @@ export class MetadataDiscovery {
1226
1275
  if (prop.kind === ReferenceKind.SCALAR) {
1227
1276
  const mappedType = this.getMappedType(prop);
1228
1277
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1229
- if (mappedType instanceof UnknownType
1230
- && !prop.columnTypes
1278
+ if (mappedType instanceof t.unknown
1231
1279
  // it could be a runtime type from reflect-metadata
1232
1280
  && !SCALAR_TYPES.includes(prop.type)
1233
1281
  // or it might be inferred via ts-morph to some generic type alias
@@ -1240,23 +1288,28 @@ export class MetadataDiscovery {
1240
1288
  }
1241
1289
  return;
1242
1290
  }
1243
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1291
+ /* v8 ignore next */
1292
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1244
1293
  prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1245
1294
  return;
1246
1295
  }
1247
- const targetMeta = this.metadata.get(prop.type);
1296
+ const targetMeta = prop.targetMeta;
1248
1297
  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)];
1298
+ // Use targetKey property if specified, otherwise use primary key properties
1299
+ const referencedProps = prop.targetKey
1300
+ ? [targetMeta.properties[prop.targetKey]]
1301
+ : targetMeta.getPrimaryProps();
1302
+ for (const referencedProp of referencedProps) {
1303
+ this.initCustomType(targetMeta, referencedProp);
1304
+ this.initColumnType(referencedProp);
1305
+ const mappedType = this.getMappedType(referencedProp);
1306
+ let columnTypes = referencedProp.columnTypes;
1307
+ if (referencedProp.autoincrement) {
1308
+ columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
1256
1309
  }
1257
1310
  prop.columnTypes.push(...columnTypes);
1258
- if (!targetMeta.compositePK) {
1259
- prop.customType = pk.customType;
1311
+ if (!targetMeta.compositePK || prop.targetKey) {
1312
+ prop.customType = referencedProp.customType;
1260
1313
  }
1261
1314
  }
1262
1315
  }
@@ -1270,7 +1323,7 @@ export class MetadataDiscovery {
1270
1323
  t = 'enum';
1271
1324
  }
1272
1325
  else if (prop.enum) {
1273
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1326
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1274
1327
  }
1275
1328
  if (t === 'Date') {
1276
1329
  t = 'datetime';
@@ -1294,7 +1347,7 @@ export class MetadataDiscovery {
1294
1347
  return;
1295
1348
  }
1296
1349
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1297
- const meta2 = this.metadata.get(prop.type);
1350
+ const meta2 = prop.targetMeta;
1298
1351
  prop.unsigned = meta2.getPrimaryProps().some(pk => {
1299
1352
  this.initUnsigned(pk);
1300
1353
  return pk.unsigned;
@@ -1309,30 +1362,6 @@ export class MetadataDiscovery {
1309
1362
  prop.index ??= true;
1310
1363
  }
1311
1364
  }
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
1365
  shouldForceConstructorUsage(meta) {
1337
1366
  const forceConstructor = this.config.get('forceEntityConstructor');
1338
1367
  if (Array.isArray(forceConstructor)) {