@mikro-orm/core 7.0.0-dev.18 → 7.0.0-dev.181

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 +35 -15
  27. package/entity/EntityLoader.d.ts +6 -6
  28. package/entity/EntityLoader.js +117 -77
  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 +583 -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 +20 -22
  64. package/metadata/EntitySchema.js +59 -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 +96 -39
  73. package/metadata/discover-entities.d.ts +5 -0
  74. package/metadata/discover-entities.js +40 -0
  75. package/metadata/index.d.ts +1 -1
  76. package/metadata/index.js +1 -1
  77. package/metadata/types.d.ts +502 -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 +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 +189 -116
  120. package/typings.js +59 -44
  121. package/unit-of-work/ChangeSet.d.ts +2 -6
  122. package/unit-of-work/ChangeSet.js +4 -5
  123. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  124. package/unit-of-work/ChangeSetComputer.js +26 -13
  125. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  126. package/unit-of-work/ChangeSetPersister.js +70 -34
  127. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  128. package/unit-of-work/CommitOrderCalculator.js +13 -13
  129. package/unit-of-work/IdentityMap.d.ts +12 -0
  130. package/unit-of-work/IdentityMap.js +39 -1
  131. package/unit-of-work/UnitOfWork.d.ts +23 -3
  132. package/unit-of-work/UnitOfWork.js +175 -98
  133. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  134. package/utils/AbstractSchemaGenerator.js +18 -16
  135. package/utils/AsyncContext.d.ts +6 -0
  136. package/utils/AsyncContext.js +42 -0
  137. package/utils/Configuration.d.ts +764 -207
  138. package/utils/Configuration.js +146 -190
  139. package/utils/ConfigurationLoader.d.ts +1 -54
  140. package/utils/ConfigurationLoader.js +1 -352
  141. package/utils/Cursor.d.ts +0 -3
  142. package/utils/Cursor.js +27 -11
  143. package/utils/DataloaderUtils.d.ts +15 -5
  144. package/utils/DataloaderUtils.js +64 -30
  145. package/utils/EntityComparator.d.ts +13 -9
  146. package/utils/EntityComparator.js +101 -42
  147. package/utils/QueryHelper.d.ts +14 -6
  148. package/utils/QueryHelper.js +87 -25
  149. package/utils/RawQueryFragment.d.ts +48 -25
  150. package/utils/RawQueryFragment.js +66 -70
  151. package/utils/RequestContext.js +2 -2
  152. package/utils/TransactionContext.js +2 -2
  153. package/utils/TransactionManager.d.ts +65 -0
  154. package/utils/TransactionManager.js +223 -0
  155. package/utils/Utils.d.ts +13 -126
  156. package/utils/Utils.js +100 -391
  157. package/utils/clone.js +8 -23
  158. package/utils/env-vars.d.ts +7 -0
  159. package/utils/env-vars.js +97 -0
  160. package/utils/fs-utils.d.ts +32 -0
  161. package/utils/fs-utils.js +178 -0
  162. package/utils/index.d.ts +2 -1
  163. package/utils/index.js +2 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +55 -4
  166. package/decorators/Check.d.ts +0 -3
  167. package/decorators/Check.js +0 -13
  168. package/decorators/CreateRequestContext.d.ts +0 -3
  169. package/decorators/CreateRequestContext.js +0 -32
  170. package/decorators/Embeddable.d.ts +0 -8
  171. package/decorators/Embeddable.js +0 -11
  172. package/decorators/Embedded.d.ts +0 -12
  173. package/decorators/Embedded.js +0 -18
  174. package/decorators/Entity.d.ts +0 -18
  175. package/decorators/Entity.js +0 -12
  176. package/decorators/Enum.d.ts +0 -9
  177. package/decorators/Enum.js +0 -16
  178. package/decorators/Filter.d.ts +0 -2
  179. package/decorators/Filter.js +0 -8
  180. package/decorators/Formula.d.ts +0 -4
  181. package/decorators/Formula.js +0 -15
  182. package/decorators/Indexed.d.ts +0 -19
  183. package/decorators/Indexed.js +0 -20
  184. package/decorators/ManyToMany.d.ts +0 -40
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -32
  187. package/decorators/ManyToOne.js +0 -14
  188. package/decorators/OneToMany.d.ts +0 -28
  189. package/decorators/OneToMany.js +0 -17
  190. package/decorators/OneToOne.d.ts +0 -26
  191. package/decorators/OneToOne.js +0 -7
  192. package/decorators/PrimaryKey.d.ts +0 -8
  193. package/decorators/PrimaryKey.js +0 -20
  194. package/decorators/Property.d.ts +0 -250
  195. package/decorators/Property.js +0 -32
  196. package/decorators/Transactional.d.ts +0 -13
  197. package/decorators/Transactional.js +0 -28
  198. package/decorators/hooks.d.ts +0 -16
  199. package/decorators/hooks.js +0 -47
  200. package/decorators/index.d.ts +0 -17
  201. package/decorators/index.js +0 -17
  202. package/entity/ArrayCollection.d.ts +0 -116
  203. package/entity/ArrayCollection.js +0 -402
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/exports.d.ts +0 -24
  207. package/exports.js +0 -23
  208. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  209. package/metadata/ReflectMetadataProvider.js +0 -44
  210. package/utils/resolveContextProvider.d.ts +0 -10
  211. package/utils/resolveContextProvider.js +0 -28
@@ -1,22 +1,21 @@
1
- import { basename, extname } from 'node:path';
2
- import globby from 'globby';
3
1
  import { EntityMetadata, } from '../typings.js';
4
- import { Utils } from '../utils/Utils.js';
2
+ import { compareArrays, Utils } from '../utils/Utils.js';
5
3
  import { MetadataValidator } from './MetadataValidator.js';
4
+ import { MetadataProvider } from './MetadataProvider.js';
6
5
  import { MetadataStorage } from './MetadataStorage.js';
7
6
  import { EntitySchema } from './EntitySchema.js';
8
7
  import { Cascade, ReferenceKind } from '../enums.js';
9
8
  import { MetadataError } from '../errors.js';
10
- import { ArrayType, BigIntType, BlobType, DateType, DecimalType, DoubleType, EnumArrayType, IntervalType, JsonType, t, Type, Uint8ArrayType, UnknownType, } from '../types/index.js';
9
+ import { t, Type } from '../types/index.js';
11
10
  import { colors } from '../logging/colors.js';
12
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
11
+ import { raw, Raw } from '../utils/RawQueryFragment.js';
12
+ import { BaseEntity } from '../entity/BaseEntity.js';
13
13
  export class MetadataDiscovery {
14
14
  metadata;
15
15
  platform;
16
16
  config;
17
17
  namingStrategy;
18
18
  metadataProvider;
19
- cache;
20
19
  logger;
21
20
  schemaHelper;
22
21
  validator = new MetadataValidator();
@@ -27,13 +26,14 @@ export class MetadataDiscovery {
27
26
  this.config = config;
28
27
  this.namingStrategy = this.config.getNamingStrategy();
29
28
  this.metadataProvider = this.config.getMetadataProvider();
30
- this.cache = this.config.getMetadataCacheAdapter();
31
29
  this.logger = this.config.getLogger();
32
30
  this.schemaHelper = this.platform.getSchemaHelper();
33
31
  }
34
32
  async discover(preferTs = true) {
33
+ this.discovered.length = 0;
35
34
  const startTime = Date.now();
36
- this.logger.log('discovery', `ORM entity discovery started, using ${colors.cyan(this.metadataProvider.constructor.name)}`);
35
+ const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
36
+ this.logger.log('discovery', `ORM entity discovery started${suffix}`);
37
37
  await this.findEntities(preferTs);
38
38
  for (const meta of this.discovered) {
39
39
  /* v8 ignore next */
@@ -47,10 +47,13 @@ export class MetadataDiscovery {
47
47
  await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
48
48
  return storage;
49
49
  }
50
- discoverSync(preferTs = true) {
50
+ discoverSync() {
51
+ this.discovered.length = 0;
51
52
  const startTime = Date.now();
52
- this.logger.log('discovery', `ORM entity discovery started, using ${colors.cyan(this.metadataProvider.constructor.name)} in sync mode`);
53
- this.findEntities(preferTs, true);
53
+ const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
54
+ this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
55
+ const refs = this.config.get('entities');
56
+ this.discoverReferences(refs);
54
57
  for (const meta of this.discovered) {
55
58
  /* v8 ignore next */
56
59
  void this.config.get('discovery').onMetadata?.(meta, this.platform);
@@ -70,15 +73,48 @@ export class MetadataDiscovery {
70
73
  .sort((a, b) => b.root.name.localeCompare(a.root.name))
71
74
  .forEach(meta => {
72
75
  this.platform.validateMetadata(meta);
73
- discovered.set(meta.className, meta);
76
+ discovered.set(meta.class, meta);
74
77
  });
78
+ for (const meta of discovered) {
79
+ meta.root = discovered.get(meta.root.class);
80
+ }
75
81
  return discovered;
76
82
  }
83
+ initAccessors(meta) {
84
+ for (const prop of Object.values(meta.properties)) {
85
+ if (!prop.accessor || meta.properties[prop.accessor]) {
86
+ continue;
87
+ }
88
+ const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
89
+ if (desc?.get || desc?.set) {
90
+ this.initFieldName(prop);
91
+ const accessor = prop.name;
92
+ prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
93
+ if (prop.accessor === true) {
94
+ prop.getter = prop.setter = true;
95
+ }
96
+ else {
97
+ prop.getter = prop.setter = false;
98
+ }
99
+ prop.accessor = accessor;
100
+ prop.serializedName ??= accessor;
101
+ Utils.renameKey(meta.properties, accessor, prop.name);
102
+ }
103
+ else {
104
+ const name = prop.name;
105
+ prop.name = prop.accessor;
106
+ this.initFieldName(prop);
107
+ prop.serializedName ??= prop.accessor;
108
+ prop.name = name;
109
+ }
110
+ }
111
+ }
77
112
  processDiscoveredEntities(discovered) {
78
113
  for (const meta of discovered) {
79
114
  let i = 1;
80
115
  Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
81
116
  Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
117
+ this.initAccessors(meta);
82
118
  }
83
119
  // ignore base entities (not annotated with @Entity)
84
120
  const filtered = discovered.filter(meta => meta.root.name);
@@ -86,66 +122,61 @@ export class MetadataDiscovery {
86
122
  filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
87
123
  filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
88
124
  filtered.forEach(meta => this.defineBaseEntityProperties(meta));
89
- filtered.forEach(meta => this.metadata.set(meta.className, EntitySchema.fromMetadata(meta).init().meta));
125
+ filtered.forEach(meta => {
126
+ const newMeta = EntitySchema.fromMetadata(meta).init().meta;
127
+ return this.metadata.set(newMeta.class, newMeta);
128
+ });
90
129
  filtered.forEach(meta => this.initAutoincrement(meta));
91
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initEmbeddables(meta, prop)));
92
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initFactoryField(meta, prop)));
93
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initFieldName(prop)));
94
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initVersionProperty(meta, prop)));
95
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initCustomType(meta, prop)));
96
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initGeneratedColumn(meta, prop)));
130
+ const forEachProp = (cb) => {
131
+ filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
132
+ };
133
+ forEachProp((m, p) => this.initFactoryField(m, p));
134
+ forEachProp((_m, p) => this.initRelation(p));
135
+ forEachProp((m, p) => this.initEmbeddables(m, p));
136
+ forEachProp((_m, p) => this.initFieldName(p));
137
+ forEachProp((m, p) => this.initVersionProperty(m, p));
138
+ forEachProp((m, p) => this.initCustomType(m, p));
139
+ forEachProp((m, p) => this.initGeneratedColumn(m, p));
97
140
  filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
98
141
  filtered.forEach(meta => this.initCheckConstraints(meta));
99
- for (const meta of filtered) {
100
- for (const prop of Object.values(meta.properties)) {
101
- this.initDefaultValue(prop);
102
- this.inferTypeFromDefault(prop);
103
- this.initColumnType(prop);
104
- // change tracking on scalars is used only for "auto" flushMode
105
- if (this.config.get('flushMode') !== 'auto' && [ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind)) {
106
- prop.trackChanges = false;
107
- }
108
- }
109
- }
110
- filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initIndexes(meta, prop)));
142
+ forEachProp((_m, p) => {
143
+ this.initDefaultValue(p);
144
+ this.inferTypeFromDefault(p);
145
+ this.initRelation(p);
146
+ this.initColumnType(p);
147
+ });
148
+ forEachProp((m, p) => this.initIndexes(m, p));
111
149
  filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
112
- filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
113
150
  for (const meta of filtered) {
114
151
  discovered.push(...this.processEntity(meta));
115
152
  }
116
153
  discovered.forEach(meta => meta.sync(true));
117
- const combinedCachePath = this.cache.combine?.();
118
- // override the path in the options, so we can log it from the CLI in `cache:generate` command
119
- if (combinedCachePath) {
120
- this.config.get('metadataCache').combined = combinedCachePath;
121
- }
154
+ this.metadataProvider.combineCache();
122
155
  return discovered.map(meta => {
123
- meta = this.metadata.get(meta.className);
156
+ meta = this.metadata.get(meta.class);
124
157
  meta.sync(true);
158
+ this.findReferencingProperties(meta, filtered);
125
159
  return meta;
126
160
  });
127
161
  }
128
- findEntities(preferTs, sync = false) {
129
- this.discovered.length = 0;
130
- const options = this.config.get('discovery');
131
- const key = (preferTs && this.config.get('preferTs', Utils.detectTypeScriptSupport()) && this.config.get('entitiesTs').length > 0) ? 'entitiesTs' : 'entities';
132
- const paths = this.config.get(key).filter(item => Utils.isString(item));
133
- const refs = this.config.get(key).filter(item => !Utils.isString(item));
134
- if (paths.length > 0) {
135
- if (sync || options.requireEntitiesArray) {
136
- throw new Error(`[requireEntitiesArray] Explicit list of entities is required, please use the 'entities' option.`);
162
+ async findEntities(preferTs) {
163
+ const { entities, entitiesTs, baseDir } = this.config.getAll();
164
+ const targets = (preferTs && entitiesTs.length > 0) ? entitiesTs : entities;
165
+ const processed = [];
166
+ const paths = [];
167
+ for (const entity of targets) {
168
+ if (typeof entity === 'string') {
169
+ paths.push(entity);
170
+ }
171
+ else {
172
+ processed.push(entity);
137
173
  }
138
- return this.discoverDirectories(paths).then(() => {
139
- this.discoverReferences(refs);
140
- this.discoverMissingTargets();
141
- this.validator.validateDiscovered(this.discovered, options);
142
- return this.discovered;
143
- });
144
174
  }
145
- this.discoverReferences(refs);
146
- this.discoverMissingTargets();
147
- this.validator.validateDiscovered(this.discovered, options);
148
- return this.discovered;
175
+ if (paths.length > 0) {
176
+ const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
177
+ processed.push(...await discoverEntities(paths, { baseDir }));
178
+ }
179
+ return this.discoverReferences(processed);
149
180
  }
150
181
  discoverMissingTargets() {
151
182
  const unwrap = (type) => type
@@ -154,17 +185,22 @@ export class MetadataDiscovery {
154
185
  .replace(/\((.*)\)/, '$1'); // unwrap union types
155
186
  const missing = [];
156
187
  this.discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
157
- if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity && !this.discovered.find(m => m.className === Utils.className(prop.pivotEntity))) {
158
- const target = typeof prop.pivotEntity === 'function'
159
- ? prop.pivotEntity()
160
- : prop.pivotEntity;
161
- missing.push(target);
188
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
189
+ const pivotEntity = prop.pivotEntity;
190
+ const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
191
+ ? pivotEntity()
192
+ : pivotEntity;
193
+ if (!this.discovered.find(m => m.className === Utils.className(target))) {
194
+ missing.push(target);
195
+ }
162
196
  }
163
- if (prop.kind !== ReferenceKind.SCALAR && !unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
164
- const target = typeof prop.entity === 'function'
197
+ if (prop.kind !== ReferenceKind.SCALAR) {
198
+ const target = typeof prop.entity === 'function' && !prop.entity.prototype
165
199
  ? prop.entity()
166
200
  : prop.type;
167
- missing.push(...Utils.asArray(target));
201
+ if (!unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
202
+ missing.push(...Utils.asArray(target));
203
+ }
168
204
  }
169
205
  }));
170
206
  if (missing.length > 0) {
@@ -173,123 +209,100 @@ export class MetadataDiscovery {
173
209
  }
174
210
  tryDiscoverTargets(targets) {
175
211
  for (const target of targets) {
176
- if (typeof target === 'function' && target.name && !this.metadata.has(target.name)) {
177
- this.discoverReferences([target]);
212
+ const isDiscoverable = typeof target === 'function' || target instanceof EntitySchema;
213
+ if (isDiscoverable && target.name && !this.metadata.has(target)) {
214
+ this.discoverReferences([target], false);
178
215
  this.discoverMissingTargets();
179
216
  }
180
217
  }
181
218
  }
182
- async discoverDirectories(paths) {
183
- paths = paths.map(path => Utils.normalizePath(path));
184
- const files = await globby(paths, { cwd: Utils.normalizePath(this.config.get('baseDir')) });
185
- this.logger.log('discovery', `- processing ${colors.cyan('' + files.length)} files`);
186
- const found = [];
187
- for (const filepath of files) {
188
- const filename = basename(filepath);
189
- if (!filename.match(/\.[cm]?[jt]s$/) ||
190
- filename.endsWith('.js.map') ||
191
- filename.match(/\.d\.[cm]?ts/) ||
192
- filename.startsWith('.') ||
193
- filename.match(/index\.[cm]?[jt]s$/)) {
194
- this.logger.log('discovery', `- ignoring file ${filename}`);
195
- continue;
196
- }
197
- const name = this.namingStrategy.getClassName(filename);
198
- const path = Utils.normalizePath(this.config.get('baseDir'), filepath);
199
- const targets = await this.getEntityClassOrSchema(path, name);
200
- for (const target of targets) {
201
- if (!(target instanceof Function) && !(target instanceof EntitySchema)) {
202
- this.logger.log('discovery', `- ignoring file ${filename}`);
203
- continue;
204
- }
205
- const entity = this.prepare(target);
206
- const schema = this.getSchema(entity, path);
207
- const meta = schema.init().meta;
208
- this.metadata.set(meta.className, meta);
209
- found.push([schema, path]);
210
- }
211
- }
212
- for (const [schema, path] of found) {
213
- this.discoverEntity(schema, path);
214
- }
215
- }
216
- discoverReferences(refs) {
219
+ discoverReferences(refs, validate = true) {
217
220
  const found = [];
218
221
  for (const entity of refs) {
219
- const schema = this.getSchema(this.prepare(entity));
222
+ if (typeof entity === 'string') {
223
+ throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
224
+ }
225
+ const schema = this.getSchema(entity);
220
226
  const meta = schema.init().meta;
221
- this.metadata.set(meta.className, meta);
227
+ this.metadata.set(meta.class, meta);
222
228
  found.push(schema);
223
229
  }
224
230
  // discover parents (base entities) automatically
225
231
  for (const meta of this.metadata) {
226
232
  let parent = meta.extends;
227
- if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.className)) {
228
- this.discoverReferences([parent]);
233
+ if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
234
+ this.discoverReferences([parent], false);
229
235
  }
230
- /* v8 ignore next 3 */
236
+ if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
237
+ this.discoverReferences([parent], false);
238
+ }
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
  }
@@ -402,23 +383,23 @@ export class MetadataDiscovery {
402
383
  prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
403
384
  }
404
385
  }
405
- initManyToOneFieldName(prop, name) {
406
- const meta2 = this.metadata.get(prop.type);
386
+ initManyToOneFieldName(prop, name, tableName) {
387
+ const meta2 = prop.targetMeta;
407
388
  const ret = [];
408
389
  for (const primaryKey of meta2.primaryKeys) {
409
390
  this.initFieldName(meta2.properties[primaryKey]);
410
391
  for (const fieldName of meta2.properties[primaryKey].fieldNames) {
411
- ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
392
+ ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
412
393
  }
413
394
  }
414
395
  return ret;
415
396
  }
416
397
  initManyToManyFieldName(prop, name) {
417
- const meta2 = this.metadata.get(prop.type);
398
+ const meta2 = prop.targetMeta;
418
399
  return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
419
400
  }
420
401
  initManyToManyFields(meta, prop) {
421
- const meta2 = this.metadata.get(prop.type);
402
+ const meta2 = prop.targetMeta;
422
403
  Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
423
404
  const pivotMeta = this.metadata.find(prop.pivotEntity);
424
405
  const props = Object.values(pivotMeta?.properties ?? {});
@@ -439,35 +420,58 @@ 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));
456
- prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
436
+ const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
437
+ const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
438
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
439
+ prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
440
+ }
441
+ isExplicitTableName(meta) {
442
+ return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
457
443
  }
458
444
  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);
445
+ const meta2 = prop.targetMeta;
446
+ let fieldNames;
447
+ // If targetKey is specified, use that property's field names instead of PKs
448
+ if (prop.targetKey) {
449
+ const targetProp = meta2.properties[prop.targetKey];
450
+ fieldNames = targetProp.fieldNames;
451
+ }
452
+ else {
453
+ fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
454
+ }
455
+ Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
462
456
  if (!prop.joinColumns) {
463
457
  prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
464
458
  }
465
459
  if (!prop.referencedColumnNames) {
466
460
  prop.referencedColumnNames = fieldNames;
467
461
  }
462
+ // Relations to composite PK targets need cascade update by default,
463
+ // since composite PKs are more likely to have mutable components
464
+ if (meta2.compositePK) {
465
+ prop.updateRule ??= 'cascade';
466
+ }
467
+ // Nullable relations default to 'set null' on delete - when the referenced
468
+ // entity is deleted, set the FK to null rather than failing
469
+ if (prop.nullable) {
470
+ prop.deleteRule ??= 'set null';
471
+ }
468
472
  }
469
473
  initOneToManyFields(prop) {
470
- const meta2 = this.metadata.get(prop.type);
474
+ const meta2 = prop.targetMeta;
471
475
  if (!prop.joinColumns) {
472
476
  prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
473
477
  }
@@ -480,12 +484,17 @@ export class MetadataDiscovery {
480
484
  const pks = Object.values(meta.properties).filter(prop => prop.primary);
481
485
  meta.primaryKeys = pks.map(prop => prop.name);
482
486
  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';
487
+ // FK used as PK, we need to cascade - applies to both single FK-as-PK
488
+ // and composite PKs where all PKs are FKs (e.g., pivot-like entities)
489
+ const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
490
+ if (fkPks.length > 0 && fkPks.length === pks.length) {
491
+ for (const pk of fkPks) {
492
+ pk.deleteRule ??= 'cascade';
493
+ pk.updateRule ??= 'cascade';
494
+ }
486
495
  }
487
496
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
488
- this.validator.validateEntityDefinition(this.metadata, meta.className, this.config.get('discovery'));
497
+ this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
489
498
  for (const prop of Object.values(meta.properties)) {
490
499
  this.initNullability(prop);
491
500
  this.applyNamingStrategy(meta, prop);
@@ -498,15 +507,21 @@ export class MetadataDiscovery {
498
507
  }
499
508
  this.initOwnColumns(meta);
500
509
  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;
510
+ meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
511
+ if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
512
+ meta.properties[meta.serializedPrimaryKey].persist ??= false;
505
513
  }
506
514
  if (this.platform.usesPivotTable()) {
507
515
  return Object.values(meta.properties)
508
516
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
509
- .map(prop => this.definePivotTableEntity(meta, prop));
517
+ .map(prop => {
518
+ const pivotMeta = this.definePivotTableEntity(meta, prop);
519
+ prop.pivotEntity = pivotMeta.class;
520
+ if (prop.inversedBy) {
521
+ prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
522
+ }
523
+ return pivotMeta;
524
+ });
510
525
  }
511
526
  return [];
512
527
  }
@@ -523,8 +538,11 @@ export class MetadataDiscovery {
523
538
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
524
539
  const value = prop[type];
525
540
  if (value instanceof Function) {
526
- const meta2 = this.metadata.get(prop.type);
541
+ const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
527
542
  prop[type] = value(meta2.properties)?.name;
543
+ if (type === 'pivotEntity' && value) {
544
+ prop[type] = value(meta2.properties);
545
+ }
528
546
  if (prop[type] == null) {
529
547
  throw MetadataError.fromWrongReference(meta, prop, type);
530
548
  }
@@ -540,9 +558,9 @@ export class MetadataDiscovery {
540
558
  }
541
559
  else if (fks.length >= 2) {
542
560
  [first, second] = fks;
543
- /* v8 ignore next 3 */
544
561
  }
545
562
  else {
563
+ /* v8 ignore next */
546
564
  return [];
547
565
  }
548
566
  // wrong FK order, first FK needs to point to the owning side
@@ -556,7 +574,9 @@ export class MetadataDiscovery {
556
574
  return [first, second];
557
575
  }
558
576
  definePivotTableEntity(meta, prop) {
559
- const pivotMeta = this.metadata.find(prop.pivotEntity);
577
+ const pivotMeta = prop.pivotEntity
578
+ ? this.metadata.find(prop.pivotEntity)
579
+ : this.metadata.getByClassName(prop.pivotTable, false);
560
580
  // ensure inverse side exists so we can join it when populating via pivot tables
561
581
  if (!prop.inversedBy && prop.targetMeta) {
562
582
  const inverseName = `${meta.className}_${prop.name}__inverse`;
@@ -565,6 +585,8 @@ export class MetadataDiscovery {
565
585
  name: inverseName,
566
586
  kind: ReferenceKind.MANY_TO_MANY,
567
587
  type: meta.className,
588
+ target: meta.class,
589
+ targetMeta: meta,
568
590
  mappedBy: prop.name,
569
591
  pivotEntity: prop.pivotEntity,
570
592
  pivotTable: prop.pivotTable,
@@ -573,55 +595,61 @@ export class MetadataDiscovery {
573
595
  };
574
596
  this.applyNamingStrategy(prop.targetMeta, inverseProp);
575
597
  this.initCustomType(prop.targetMeta, inverseProp);
576
- this.initRelation(inverseProp);
577
598
  prop.targetMeta.properties[inverseName] = inverseProp;
578
599
  }
579
600
  if (pivotMeta) {
601
+ prop.pivotEntity = pivotMeta.class;
580
602
  this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
581
603
  return pivotMeta;
582
604
  }
583
- const exists = this.metadata.find(prop.pivotTable);
584
- if (exists) {
585
- prop.pivotEntity = exists.className;
586
- return exists;
587
- }
588
605
  let tableName = prop.pivotTable;
589
606
  let schemaName;
590
607
  if (prop.pivotTable.includes('.')) {
591
608
  [schemaName, tableName] = prop.pivotTable.split('.');
592
609
  }
593
610
  schemaName ??= meta.schema;
594
- const targetType = prop.targetMeta.className;
595
- const data = new EntityMetadata({
611
+ const targetMeta = prop.targetMeta;
612
+ const targetType = targetMeta.className;
613
+ const pivotMeta2 = new EntityMetadata({
596
614
  name: prop.pivotTable,
597
615
  className: prop.pivotTable,
598
616
  collection: tableName,
599
617
  schema: schemaName,
600
618
  pivotTable: true,
601
619
  });
602
- prop.pivotEntity = data.className;
620
+ prop.pivotEntity = pivotMeta2.class;
603
621
  if (prop.fixedOrder) {
604
- const primaryProp = this.defineFixedOrderProperty(prop, targetType);
605
- data.properties[primaryProp.name] = primaryProp;
622
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
623
+ pivotMeta2.properties[primaryProp.name] = primaryProp;
606
624
  }
607
625
  else {
608
- data.compositePK = true;
626
+ pivotMeta2.compositePK = true;
609
627
  }
610
628
  // handle self-referenced m:n with same default field names
611
629
  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));
630
+ // use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
631
+ const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
632
+ prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
633
+ prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
614
634
  if (prop.inversedBy) {
615
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
635
+ const prop2 = targetMeta.properties[prop.inversedBy];
616
636
  prop2.inverseJoinColumns = prop.joinColumns;
617
637
  prop2.joinColumns = prop.inverseJoinColumns;
618
638
  }
639
+ // propagate updated joinColumns to all child entities that inherit this property (STI)
640
+ for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
641
+ const childProp = childMeta.properties[prop.name];
642
+ if (childProp) {
643
+ childProp.joinColumns = prop.joinColumns;
644
+ childProp.inverseJoinColumns = prop.inverseJoinColumns;
645
+ }
646
+ }
619
647
  }
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);
648
+ pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
649
+ pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
650
+ return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
623
651
  }
624
- defineFixedOrderProperty(prop, targetType) {
652
+ defineFixedOrderProperty(prop, targetMeta) {
625
653
  const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
626
654
  const primaryProp = {
627
655
  name: pk,
@@ -635,7 +663,7 @@ export class MetadataDiscovery {
635
663
  this.initColumnType(primaryProp);
636
664
  prop.fixedOrderColumn = pk;
637
665
  if (prop.inversedBy) {
638
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
666
+ const prop2 = targetMeta.properties[prop.inversedBy];
639
667
  prop2.fixedOrder = true;
640
668
  prop2.fixedOrderColumn = pk;
641
669
  }
@@ -644,7 +672,8 @@ export class MetadataDiscovery {
644
672
  definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
645
673
  const ret = {
646
674
  name,
647
- type,
675
+ type: Utils.className(type),
676
+ target: type,
648
677
  kind: ReferenceKind.MANY_TO_ONE,
649
678
  cascade: [Cascade.ALL],
650
679
  fixedOrder: prop.fixedOrder,
@@ -654,11 +683,11 @@ export class MetadataDiscovery {
654
683
  autoincrement: false,
655
684
  updateRule: prop.updateRule,
656
685
  deleteRule: prop.deleteRule,
686
+ createForeignKeyConstraint: prop.createForeignKeyConstraint,
657
687
  };
658
- if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
659
- ret.updateRule ??= 'no action';
660
- ret.deleteRule ??= 'no action';
661
- }
688
+ const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
689
+ ret.updateRule ??= defaultRule;
690
+ ret.deleteRule ??= defaultRule;
662
691
  const meta = this.metadata.get(type);
663
692
  ret.targetMeta = meta;
664
693
  ret.joinColumns = [];
@@ -701,7 +730,7 @@ export class MetadataDiscovery {
701
730
  Object.values(meta.properties)
702
731
  .filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
703
732
  .forEach(prop => {
704
- const meta2 = this.metadata.get(prop.type);
733
+ const meta2 = prop.targetMeta;
705
734
  const prop2 = meta2.properties[prop.mappedBy];
706
735
  if (prop2 && !prop2.inversedBy) {
707
736
  prop2.inversedBy = prop.name;
@@ -709,7 +738,7 @@ export class MetadataDiscovery {
709
738
  });
710
739
  }
711
740
  defineBaseEntityProperties(meta) {
712
- const base = meta.extends && this.metadata.get(Utils.className(meta.extends));
741
+ const base = meta.extends && this.metadata.get(meta.extends);
713
742
  if (!base || base === meta) { // make sure we do not fall into infinite loop
714
743
  return 0;
715
744
  }
@@ -740,12 +769,9 @@ export class MetadataDiscovery {
740
769
  Utils.keys(base.hooks).forEach(type => {
741
770
  meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
742
771
  });
743
- if (meta.constructorParams.length === 0 && base.constructorParams.length > 0) {
772
+ if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
744
773
  meta.constructorParams = [...base.constructorParams];
745
774
  }
746
- if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
747
- meta.toJsonParams = [...base.toJsonParams];
748
- }
749
775
  return order;
750
776
  }
751
777
  initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
@@ -821,22 +847,30 @@ export class MetadataDiscovery {
821
847
  }
822
848
  return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
823
849
  };
850
+ const isParentArray = (prop) => {
851
+ if (prop.array) {
852
+ return true;
853
+ }
854
+ return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
855
+ };
824
856
  const rootProperty = getRootProperty(embeddedProp);
825
857
  const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
826
858
  const object = isParentObject(embeddedProp);
859
+ const array = isParentArray(embeddedProp);
827
860
  this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
828
861
  // the prefix of the parent cannot be a boolean; it already passed here
829
862
  const prefix = this.getPrefix(embeddedProp, parentProperty);
830
863
  const glue = object ? '~' : '_';
831
864
  for (const prop of Object.values(embeddable.properties)) {
832
865
  const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
833
- meta.properties[name] = Utils.copy(prop, false);
866
+ meta.properties[name] = Utils.copy(prop);
834
867
  meta.properties[name].name = name;
835
868
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
836
869
  meta.propertyOrder.set(name, (order += 0.01));
837
870
  embeddedProp.embeddedProps[prop.name] = meta.properties[name];
838
871
  meta.properties[name].persist ??= embeddedProp.persist;
839
- if (embeddedProp.nullable) {
872
+ const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
873
+ if (embeddedProp.nullable || refInArray) {
840
874
  meta.properties[name].nullable = true;
841
875
  }
842
876
  if (meta.properties[name].fieldNames) {
@@ -866,14 +900,17 @@ export class MetadataDiscovery {
866
900
  path = [embeddedProp.fieldNames[0]];
867
901
  }
868
902
  this.initFieldName(prop, true);
903
+ this.initRelation(prop);
869
904
  path.push(prop.fieldNames[0]);
870
905
  meta.properties[name].fieldNames = prop.fieldNames;
871
906
  meta.properties[name].embeddedPath = path;
872
- const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), prop.runtimeType ?? prop.type, true));
907
+ const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
908
+ const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
873
909
  meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
874
910
  meta.properties[name].persist = false; // only virtual as we store the whole object
875
911
  meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
876
912
  meta.properties[name].object = true;
913
+ this.initCustomType(meta, meta.properties[name], false, true);
877
914
  }
878
915
  this.initEmbeddables(meta, meta.properties[name], visited);
879
916
  }
@@ -896,7 +933,7 @@ export class MetadataDiscovery {
896
933
  }
897
934
  initSingleTableInheritance(meta, metadata) {
898
935
  if (meta.root !== meta && !meta.__processed) {
899
- meta.root = metadata.find(m => m.className === meta.root.className);
936
+ meta.root = metadata.find(m => m.class === meta.root.class);
900
937
  meta.root.__processed = true;
901
938
  }
902
939
  else {
@@ -905,17 +942,23 @@ export class MetadataDiscovery {
905
942
  if (!meta.root.discriminatorColumn) {
906
943
  return;
907
944
  }
908
- if (!meta.root.discriminatorMap) {
945
+ if (meta.root.discriminatorMap) {
946
+ const map = meta.root.discriminatorMap;
947
+ Object.keys(map)
948
+ .filter(key => typeof map[key] === 'string')
949
+ .forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
950
+ }
951
+ else {
909
952
  meta.root.discriminatorMap = {};
910
953
  const children = metadata
911
- .filter(m => m.root.className === meta.root.className && !m.abstract)
954
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
912
955
  .sort((a, b) => a.className.localeCompare(b.className));
913
956
  for (const m of children) {
914
957
  const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
915
- meta.root.discriminatorMap[name] = m.className;
958
+ meta.root.discriminatorMap[name] = m.class;
916
959
  }
917
960
  }
918
- meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, className]) => className === meta.className)?.[0];
961
+ meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
919
962
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
920
963
  this.createDiscriminatorProperty(meta.root);
921
964
  }
@@ -924,28 +967,46 @@ export class MetadataDiscovery {
924
967
  if (meta.root === meta) {
925
968
  return;
926
969
  }
927
- let i = 1;
928
970
  Object.values(meta.properties).forEach(prop => {
929
971
  const newProp = { ...prop };
930
- if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
972
+ const rootProp = meta.root.properties[prop.name];
973
+ if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
931
974
  const name = newProp.name;
932
975
  this.initFieldName(newProp, newProp.object);
933
- newProp.name = name + '_' + (i++);
976
+ newProp.renamedFrom = name;
977
+ newProp.name = `${name}_${meta._id}`;
934
978
  meta.root.addProperty(newProp);
979
+ this.initFieldName(prop, prop.object);
980
+ // Track all field variants and map discriminator values to field names
981
+ if (!rootProp.stiFieldNames) {
982
+ this.initFieldName(rootProp, rootProp.object);
983
+ rootProp.stiFieldNames = [...rootProp.fieldNames];
984
+ rootProp.stiFieldNameMap = {};
985
+ // Find which discriminator owns the original fieldNames
986
+ for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
987
+ const childMeta = this.metadata.find(childClass);
988
+ if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
989
+ rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
990
+ break;
991
+ }
992
+ }
993
+ }
994
+ rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
995
+ rootProp.stiFieldNames.push(...prop.fieldNames);
935
996
  newProp.nullable = true;
936
997
  newProp.name = name;
937
998
  newProp.hydrate = false;
938
999
  newProp.inherited = true;
939
1000
  return;
940
1001
  }
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]);
1002
+ if (prop.enum && prop.items && rootProp?.items) {
1003
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
943
1004
  }
944
1005
  newProp.nullable = true;
945
- newProp.inherited = true;
1006
+ newProp.inherited = !rootProp;
946
1007
  meta.root.addProperty(newProp);
947
1008
  });
948
- meta.collection = meta.root.collection;
1009
+ meta.tableName = meta.root.tableName;
949
1010
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
950
1011
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
951
1012
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
@@ -967,7 +1028,7 @@ export class MetadataDiscovery {
967
1028
  }
968
1029
  }
969
1030
  initCheckConstraints(meta) {
970
- const map = this.createColumnMappingObject(meta);
1031
+ const map = meta.createColumnMappingObject();
971
1032
  for (const check of meta.checks) {
972
1033
  const columns = check.property ? meta.properties[check.property].fieldNames : [];
973
1034
  check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
@@ -977,7 +1038,7 @@ export class MetadataDiscovery {
977
1038
  }
978
1039
  if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
979
1040
  for (const prop of meta.props) {
980
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => Utils.isString(item))) {
1041
+ if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
981
1042
  this.initFieldName(prop);
982
1043
  meta.checks.push({
983
1044
  name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
@@ -1002,38 +1063,28 @@ export class MetadataDiscovery {
1002
1063
  }
1003
1064
  return;
1004
1065
  }
1005
- const map = this.createColumnMappingObject(meta);
1066
+ const map = meta.createColumnMappingObject();
1006
1067
  if (prop.generated instanceof Function) {
1007
1068
  prop.generated = prop.generated(map);
1008
1069
  }
1009
1070
  }
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) {
1071
+ getDefaultVersionValue(meta, prop) {
1019
1072
  if (typeof prop.defaultRaw !== 'undefined') {
1020
1073
  return prop.defaultRaw;
1021
1074
  }
1022
- /* v8 ignore next 3 */
1075
+ /* v8 ignore next */
1023
1076
  if (prop.default != null) {
1024
1077
  return '' + this.platform.quoteVersionValue(prop.default, prop);
1025
1078
  }
1026
- if (prop.type.toLowerCase() === 'date') {
1079
+ this.initCustomType(meta, prop, true);
1080
+ const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
1081
+ if (type === 'Date') {
1027
1082
  prop.length ??= this.platform.getDefaultVersionLength();
1028
1083
  return this.platform.getCurrentTimestampSQL(prop.length);
1029
1084
  }
1030
1085
  return '1';
1031
1086
  }
1032
1087
  inferDefaultValue(meta, prop) {
1033
- /* v8 ignore next 3 */
1034
- if (!meta.class) {
1035
- return;
1036
- }
1037
1088
  try {
1038
1089
  // try to create two entity instances to detect the value is stable
1039
1090
  const now = Date.now();
@@ -1061,12 +1112,12 @@ export class MetadataDiscovery {
1061
1112
  return;
1062
1113
  }
1063
1114
  let val = prop.default;
1064
- const raw = RawQueryFragment.getKnownFragment(val);
1115
+ const raw = Raw.getKnownFragment(val);
1065
1116
  if (raw) {
1066
1117
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1067
1118
  return;
1068
1119
  }
1069
- if (prop.customType instanceof ArrayType && Array.isArray(prop.default)) {
1120
+ if (Array.isArray(prop.default) && prop.customType) {
1070
1121
  val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1071
1122
  }
1072
1123
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
@@ -1094,13 +1145,13 @@ export class MetadataDiscovery {
1094
1145
  if (prop.version) {
1095
1146
  this.initDefaultValue(prop);
1096
1147
  meta.versionProperty = prop.name;
1097
- prop.defaultRaw = this.getDefaultVersionValue(prop);
1148
+ prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
1098
1149
  }
1099
1150
  if (prop.concurrencyCheck && !prop.primary) {
1100
1151
  meta.concurrencyCheckKeys.add(prop.name);
1101
1152
  }
1102
1153
  }
1103
- initCustomType(meta, prop) {
1154
+ initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
1104
1155
  // `prop.type` might be actually instance of custom type class
1105
1156
  if (Type.isMappedType(prop.type) && !prop.customType) {
1106
1157
  prop.customType = prop.type;
@@ -1108,41 +1159,61 @@ export class MetadataDiscovery {
1108
1159
  }
1109
1160
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1110
1161
  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;
1162
+ // if the type is an ORM defined mapped type without `ensureComparable: true`,
1163
+ // we use just the type name, to have more performant hydration code
1164
+ const type = Utils.keys(t).find(type => {
1165
+ return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
1166
+ });
1167
+ if (type) {
1168
+ prop.type = type === 'datetime' ? 'Date' : type;
1169
+ }
1170
+ else {
1171
+ prop.customType = new prop.type();
1172
+ prop.type = prop.customType.constructor.name;
1173
+ }
1174
+ }
1175
+ if (simple) {
1176
+ return;
1113
1177
  }
1114
1178
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1115
- prop.customType = new JsonType();
1179
+ prop.customType = new t.json();
1116
1180
  }
1117
1181
  if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1118
- prop.customType = new JsonType();
1182
+ prop.customType = new t.json();
1183
+ }
1184
+ if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1185
+ prop.customType = new t.json();
1119
1186
  }
1120
1187
  if (!prop.customType && prop.array && prop.items) {
1121
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1188
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1189
+ }
1190
+ const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1191
+ if (objectEmbeddable && !prop.customType && isArray) {
1192
+ prop.customType = new t.json();
1122
1193
  }
1123
1194
  // for number arrays we make sure to convert the items to numbers
1124
1195
  if (!prop.customType && prop.type === 'number[]') {
1125
- prop.customType = new ArrayType(i => +i);
1196
+ prop.customType = new t.array(i => +i);
1126
1197
  }
1127
1198
  // `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();
1199
+ if (!prop.customType && isArray) {
1200
+ prop.customType = new t.array();
1130
1201
  }
1131
1202
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1132
- prop.customType = new BlobType();
1203
+ prop.customType = new t.blob();
1133
1204
  }
1134
1205
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1135
- prop.customType = new Uint8ArrayType();
1206
+ prop.customType = new t.uint8array();
1136
1207
  }
1137
1208
  const mappedType = this.getMappedType(prop);
1138
1209
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1139
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1210
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1140
1211
  .filter(type => mappedType instanceof type)
1141
- .forEach(type => prop.customType = new type());
1212
+ .forEach((type) => prop.customType = new type());
1142
1213
  }
1143
1214
  if (prop.customType && !prop.columnTypes) {
1144
1215
  const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1145
- if (prop.customType.compareAsType() === 'any' && ![JsonType].some(t => prop.customType instanceof t)) {
1216
+ if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
1146
1217
  prop.runtimeType ??= mappedType.runtimeType;
1147
1218
  }
1148
1219
  else {
@@ -1162,16 +1233,16 @@ export class MetadataDiscovery {
1162
1233
  prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1163
1234
  prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1164
1235
  prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1165
- if (prop.customType instanceof BigIntType && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1236
+ if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1166
1237
  prop.customType.mode = prop.runtimeType.toLowerCase();
1167
1238
  }
1168
1239
  }
1169
- if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !prop.type?.toString().endsWith('[]')) {
1240
+ if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1170
1241
  prop.type = prop.customType.name;
1171
1242
  }
1172
- if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && this.metadata.get(prop.type).compositePK) {
1243
+ if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
1173
1244
  prop.customTypes = [];
1174
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1245
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1175
1246
  if (pk.customType) {
1176
1247
  prop.customTypes.push(pk.customType);
1177
1248
  prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
@@ -1183,7 +1254,7 @@ export class MetadataDiscovery {
1183
1254
  }
1184
1255
  }
1185
1256
  }
1186
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1257
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1187
1258
  if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1188
1259
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1189
1260
  }
@@ -1201,19 +1272,28 @@ export class MetadataDiscovery {
1201
1272
  if (prop.kind === ReferenceKind.SCALAR) {
1202
1273
  return;
1203
1274
  }
1204
- const meta2 = this.discovered.find(m => m.className === prop.type);
1205
- prop.referencedPKs = meta2.primaryKeys;
1275
+ // 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
1276
+ const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
1277
+ // If targetKey is specified, use that property instead of PKs for referencedPKs
1278
+ prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
1206
1279
  prop.targetMeta = meta2;
1280
+ if (meta2.view) {
1281
+ prop.createForeignKeyConstraint = false;
1282
+ }
1207
1283
  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])}`;
1284
+ prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1209
1285
  }
1210
1286
  }
1211
1287
  initColumnType(prop) {
1212
1288
  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;
1289
+ // Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
1290
+ const targetProps = prop.targetMeta
1291
+ ? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
1292
+ : [];
1293
+ targetProps.map(targetProp => {
1294
+ prop.length ??= targetProp.length;
1295
+ prop.precision ??= targetProp.precision;
1296
+ prop.scale ??= targetProp.scale;
1217
1297
  });
1218
1298
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1219
1299
  delete prop.type;
@@ -1226,8 +1306,7 @@ export class MetadataDiscovery {
1226
1306
  if (prop.kind === ReferenceKind.SCALAR) {
1227
1307
  const mappedType = this.getMappedType(prop);
1228
1308
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1229
- if (mappedType instanceof UnknownType
1230
- && !prop.columnTypes
1309
+ if (mappedType instanceof t.unknown
1231
1310
  // it could be a runtime type from reflect-metadata
1232
1311
  && !SCALAR_TYPES.includes(prop.type)
1233
1312
  // or it might be inferred via ts-morph to some generic type alias
@@ -1240,23 +1319,28 @@ export class MetadataDiscovery {
1240
1319
  }
1241
1320
  return;
1242
1321
  }
1243
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1322
+ /* v8 ignore next */
1323
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1244
1324
  prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1245
1325
  return;
1246
1326
  }
1247
- const targetMeta = this.metadata.get(prop.type);
1327
+ const targetMeta = prop.targetMeta;
1248
1328
  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)];
1329
+ // Use targetKey property if specified, otherwise use primary key properties
1330
+ const referencedProps = prop.targetKey
1331
+ ? [targetMeta.properties[prop.targetKey]]
1332
+ : targetMeta.getPrimaryProps();
1333
+ for (const referencedProp of referencedProps) {
1334
+ this.initCustomType(targetMeta, referencedProp);
1335
+ this.initColumnType(referencedProp);
1336
+ const mappedType = this.getMappedType(referencedProp);
1337
+ let columnTypes = referencedProp.columnTypes;
1338
+ if (referencedProp.autoincrement) {
1339
+ columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
1256
1340
  }
1257
1341
  prop.columnTypes.push(...columnTypes);
1258
- if (!targetMeta.compositePK) {
1259
- prop.customType = pk.customType;
1342
+ if (!targetMeta.compositePK || prop.targetKey) {
1343
+ prop.customType = referencedProp.customType;
1260
1344
  }
1261
1345
  }
1262
1346
  }
@@ -1270,7 +1354,7 @@ export class MetadataDiscovery {
1270
1354
  t = 'enum';
1271
1355
  }
1272
1356
  else if (prop.enum) {
1273
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1357
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1274
1358
  }
1275
1359
  if (t === 'Date') {
1276
1360
  t = 'datetime';
@@ -1294,7 +1378,7 @@ export class MetadataDiscovery {
1294
1378
  return;
1295
1379
  }
1296
1380
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1297
- const meta2 = this.metadata.get(prop.type);
1381
+ const meta2 = prop.targetMeta;
1298
1382
  prop.unsigned = meta2.getPrimaryProps().some(pk => {
1299
1383
  this.initUnsigned(pk);
1300
1384
  return pk.unsigned;
@@ -1309,30 +1393,6 @@ export class MetadataDiscovery {
1309
1393
  prop.index ??= true;
1310
1394
  }
1311
1395
  }
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
1396
  shouldForceConstructorUsage(meta) {
1337
1397
  const forceConstructor = this.config.get('forceEntityConstructor');
1338
1398
  if (Array.isArray(forceConstructor)) {