@mikro-orm/core 7.0.0-dev.19 → 7.0.0-dev.190

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 +585 -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 +396 -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 +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 +221 -124
  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,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 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;
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,14 +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;
918
+ this.initCustomType(meta, meta.properties[name], false, true);
877
919
  }
878
920
  this.initEmbeddables(meta, meta.properties[name], visited);
879
921
  }
@@ -896,7 +938,7 @@ export class MetadataDiscovery {
896
938
  }
897
939
  initSingleTableInheritance(meta, metadata) {
898
940
  if (meta.root !== meta && !meta.__processed) {
899
- meta.root = metadata.find(m => m.className === meta.root.className);
941
+ meta.root = metadata.find(m => m.class === meta.root.class);
900
942
  meta.root.__processed = true;
901
943
  }
902
944
  else {
@@ -905,17 +947,23 @@ export class MetadataDiscovery {
905
947
  if (!meta.root.discriminatorColumn) {
906
948
  return;
907
949
  }
908
- 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 {
909
957
  meta.root.discriminatorMap = {};
910
958
  const children = metadata
911
- .filter(m => m.root.className === meta.root.className && !m.abstract)
959
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
912
960
  .sort((a, b) => a.className.localeCompare(b.className));
913
961
  for (const m of children) {
914
962
  const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
915
- meta.root.discriminatorMap[name] = m.className;
963
+ meta.root.discriminatorMap[name] = m.class;
916
964
  }
917
965
  }
918
- 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];
919
967
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
920
968
  this.createDiscriminatorProperty(meta.root);
921
969
  }
@@ -924,28 +972,46 @@ export class MetadataDiscovery {
924
972
  if (meta.root === meta) {
925
973
  return;
926
974
  }
927
- let i = 1;
928
975
  Object.values(meta.properties).forEach(prop => {
929
976
  const newProp = { ...prop };
930
- 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)))) {
931
979
  const name = newProp.name;
932
980
  this.initFieldName(newProp, newProp.object);
933
- newProp.name = name + '_' + (i++);
981
+ newProp.renamedFrom = name;
982
+ newProp.name = `${name}_${meta._id}`;
934
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);
935
1001
  newProp.nullable = true;
936
1002
  newProp.name = name;
937
1003
  newProp.hydrate = false;
938
1004
  newProp.inherited = true;
939
1005
  return;
940
1006
  }
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]);
1007
+ if (prop.enum && prop.items && rootProp?.items) {
1008
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
943
1009
  }
944
1010
  newProp.nullable = true;
945
- newProp.inherited = true;
1011
+ newProp.inherited = !rootProp;
946
1012
  meta.root.addProperty(newProp);
947
1013
  });
948
- meta.collection = meta.root.collection;
1014
+ meta.tableName = meta.root.tableName;
949
1015
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
950
1016
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
951
1017
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
@@ -967,7 +1033,7 @@ export class MetadataDiscovery {
967
1033
  }
968
1034
  }
969
1035
  initCheckConstraints(meta) {
970
- const map = this.createColumnMappingObject(meta);
1036
+ const map = meta.createColumnMappingObject();
971
1037
  for (const check of meta.checks) {
972
1038
  const columns = check.property ? meta.properties[check.property].fieldNames : [];
973
1039
  check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
@@ -977,7 +1043,7 @@ export class MetadataDiscovery {
977
1043
  }
978
1044
  if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
979
1045
  for (const prop of meta.props) {
980
- 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')) {
981
1047
  this.initFieldName(prop);
982
1048
  meta.checks.push({
983
1049
  name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
@@ -1002,38 +1068,28 @@ export class MetadataDiscovery {
1002
1068
  }
1003
1069
  return;
1004
1070
  }
1005
- const map = this.createColumnMappingObject(meta);
1071
+ const map = meta.createColumnMappingObject();
1006
1072
  if (prop.generated instanceof Function) {
1007
1073
  prop.generated = prop.generated(map);
1008
1074
  }
1009
1075
  }
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) {
1076
+ getDefaultVersionValue(meta, prop) {
1019
1077
  if (typeof prop.defaultRaw !== 'undefined') {
1020
1078
  return prop.defaultRaw;
1021
1079
  }
1022
- /* v8 ignore next 3 */
1080
+ /* v8 ignore next */
1023
1081
  if (prop.default != null) {
1024
1082
  return '' + this.platform.quoteVersionValue(prop.default, prop);
1025
1083
  }
1026
- 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') {
1027
1087
  prop.length ??= this.platform.getDefaultVersionLength();
1028
1088
  return this.platform.getCurrentTimestampSQL(prop.length);
1029
1089
  }
1030
1090
  return '1';
1031
1091
  }
1032
1092
  inferDefaultValue(meta, prop) {
1033
- /* v8 ignore next 3 */
1034
- if (!meta.class) {
1035
- return;
1036
- }
1037
1093
  try {
1038
1094
  // try to create two entity instances to detect the value is stable
1039
1095
  const now = Date.now();
@@ -1061,12 +1117,12 @@ export class MetadataDiscovery {
1061
1117
  return;
1062
1118
  }
1063
1119
  let val = prop.default;
1064
- const raw = RawQueryFragment.getKnownFragment(val);
1120
+ const raw = Raw.getKnownFragment(val);
1065
1121
  if (raw) {
1066
1122
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1067
1123
  return;
1068
1124
  }
1069
- if (prop.customType instanceof ArrayType && Array.isArray(prop.default)) {
1125
+ if (Array.isArray(prop.default) && prop.customType) {
1070
1126
  val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1071
1127
  }
1072
1128
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
@@ -1094,13 +1150,13 @@ export class MetadataDiscovery {
1094
1150
  if (prop.version) {
1095
1151
  this.initDefaultValue(prop);
1096
1152
  meta.versionProperty = prop.name;
1097
- prop.defaultRaw = this.getDefaultVersionValue(prop);
1153
+ prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
1098
1154
  }
1099
1155
  if (prop.concurrencyCheck && !prop.primary) {
1100
1156
  meta.concurrencyCheckKeys.add(prop.name);
1101
1157
  }
1102
1158
  }
1103
- initCustomType(meta, prop) {
1159
+ initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
1104
1160
  // `prop.type` might be actually instance of custom type class
1105
1161
  if (Type.isMappedType(prop.type) && !prop.customType) {
1106
1162
  prop.customType = prop.type;
@@ -1108,41 +1164,61 @@ export class MetadataDiscovery {
1108
1164
  }
1109
1165
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1110
1166
  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;
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;
1113
1182
  }
1114
1183
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1115
- prop.customType = new JsonType();
1184
+ prop.customType = new t.json();
1116
1185
  }
1117
1186
  if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1118
- 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();
1119
1191
  }
1120
1192
  if (!prop.customType && prop.array && prop.items) {
1121
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1193
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1194
+ }
1195
+ const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1196
+ if (objectEmbeddable && !prop.customType && isArray) {
1197
+ prop.customType = new t.json();
1122
1198
  }
1123
1199
  // for number arrays we make sure to convert the items to numbers
1124
1200
  if (!prop.customType && prop.type === 'number[]') {
1125
- prop.customType = new ArrayType(i => +i);
1201
+ prop.customType = new t.array(i => +i);
1126
1202
  }
1127
1203
  // `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();
1204
+ if (!prop.customType && isArray) {
1205
+ prop.customType = new t.array();
1130
1206
  }
1131
1207
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1132
- prop.customType = new BlobType();
1208
+ prop.customType = new t.blob();
1133
1209
  }
1134
1210
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1135
- prop.customType = new Uint8ArrayType();
1211
+ prop.customType = new t.uint8array();
1136
1212
  }
1137
1213
  const mappedType = this.getMappedType(prop);
1138
1214
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1139
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1215
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1140
1216
  .filter(type => mappedType instanceof type)
1141
- .forEach(type => prop.customType = new type());
1217
+ .forEach((type) => prop.customType = new type());
1142
1218
  }
1143
1219
  if (prop.customType && !prop.columnTypes) {
1144
1220
  const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1145
- 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)) {
1146
1222
  prop.runtimeType ??= mappedType.runtimeType;
1147
1223
  }
1148
1224
  else {
@@ -1162,16 +1238,16 @@ export class MetadataDiscovery {
1162
1238
  prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1163
1239
  prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1164
1240
  prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1165
- 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())) {
1166
1242
  prop.customType.mode = prop.runtimeType.toLowerCase();
1167
1243
  }
1168
1244
  }
1169
- if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !prop.type?.toString().endsWith('[]')) {
1245
+ if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1170
1246
  prop.type = prop.customType.name;
1171
1247
  }
1172
- 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) {
1173
1249
  prop.customTypes = [];
1174
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1250
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1175
1251
  if (pk.customType) {
1176
1252
  prop.customTypes.push(pk.customType);
1177
1253
  prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
@@ -1183,7 +1259,7 @@ export class MetadataDiscovery {
1183
1259
  }
1184
1260
  }
1185
1261
  }
1186
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1262
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1187
1263
  if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1188
1264
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1189
1265
  }
@@ -1201,19 +1277,28 @@ export class MetadataDiscovery {
1201
1277
  if (prop.kind === ReferenceKind.SCALAR) {
1202
1278
  return;
1203
1279
  }
1204
- const meta2 = this.discovered.find(m => m.className === prop.type);
1205
- 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;
1206
1284
  prop.targetMeta = meta2;
1285
+ if (meta2.view) {
1286
+ prop.createForeignKeyConstraint = false;
1287
+ }
1207
1288
  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])}`;
1289
+ prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1209
1290
  }
1210
1291
  }
1211
1292
  initColumnType(prop) {
1212
1293
  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;
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;
1217
1302
  });
1218
1303
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1219
1304
  delete prop.type;
@@ -1226,8 +1311,7 @@ export class MetadataDiscovery {
1226
1311
  if (prop.kind === ReferenceKind.SCALAR) {
1227
1312
  const mappedType = this.getMappedType(prop);
1228
1313
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1229
- if (mappedType instanceof UnknownType
1230
- && !prop.columnTypes
1314
+ if (mappedType instanceof t.unknown
1231
1315
  // it could be a runtime type from reflect-metadata
1232
1316
  && !SCALAR_TYPES.includes(prop.type)
1233
1317
  // or it might be inferred via ts-morph to some generic type alias
@@ -1240,23 +1324,28 @@ export class MetadataDiscovery {
1240
1324
  }
1241
1325
  return;
1242
1326
  }
1243
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1327
+ /* v8 ignore next */
1328
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1244
1329
  prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1245
1330
  return;
1246
1331
  }
1247
- const targetMeta = this.metadata.get(prop.type);
1332
+ const targetMeta = prop.targetMeta;
1248
1333
  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)];
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)];
1256
1345
  }
1257
1346
  prop.columnTypes.push(...columnTypes);
1258
- if (!targetMeta.compositePK) {
1259
- prop.customType = pk.customType;
1347
+ if (!targetMeta.compositePK || prop.targetKey) {
1348
+ prop.customType = referencedProp.customType;
1260
1349
  }
1261
1350
  }
1262
1351
  }
@@ -1270,7 +1359,7 @@ export class MetadataDiscovery {
1270
1359
  t = 'enum';
1271
1360
  }
1272
1361
  else if (prop.enum) {
1273
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1362
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1274
1363
  }
1275
1364
  if (t === 'Date') {
1276
1365
  t = 'datetime';
@@ -1294,7 +1383,7 @@ export class MetadataDiscovery {
1294
1383
  return;
1295
1384
  }
1296
1385
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1297
- const meta2 = this.metadata.get(prop.type);
1386
+ const meta2 = prop.targetMeta;
1298
1387
  prop.unsigned = meta2.getPrimaryProps().some(pk => {
1299
1388
  this.initUnsigned(pk);
1300
1389
  return pk.unsigned;
@@ -1309,30 +1398,6 @@ export class MetadataDiscovery {
1309
1398
  prop.index ??= true;
1310
1399
  }
1311
1400
  }
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
1401
  shouldForceConstructorUsage(meta) {
1337
1402
  const forceConstructor = this.config.get('forceEntityConstructor');
1338
1403
  if (Array.isArray(forceConstructor)) {