@mikro-orm/core 7.0.0-dev.21 → 7.0.0-dev.211

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 +302 -276
  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 +40 -15
  27. package/entity/EntityLoader.d.ts +6 -6
  28. package/entity/EntityLoader.js +119 -82
  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 +594 -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 +23 -9
  46. package/errors.js +59 -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 +40 -23
  64. package/metadata/EntitySchema.js +81 -34
  65. package/metadata/MetadataDiscovery.d.ts +7 -10
  66. package/metadata/MetadataDiscovery.js +391 -331
  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 +97 -40
  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 +502 -0
  78. package/metadata/types.js +1 -0
  79. package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
  80. package/naming-strategy/AbstractNamingStrategy.js +14 -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 +24 -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 +7 -13
  93. package/platforms/Platform.js +20 -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 +290 -137
  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 +785 -207
  138. package/utils/Configuration.js +147 -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 { glob } from 'tinyglobby';
3
1
  import { EntityMetadata, } from '../typings.js';
4
- import { Utils } from '../utils/Utils.js';
2
+ import { compareArrays, Utils } from '../utils/Utils.js';
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,105 @@ 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]);
178
- this.discoverMissingTargets();
179
- }
180
- }
181
- }
182
- async discoverDirectories(paths) {
183
- paths = paths.map(path => Utils.normalizePath(path));
184
- const files = await glob(paths, { cwd: Utils.normalizePath(this.config.get('baseDir')) });
185
- this.logger.log('discovery', `- processing ${colors.cyan('' + files.length)} files`);
186
- const found = [];
187
- for (const filepath of files) {
188
- const filename = basename(filepath);
189
- if (!filename.match(/\.[cm]?[jt]s$/) ||
190
- filename.endsWith('.js.map') ||
191
- filename.match(/\.d\.[cm]?ts/) ||
192
- filename.startsWith('.') ||
193
- filename.match(/index\.[cm]?[jt]s$/)) {
194
- this.logger.log('discovery', `- ignoring file ${filename}`);
195
- continue;
196
- }
197
- const name = this.namingStrategy.getClassName(filename);
198
- const path = Utils.normalizePath(this.config.get('baseDir'), filepath);
199
- const targets = await this.getEntityClassOrSchema(path, name);
200
- for (const target of targets) {
201
- if (!(target instanceof Function) && !(target instanceof EntitySchema)) {
202
- this.logger.log('discovery', `- ignoring file ${filename}`);
203
- continue;
212
+ const schema = target instanceof EntitySchema ? target : undefined;
213
+ const isDiscoverable = typeof target === 'function' || schema;
214
+ if (isDiscoverable && target.name) {
215
+ // Get the actual class for EntitySchema, or use target directly for classes
216
+ const targetClass = schema ? schema.meta.class : target;
217
+ if (!this.metadata.has(targetClass)) {
218
+ this.discoverReferences([target], false);
219
+ this.discoverMissingTargets();
204
220
  }
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
221
  }
211
222
  }
212
- for (const [schema, path] of found) {
213
- this.discoverEntity(schema, path);
214
- }
215
223
  }
216
- discoverReferences(refs) {
224
+ discoverReferences(refs, validate = true) {
217
225
  const found = [];
218
226
  for (const entity of refs) {
219
- const schema = this.getSchema(this.prepare(entity));
227
+ if (typeof entity === 'string') {
228
+ throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
229
+ }
230
+ const schema = this.getSchema(entity);
220
231
  const meta = schema.init().meta;
221
- this.metadata.set(meta.className, meta);
232
+ this.metadata.set(meta.class, meta);
222
233
  found.push(schema);
223
234
  }
224
235
  // discover parents (base entities) automatically
225
236
  for (const meta of this.metadata) {
226
237
  let parent = meta.extends;
227
- if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.className)) {
228
- this.discoverReferences([parent]);
238
+ if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
239
+ this.discoverReferences([parent], false);
240
+ }
241
+ if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
242
+ this.discoverReferences([parent], false);
229
243
  }
230
- /* v8 ignore next 3 */
244
+ /* v8 ignore next */
231
245
  if (!meta.class) {
232
246
  continue;
233
247
  }
234
248
  parent = Object.getPrototypeOf(meta.class);
235
- if (parent.name !== '' && !this.metadata.has(parent.name)) {
236
- this.discoverReferences([parent]);
249
+ // Skip if parent is the auto-generated base class for the same entity (from setClass usage)
250
+ if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
251
+ this.discoverReferences([parent], false);
237
252
  }
238
253
  }
239
254
  for (const schema of found) {
240
255
  this.discoverEntity(schema);
241
256
  }
257
+ this.discoverMissingTargets();
258
+ if (validate) {
259
+ this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
260
+ }
242
261
  return this.discovered.filter(meta => found.find(m => m.name === meta.className));
243
262
  }
244
- reset(className) {
245
- const exists = this.discovered.findIndex(m => m.className === className);
263
+ reset(entityName) {
264
+ const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
246
265
  if (exists !== -1) {
247
- this.metadata.reset(this.discovered[exists].className);
266
+ this.metadata.reset(this.discovered[exists].class);
248
267
  this.discovered.splice(exists, 1);
249
268
  }
250
269
  }
251
- prepare(entity) {
252
- /* v8 ignore next 3 */
253
- if ('schema' in entity && entity.schema instanceof EntitySchema) {
254
- return entity.schema;
255
- }
270
+ getSchema(entity) {
256
271
  if (EntitySchema.REGISTRY.has(entity)) {
257
- return EntitySchema.REGISTRY.get(entity);
272
+ entity = EntitySchema.REGISTRY.get(entity);
258
273
  }
259
- return entity;
260
- }
261
- getSchema(entity, filepath) {
262
274
  if (entity instanceof EntitySchema) {
263
- if (filepath) {
264
- // initialize global metadata for given entity
265
- MetadataStorage.getMetadata(entity.meta.className, filepath);
266
- }
267
275
  const meta = Utils.copy(entity.meta, false);
268
276
  return EntitySchema.fromMetadata(meta);
269
277
  }
270
278
  const path = entity[MetadataStorage.PATH_SYMBOL];
271
279
  if (path) {
272
280
  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);
281
+ meta.path = path;
282
+ this.metadata.set(entity, meta);
275
283
  }
276
- const exists = this.metadata.has(entity.name);
277
- const meta = this.metadata.get(entity.name, true);
284
+ const exists = this.metadata.has(entity);
285
+ const meta = this.metadata.get(entity, true);
278
286
  meta.abstract ??= !(exists && meta.name);
279
287
  const schema = EntitySchema.fromMetadata(meta);
280
288
  schema.setClass(entity);
281
289
  return schema;
282
290
  }
283
- discoverEntity(schema, path) {
284
- this.logger.log('discovery', `- processing entity ${colors.cyan(schema.meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
291
+ getRootEntity(meta) {
292
+ const base = meta.extends && this.metadata.find(meta.extends);
293
+ if (!base || base === meta) { // make sure we do not fall into infinite loop
294
+ return meta;
295
+ }
296
+ const root = this.getRootEntity(base);
297
+ if (root.discriminatorColumn) {
298
+ return root;
299
+ }
300
+ return meta;
301
+ }
302
+ discoverEntity(schema) {
285
303
  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));
304
+ const path = meta.path;
305
+ this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
306
+ const root = this.getRootEntity(meta);
307
+ schema.meta.path = meta.path;
308
+ const cache = this.metadataProvider.getCachedMetadata(meta, root);
289
309
  if (cache) {
290
310
  this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
291
- this.metadataProvider.loadFromCache(meta, cache);
292
- meta.root = root;
293
311
  this.discovered.push(meta);
294
312
  return;
295
313
  }
@@ -298,50 +316,18 @@ export class MetadataDiscovery {
298
316
  this.inferDefaultValue(meta, prop);
299
317
  }
300
318
  // 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) {
319
+ this.metadataProvider.loadEntityMetadata(meta);
320
+ if (!meta.tableName && meta.name) {
303
321
  const entityName = root.discriminatorColumn ? root.name : meta.name;
304
- meta.collection = this.namingStrategy.classToTableName(entityName);
322
+ meta.tableName = this.namingStrategy.classToTableName(entityName);
305
323
  }
306
- delete meta.root; // to allow caching (as root can contain cycles)
307
- this.saveToCache(meta);
324
+ this.metadataProvider.saveToCache(meta);
308
325
  meta.root = root;
309
326
  this.discovered.push(meta);
310
327
  }
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
328
  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
329
  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));
330
+ return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
345
331
  }
346
332
  return Utils.defaultValue(prop, 'nullable', prop.optional);
347
333
  }
@@ -376,7 +362,7 @@ export class MetadataDiscovery {
376
362
  if (prop.joinColumns.length !== prop.columnTypes.length) {
377
363
  prop.columnTypes = prop.joinColumns.flatMap(field => {
378
364
  const matched = meta.props.find(p => p.fieldNames?.includes(field));
379
- /* v8 ignore next 3 */
365
+ /* v8 ignore next */
380
366
  if (!matched) {
381
367
  throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
382
368
  }
@@ -402,23 +388,23 @@ export class MetadataDiscovery {
402
388
  prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
403
389
  }
404
390
  }
405
- initManyToOneFieldName(prop, name) {
406
- const meta2 = this.metadata.get(prop.type);
391
+ initManyToOneFieldName(prop, name, tableName) {
392
+ const meta2 = prop.targetMeta;
407
393
  const ret = [];
408
394
  for (const primaryKey of meta2.primaryKeys) {
409
395
  this.initFieldName(meta2.properties[primaryKey]);
410
396
  for (const fieldName of meta2.properties[primaryKey].fieldNames) {
411
- ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
397
+ ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
412
398
  }
413
399
  }
414
400
  return ret;
415
401
  }
416
402
  initManyToManyFieldName(prop, name) {
417
- const meta2 = this.metadata.get(prop.type);
403
+ const meta2 = prop.targetMeta;
418
404
  return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
419
405
  }
420
406
  initManyToManyFields(meta, prop) {
421
- const meta2 = this.metadata.get(prop.type);
407
+ const meta2 = prop.targetMeta;
422
408
  Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
423
409
  const pivotMeta = this.metadata.find(prop.pivotEntity);
424
410
  const props = Object.values(pivotMeta?.properties ?? {});
@@ -439,35 +425,58 @@ export class MetadataDiscovery {
439
425
  prop.inverseJoinColumns ??= second.fieldNames;
440
426
  }
441
427
  if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
442
- prop.pivotTable = this.namingStrategy.joinTableName(meta.tableName, meta2.tableName, prop.name);
428
+ prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
443
429
  }
444
430
  if (prop.mappedBy) {
445
431
  const prop2 = meta2.properties[prop.mappedBy];
446
432
  this.initManyToManyFields(meta2, prop2);
447
433
  prop.pivotTable = prop2.pivotTable;
448
- prop.pivotEntity = prop2.pivotEntity ?? prop2.pivotTable;
434
+ prop.pivotEntity = prop2.pivotEntity;
449
435
  prop.fixedOrder = prop2.fixedOrder;
450
436
  prop.fixedOrderColumn = prop2.fixedOrderColumn;
451
437
  prop.joinColumns = prop2.inverseJoinColumns;
452
438
  prop.inverseJoinColumns = prop2.joinColumns;
453
439
  }
454
440
  prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
455
- prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK));
456
- prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
441
+ const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
442
+ const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
443
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
444
+ prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
445
+ }
446
+ isExplicitTableName(meta) {
447
+ return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
457
448
  }
458
449
  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);
450
+ const meta2 = prop.targetMeta;
451
+ let fieldNames;
452
+ // If targetKey is specified, use that property's field names instead of PKs
453
+ if (prop.targetKey) {
454
+ const targetProp = meta2.properties[prop.targetKey];
455
+ fieldNames = targetProp.fieldNames;
456
+ }
457
+ else {
458
+ fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
459
+ }
460
+ Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
462
461
  if (!prop.joinColumns) {
463
462
  prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
464
463
  }
465
464
  if (!prop.referencedColumnNames) {
466
465
  prop.referencedColumnNames = fieldNames;
467
466
  }
467
+ // Relations to composite PK targets need cascade update by default,
468
+ // since composite PKs are more likely to have mutable components
469
+ if (meta2.compositePK) {
470
+ prop.updateRule ??= 'cascade';
471
+ }
472
+ // Nullable relations default to 'set null' on delete - when the referenced
473
+ // entity is deleted, set the FK to null rather than failing
474
+ if (prop.nullable) {
475
+ prop.deleteRule ??= 'set null';
476
+ }
468
477
  }
469
478
  initOneToManyFields(prop) {
470
- const meta2 = this.metadata.get(prop.type);
479
+ const meta2 = prop.targetMeta;
471
480
  if (!prop.joinColumns) {
472
481
  prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
473
482
  }
@@ -480,12 +489,17 @@ export class MetadataDiscovery {
480
489
  const pks = Object.values(meta.properties).filter(prop => prop.primary);
481
490
  meta.primaryKeys = pks.map(prop => prop.name);
482
491
  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';
492
+ // FK used as PK, we need to cascade - applies to both single FK-as-PK
493
+ // and composite PKs where all PKs are FKs (e.g., pivot-like entities)
494
+ const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
495
+ if (fkPks.length > 0 && fkPks.length === pks.length) {
496
+ for (const pk of fkPks) {
497
+ pk.deleteRule ??= 'cascade';
498
+ pk.updateRule ??= 'cascade';
499
+ }
486
500
  }
487
501
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
488
- this.validator.validateEntityDefinition(this.metadata, meta.className, this.config.get('discovery'));
502
+ this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
489
503
  for (const prop of Object.values(meta.properties)) {
490
504
  this.initNullability(prop);
491
505
  this.applyNamingStrategy(meta, prop);
@@ -498,15 +512,21 @@ export class MetadataDiscovery {
498
512
  }
499
513
  this.initOwnColumns(meta);
500
514
  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;
515
+ meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
516
+ if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
517
+ meta.properties[meta.serializedPrimaryKey].persist ??= false;
505
518
  }
506
519
  if (this.platform.usesPivotTable()) {
507
520
  return Object.values(meta.properties)
508
521
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
509
- .map(prop => this.definePivotTableEntity(meta, prop));
522
+ .map(prop => {
523
+ const pivotMeta = this.definePivotTableEntity(meta, prop);
524
+ prop.pivotEntity = pivotMeta.class;
525
+ if (prop.inversedBy) {
526
+ prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
527
+ }
528
+ return pivotMeta;
529
+ });
510
530
  }
511
531
  return [];
512
532
  }
@@ -523,8 +543,11 @@ export class MetadataDiscovery {
523
543
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
524
544
  const value = prop[type];
525
545
  if (value instanceof Function) {
526
- const meta2 = this.metadata.get(prop.type);
546
+ const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
527
547
  prop[type] = value(meta2.properties)?.name;
548
+ if (type === 'pivotEntity' && value) {
549
+ prop[type] = value(meta2.properties);
550
+ }
528
551
  if (prop[type] == null) {
529
552
  throw MetadataError.fromWrongReference(meta, prop, type);
530
553
  }
@@ -540,9 +563,9 @@ export class MetadataDiscovery {
540
563
  }
541
564
  else if (fks.length >= 2) {
542
565
  [first, second] = fks;
543
- /* v8 ignore next 3 */
544
566
  }
545
567
  else {
568
+ /* v8 ignore next */
546
569
  return [];
547
570
  }
548
571
  // wrong FK order, first FK needs to point to the owning side
@@ -556,7 +579,9 @@ export class MetadataDiscovery {
556
579
  return [first, second];
557
580
  }
558
581
  definePivotTableEntity(meta, prop) {
559
- const pivotMeta = this.metadata.find(prop.pivotEntity);
582
+ const pivotMeta = prop.pivotEntity
583
+ ? this.metadata.find(prop.pivotEntity)
584
+ : this.metadata.getByClassName(prop.pivotTable, false);
560
585
  // ensure inverse side exists so we can join it when populating via pivot tables
561
586
  if (!prop.inversedBy && prop.targetMeta) {
562
587
  const inverseName = `${meta.className}_${prop.name}__inverse`;
@@ -565,6 +590,8 @@ export class MetadataDiscovery {
565
590
  name: inverseName,
566
591
  kind: ReferenceKind.MANY_TO_MANY,
567
592
  type: meta.className,
593
+ target: meta.class,
594
+ targetMeta: meta,
568
595
  mappedBy: prop.name,
569
596
  pivotEntity: prop.pivotEntity,
570
597
  pivotTable: prop.pivotTable,
@@ -573,55 +600,61 @@ export class MetadataDiscovery {
573
600
  };
574
601
  this.applyNamingStrategy(prop.targetMeta, inverseProp);
575
602
  this.initCustomType(prop.targetMeta, inverseProp);
576
- this.initRelation(inverseProp);
577
603
  prop.targetMeta.properties[inverseName] = inverseProp;
578
604
  }
579
605
  if (pivotMeta) {
606
+ prop.pivotEntity = pivotMeta.class;
580
607
  this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
581
608
  return pivotMeta;
582
609
  }
583
- const exists = this.metadata.find(prop.pivotTable);
584
- if (exists) {
585
- prop.pivotEntity = exists.className;
586
- return exists;
587
- }
588
610
  let tableName = prop.pivotTable;
589
611
  let schemaName;
590
612
  if (prop.pivotTable.includes('.')) {
591
613
  [schemaName, tableName] = prop.pivotTable.split('.');
592
614
  }
593
615
  schemaName ??= meta.schema;
594
- const targetType = prop.targetMeta.className;
595
- const data = new EntityMetadata({
616
+ const targetMeta = prop.targetMeta;
617
+ const targetType = targetMeta.className;
618
+ const pivotMeta2 = new EntityMetadata({
596
619
  name: prop.pivotTable,
597
620
  className: prop.pivotTable,
598
621
  collection: tableName,
599
622
  schema: schemaName,
600
623
  pivotTable: true,
601
624
  });
602
- prop.pivotEntity = data.className;
625
+ prop.pivotEntity = pivotMeta2.class;
603
626
  if (prop.fixedOrder) {
604
- const primaryProp = this.defineFixedOrderProperty(prop, targetType);
605
- data.properties[primaryProp.name] = primaryProp;
627
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
628
+ pivotMeta2.properties[primaryProp.name] = primaryProp;
606
629
  }
607
630
  else {
608
- data.compositePK = true;
631
+ pivotMeta2.compositePK = true;
609
632
  }
610
633
  // handle self-referenced m:n with same default field names
611
634
  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));
635
+ // use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
636
+ const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
637
+ prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
638
+ prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
614
639
  if (prop.inversedBy) {
615
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
640
+ const prop2 = targetMeta.properties[prop.inversedBy];
616
641
  prop2.inverseJoinColumns = prop.joinColumns;
617
642
  prop2.joinColumns = prop.inverseJoinColumns;
618
643
  }
644
+ // propagate updated joinColumns to all child entities that inherit this property (STI)
645
+ for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
646
+ const childProp = childMeta.properties[prop.name];
647
+ if (childProp) {
648
+ childProp.joinColumns = prop.joinColumns;
649
+ childProp.inverseJoinColumns = prop.inverseJoinColumns;
650
+ }
651
+ }
619
652
  }
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);
653
+ pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
654
+ pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
655
+ return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
623
656
  }
624
- defineFixedOrderProperty(prop, targetType) {
657
+ defineFixedOrderProperty(prop, targetMeta) {
625
658
  const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
626
659
  const primaryProp = {
627
660
  name: pk,
@@ -635,7 +668,7 @@ export class MetadataDiscovery {
635
668
  this.initColumnType(primaryProp);
636
669
  prop.fixedOrderColumn = pk;
637
670
  if (prop.inversedBy) {
638
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
671
+ const prop2 = targetMeta.properties[prop.inversedBy];
639
672
  prop2.fixedOrder = true;
640
673
  prop2.fixedOrderColumn = pk;
641
674
  }
@@ -644,7 +677,8 @@ export class MetadataDiscovery {
644
677
  definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
645
678
  const ret = {
646
679
  name,
647
- type,
680
+ type: Utils.className(type),
681
+ target: type,
648
682
  kind: ReferenceKind.MANY_TO_ONE,
649
683
  cascade: [Cascade.ALL],
650
684
  fixedOrder: prop.fixedOrder,
@@ -654,11 +688,11 @@ export class MetadataDiscovery {
654
688
  autoincrement: false,
655
689
  updateRule: prop.updateRule,
656
690
  deleteRule: prop.deleteRule,
691
+ createForeignKeyConstraint: prop.createForeignKeyConstraint,
657
692
  };
658
- if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
659
- ret.updateRule ??= 'no action';
660
- ret.deleteRule ??= 'no action';
661
- }
693
+ const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
694
+ ret.updateRule ??= defaultRule;
695
+ ret.deleteRule ??= defaultRule;
662
696
  const meta = this.metadata.get(type);
663
697
  ret.targetMeta = meta;
664
698
  ret.joinColumns = [];
@@ -701,7 +735,7 @@ export class MetadataDiscovery {
701
735
  Object.values(meta.properties)
702
736
  .filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
703
737
  .forEach(prop => {
704
- const meta2 = this.metadata.get(prop.type);
738
+ const meta2 = prop.targetMeta;
705
739
  const prop2 = meta2.properties[prop.mappedBy];
706
740
  if (prop2 && !prop2.inversedBy) {
707
741
  prop2.inversedBy = prop.name;
@@ -709,7 +743,7 @@ export class MetadataDiscovery {
709
743
  });
710
744
  }
711
745
  defineBaseEntityProperties(meta) {
712
- const base = meta.extends && this.metadata.get(Utils.className(meta.extends));
746
+ const base = meta.extends && this.metadata.get(meta.extends);
713
747
  if (!base || base === meta) { // make sure we do not fall into infinite loop
714
748
  return 0;
715
749
  }
@@ -740,12 +774,9 @@ export class MetadataDiscovery {
740
774
  Utils.keys(base.hooks).forEach(type => {
741
775
  meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
742
776
  });
743
- if (meta.constructorParams.length === 0 && base.constructorParams.length > 0) {
777
+ if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
744
778
  meta.constructorParams = [...base.constructorParams];
745
779
  }
746
- if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
747
- meta.toJsonParams = [...base.toJsonParams];
748
- }
749
780
  return order;
750
781
  }
751
782
  initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
@@ -821,22 +852,30 @@ export class MetadataDiscovery {
821
852
  }
822
853
  return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
823
854
  };
855
+ const isParentArray = (prop) => {
856
+ if (prop.array) {
857
+ return true;
858
+ }
859
+ return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
860
+ };
824
861
  const rootProperty = getRootProperty(embeddedProp);
825
862
  const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
826
863
  const object = isParentObject(embeddedProp);
864
+ const array = isParentArray(embeddedProp);
827
865
  this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
828
866
  // the prefix of the parent cannot be a boolean; it already passed here
829
867
  const prefix = this.getPrefix(embeddedProp, parentProperty);
830
868
  const glue = object ? '~' : '_';
831
869
  for (const prop of Object.values(embeddable.properties)) {
832
870
  const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
833
- meta.properties[name] = Utils.copy(prop, false);
871
+ meta.properties[name] = Utils.copy(prop);
834
872
  meta.properties[name].name = name;
835
873
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
836
874
  meta.propertyOrder.set(name, (order += 0.01));
837
875
  embeddedProp.embeddedProps[prop.name] = meta.properties[name];
838
876
  meta.properties[name].persist ??= embeddedProp.persist;
839
- if (embeddedProp.nullable) {
877
+ const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
878
+ if (embeddedProp.nullable || refInArray) {
840
879
  meta.properties[name].nullable = true;
841
880
  }
842
881
  if (meta.properties[name].fieldNames) {
@@ -866,15 +905,17 @@ export class MetadataDiscovery {
866
905
  path = [embeddedProp.fieldNames[0]];
867
906
  }
868
907
  this.initFieldName(prop, true);
908
+ this.initRelation(prop);
869
909
  path.push(prop.fieldNames[0]);
870
910
  meta.properties[name].fieldNames = prop.fieldNames;
871
911
  meta.properties[name].embeddedPath = path;
872
- const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), prop.runtimeType ?? prop.type, true));
912
+ const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
913
+ const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
873
914
  meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
874
915
  meta.properties[name].persist = false; // only virtual as we store the whole object
875
916
  meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
876
917
  meta.properties[name].object = true;
877
- this.initCustomType(meta, meta.properties[name], true);
918
+ this.initCustomType(meta, meta.properties[name], false, true);
878
919
  }
879
920
  this.initEmbeddables(meta, meta.properties[name], visited);
880
921
  }
@@ -897,7 +938,7 @@ export class MetadataDiscovery {
897
938
  }
898
939
  initSingleTableInheritance(meta, metadata) {
899
940
  if (meta.root !== meta && !meta.__processed) {
900
- meta.root = metadata.find(m => m.className === meta.root.className);
941
+ meta.root = metadata.find(m => m.class === meta.root.class);
901
942
  meta.root.__processed = true;
902
943
  }
903
944
  else {
@@ -906,17 +947,23 @@ export class MetadataDiscovery {
906
947
  if (!meta.root.discriminatorColumn) {
907
948
  return;
908
949
  }
909
- if (!meta.root.discriminatorMap) {
950
+ if (meta.root.discriminatorMap) {
951
+ const map = meta.root.discriminatorMap;
952
+ Object.keys(map)
953
+ .filter(key => typeof map[key] === 'string')
954
+ .forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
955
+ }
956
+ else {
910
957
  meta.root.discriminatorMap = {};
911
958
  const children = metadata
912
- .filter(m => m.root.className === meta.root.className && !m.abstract)
959
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
913
960
  .sort((a, b) => a.className.localeCompare(b.className));
914
961
  for (const m of children) {
915
962
  const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
916
- meta.root.discriminatorMap[name] = m.className;
963
+ meta.root.discriminatorMap[name] = m.class;
917
964
  }
918
965
  }
919
- meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, className]) => className === meta.className)?.[0];
966
+ meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
920
967
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
921
968
  this.createDiscriminatorProperty(meta.root);
922
969
  }
@@ -925,28 +972,46 @@ export class MetadataDiscovery {
925
972
  if (meta.root === meta) {
926
973
  return;
927
974
  }
928
- let i = 1;
929
975
  Object.values(meta.properties).forEach(prop => {
930
976
  const newProp = { ...prop };
931
- if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
977
+ const rootProp = meta.root.properties[prop.name];
978
+ if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
932
979
  const name = newProp.name;
933
980
  this.initFieldName(newProp, newProp.object);
934
- newProp.name = name + '_' + (i++);
981
+ newProp.renamedFrom = name;
982
+ newProp.name = `${name}_${meta._id}`;
935
983
  meta.root.addProperty(newProp);
984
+ this.initFieldName(prop, prop.object);
985
+ // Track all field variants and map discriminator values to field names
986
+ if (!rootProp.stiFieldNames) {
987
+ this.initFieldName(rootProp, rootProp.object);
988
+ rootProp.stiFieldNames = [...rootProp.fieldNames];
989
+ rootProp.stiFieldNameMap = {};
990
+ // Find which discriminator owns the original fieldNames
991
+ for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
992
+ const childMeta = this.metadata.find(childClass);
993
+ if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
994
+ rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
995
+ break;
996
+ }
997
+ }
998
+ }
999
+ rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
1000
+ rootProp.stiFieldNames.push(...prop.fieldNames);
936
1001
  newProp.nullable = true;
937
1002
  newProp.name = name;
938
1003
  newProp.hydrate = false;
939
1004
  newProp.inherited = true;
940
1005
  return;
941
1006
  }
942
- if (prop.enum && prop.items && meta.root.properties[prop.name]?.items) {
943
- newProp.items = Utils.unique([...meta.root.properties[prop.name].items, ...prop.items]);
1007
+ if (prop.enum && prop.items && rootProp?.items) {
1008
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
944
1009
  }
945
1010
  newProp.nullable = true;
946
- newProp.inherited = true;
1011
+ newProp.inherited = !rootProp;
947
1012
  meta.root.addProperty(newProp);
948
1013
  });
949
- meta.collection = meta.root.collection;
1014
+ meta.tableName = meta.root.tableName;
950
1015
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
951
1016
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
952
1017
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
@@ -968,7 +1033,7 @@ export class MetadataDiscovery {
968
1033
  }
969
1034
  }
970
1035
  initCheckConstraints(meta) {
971
- const map = this.createColumnMappingObject(meta);
1036
+ const map = meta.createColumnMappingObject();
972
1037
  for (const check of meta.checks) {
973
1038
  const columns = check.property ? meta.properties[check.property].fieldNames : [];
974
1039
  check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
@@ -978,7 +1043,7 @@ export class MetadataDiscovery {
978
1043
  }
979
1044
  if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
980
1045
  for (const prop of meta.props) {
981
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => Utils.isString(item))) {
1046
+ if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
982
1047
  this.initFieldName(prop);
983
1048
  meta.checks.push({
984
1049
  name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
@@ -1003,38 +1068,28 @@ export class MetadataDiscovery {
1003
1068
  }
1004
1069
  return;
1005
1070
  }
1006
- const map = this.createColumnMappingObject(meta);
1071
+ const map = meta.createColumnMappingObject();
1007
1072
  if (prop.generated instanceof Function) {
1008
1073
  prop.generated = prop.generated(map);
1009
1074
  }
1010
1075
  }
1011
- createColumnMappingObject(meta) {
1012
- return Object.values(meta.properties).reduce((o, prop) => {
1013
- if (prop.fieldNames) {
1014
- o[prop.name] = prop.fieldNames[0];
1015
- }
1016
- return o;
1017
- }, {});
1018
- }
1019
- getDefaultVersionValue(prop) {
1076
+ getDefaultVersionValue(meta, prop) {
1020
1077
  if (typeof prop.defaultRaw !== 'undefined') {
1021
1078
  return prop.defaultRaw;
1022
1079
  }
1023
- /* v8 ignore next 3 */
1080
+ /* v8 ignore next */
1024
1081
  if (prop.default != null) {
1025
1082
  return '' + this.platform.quoteVersionValue(prop.default, prop);
1026
1083
  }
1027
- if (prop.type.toLowerCase() === 'date') {
1084
+ this.initCustomType(meta, prop, true);
1085
+ const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
1086
+ if (type === 'Date') {
1028
1087
  prop.length ??= this.platform.getDefaultVersionLength();
1029
1088
  return this.platform.getCurrentTimestampSQL(prop.length);
1030
1089
  }
1031
1090
  return '1';
1032
1091
  }
1033
1092
  inferDefaultValue(meta, prop) {
1034
- /* v8 ignore next 3 */
1035
- if (!meta.class) {
1036
- return;
1037
- }
1038
1093
  try {
1039
1094
  // try to create two entity instances to detect the value is stable
1040
1095
  const now = Date.now();
@@ -1062,12 +1117,12 @@ export class MetadataDiscovery {
1062
1117
  return;
1063
1118
  }
1064
1119
  let val = prop.default;
1065
- const raw = RawQueryFragment.getKnownFragment(val);
1120
+ const raw = Raw.getKnownFragment(val);
1066
1121
  if (raw) {
1067
1122
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1068
1123
  return;
1069
1124
  }
1070
- if (prop.customType instanceof ArrayType && Array.isArray(prop.default)) {
1125
+ if (Array.isArray(prop.default) && prop.customType) {
1071
1126
  val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1072
1127
  }
1073
1128
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
@@ -1095,13 +1150,13 @@ export class MetadataDiscovery {
1095
1150
  if (prop.version) {
1096
1151
  this.initDefaultValue(prop);
1097
1152
  meta.versionProperty = prop.name;
1098
- prop.defaultRaw = this.getDefaultVersionValue(prop);
1153
+ prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
1099
1154
  }
1100
1155
  if (prop.concurrencyCheck && !prop.primary) {
1101
1156
  meta.concurrencyCheckKeys.add(prop.name);
1102
1157
  }
1103
1158
  }
1104
- initCustomType(meta, prop, objectEmbeddable = false) {
1159
+ initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
1105
1160
  // `prop.type` might be actually instance of custom type class
1106
1161
  if (Type.isMappedType(prop.type) && !prop.customType) {
1107
1162
  prop.customType = prop.type;
@@ -1109,45 +1164,61 @@ export class MetadataDiscovery {
1109
1164
  }
1110
1165
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1111
1166
  if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
1112
- prop.customType = new prop.type();
1113
- prop.type = prop.customType.constructor.name;
1167
+ // if the type is an ORM defined mapped type without `ensureComparable: true`,
1168
+ // we use just the type name, to have more performant hydration code
1169
+ const type = Utils.keys(t).find(type => {
1170
+ return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
1171
+ });
1172
+ if (type) {
1173
+ prop.type = type === 'datetime' ? 'Date' : type;
1174
+ }
1175
+ else {
1176
+ prop.customType = new prop.type();
1177
+ prop.type = prop.customType.constructor.name;
1178
+ }
1179
+ }
1180
+ if (simple) {
1181
+ return;
1114
1182
  }
1115
1183
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1116
- prop.customType = new JsonType();
1184
+ prop.customType = new t.json();
1117
1185
  }
1118
1186
  if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1119
- prop.customType = new JsonType();
1187
+ prop.customType = new t.json();
1188
+ }
1189
+ if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1190
+ prop.customType = new t.json();
1120
1191
  }
1121
1192
  if (!prop.customType && prop.array && prop.items) {
1122
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1193
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1123
1194
  }
1124
1195
  const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1125
1196
  if (objectEmbeddable && !prop.customType && isArray) {
1126
- prop.customType = new JsonType();
1197
+ prop.customType = new t.json();
1127
1198
  }
1128
1199
  // for number arrays we make sure to convert the items to numbers
1129
1200
  if (!prop.customType && prop.type === 'number[]') {
1130
- prop.customType = new ArrayType(i => +i);
1201
+ prop.customType = new t.array(i => +i);
1131
1202
  }
1132
1203
  // `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
1133
1204
  if (!prop.customType && isArray) {
1134
- prop.customType = new ArrayType();
1205
+ prop.customType = new t.array();
1135
1206
  }
1136
1207
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1137
- prop.customType = new BlobType();
1208
+ prop.customType = new t.blob();
1138
1209
  }
1139
1210
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1140
- prop.customType = new Uint8ArrayType();
1211
+ prop.customType = new t.uint8array();
1141
1212
  }
1142
1213
  const mappedType = this.getMappedType(prop);
1143
1214
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1144
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1215
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1145
1216
  .filter(type => mappedType instanceof type)
1146
- .forEach(type => prop.customType = new type());
1217
+ .forEach((type) => prop.customType = new type());
1147
1218
  }
1148
1219
  if (prop.customType && !prop.columnTypes) {
1149
1220
  const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1150
- if (prop.customType.compareAsType() === 'any' && ![JsonType].some(t => prop.customType instanceof t)) {
1221
+ if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
1151
1222
  prop.runtimeType ??= mappedType.runtimeType;
1152
1223
  }
1153
1224
  else {
@@ -1167,16 +1238,16 @@ export class MetadataDiscovery {
1167
1238
  prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1168
1239
  prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1169
1240
  prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1170
- if (prop.customType instanceof BigIntType && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1241
+ if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1171
1242
  prop.customType.mode = prop.runtimeType.toLowerCase();
1172
1243
  }
1173
1244
  }
1174
1245
  if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1175
1246
  prop.type = prop.customType.name;
1176
1247
  }
1177
- if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && this.metadata.get(prop.type).compositePK) {
1248
+ if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
1178
1249
  prop.customTypes = [];
1179
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1250
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1180
1251
  if (pk.customType) {
1181
1252
  prop.customTypes.push(pk.customType);
1182
1253
  prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
@@ -1188,7 +1259,7 @@ export class MetadataDiscovery {
1188
1259
  }
1189
1260
  }
1190
1261
  }
1191
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1262
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1192
1263
  if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1193
1264
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1194
1265
  }
@@ -1206,19 +1277,28 @@ export class MetadataDiscovery {
1206
1277
  if (prop.kind === ReferenceKind.SCALAR) {
1207
1278
  return;
1208
1279
  }
1209
- const meta2 = this.discovered.find(m => m.className === prop.type);
1210
- prop.referencedPKs = meta2.primaryKeys;
1280
+ // 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
1281
+ const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
1282
+ // If targetKey is specified, use that property instead of PKs for referencedPKs
1283
+ prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
1211
1284
  prop.targetMeta = meta2;
1285
+ if (meta2.view) {
1286
+ prop.createForeignKeyConstraint = false;
1287
+ }
1212
1288
  if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
1213
- prop.formula = a => `${a}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1289
+ prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1214
1290
  }
1215
1291
  }
1216
1292
  initColumnType(prop) {
1217
1293
  this.initUnsigned(prop);
1218
- this.metadata.find(prop.type)?.getPrimaryProps().map(pk => {
1219
- prop.length ??= pk.length;
1220
- prop.precision ??= pk.precision;
1221
- prop.scale ??= pk.scale;
1294
+ // Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
1295
+ const targetProps = prop.targetMeta
1296
+ ? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
1297
+ : [];
1298
+ targetProps.map(targetProp => {
1299
+ prop.length ??= targetProp.length;
1300
+ prop.precision ??= targetProp.precision;
1301
+ prop.scale ??= targetProp.scale;
1222
1302
  });
1223
1303
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1224
1304
  delete prop.type;
@@ -1231,8 +1311,7 @@ export class MetadataDiscovery {
1231
1311
  if (prop.kind === ReferenceKind.SCALAR) {
1232
1312
  const mappedType = this.getMappedType(prop);
1233
1313
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1234
- if (mappedType instanceof UnknownType
1235
- && !prop.columnTypes
1314
+ if (mappedType instanceof t.unknown
1236
1315
  // it could be a runtime type from reflect-metadata
1237
1316
  && !SCALAR_TYPES.includes(prop.type)
1238
1317
  // or it might be inferred via ts-morph to some generic type alias
@@ -1245,23 +1324,28 @@ export class MetadataDiscovery {
1245
1324
  }
1246
1325
  return;
1247
1326
  }
1248
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1327
+ /* v8 ignore next */
1328
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1249
1329
  prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1250
1330
  return;
1251
1331
  }
1252
- const targetMeta = this.metadata.get(prop.type);
1332
+ const targetMeta = prop.targetMeta;
1253
1333
  prop.columnTypes = [];
1254
- for (const pk of targetMeta.getPrimaryProps()) {
1255
- this.initCustomType(targetMeta, pk);
1256
- this.initColumnType(pk);
1257
- const mappedType = this.getMappedType(pk);
1258
- let columnTypes = pk.columnTypes;
1259
- if (pk.autoincrement) {
1260
- columnTypes = [mappedType.getColumnType({ ...pk, autoincrement: false }, this.platform)];
1334
+ // Use targetKey property if specified, otherwise use primary key properties
1335
+ const referencedProps = prop.targetKey
1336
+ ? [targetMeta.properties[prop.targetKey]]
1337
+ : targetMeta.getPrimaryProps();
1338
+ for (const referencedProp of referencedProps) {
1339
+ this.initCustomType(targetMeta, referencedProp);
1340
+ this.initColumnType(referencedProp);
1341
+ const mappedType = this.getMappedType(referencedProp);
1342
+ let columnTypes = referencedProp.columnTypes;
1343
+ if (referencedProp.autoincrement) {
1344
+ columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
1261
1345
  }
1262
1346
  prop.columnTypes.push(...columnTypes);
1263
- if (!targetMeta.compositePK) {
1264
- prop.customType = pk.customType;
1347
+ if (!targetMeta.compositePK || prop.targetKey) {
1348
+ prop.customType = referencedProp.customType;
1265
1349
  }
1266
1350
  }
1267
1351
  }
@@ -1275,7 +1359,7 @@ export class MetadataDiscovery {
1275
1359
  t = 'enum';
1276
1360
  }
1277
1361
  else if (prop.enum) {
1278
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1362
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1279
1363
  }
1280
1364
  if (t === 'Date') {
1281
1365
  t = 'datetime';
@@ -1299,7 +1383,7 @@ export class MetadataDiscovery {
1299
1383
  return;
1300
1384
  }
1301
1385
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1302
- const meta2 = this.metadata.get(prop.type);
1386
+ const meta2 = prop.targetMeta;
1303
1387
  prop.unsigned = meta2.getPrimaryProps().some(pk => {
1304
1388
  this.initUnsigned(pk);
1305
1389
  return pk.unsigned;
@@ -1314,30 +1398,6 @@ export class MetadataDiscovery {
1314
1398
  prop.index ??= true;
1315
1399
  }
1316
1400
  }
1317
- async getEntityClassOrSchema(path, name) {
1318
- const exports = await Utils.dynamicImport(path);
1319
- const targets = Object.values(exports)
1320
- .filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
1321
- // ignore class implementations that are linked from an EntitySchema
1322
- for (const item of targets) {
1323
- if (item instanceof EntitySchema) {
1324
- targets.forEach((item2, idx) => {
1325
- if (item.meta.class === item2) {
1326
- targets.splice(idx, 1);
1327
- }
1328
- });
1329
- }
1330
- }
1331
- if (targets.length > 0) {
1332
- return targets;
1333
- }
1334
- const target = exports.default ?? exports[name];
1335
- /* v8 ignore next 3 */
1336
- if (!target) {
1337
- throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
1338
- }
1339
- return [target];
1340
- }
1341
1401
  shouldForceConstructorUsage(meta) {
1342
1402
  const forceConstructor = this.config.get('forceEntityConstructor');
1343
1403
  if (Array.isArray(forceConstructor)) {