@mikro-orm/core 7.0.0-dev.33 → 7.0.0-dev.331

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 (218) hide show
  1. package/EntityManager.d.ts +70 -75
  2. package/EntityManager.js +487 -402
  3. package/MikroORM.d.ts +45 -38
  4. package/MikroORM.js +123 -156
  5. package/README.md +7 -4
  6. package/cache/FileCacheAdapter.d.ts +2 -7
  7. package/cache/FileCacheAdapter.js +35 -30
  8. package/cache/GeneratedCacheAdapter.d.ts +1 -2
  9. package/cache/GeneratedCacheAdapter.js +6 -8
  10. package/cache/MemoryCacheAdapter.d.ts +1 -2
  11. package/cache/MemoryCacheAdapter.js +8 -8
  12. package/cache/index.d.ts +1 -2
  13. package/cache/index.js +0 -2
  14. package/connections/Connection.d.ts +12 -5
  15. package/connections/Connection.js +37 -15
  16. package/drivers/DatabaseDriver.d.ts +25 -18
  17. package/drivers/DatabaseDriver.js +144 -45
  18. package/drivers/IDatabaseDriver.d.ts +118 -23
  19. package/entity/BaseEntity.d.ts +63 -4
  20. package/entity/BaseEntity.js +0 -3
  21. package/entity/Collection.d.ts +95 -31
  22. package/entity/Collection.js +487 -139
  23. package/entity/EntityAssigner.js +37 -25
  24. package/entity/EntityFactory.d.ts +8 -9
  25. package/entity/EntityFactory.js +152 -100
  26. package/entity/EntityHelper.d.ts +2 -2
  27. package/entity/EntityHelper.js +69 -27
  28. package/entity/EntityLoader.d.ts +12 -13
  29. package/entity/EntityLoader.js +286 -125
  30. package/entity/EntityRepository.d.ts +28 -8
  31. package/entity/EntityRepository.js +8 -2
  32. package/entity/PolymorphicRef.d.ts +12 -0
  33. package/entity/PolymorphicRef.js +18 -0
  34. package/entity/Reference.d.ts +3 -8
  35. package/entity/Reference.js +62 -29
  36. package/entity/WrappedEntity.d.ts +7 -10
  37. package/entity/WrappedEntity.js +6 -7
  38. package/entity/defineEntity.d.ts +472 -313
  39. package/entity/defineEntity.js +134 -290
  40. package/entity/index.d.ts +2 -2
  41. package/entity/index.js +2 -2
  42. package/entity/utils.d.ts +6 -1
  43. package/entity/utils.js +46 -11
  44. package/entity/validators.d.ts +11 -0
  45. package/entity/validators.js +66 -0
  46. package/enums.d.ts +8 -6
  47. package/enums.js +13 -17
  48. package/errors.d.ts +26 -16
  49. package/errors.js +63 -31
  50. package/events/EventManager.d.ts +3 -5
  51. package/events/EventManager.js +37 -26
  52. package/events/index.d.ts +1 -1
  53. package/events/index.js +0 -1
  54. package/exceptions.js +9 -2
  55. package/hydration/Hydrator.js +1 -2
  56. package/hydration/ObjectHydrator.d.ts +5 -6
  57. package/hydration/ObjectHydrator.js +109 -50
  58. package/index.d.ts +2 -2
  59. package/index.js +1 -2
  60. package/logging/DefaultLogger.d.ts +1 -1
  61. package/logging/DefaultLogger.js +3 -4
  62. package/logging/SimpleLogger.d.ts +1 -1
  63. package/logging/colors.d.ts +1 -1
  64. package/logging/colors.js +4 -6
  65. package/logging/index.d.ts +2 -1
  66. package/logging/index.js +1 -1
  67. package/logging/inspect.d.ts +2 -0
  68. package/logging/inspect.js +11 -0
  69. package/metadata/EntitySchema.d.ts +47 -23
  70. package/metadata/EntitySchema.js +103 -34
  71. package/metadata/MetadataDiscovery.d.ts +65 -18
  72. package/metadata/MetadataDiscovery.js +940 -424
  73. package/metadata/MetadataProvider.d.ts +11 -2
  74. package/metadata/MetadataProvider.js +71 -2
  75. package/metadata/MetadataStorage.d.ts +11 -13
  76. package/metadata/MetadataStorage.js +79 -48
  77. package/metadata/MetadataValidator.d.ts +32 -9
  78. package/metadata/MetadataValidator.js +214 -44
  79. package/metadata/discover-entities.d.ts +5 -0
  80. package/metadata/discover-entities.js +40 -0
  81. package/metadata/index.d.ts +1 -1
  82. package/metadata/index.js +0 -1
  83. package/metadata/types.d.ts +577 -0
  84. package/metadata/types.js +1 -0
  85. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  86. package/naming-strategy/AbstractNamingStrategy.js +26 -5
  87. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/EntityCaseNamingStrategy.js +7 -6
  89. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  90. package/naming-strategy/MongoNamingStrategy.js +6 -6
  91. package/naming-strategy/NamingStrategy.d.ts +28 -4
  92. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  93. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  94. package/naming-strategy/index.d.ts +1 -1
  95. package/naming-strategy/index.js +0 -1
  96. package/not-supported.d.ts +2 -0
  97. package/not-supported.js +8 -0
  98. package/package.json +47 -36
  99. package/platforms/ExceptionConverter.js +1 -1
  100. package/platforms/Platform.d.ts +33 -15
  101. package/platforms/Platform.js +125 -69
  102. package/serialization/EntitySerializer.d.ts +6 -3
  103. package/serialization/EntitySerializer.js +54 -30
  104. package/serialization/EntityTransformer.js +37 -22
  105. package/serialization/SerializationContext.d.ts +10 -14
  106. package/serialization/SerializationContext.js +24 -19
  107. package/types/ArrayType.d.ts +1 -1
  108. package/types/ArrayType.js +2 -3
  109. package/types/BigIntType.js +1 -1
  110. package/types/BlobType.d.ts +0 -1
  111. package/types/BlobType.js +0 -3
  112. package/types/BooleanType.d.ts +1 -0
  113. package/types/BooleanType.js +3 -0
  114. package/types/DecimalType.js +2 -2
  115. package/types/DoubleType.js +1 -1
  116. package/types/EnumArrayType.js +1 -2
  117. package/types/JsonType.d.ts +1 -1
  118. package/types/JsonType.js +7 -2
  119. package/types/TinyIntType.js +1 -1
  120. package/types/Type.d.ts +2 -4
  121. package/types/Type.js +3 -3
  122. package/types/Uint8ArrayType.d.ts +0 -1
  123. package/types/Uint8ArrayType.js +1 -4
  124. package/types/UuidType.d.ts +2 -0
  125. package/types/UuidType.js +14 -2
  126. package/types/index.d.ts +3 -2
  127. package/typings.d.ts +427 -170
  128. package/typings.js +100 -45
  129. package/unit-of-work/ChangeSet.d.ts +4 -6
  130. package/unit-of-work/ChangeSet.js +8 -9
  131. package/unit-of-work/ChangeSetComputer.d.ts +2 -12
  132. package/unit-of-work/ChangeSetComputer.js +61 -38
  133. package/unit-of-work/ChangeSetPersister.d.ts +10 -17
  134. package/unit-of-work/ChangeSetPersister.js +136 -73
  135. package/unit-of-work/CommitOrderCalculator.d.ts +13 -14
  136. package/unit-of-work/CommitOrderCalculator.js +22 -20
  137. package/unit-of-work/IdentityMap.d.ts +12 -3
  138. package/unit-of-work/IdentityMap.js +51 -13
  139. package/unit-of-work/UnitOfWork.d.ts +39 -23
  140. package/unit-of-work/UnitOfWork.js +441 -246
  141. package/utils/AbstractMigrator.d.ts +101 -0
  142. package/utils/AbstractMigrator.js +303 -0
  143. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  144. package/utils/AbstractSchemaGenerator.js +30 -18
  145. package/utils/AsyncContext.d.ts +6 -0
  146. package/utils/AsyncContext.js +42 -0
  147. package/utils/Configuration.d.ts +647 -185
  148. package/utils/Configuration.js +215 -252
  149. package/utils/ConfigurationLoader.d.ts +1 -52
  150. package/utils/ConfigurationLoader.js +1 -330
  151. package/utils/Cursor.d.ts +3 -6
  152. package/utils/Cursor.js +32 -17
  153. package/utils/DataloaderUtils.d.ts +10 -5
  154. package/utils/DataloaderUtils.js +42 -22
  155. package/utils/EntityComparator.d.ts +21 -21
  156. package/utils/EntityComparator.js +224 -118
  157. package/utils/QueryHelper.d.ts +34 -7
  158. package/utils/QueryHelper.js +183 -72
  159. package/utils/RawQueryFragment.d.ts +28 -34
  160. package/utils/RawQueryFragment.js +37 -72
  161. package/utils/RequestContext.js +2 -2
  162. package/utils/TransactionContext.js +2 -2
  163. package/utils/TransactionManager.js +11 -8
  164. package/utils/Utils.d.ts +16 -127
  165. package/utils/Utils.js +104 -402
  166. package/utils/clone.js +13 -23
  167. package/utils/env-vars.d.ts +7 -0
  168. package/utils/env-vars.js +98 -0
  169. package/utils/fs-utils.d.ts +20 -0
  170. package/utils/fs-utils.js +193 -0
  171. package/utils/index.d.ts +1 -3
  172. package/utils/index.js +1 -3
  173. package/utils/upsert-utils.d.ts +9 -4
  174. package/utils/upsert-utils.js +51 -5
  175. package/decorators/Check.d.ts +0 -3
  176. package/decorators/Check.js +0 -13
  177. package/decorators/CreateRequestContext.d.ts +0 -3
  178. package/decorators/CreateRequestContext.js +0 -32
  179. package/decorators/Embeddable.d.ts +0 -8
  180. package/decorators/Embeddable.js +0 -11
  181. package/decorators/Embedded.d.ts +0 -12
  182. package/decorators/Embedded.js +0 -18
  183. package/decorators/Entity.d.ts +0 -33
  184. package/decorators/Entity.js +0 -12
  185. package/decorators/Enum.d.ts +0 -9
  186. package/decorators/Enum.js +0 -16
  187. package/decorators/Filter.d.ts +0 -2
  188. package/decorators/Filter.js +0 -8
  189. package/decorators/Formula.d.ts +0 -4
  190. package/decorators/Formula.js +0 -15
  191. package/decorators/Indexed.d.ts +0 -19
  192. package/decorators/Indexed.js +0 -20
  193. package/decorators/ManyToMany.d.ts +0 -42
  194. package/decorators/ManyToMany.js +0 -14
  195. package/decorators/ManyToOne.d.ts +0 -34
  196. package/decorators/ManyToOne.js +0 -14
  197. package/decorators/OneToMany.d.ts +0 -28
  198. package/decorators/OneToMany.js +0 -17
  199. package/decorators/OneToOne.d.ts +0 -28
  200. package/decorators/OneToOne.js +0 -7
  201. package/decorators/PrimaryKey.d.ts +0 -8
  202. package/decorators/PrimaryKey.js +0 -20
  203. package/decorators/Property.d.ts +0 -250
  204. package/decorators/Property.js +0 -32
  205. package/decorators/Transactional.d.ts +0 -14
  206. package/decorators/Transactional.js +0 -28
  207. package/decorators/hooks.d.ts +0 -16
  208. package/decorators/hooks.js +0 -47
  209. package/decorators/index.d.ts +0 -17
  210. package/decorators/index.js +0 -17
  211. package/entity/ArrayCollection.d.ts +0 -118
  212. package/entity/ArrayCollection.js +0 -407
  213. package/entity/EntityValidator.d.ts +0 -19
  214. package/entity/EntityValidator.js +0 -150
  215. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  216. package/metadata/ReflectMetadataProvider.js +0 -44
  217. package/utils/resolveContextProvider.d.ts +0 -10
  218. package/utils/resolveContextProvider.js +0 -28
@@ -1,151 +1,201 @@
1
- import { basename, extname } from 'node:path';
2
- import { glob } from 'tinyglobby';
3
1
  import { EntityMetadata, } from '../typings.js';
4
- import { Utils } from '../utils/Utils.js';
2
+ import { compareArrays, Utils } from '../utils/Utils.js';
3
+ import { QueryHelper } from '../utils/QueryHelper.js';
5
4
  import { MetadataValidator } from './MetadataValidator.js';
5
+ import { MetadataProvider } from './MetadataProvider.js';
6
6
  import { MetadataStorage } from './MetadataStorage.js';
7
7
  import { EntitySchema } from './EntitySchema.js';
8
8
  import { Cascade, ReferenceKind } from '../enums.js';
9
9
  import { MetadataError } from '../errors.js';
10
- import { ArrayType, BigIntType, BlobType, DateType, DecimalType, DoubleType, EnumArrayType, IntervalType, JsonType, t, Type, Uint8ArrayType, UnknownType, } from '../types/index.js';
10
+ import { t, Type } from '../types/index.js';
11
11
  import { colors } from '../logging/colors.js';
12
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
12
+ import { raw, Raw } from '../utils/RawQueryFragment.js';
13
+ import { BaseEntity } from '../entity/BaseEntity.js';
13
14
  export class MetadataDiscovery {
14
- metadata;
15
- platform;
16
- config;
17
- namingStrategy;
18
- metadataProvider;
19
- cache;
20
- logger;
21
- schemaHelper;
22
- validator = new MetadataValidator();
23
- discovered = [];
15
+ #namingStrategy;
16
+ #metadataProvider;
17
+ #logger;
18
+ #schemaHelper;
19
+ #validator = new MetadataValidator();
20
+ #discovered = [];
21
+ #metadata;
22
+ #platform;
23
+ #config;
24
24
  constructor(metadata, platform, config) {
25
- this.metadata = metadata;
26
- this.platform = platform;
27
- this.config = config;
28
- this.namingStrategy = this.config.getNamingStrategy();
29
- this.metadataProvider = this.config.getMetadataProvider();
30
- this.cache = this.config.getMetadataCacheAdapter();
31
- this.logger = this.config.getLogger();
32
- this.schemaHelper = this.platform.getSchemaHelper();
25
+ this.#metadata = metadata;
26
+ this.#platform = platform;
27
+ this.#config = config;
28
+ this.#namingStrategy = this.#config.getNamingStrategy();
29
+ this.#metadataProvider = this.#config.getMetadataProvider();
30
+ this.#logger = this.#config.getLogger();
31
+ this.#schemaHelper = this.#platform.getSchemaHelper();
33
32
  }
34
33
  async discover(preferTs = true) {
34
+ this.#discovered.length = 0;
35
35
  const startTime = Date.now();
36
- this.logger.log('discovery', `ORM entity discovery started, using ${colors.cyan(this.metadataProvider.constructor.name)}`);
36
+ const suffix = this.#metadataProvider.constructor === MetadataProvider
37
+ ? ''
38
+ : `, using ${colors.cyan(this.#metadataProvider.constructor.name)}`;
39
+ this.#logger.log('discovery', `ORM entity discovery started${suffix}`);
37
40
  await this.findEntities(preferTs);
38
- for (const meta of this.discovered) {
41
+ for (const meta of this.#discovered) {
39
42
  /* v8 ignore next */
40
- await this.config.get('discovery').onMetadata?.(meta, this.platform);
43
+ await this.#config.get('discovery').onMetadata?.(meta, this.#platform);
41
44
  }
42
- this.processDiscoveredEntities(this.discovered);
45
+ this.processDiscoveredEntities(this.#discovered);
43
46
  const diff = Date.now() - startTime;
44
- this.logger.log('discovery', `- entity discovery finished, found ${colors.green('' + this.discovered.length)} entities, took ${colors.green(`${diff} ms`)}`);
47
+ this.#logger.log('discovery', `- entity discovery finished, found ${colors.green('' + this.#discovered.length)} entities, took ${colors.green(`${diff} ms`)}`);
45
48
  const storage = this.mapDiscoveredEntities();
46
49
  /* v8 ignore next */
47
- await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
50
+ await this.#config.get('discovery').afterDiscovered?.(storage, this.#platform);
48
51
  return storage;
49
52
  }
50
- discoverSync(preferTs = true) {
53
+ discoverSync() {
54
+ this.#discovered.length = 0;
51
55
  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);
54
- for (const meta of this.discovered) {
56
+ const suffix = this.#metadataProvider.constructor === MetadataProvider
57
+ ? ''
58
+ : `, using ${colors.cyan(this.#metadataProvider.constructor.name)}`;
59
+ this.#logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
60
+ const refs = this.#config.get('entities');
61
+ this.discoverReferences(refs);
62
+ for (const meta of this.#discovered) {
55
63
  /* v8 ignore next */
56
- void this.config.get('discovery').onMetadata?.(meta, this.platform);
64
+ void this.#config.get('discovery').onMetadata?.(meta, this.#platform);
57
65
  }
58
- this.processDiscoveredEntities(this.discovered);
66
+ this.processDiscoveredEntities(this.#discovered);
59
67
  const diff = Date.now() - startTime;
60
- this.logger.log('discovery', `- entity discovery finished, found ${colors.green('' + this.discovered.length)} entities, took ${colors.green(`${diff} ms`)}`);
68
+ this.#logger.log('discovery', `- entity discovery finished, found ${colors.green('' + this.#discovered.length)} entities, took ${colors.green(`${diff} ms`)}`);
61
69
  const storage = this.mapDiscoveredEntities();
62
70
  /* v8 ignore next */
63
- void this.config.get('discovery').afterDiscovered?.(storage, this.platform);
71
+ void this.#config.get('discovery').afterDiscovered?.(storage, this.#platform);
64
72
  return storage;
65
73
  }
66
74
  mapDiscoveredEntities() {
67
75
  const discovered = new MetadataStorage();
68
- this.discovered
76
+ this.#discovered
69
77
  .filter(meta => meta.root.name)
70
78
  .sort((a, b) => b.root.name.localeCompare(a.root.name))
71
79
  .forEach(meta => {
72
- this.platform.validateMetadata(meta);
73
- discovered.set(meta.className, meta);
80
+ this.#platform.validateMetadata(meta);
81
+ discovered.set(meta.class, meta);
74
82
  });
83
+ for (const meta of discovered) {
84
+ meta.root = discovered.get(meta.root.class);
85
+ if (meta.inheritanceType === 'tpt') {
86
+ this.computeTPTOwnProps(meta);
87
+ }
88
+ }
75
89
  return discovered;
76
90
  }
91
+ initAccessors(meta) {
92
+ for (const prop of Object.values(meta.properties)) {
93
+ if (!prop.accessor || meta.properties[prop.accessor]) {
94
+ continue;
95
+ }
96
+ const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
97
+ if (desc?.get || desc?.set) {
98
+ this.initRelation(prop);
99
+ this.initFieldName(prop);
100
+ const accessor = prop.name;
101
+ prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
102
+ if (prop.accessor === true) {
103
+ prop.getter = prop.setter = true;
104
+ }
105
+ else {
106
+ prop.getter = prop.setter = false;
107
+ }
108
+ prop.accessor = accessor;
109
+ prop.serializedName ??= accessor;
110
+ Utils.renameKey(meta.properties, accessor, prop.name);
111
+ }
112
+ else {
113
+ const name = prop.name;
114
+ if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
115
+ prop.name = prop.accessor;
116
+ }
117
+ this.initRelation(prop);
118
+ this.initFieldName(prop);
119
+ prop.serializedName ??= prop.accessor;
120
+ prop.name = name;
121
+ }
122
+ }
123
+ }
77
124
  processDiscoveredEntities(discovered) {
78
125
  for (const meta of discovered) {
79
126
  let i = 1;
80
127
  Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
81
128
  Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
129
+ this.initAccessors(meta);
82
130
  }
83
131
  // ignore base entities (not annotated with @Entity)
84
132
  const filtered = discovered.filter(meta => meta.root.name);
85
133
  // sort so we discover entities first to get around issues with nested embeddables
86
- filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
134
+ filtered.sort((a, b) => (!a.embeddable === !b.embeddable ? 0 : a.embeddable ? 1 : -1));
87
135
  filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
136
+ filtered.forEach(meta => this.initTPTRelationships(meta, filtered));
88
137
  filtered.forEach(meta => this.defineBaseEntityProperties(meta));
89
- filtered.forEach(meta => this.metadata.set(meta.className, EntitySchema.fromMetadata(meta).init().meta));
138
+ filtered.forEach(meta => {
139
+ const newMeta = EntitySchema.fromMetadata(meta).init().meta;
140
+ return this.#metadata.set(newMeta.class, newMeta);
141
+ });
90
142
  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)));
143
+ const forEachProp = (cb) => {
144
+ filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
145
+ };
146
+ forEachProp((m, p) => this.initFactoryField(m, p));
147
+ forEachProp((m, p) => this.initPolymorphicRelation(m, p, filtered));
148
+ forEachProp((_m, p) => this.initRelation(p));
149
+ forEachProp((m, p) => this.initEmbeddables(m, p));
150
+ forEachProp((_m, p) => this.initFieldName(p));
151
+ filtered.forEach(meta => this.finalizeTPTInheritance(meta, filtered));
152
+ filtered.forEach(meta => this.computeTPTOwnProps(meta));
153
+ forEachProp((m, p) => this.initVersionProperty(m, p));
154
+ forEachProp((m, p) => this.initCustomType(m, p));
155
+ forEachProp((m, p) => this.initGeneratedColumn(m, p));
97
156
  filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
98
157
  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)));
158
+ forEachProp((_m, p) => {
159
+ this.initDefaultValue(p);
160
+ this.inferTypeFromDefault(p);
161
+ this.initRelation(p);
162
+ this.initColumnType(p);
163
+ });
164
+ forEachProp((m, p) => this.initIndexes(m, p));
111
165
  filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
112
- filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
113
166
  for (const meta of filtered) {
114
167
  discovered.push(...this.processEntity(meta));
115
168
  }
116
169
  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
- }
170
+ this.#metadataProvider.combineCache();
122
171
  return discovered.map(meta => {
123
- meta = this.metadata.get(meta.className);
172
+ meta = this.#metadata.get(meta.class);
124
173
  meta.sync(true);
174
+ this.findReferencingProperties(meta, filtered);
175
+ if (meta.inheritanceType === 'tpt') {
176
+ this.computeTPTOwnProps(meta);
177
+ }
125
178
  return meta;
126
179
  });
127
180
  }
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));
181
+ async findEntities(preferTs) {
182
+ const { entities, entitiesTs, baseDir } = this.#config.getAll();
183
+ const targets = preferTs && entitiesTs.length > 0 ? entitiesTs : entities;
184
+ const processed = [];
185
+ const paths = [];
186
+ for (const entity of targets) {
187
+ if (typeof entity === 'string') {
188
+ paths.push(entity);
189
+ }
190
+ else {
191
+ processed.push(entity);
192
+ }
193
+ }
134
194
  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.`);
137
- }
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
- });
195
+ const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
196
+ processed.push(...(await discoverEntities(paths, { baseDir })));
144
197
  }
145
- this.discoverReferences(refs);
146
- this.discoverMissingTargets();
147
- this.validator.validateDiscovered(this.discovered, options);
148
- return this.discovered;
198
+ return this.discoverReferences(processed);
149
199
  }
150
200
  discoverMissingTargets() {
151
201
  const unwrap = (type) => type
@@ -153,18 +203,23 @@ export class MetadataDiscovery {
153
203
  .replace(/\[]$/, '') // remove array suffix
154
204
  .replace(/\((.*)\)/, '$1'); // unwrap union types
155
205
  const missing = [];
156
- 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);
162
- }
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'
165
- ? prop.entity()
166
- : prop.type;
167
- missing.push(...Utils.asArray(target));
206
+ this.#discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
207
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
208
+ const pivotEntity = prop.pivotEntity;
209
+ const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
210
+ ? pivotEntity()
211
+ : pivotEntity;
212
+ if (!this.#discovered.find(m => m.className === Utils.className(target))) {
213
+ missing.push(target);
214
+ }
215
+ }
216
+ if (prop.kind !== ReferenceKind.SCALAR) {
217
+ const target = typeof prop.entity === 'function' && !prop.entity.prototype ? prop.entity() : prop.type;
218
+ if (!unwrap(prop.type)
219
+ .split(/ ?\| ?/)
220
+ .every(type => this.#discovered.find(m => m.className === type))) {
221
+ missing.push(...Utils.asArray(target));
222
+ }
168
223
  }
169
224
  }));
170
225
  if (missing.length > 0) {
@@ -173,124 +228,112 @@ export class MetadataDiscovery {
173
228
  }
174
229
  tryDiscoverTargets(targets) {
175
230
  for (const target of targets) {
176
- if (typeof target === 'function' && target.name && !this.metadata.has(target.name)) {
177
- this.discoverReferences([target]);
178
- this.discoverMissingTargets();
179
- }
180
- }
181
- }
182
- async discoverDirectories(paths) {
183
- paths = paths.map(path => Utils.normalizePath(path));
184
- const files = await glob(paths, { cwd: Utils.normalizePath(this.config.get('baseDir')) });
185
- this.logger.log('discovery', `- processing ${colors.cyan('' + files.length)} files`);
186
- const found = [];
187
- for (const filepath of files) {
188
- const filename = basename(filepath);
189
- if (!filename.match(/\.[cm]?[jt]s$/) ||
190
- filename.endsWith('.js.map') ||
191
- filename.match(/\.d\.[cm]?ts/) ||
192
- filename.startsWith('.') ||
193
- filename.match(/index\.[cm]?[jt]s$/)) {
194
- this.logger.log('discovery', `- ignoring file ${filename}`);
195
- continue;
196
- }
197
- const name = this.namingStrategy.getClassName(filename);
198
- const path = Utils.normalizePath(this.config.get('baseDir'), filepath);
199
- const targets = await this.getEntityClassOrSchema(path, name);
200
- for (const target of targets) {
201
- if (!(target instanceof Function) && !(target instanceof EntitySchema)) {
202
- this.logger.log('discovery', `- ignoring file ${filename}`);
203
- continue;
231
+ const schema = target instanceof EntitySchema ? target : undefined;
232
+ const isDiscoverable = typeof target === 'function' || schema;
233
+ if (isDiscoverable && target.name) {
234
+ // Get the actual class for EntitySchema, or use target directly for classes
235
+ const targetClass = schema ? schema.meta.class : target;
236
+ if (!this.#metadata.has(targetClass)) {
237
+ this.discoverReferences([target], false);
238
+ this.discoverMissingTargets();
204
239
  }
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
240
  }
211
241
  }
212
- for (const [schema, path] of found) {
213
- this.discoverEntity(schema, path);
214
- }
215
242
  }
216
- discoverReferences(refs) {
243
+ discoverReferences(refs, validate = true) {
217
244
  const found = [];
218
245
  for (const entity of refs) {
219
- const schema = this.getSchema(this.prepare(entity));
246
+ if (typeof entity === 'string') {
247
+ throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
248
+ }
249
+ const schema = this.getSchema(entity);
220
250
  const meta = schema.init().meta;
221
- this.metadata.set(meta.className, meta);
251
+ this.#metadata.set(meta.class, meta);
222
252
  found.push(schema);
223
253
  }
224
254
  // discover parents (base entities) automatically
225
- for (const meta of this.metadata) {
255
+ for (const meta of this.#metadata) {
226
256
  let parent = meta.extends;
227
- if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.className)) {
228
- this.discoverReferences([parent]);
257
+ if (parent instanceof EntitySchema && !this.#metadata.has(parent.init().meta.class)) {
258
+ this.discoverReferences([parent], false);
259
+ }
260
+ if (typeof parent === 'function' && parent.name && !this.#metadata.has(parent)) {
261
+ this.discoverReferences([parent], false);
229
262
  }
230
- /* v8 ignore next 3 */
263
+ /* v8 ignore next */
231
264
  if (!meta.class) {
232
265
  continue;
233
266
  }
234
267
  parent = Object.getPrototypeOf(meta.class);
235
- if (parent.name !== '' && !this.metadata.has(parent.name)) {
236
- this.discoverReferences([parent]);
268
+ // Skip if parent is the auto-generated base class for the same entity (from setClass usage)
269
+ if (parent.name !== '' &&
270
+ parent.name !== meta.className &&
271
+ !this.#metadata.has(parent) &&
272
+ parent !== BaseEntity) {
273
+ this.discoverReferences([parent], false);
237
274
  }
238
275
  }
239
276
  for (const schema of found) {
240
277
  this.discoverEntity(schema);
241
278
  }
242
- return this.discovered.filter(meta => found.find(m => m.name === meta.className));
279
+ this.discoverMissingTargets();
280
+ if (validate) {
281
+ this.#validator.validateDiscovered(this.#discovered, this.#config.get('discovery'));
282
+ }
283
+ return this.#discovered.filter(meta => found.find(m => m.name === meta.className));
243
284
  }
244
- reset(className) {
245
- const exists = this.discovered.findIndex(m => m.className === className);
285
+ reset(entityName) {
286
+ const exists = this.#discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
246
287
  if (exists !== -1) {
247
- this.metadata.reset(this.discovered[exists].className);
248
- this.discovered.splice(exists, 1);
288
+ this.#metadata.reset(this.#discovered[exists].class);
289
+ this.#discovered.splice(exists, 1);
249
290
  }
250
291
  }
251
- prepare(entity) {
252
- /* v8 ignore next 3 */
253
- if ('schema' in entity && entity.schema instanceof EntitySchema) {
254
- return entity.schema;
255
- }
292
+ getSchema(entity) {
256
293
  if (EntitySchema.REGISTRY.has(entity)) {
257
- return EntitySchema.REGISTRY.get(entity);
294
+ entity = EntitySchema.REGISTRY.get(entity);
258
295
  }
259
- return entity;
260
- }
261
- getSchema(entity, filepath) {
262
296
  if (entity instanceof EntitySchema) {
263
- if (filepath) {
264
- // initialize global metadata for given entity
265
- MetadataStorage.getMetadata(entity.meta.className, filepath);
266
- }
267
297
  const meta = Utils.copy(entity.meta, false);
268
298
  return EntitySchema.fromMetadata(meta);
269
299
  }
270
300
  const path = entity[MetadataStorage.PATH_SYMBOL];
271
301
  if (path) {
272
302
  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);
303
+ meta.path = path;
304
+ this.#metadata.set(entity, meta);
275
305
  }
276
- const exists = this.metadata.has(entity.name);
277
- const meta = this.metadata.get(entity.name, true);
306
+ const exists = this.#metadata.has(entity);
307
+ const meta = this.#metadata.get(entity, true);
278
308
  meta.abstract ??= !(exists && meta.name);
279
309
  const schema = EntitySchema.fromMetadata(meta);
280
310
  schema.setClass(entity);
281
311
  return schema;
282
312
  }
283
- discoverEntity(schema, path) {
284
- this.logger.log('discovery', `- processing entity ${colors.cyan(schema.meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
313
+ getRootEntity(meta) {
314
+ const base = meta.extends && this.#metadata.find(meta.extends);
315
+ if (!base || base === meta) {
316
+ // make sure we do not fall into infinite loop
317
+ return meta;
318
+ }
319
+ const root = this.getRootEntity(base);
320
+ // For STI or TPT, use the root entity.
321
+ // Check both `inheritanceType` (set during discovery) and raw `inheritance` option (set before discovery).
322
+ if (root.discriminatorColumn || root.inheritanceType || root.inheritance === 'tpt') {
323
+ return root;
324
+ }
325
+ return meta;
326
+ }
327
+ discoverEntity(schema) {
285
328
  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));
329
+ const path = meta.path;
330
+ this.#logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
331
+ const root = this.getRootEntity(meta);
332
+ schema.meta.path = meta.path;
333
+ const cache = this.#metadataProvider.getCachedMetadata(meta, root);
289
334
  if (cache) {
290
- this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
291
- this.metadataProvider.loadFromCache(meta, cache);
292
- meta.root = root;
293
- this.discovered.push(meta);
335
+ this.#logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
336
+ this.#discovered.push(meta);
294
337
  return;
295
338
  }
296
339
  // infer default value from property initializer early, as the metadata provider might use some defaults, e.g. string for reflect-metadata
@@ -298,50 +341,18 @@ export class MetadataDiscovery {
298
341
  this.inferDefaultValue(meta, prop);
299
342
  }
300
343
  // 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) {
344
+ this.#metadataProvider.loadEntityMetadata(meta);
345
+ if (!meta.tableName && meta.name) {
303
346
  const entityName = root.discriminatorColumn ? root.name : meta.name;
304
- meta.collection = this.namingStrategy.classToTableName(entityName);
347
+ meta.tableName = this.#namingStrategy.classToTableName(entityName);
305
348
  }
306
- delete meta.root; // to allow caching (as root can contain cycles)
307
- this.saveToCache(meta);
349
+ this.#metadataProvider.saveToCache(meta);
308
350
  meta.root = root;
309
- this.discovered.push(meta);
310
- }
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
- }
351
+ this.#discovered.push(meta);
338
352
  }
339
353
  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
354
  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));
355
+ return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
345
356
  }
346
357
  return Utils.defaultValue(prop, 'nullable', prop.optional);
347
358
  }
@@ -362,7 +373,16 @@ export class MetadataDiscovery {
362
373
  initOwnColumns(meta) {
363
374
  meta.sync();
364
375
  for (const prop of meta.props) {
365
- if (!prop.joinColumns || !prop.columnTypes || prop.ownColumns || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
376
+ if (!prop.joinColumns ||
377
+ !prop.columnTypes ||
378
+ prop.ownColumns ||
379
+ ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
380
+ continue;
381
+ }
382
+ // For polymorphic relations, ownColumns should include all fieldNames
383
+ // (discriminator + ID columns) since they are all owned by this relation
384
+ if (prop.polymorphic) {
385
+ prop.ownColumns = prop.fieldNames;
366
386
  continue;
367
387
  }
368
388
  if (prop.joinColumns.length > 1) {
@@ -376,7 +396,7 @@ export class MetadataDiscovery {
376
396
  if (prop.joinColumns.length !== prop.columnTypes.length) {
377
397
  prop.columnTypes = prop.joinColumns.flatMap(field => {
378
398
  const matched = meta.props.find(p => p.fieldNames?.includes(field));
379
- /* v8 ignore next 3 */
399
+ /* v8 ignore next */
380
400
  if (!matched) {
381
401
  throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
382
402
  }
@@ -393,34 +413,34 @@ export class MetadataDiscovery {
393
413
  return;
394
414
  }
395
415
  if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
396
- prop.fieldNames = [this.namingStrategy.propertyToColumnName(prop.name, object)];
416
+ prop.fieldNames = [this.#namingStrategy.propertyToColumnName(prop.name, object)];
397
417
  }
398
- else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
418
+ else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
399
419
  prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
400
420
  }
401
421
  else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
402
422
  prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
403
423
  }
404
424
  }
405
- initManyToOneFieldName(prop, name) {
406
- const meta2 = this.metadata.get(prop.type);
425
+ initManyToOneFieldName(prop, name, tableName) {
426
+ const meta2 = prop.targetMeta;
407
427
  const ret = [];
408
428
  for (const primaryKey of meta2.primaryKeys) {
409
429
  this.initFieldName(meta2.properties[primaryKey]);
410
430
  for (const fieldName of meta2.properties[primaryKey].fieldNames) {
411
- ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
431
+ ret.push(this.#namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
412
432
  }
413
433
  }
414
434
  return ret;
415
435
  }
416
436
  initManyToManyFieldName(prop, name) {
417
- const meta2 = this.metadata.get(prop.type);
418
- return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
437
+ const meta2 = prop.targetMeta;
438
+ return meta2.primaryKeys.map(() => this.#namingStrategy.propertyToColumnName(name));
419
439
  }
420
440
  initManyToManyFields(meta, prop) {
421
- const meta2 = this.metadata.get(prop.type);
441
+ const meta2 = prop.targetMeta;
422
442
  Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
423
- const pivotMeta = this.metadata.find(prop.pivotEntity);
443
+ const pivotMeta = this.#metadata.find(prop.pivotEntity);
424
444
  const props = Object.values(pivotMeta?.properties ?? {});
425
445
  const pks = props.filter(p => p.primary);
426
446
  const fks = props.filter(p => p.kind === ReferenceKind.MANY_TO_ONE);
@@ -438,38 +458,80 @@ export class MetadataDiscovery {
438
458
  prop.joinColumns ??= first.fieldNames;
439
459
  prop.inverseJoinColumns ??= second.fieldNames;
440
460
  }
441
- if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
442
- prop.pivotTable = this.namingStrategy.joinTableName(meta.tableName, meta2.tableName, prop.name);
461
+ if (!prop.pivotTable && prop.owner && this.#platform.usesPivotTable()) {
462
+ prop.pivotTable = this.#namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
443
463
  }
444
464
  if (prop.mappedBy) {
445
465
  const prop2 = meta2.properties[prop.mappedBy];
446
466
  this.initManyToManyFields(meta2, prop2);
447
467
  prop.pivotTable = prop2.pivotTable;
448
- prop.pivotEntity = prop2.pivotEntity ?? prop2.pivotTable;
468
+ prop.pivotEntity = prop2.pivotEntity;
449
469
  prop.fixedOrder = prop2.fixedOrder;
450
470
  prop.fixedOrderColumn = prop2.fixedOrderColumn;
451
471
  prop.joinColumns = prop2.inverseJoinColumns;
452
472
  prop.inverseJoinColumns = prop2.joinColumns;
473
+ prop.polymorphic = prop2.polymorphic;
474
+ prop.discriminator = prop2.discriminator;
475
+ prop.discriminatorColumn = prop2.discriminatorColumn;
476
+ prop.discriminatorValue = prop2.discriminatorValue;
453
477
  }
454
478
  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);
479
+ // For polymorphic M:N, use discriminator base name for FK column (e.g., taggable_id instead of post_id)
480
+ if (prop.polymorphic && prop.discriminator) {
481
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
482
+ }
483
+ else {
484
+ const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
485
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
486
+ }
487
+ const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
488
+ prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
489
+ }
490
+ isExplicitTableName(meta) {
491
+ return meta.tableName !== this.#namingStrategy.classToTableName(meta.className);
457
492
  }
458
493
  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);
494
+ if (prop.polymorphic && prop.polymorphTargets) {
495
+ const fieldNames1 = prop.targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
496
+ const idColumns = fieldNames1.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, fieldNames1.length > 1));
497
+ prop.fieldNames ??= [prop.discriminatorColumn, ...idColumns];
498
+ prop.joinColumns ??= idColumns;
499
+ prop.referencedColumnNames ??= fieldNames1;
500
+ prop.referencedTableName ??= prop.targetMeta.tableName;
501
+ return;
502
+ }
503
+ const meta2 = prop.targetMeta;
504
+ let fieldNames;
505
+ // If targetKey is specified, use that property's field names instead of PKs
506
+ if (prop.targetKey) {
507
+ const targetProp = meta2.properties[prop.targetKey];
508
+ fieldNames = targetProp.fieldNames;
509
+ }
510
+ else {
511
+ fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
512
+ }
513
+ Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
462
514
  if (!prop.joinColumns) {
463
- prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
515
+ prop.joinColumns = fieldNames.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
464
516
  }
465
517
  if (!prop.referencedColumnNames) {
466
518
  prop.referencedColumnNames = fieldNames;
467
519
  }
520
+ // Relations to composite PK targets need cascade update by default,
521
+ // since composite PKs are more likely to have mutable components
522
+ if (meta2.compositePK) {
523
+ prop.updateRule ??= 'cascade';
524
+ }
525
+ // Nullable relations default to 'set null' on delete - when the referenced
526
+ // entity is deleted, set the FK to null rather than failing
527
+ if (prop.nullable) {
528
+ prop.deleteRule ??= 'set null';
529
+ }
468
530
  }
469
531
  initOneToManyFields(prop) {
470
- const meta2 = this.metadata.get(prop.type);
532
+ const meta2 = prop.targetMeta;
471
533
  if (!prop.joinColumns) {
472
- prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
534
+ prop.joinColumns = [this.#namingStrategy.joinColumnName(prop.name)];
473
535
  }
474
536
  if (!prop.referencedColumnNames) {
475
537
  meta2.getPrimaryProps().forEach(pk => this.applyNamingStrategy(meta2, pk));
@@ -480,12 +542,17 @@ export class MetadataDiscovery {
480
542
  const pks = Object.values(meta.properties).filter(prop => prop.primary);
481
543
  meta.primaryKeys = pks.map(prop => prop.name);
482
544
  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';
545
+ // FK used as PK, we need to cascade - applies to both single FK-as-PK
546
+ // and composite PKs where all PKs are FKs (e.g., pivot-like entities)
547
+ const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
548
+ if (fkPks.length > 0 && fkPks.length === pks.length) {
549
+ for (const pk of fkPks) {
550
+ pk.deleteRule ??= 'cascade';
551
+ pk.updateRule ??= 'cascade';
552
+ }
486
553
  }
487
554
  meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
488
- this.validator.validateEntityDefinition(this.metadata, meta.className, this.config.get('discovery'));
555
+ this.#validator.validateEntityDefinition(this.#metadata, meta.class, this.#config.get('discovery'));
489
556
  for (const prop of Object.values(meta.properties)) {
490
557
  this.initNullability(prop);
491
558
  this.applyNamingStrategy(meta, prop);
@@ -497,16 +564,23 @@ export class MetadataDiscovery {
497
564
  this.initRelation(prop);
498
565
  }
499
566
  this.initOwnColumns(meta);
500
- 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;
567
+ meta.simplePK =
568
+ pks.length === 1 && pks[0].kind === ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
569
+ meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
570
+ if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
571
+ meta.properties[meta.serializedPrimaryKey].persist ??= false;
505
572
  }
506
- if (this.platform.usesPivotTable()) {
573
+ if (this.#platform.usesPivotTable()) {
507
574
  return Object.values(meta.properties)
508
575
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
509
- .map(prop => this.definePivotTableEntity(meta, prop));
576
+ .map(prop => {
577
+ const pivotMeta = this.definePivotTableEntity(meta, prop);
578
+ prop.pivotEntity = pivotMeta.class;
579
+ if (prop.inversedBy) {
580
+ prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
581
+ }
582
+ return pivotMeta;
583
+ });
510
584
  }
511
585
  return [];
512
586
  }
@@ -523,8 +597,11 @@ export class MetadataDiscovery {
523
597
  ['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
524
598
  const value = prop[type];
525
599
  if (value instanceof Function) {
526
- const meta2 = this.metadata.get(prop.type);
600
+ const meta2 = prop.targetMeta ?? this.#metadata.get(prop.target);
527
601
  prop[type] = value(meta2.properties)?.name;
602
+ if (type === 'pivotEntity' && value) {
603
+ prop[type] = value(meta2.properties);
604
+ }
528
605
  if (prop[type] == null) {
529
606
  throw MetadataError.fromWrongReference(meta, prop, type);
530
607
  }
@@ -540,9 +617,9 @@ export class MetadataDiscovery {
540
617
  }
541
618
  else if (fks.length >= 2) {
542
619
  [first, second] = fks;
543
- /* v8 ignore next 3 */
544
620
  }
545
621
  else {
622
+ /* v8 ignore next */
546
623
  return [];
547
624
  }
548
625
  // wrong FK order, first FK needs to point to the owning side
@@ -556,7 +633,9 @@ export class MetadataDiscovery {
556
633
  return [first, second];
557
634
  }
558
635
  definePivotTableEntity(meta, prop) {
559
- const pivotMeta = this.metadata.find(prop.pivotEntity);
636
+ const pivotMeta = prop.pivotEntity
637
+ ? this.#metadata.find(prop.pivotEntity)
638
+ : this.#metadata.getByClassName(prop.pivotTable, false);
560
639
  // ensure inverse side exists so we can join it when populating via pivot tables
561
640
  if (!prop.inversedBy && prop.targetMeta) {
562
641
  const inverseName = `${meta.className}_${prop.name}__inverse`;
@@ -565,6 +644,8 @@ export class MetadataDiscovery {
565
644
  name: inverseName,
566
645
  kind: ReferenceKind.MANY_TO_MANY,
567
646
  type: meta.className,
647
+ target: meta.class,
648
+ targetMeta: meta,
568
649
  mappedBy: prop.name,
569
650
  pivotEntity: prop.pivotEntity,
570
651
  pivotTable: prop.pivotTable,
@@ -573,69 +654,208 @@ export class MetadataDiscovery {
573
654
  };
574
655
  this.applyNamingStrategy(prop.targetMeta, inverseProp);
575
656
  this.initCustomType(prop.targetMeta, inverseProp);
576
- this.initRelation(inverseProp);
577
657
  prop.targetMeta.properties[inverseName] = inverseProp;
578
658
  }
579
659
  if (pivotMeta) {
660
+ prop.pivotEntity = pivotMeta.class;
580
661
  this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
662
+ if (prop.polymorphic && prop.discriminatorValue) {
663
+ pivotMeta.polymorphicDiscriminatorMap ??= {};
664
+ pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
665
+ // For composite PK entities sharing a polymorphic pivot table,
666
+ // we need to add columns for each entity type's PKs
667
+ this.addPolymorphicPivotColumns(pivotMeta, meta, prop);
668
+ // Add virtual M:1 relation for this polymorphic owner (for join loading)
669
+ const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
670
+ if (!pivotMeta.properties[ownerRelationName]) {
671
+ pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
672
+ }
673
+ }
581
674
  return pivotMeta;
582
675
  }
583
- const exists = this.metadata.find(prop.pivotTable);
584
- if (exists) {
585
- prop.pivotEntity = exists.className;
586
- return exists;
587
- }
588
676
  let tableName = prop.pivotTable;
589
677
  let schemaName;
590
678
  if (prop.pivotTable.includes('.')) {
591
679
  [schemaName, tableName] = prop.pivotTable.split('.');
592
680
  }
593
681
  schemaName ??= meta.schema;
594
- const targetType = prop.targetMeta.className;
595
- const data = new EntityMetadata({
682
+ const targetMeta = prop.targetMeta;
683
+ const targetType = targetMeta.className;
684
+ const pivotMeta2 = new EntityMetadata({
596
685
  name: prop.pivotTable,
597
686
  className: prop.pivotTable,
598
687
  collection: tableName,
599
688
  schema: schemaName,
600
689
  pivotTable: true,
601
690
  });
602
- prop.pivotEntity = data.className;
691
+ prop.pivotEntity = pivotMeta2.class;
603
692
  if (prop.fixedOrder) {
604
- const primaryProp = this.defineFixedOrderProperty(prop, targetType);
605
- data.properties[primaryProp.name] = primaryProp;
693
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
694
+ pivotMeta2.properties[primaryProp.name] = primaryProp;
606
695
  }
607
696
  else {
608
- data.compositePK = true;
697
+ pivotMeta2.compositePK = true;
609
698
  }
610
699
  // handle self-referenced m:n with same default field names
611
- 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));
700
+ if (meta.className === targetType &&
701
+ prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
702
+ // use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
703
+ const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
704
+ prop.joinColumns = prop.referencedColumnNames.map(name => this.#namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
705
+ prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.#namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
614
706
  if (prop.inversedBy) {
615
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
707
+ const prop2 = targetMeta.properties[prop.inversedBy];
616
708
  prop2.inverseJoinColumns = prop.joinColumns;
617
709
  prop2.joinColumns = prop.inverseJoinColumns;
618
710
  }
711
+ // propagate updated joinColumns to all child entities that inherit this property (STI)
712
+ for (const childMeta of this.#discovered.filter(m => m.root === meta && m !== meta)) {
713
+ const childProp = childMeta.properties[prop.name];
714
+ if (childProp) {
715
+ childProp.joinColumns = prop.joinColumns;
716
+ childProp.inverseJoinColumns = prop.inverseJoinColumns;
717
+ }
718
+ }
719
+ }
720
+ // For polymorphic M:N, create discriminator column and polymorphic FK
721
+ if (prop.polymorphic && prop.discriminatorColumn) {
722
+ this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
619
723
  }
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, EntitySchema.fromMetadata(data).init().meta);
724
+ else {
725
+ pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
726
+ pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
727
+ }
728
+ return this.#metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
729
+ }
730
+ /**
731
+ * Create a scalar property for a pivot table column.
732
+ */
733
+ createPivotScalarProperty(name, columnTypes, fieldNames = [name], options = {}) {
734
+ return {
735
+ name,
736
+ fieldNames,
737
+ columnTypes,
738
+ type: options.type ?? 'number',
739
+ kind: ReferenceKind.SCALAR,
740
+ primary: options.primary ?? false,
741
+ nullable: options.nullable ?? true,
742
+ ...(options.persist !== undefined && { persist: options.persist }),
743
+ };
623
744
  }
624
- defineFixedOrderProperty(prop, targetType) {
625
- const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
745
+ /**
746
+ * Get column types for an entity's primary keys, initializing them if needed.
747
+ */
748
+ getPrimaryKeyColumnTypes(meta) {
749
+ const columnTypes = [];
750
+ for (const pk of meta.primaryKeys) {
751
+ const pkProp = meta.properties[pk];
752
+ this.initCustomType(meta, pkProp);
753
+ this.initColumnType(pkProp);
754
+ columnTypes.push(...pkProp.columnTypes);
755
+ }
756
+ return columnTypes;
757
+ }
758
+ /**
759
+ * Add missing FK columns for a polymorphic entity to an existing pivot table.
760
+ */
761
+ addPolymorphicPivotColumns(pivotMeta, meta, prop) {
762
+ const existingFieldNames = new Set(Object.values(pivotMeta.properties).flatMap(p => p.fieldNames ?? []));
763
+ const columnTypes = this.getPrimaryKeyColumnTypes(meta);
764
+ for (let i = 0; i < prop.joinColumns.length; i++) {
765
+ const joinColumn = prop.joinColumns[i];
766
+ if (!existingFieldNames.has(joinColumn)) {
767
+ pivotMeta.properties[joinColumn] = this.createPivotScalarProperty(joinColumn, [columnTypes[i]]);
768
+ }
769
+ }
770
+ }
771
+ /**
772
+ * Define properties for a polymorphic pivot table.
773
+ */
774
+ definePolymorphicPivotProperties(pivotMeta, meta, prop, targetMeta) {
775
+ const discriminatorColumn = prop.discriminatorColumn;
776
+ const isCompositePK = meta.compositePK;
777
+ // For composite PK polymorphic M:N, we need fixedOrder (auto-increment PK)
778
+ if (isCompositePK && !prop.fixedOrder) {
779
+ prop.fixedOrder = true;
780
+ const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
781
+ pivotMeta.properties[primaryProp.name] = primaryProp;
782
+ pivotMeta.compositePK = false;
783
+ }
784
+ const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.#platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: !isCompositePK, nullable: false });
785
+ this.initFieldName(discriminatorProp);
786
+ pivotMeta.properties[discriminatorColumn] = discriminatorProp;
787
+ const columnTypes = this.getPrimaryKeyColumnTypes(meta);
788
+ if (isCompositePK) {
789
+ // Create separate properties for each PK column (nullable for other entity types)
790
+ for (let i = 0; i < prop.joinColumns.length; i++) {
791
+ pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [
792
+ columnTypes[i],
793
+ ]);
794
+ }
795
+ // Virtual property combining all columns (for compatibility)
796
+ pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, persist: false });
797
+ }
798
+ else {
799
+ pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, primary: true, nullable: false });
800
+ }
801
+ pivotMeta.properties[targetMeta.className + '_inverse'] = this.definePivotProperty(prop, targetMeta.className + '_inverse', targetMeta.class, prop.discriminator, false, false);
802
+ // Create virtual M:1 relation to the polymorphic owner for single-query join loading
803
+ const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
804
+ pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
805
+ pivotMeta.polymorphicDiscriminatorMap ??= {};
806
+ pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
807
+ }
808
+ /**
809
+ * Create a virtual M:1 relation from pivot to a polymorphic owner entity.
810
+ * This enables single-query join loading for inverse-side polymorphic M:N.
811
+ */
812
+ definePolymorphicOwnerRelation(prop, name, targetMeta) {
813
+ const ret = {
814
+ name,
815
+ type: targetMeta.className,
816
+ target: targetMeta.class,
817
+ kind: ReferenceKind.MANY_TO_ONE,
818
+ nullable: true,
819
+ owner: true,
820
+ primary: false,
821
+ createForeignKeyConstraint: false,
822
+ persist: false,
823
+ index: false,
824
+ };
825
+ ret.targetMeta = targetMeta;
826
+ ret.fieldNames = ret.joinColumns = ret.ownColumns = [...prop.joinColumns];
827
+ ret.referencedColumnNames = [];
828
+ ret.inverseJoinColumns = [];
829
+ for (const primaryKey of targetMeta.primaryKeys) {
830
+ const pkProp = targetMeta.properties[primaryKey];
831
+ ret.referencedColumnNames.push(...pkProp.fieldNames);
832
+ ret.inverseJoinColumns.push(...pkProp.fieldNames);
833
+ ret.length = pkProp.length;
834
+ ret.precision = pkProp.precision;
835
+ ret.scale = pkProp.scale;
836
+ }
837
+ const schema = targetMeta.schema ?? this.#config.get('schema') ?? this.#platform.getDefaultSchemaName();
838
+ ret.referencedTableName = schema && schema !== '*' ? schema + '.' + targetMeta.tableName : targetMeta.tableName;
839
+ this.initColumnType(ret);
840
+ this.initRelation(ret);
841
+ return ret;
842
+ }
843
+ defineFixedOrderProperty(prop, targetMeta) {
844
+ const pk = prop.fixedOrderColumn || this.#namingStrategy.referenceColumnName();
626
845
  const primaryProp = {
627
846
  name: pk,
628
847
  type: 'number',
848
+ runtimeType: 'number',
629
849
  kind: ReferenceKind.SCALAR,
630
850
  primary: true,
631
851
  autoincrement: true,
632
- unsigned: this.platform.supportsUnsigned(),
852
+ unsigned: this.#platform.supportsUnsigned(),
633
853
  };
634
854
  this.initFieldName(primaryProp);
635
855
  this.initColumnType(primaryProp);
636
856
  prop.fixedOrderColumn = pk;
637
857
  if (prop.inversedBy) {
638
- const prop2 = this.metadata.get(targetType).properties[prop.inversedBy];
858
+ const prop2 = targetMeta.properties[prop.inversedBy];
639
859
  prop2.fixedOrder = true;
640
860
  prop2.fixedOrderColumn = pk;
641
861
  }
@@ -644,27 +864,27 @@ export class MetadataDiscovery {
644
864
  definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
645
865
  const ret = {
646
866
  name,
647
- type,
867
+ type: Utils.className(type),
868
+ target: type,
648
869
  kind: ReferenceKind.MANY_TO_ONE,
649
870
  cascade: [Cascade.ALL],
650
871
  fixedOrder: prop.fixedOrder,
651
872
  fixedOrderColumn: prop.fixedOrderColumn,
652
- index: this.platform.indexForeignKeys(),
873
+ index: this.#platform.indexForeignKeys(),
653
874
  primary: !prop.fixedOrder,
654
875
  autoincrement: false,
655
876
  updateRule: prop.updateRule,
656
877
  deleteRule: prop.deleteRule,
657
878
  createForeignKeyConstraint: prop.createForeignKeyConstraint,
658
879
  };
659
- if (selfReferencing && !this.platform.supportsMultipleCascadePaths()) {
660
- ret.updateRule ??= 'no action';
661
- ret.deleteRule ??= 'no action';
662
- }
663
- const meta = this.metadata.get(type);
880
+ const defaultRule = selfReferencing && !this.#platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
881
+ ret.updateRule ??= defaultRule;
882
+ ret.deleteRule ??= defaultRule;
883
+ const meta = this.#metadata.get(type);
664
884
  ret.targetMeta = meta;
665
885
  ret.joinColumns = [];
666
886
  ret.inverseJoinColumns = [];
667
- const schema = meta.schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
887
+ const schema = meta.schema ?? this.#config.get('schema') ?? this.#platform.getDefaultSchemaName();
668
888
  ret.referencedTableName = schema && schema !== '*' ? schema + '.' + meta.tableName : meta.tableName;
669
889
  if (owner) {
670
890
  ret.owner = true;
@@ -702,7 +922,7 @@ export class MetadataDiscovery {
702
922
  Object.values(meta.properties)
703
923
  .filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
704
924
  .forEach(prop => {
705
- const meta2 = this.metadata.get(prop.type);
925
+ const meta2 = prop.targetMeta;
706
926
  const prop2 = meta2.properties[prop.mappedBy];
707
927
  if (prop2 && !prop2.inversedBy) {
708
928
  prop2.inversedBy = prop.name;
@@ -710,8 +930,9 @@ export class MetadataDiscovery {
710
930
  });
711
931
  }
712
932
  defineBaseEntityProperties(meta) {
713
- const base = meta.extends && this.metadata.get(Utils.className(meta.extends));
714
- if (!base || base === meta) { // make sure we do not fall into infinite loop
933
+ const base = meta.extends && this.#metadata.get(meta.extends);
934
+ if (!base || base === meta) {
935
+ // make sure we do not fall into infinite loop
715
936
  return 0;
716
937
  }
717
938
  let order = this.defineBaseEntityProperties(base);
@@ -723,10 +944,12 @@ export class MetadataDiscovery {
723
944
  meta.properties[prop.name] = prop;
724
945
  }
725
946
  });
726
- ownProps.forEach(prop => meta.properties[prop.name] = prop);
947
+ ownProps.forEach(prop => (meta.properties[prop.name] = prop));
727
948
  meta.filters = { ...base.filters, ...meta.filters };
728
949
  if (!meta.discriminatorValue) {
729
- Object.values(base.properties).filter(prop => !old.includes(prop.name)).forEach(prop => {
950
+ Object.values(base.properties)
951
+ .filter(prop => !old.includes(prop.name))
952
+ .forEach(prop => {
730
953
  meta.properties[prop.name] = { ...prop };
731
954
  meta.propertyOrder.set(prop.name, (order += 0.01));
732
955
  });
@@ -734,19 +957,18 @@ export class MetadataDiscovery {
734
957
  meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
735
958
  meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
736
959
  meta.checks = Utils.unique([...base.checks, ...meta.checks]);
737
- const pks = Object.values(meta.properties).filter(p => p.primary).map(p => p.name);
960
+ const pks = Object.values(meta.properties)
961
+ .filter(p => p.primary)
962
+ .map(p => p.name);
738
963
  if (pks.length > 0 && meta.primaryKeys.length === 0) {
739
964
  meta.primaryKeys = pks;
740
965
  }
741
966
  Utils.keys(base.hooks).forEach(type => {
742
967
  meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
743
968
  });
744
- if (meta.constructorParams.length === 0 && base.constructorParams.length > 0) {
969
+ if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
745
970
  meta.constructorParams = [...base.constructorParams];
746
971
  }
747
- if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
748
- meta.toJsonParams = [...base.toJsonParams];
749
- }
750
972
  return order;
751
973
  }
752
974
  initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
@@ -755,8 +977,8 @@ export class MetadataDiscovery {
755
977
  }
756
978
  visited.add(embeddedProp);
757
979
  const types = embeddedProp.type.split(/ ?\| ?/);
758
- let embeddable = this.discovered.find(m => m.name === embeddedProp.type);
759
- const polymorphs = this.discovered.filter(m => types.includes(m.name));
980
+ let embeddable = this.#discovered.find(m => m.name === embeddedProp.type);
981
+ const polymorphs = this.#discovered.filter(m => types.includes(m.name));
760
982
  // create virtual polymorphic entity
761
983
  if (!embeddable && polymorphs.length > 0) {
762
984
  const properties = {};
@@ -770,11 +992,11 @@ export class MetadataDiscovery {
770
992
  properties[prop.name].runtimeType = 'any';
771
993
  return properties[prop.name];
772
994
  }
773
- return properties[prop.name] = prop;
995
+ return (properties[prop.name] = prop);
774
996
  });
775
997
  };
776
998
  const processExtensions = (meta) => {
777
- const parent = this.discovered.find(m => {
999
+ const parent = this.#discovered.find(m => {
778
1000
  return meta.extends && Utils.className(meta.extends) === m.className;
779
1001
  });
780
1002
  if (!parent) {
@@ -788,7 +1010,10 @@ export class MetadataDiscovery {
788
1010
  inlineProperties(meta);
789
1011
  processExtensions(meta);
790
1012
  });
791
- const name = polymorphs.map(t => t.className).sort().join(' | ');
1013
+ const name = polymorphs
1014
+ .map(t => t.className)
1015
+ .sort()
1016
+ .join(' | ');
792
1017
  embeddable = new EntityMetadata({
793
1018
  name,
794
1019
  className: name,
@@ -800,7 +1025,55 @@ export class MetadataDiscovery {
800
1025
  });
801
1026
  embeddable.sync();
802
1027
  discovered.push(embeddable);
803
- polymorphs.forEach(meta => meta.root = embeddable);
1028
+ polymorphs.forEach(meta => (meta.root = embeddable));
1029
+ }
1030
+ }
1031
+ initPolymorphicRelation(meta, prop, discovered) {
1032
+ if (!prop.discriminator && !prop.discriminatorColumn && !prop.discriminatorMap && !Array.isArray(prop.target)) {
1033
+ return;
1034
+ }
1035
+ prop.polymorphic = true;
1036
+ prop.discriminator ??= prop.name;
1037
+ prop.discriminatorColumn ??= this.#namingStrategy.discriminatorColumnName(prop.discriminator);
1038
+ prop.createForeignKeyConstraint = false;
1039
+ const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
1040
+ if (isToOne) {
1041
+ const types = prop.type.split(/ ?\| ?/);
1042
+ prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
1043
+ prop.targetMeta = prop.polymorphTargets[0];
1044
+ prop.referencedPKs = prop.targetMeta?.primaryKeys;
1045
+ }
1046
+ if (prop.discriminatorMap) {
1047
+ const normalizedMap = {};
1048
+ for (const [key, value] of Object.entries(prop.discriminatorMap)) {
1049
+ const targetMeta = this.#metadata.getByClassName(value, false);
1050
+ if (!targetMeta) {
1051
+ throw MetadataError.fromUnknownEntity(value, `${meta.className}.${prop.name} discriminatorMap`);
1052
+ }
1053
+ normalizedMap[key] = targetMeta.class;
1054
+ if (targetMeta.class === meta.class) {
1055
+ prop.discriminatorValue = key;
1056
+ }
1057
+ }
1058
+ prop.discriminatorMap = normalizedMap;
1059
+ }
1060
+ else if (isToOne) {
1061
+ prop.discriminatorMap = {};
1062
+ const tableNameToTarget = new Map();
1063
+ for (const target of prop.polymorphTargets) {
1064
+ const existing = tableNameToTarget.get(target.tableName);
1065
+ if (existing) {
1066
+ throw MetadataError.incompatiblePolymorphicTargets(meta, prop, existing, target, `both use table '${target.tableName}'. Use separate properties instead of a single polymorphic relation.`);
1067
+ }
1068
+ tableNameToTarget.set(target.tableName, target);
1069
+ prop.discriminatorMap[target.tableName] = target.class;
1070
+ }
1071
+ }
1072
+ else {
1073
+ prop.discriminatorValue ??= meta.tableName;
1074
+ if (!prop.discriminatorMap) {
1075
+ prop.discriminatorMap = { [prop.discriminatorValue]: meta.class };
1076
+ }
804
1077
  }
805
1078
  }
806
1079
  initEmbeddables(meta, embeddedProp, visited = new Set()) {
@@ -808,7 +1081,7 @@ export class MetadataDiscovery {
808
1081
  return;
809
1082
  }
810
1083
  visited.add(embeddedProp);
811
- const embeddable = this.discovered.find(m => m.name === embeddedProp.type);
1084
+ const embeddable = this.#discovered.find(m => m.name === embeddedProp.type);
812
1085
  if (!embeddable) {
813
1086
  throw MetadataError.fromUnknownEntity(embeddedProp.type, `${meta.className}.${embeddedProp.name}`);
814
1087
  }
@@ -838,7 +1111,7 @@ export class MetadataDiscovery {
838
1111
  const glue = object ? '~' : '_';
839
1112
  for (const prop of Object.values(embeddable.properties)) {
840
1113
  const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
841
- meta.properties[name] = Utils.copy(prop, false);
1114
+ meta.properties[name] = Utils.copy(prop);
842
1115
  meta.properties[name].name = name;
843
1116
  meta.properties[name].embedded = [embeddedProp.name, prop.name];
844
1117
  meta.propertyOrder.set(name, (order += 0.01));
@@ -875,10 +1148,12 @@ export class MetadataDiscovery {
875
1148
  path = [embeddedProp.fieldNames[0]];
876
1149
  }
877
1150
  this.initFieldName(prop, true);
1151
+ this.initRelation(prop);
878
1152
  path.push(prop.fieldNames[0]);
879
1153
  meta.properties[name].fieldNames = prop.fieldNames;
880
1154
  meta.properties[name].embeddedPath = path;
881
- const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), prop.runtimeType ?? prop.type, true));
1155
+ const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
1156
+ const fieldName = raw(this.#platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
882
1157
  meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
883
1158
  meta.properties[name].persist = false; // only virtual as we store the whole object
884
1159
  meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
@@ -906,7 +1181,7 @@ export class MetadataDiscovery {
906
1181
  }
907
1182
  initSingleTableInheritance(meta, metadata) {
908
1183
  if (meta.root !== meta && !meta.__processed) {
909
- meta.root = metadata.find(m => m.className === meta.root.className);
1184
+ meta.root = metadata.find(m => m.class === meta.root.class);
910
1185
  meta.root.__processed = true;
911
1186
  }
912
1187
  else {
@@ -915,17 +1190,24 @@ export class MetadataDiscovery {
915
1190
  if (!meta.root.discriminatorColumn) {
916
1191
  return;
917
1192
  }
918
- if (!meta.root.discriminatorMap) {
1193
+ meta.root.inheritanceType = 'sti';
1194
+ if (meta.root.discriminatorMap) {
1195
+ const map = meta.root.discriminatorMap;
1196
+ Object.keys(map)
1197
+ .filter(key => typeof map[key] === 'string')
1198
+ .forEach(key => (map[key] = this.#metadata.getByClassName(map[key]).class));
1199
+ }
1200
+ else {
919
1201
  meta.root.discriminatorMap = {};
920
1202
  const children = metadata
921
- .filter(m => m.root.className === meta.root.className && !m.abstract)
1203
+ .filter(m => m.root.class === meta.root.class && !m.abstract)
922
1204
  .sort((a, b) => a.className.localeCompare(b.className));
923
1205
  for (const m of children) {
924
- const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
925
- meta.root.discriminatorMap[name] = m.className;
1206
+ const name = m.discriminatorValue ?? this.#namingStrategy.classToTableName(m.className);
1207
+ meta.root.discriminatorMap[name] = m.class;
926
1208
  }
927
1209
  }
928
- meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, className]) => className === meta.className)?.[0];
1210
+ meta.discriminatorValue = QueryHelper.findDiscriminatorValue(meta.root.discriminatorMap, meta.class);
929
1211
  if (!meta.root.properties[meta.root.discriminatorColumn]) {
930
1212
  this.createDiscriminatorProperty(meta.root);
931
1213
  }
@@ -934,32 +1216,222 @@ export class MetadataDiscovery {
934
1216
  if (meta.root === meta) {
935
1217
  return;
936
1218
  }
937
- let i = 1;
938
1219
  Object.values(meta.properties).forEach(prop => {
939
1220
  const newProp = { ...prop };
940
- if (meta.root.properties[prop.name] && meta.root.properties[prop.name].type !== prop.type) {
1221
+ const rootProp = meta.root.properties[prop.name];
1222
+ if (rootProp &&
1223
+ (rootProp.type !== prop.type ||
1224
+ (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
941
1225
  const name = newProp.name;
942
1226
  this.initFieldName(newProp, newProp.object);
943
- newProp.name = name + '_' + (i++);
1227
+ newProp.renamedFrom = name;
1228
+ newProp.name = `${name}_${meta._id}`;
944
1229
  meta.root.addProperty(newProp);
1230
+ this.initFieldName(prop, prop.object);
1231
+ // Track all field variants and map discriminator values to field names
1232
+ if (!rootProp.stiFieldNames) {
1233
+ this.initFieldName(rootProp, rootProp.object);
1234
+ rootProp.stiFieldNames = [...rootProp.fieldNames];
1235
+ rootProp.stiFieldNameMap = {};
1236
+ // Find which discriminator owns the original fieldNames
1237
+ for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
1238
+ const childMeta = this.#metadata.find(childClass);
1239
+ if (childMeta?.properties[prop.name]?.fieldNames &&
1240
+ compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
1241
+ rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
1242
+ break;
1243
+ }
1244
+ }
1245
+ }
1246
+ rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
1247
+ rootProp.stiFieldNames.push(...prop.fieldNames);
945
1248
  newProp.nullable = true;
946
1249
  newProp.name = name;
947
1250
  newProp.hydrate = false;
948
1251
  newProp.inherited = true;
949
1252
  return;
950
1253
  }
951
- if (prop.enum && prop.items && meta.root.properties[prop.name]?.items) {
952
- newProp.items = Utils.unique([...meta.root.properties[prop.name].items, ...prop.items]);
1254
+ if (prop.enum && prop.items && rootProp?.items) {
1255
+ newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
953
1256
  }
954
1257
  newProp.nullable = true;
955
- newProp.inherited = true;
1258
+ newProp.inherited = !rootProp;
956
1259
  meta.root.addProperty(newProp);
957
1260
  });
958
- meta.collection = meta.root.collection;
1261
+ meta.tableName = meta.root.tableName;
959
1262
  meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
960
1263
  meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
961
1264
  meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
962
1265
  }
1266
+ /**
1267
+ * First pass of TPT initialization: sets up hierarchy relationships
1268
+ * (inheritanceType, tptParent, tptChildren) before properties have fieldNames.
1269
+ */
1270
+ initTPTRelationships(meta, metadata) {
1271
+ if (meta.root !== meta) {
1272
+ meta.root = metadata.find(m => m.class === meta.root.class);
1273
+ }
1274
+ const inheritance = meta.inheritance;
1275
+ if (inheritance === 'tpt' && meta.root === meta) {
1276
+ meta.inheritanceType = 'tpt';
1277
+ meta.tptChildren = [];
1278
+ }
1279
+ if (meta.root.inheritanceType !== 'tpt') {
1280
+ return;
1281
+ }
1282
+ const parent = this.getTPTParent(meta, metadata);
1283
+ if (parent) {
1284
+ meta.tptParent = parent;
1285
+ meta.inheritanceType = 'tpt';
1286
+ parent.tptChildren ??= [];
1287
+ if (!parent.tptChildren.includes(meta)) {
1288
+ parent.tptChildren.push(meta);
1289
+ }
1290
+ }
1291
+ }
1292
+ /**
1293
+ * Second pass of TPT initialization: re-resolves metadata references after fieldNames
1294
+ * are set, syncs to registry metadata, and sets up discriminators.
1295
+ */
1296
+ finalizeTPTInheritance(meta, metadata) {
1297
+ if (meta.inheritanceType !== 'tpt') {
1298
+ return;
1299
+ }
1300
+ if (meta.tptParent) {
1301
+ meta.tptParent = metadata.find(m => m.class === meta.tptParent.class) ?? meta.tptParent;
1302
+ }
1303
+ if (meta.tptChildren) {
1304
+ meta.tptChildren = meta.tptChildren.map(child => metadata.find(m => m.class === child.class) ?? child);
1305
+ }
1306
+ const registryMeta = this.#metadata.get(meta.class);
1307
+ if (registryMeta && registryMeta !== meta) {
1308
+ registryMeta.inheritanceType = meta.inheritanceType;
1309
+ registryMeta.tptParent = meta.tptParent ? this.#metadata.get(meta.tptParent.class) : undefined;
1310
+ registryMeta.tptChildren = meta.tptChildren?.map(child => this.#metadata.get(child.class));
1311
+ }
1312
+ this.initTPTDiscriminator(meta, metadata);
1313
+ }
1314
+ /**
1315
+ * Initialize TPT discriminator map and virtual discriminator property.
1316
+ * Unlike STI where the discriminator is a persisted column, TPT discriminator is computed
1317
+ * at query time using CASE WHEN expressions based on which child table has data.
1318
+ */
1319
+ initTPTDiscriminator(meta, metadata) {
1320
+ const allDescendants = this.collectAllTPTDescendants(meta, metadata);
1321
+ allDescendants.sort((a, b) => this.getTPTDepth(b) - this.getTPTDepth(a));
1322
+ meta.allTPTDescendants = allDescendants;
1323
+ if (meta.root !== meta) {
1324
+ return;
1325
+ }
1326
+ meta.root.discriminatorMap = {};
1327
+ for (const m of allDescendants) {
1328
+ const name = this.#namingStrategy.classToTableName(m.className);
1329
+ meta.root.discriminatorMap[name] = m.class;
1330
+ m.discriminatorValue = name;
1331
+ }
1332
+ if (!meta.abstract) {
1333
+ const name = this.#namingStrategy.classToTableName(meta.className);
1334
+ meta.root.discriminatorMap[name] = meta.class;
1335
+ meta.discriminatorValue = name;
1336
+ }
1337
+ // Virtual discriminator property - computed at query time via CASE WHEN, not persisted
1338
+ const discriminatorColumn = '__tpt_type';
1339
+ if (!meta.root.properties[discriminatorColumn]) {
1340
+ meta.root.addProperty({
1341
+ name: discriminatorColumn,
1342
+ type: 'string',
1343
+ kind: ReferenceKind.SCALAR,
1344
+ persist: false,
1345
+ userDefined: false,
1346
+ hidden: true,
1347
+ });
1348
+ }
1349
+ meta.root.tptDiscriminatorColumn = discriminatorColumn;
1350
+ }
1351
+ /**
1352
+ * Recursively collect all TPT descendants (children, grandchildren, etc.)
1353
+ */
1354
+ collectAllTPTDescendants(meta, metadata) {
1355
+ const descendants = [];
1356
+ const collect = (parent) => {
1357
+ for (const child of parent.tptChildren ?? []) {
1358
+ const resolved = metadata.find(m => m.class === child.class) ?? child;
1359
+ if (!resolved.abstract) {
1360
+ descendants.push(resolved);
1361
+ }
1362
+ collect(resolved);
1363
+ }
1364
+ };
1365
+ collect(meta);
1366
+ return descendants;
1367
+ }
1368
+ /**
1369
+ * Computes ownProps for TPT entities - only properties defined in THIS entity,
1370
+ * not inherited from parent. Also creates synthetic join properties for parent/child relationships.
1371
+ *
1372
+ * Called multiple times during discovery as metadata is progressively built.
1373
+ * Each pass overwrites earlier results to reflect the final state of properties.
1374
+ */
1375
+ computeTPTOwnProps(meta) {
1376
+ if (meta.inheritanceType !== 'tpt') {
1377
+ return;
1378
+ }
1379
+ const belongsToTable = (prop) => prop.persist !== false || prop.primary;
1380
+ // Use meta.properties (object) since meta.props (array) may not be populated yet
1381
+ const allProps = Object.values(meta.properties);
1382
+ if (!meta.tptParent) {
1383
+ meta.ownProps = allProps.filter(belongsToTable);
1384
+ return;
1385
+ }
1386
+ const parentPropNames = new Set(Object.values(meta.tptParent.properties).map(p => p.name));
1387
+ meta.ownProps = allProps.filter(prop => !parentPropNames.has(prop.name) && belongsToTable(prop));
1388
+ // Create synthetic join properties for the parent-child relationship
1389
+ const childFieldNames = meta.getPrimaryProps().flatMap(p => p.fieldNames);
1390
+ const parentFieldNames = meta.tptParent.getPrimaryProps().flatMap(p => p.fieldNames);
1391
+ meta.tptParentProp = {
1392
+ name: '[tpt:parent]',
1393
+ kind: ReferenceKind.MANY_TO_ONE,
1394
+ targetMeta: meta.tptParent,
1395
+ fieldNames: childFieldNames,
1396
+ referencedColumnNames: parentFieldNames,
1397
+ persist: false,
1398
+ };
1399
+ meta.tptInverseProp = {
1400
+ name: '[tpt:child]',
1401
+ kind: ReferenceKind.ONE_TO_ONE,
1402
+ owner: true,
1403
+ targetMeta: meta,
1404
+ fieldNames: parentFieldNames,
1405
+ joinColumns: parentFieldNames,
1406
+ referencedColumnNames: childFieldNames,
1407
+ persist: false,
1408
+ };
1409
+ }
1410
+ /** Returns the depth of a TPT entity in its hierarchy (0 for root). */
1411
+ getTPTDepth(meta) {
1412
+ let depth = 0;
1413
+ let current = meta;
1414
+ while (current.tptParent) {
1415
+ depth++;
1416
+ current = current.tptParent;
1417
+ }
1418
+ return depth;
1419
+ }
1420
+ /**
1421
+ * Find the direct TPT parent entity for the given entity.
1422
+ */
1423
+ getTPTParent(meta, metadata) {
1424
+ if (meta.root === meta) {
1425
+ return undefined;
1426
+ }
1427
+ return metadata.find(m => {
1428
+ const ext = meta.extends;
1429
+ if (ext instanceof EntitySchema) {
1430
+ return m.class === ext.meta.class || m.className === ext.meta.className;
1431
+ }
1432
+ return m.class === ext || m.className === Utils.className(ext);
1433
+ });
1434
+ }
963
1435
  createDiscriminatorProperty(meta) {
964
1436
  meta.addProperty({
965
1437
  name: meta.discriminatorColumn,
@@ -971,28 +1443,41 @@ export class MetadataDiscovery {
971
1443
  }
972
1444
  initAutoincrement(meta) {
973
1445
  const pks = meta.getPrimaryProps();
974
- if (pks.length === 1 && this.platform.isNumericProperty(pks[0])) {
1446
+ if (pks.length === 1 && this.#platform.isNumericProperty(pks[0])) {
975
1447
  /* v8 ignore next */
976
1448
  pks[0].autoincrement ??= true;
977
1449
  }
978
1450
  }
1451
+ createSchemaTable(meta) {
1452
+ const qualifiedName = meta.schema ? `${meta.schema}.${meta.tableName}` : meta.tableName;
1453
+ return {
1454
+ name: meta.tableName,
1455
+ schema: meta.schema,
1456
+ qualifiedName,
1457
+ toString: () => qualifiedName,
1458
+ };
1459
+ }
979
1460
  initCheckConstraints(meta) {
980
- const map = meta.createColumnMappingObject();
1461
+ const columns = meta.createSchemaColumnMappingObject();
1462
+ const table = this.createSchemaTable(meta);
981
1463
  for (const check of meta.checks) {
982
- const columns = check.property ? meta.properties[check.property].fieldNames : [];
983
- check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
1464
+ const fieldNames = check.property ? meta.properties[check.property].fieldNames : [];
1465
+ check.name ??= this.#namingStrategy.indexName(meta.tableName, fieldNames, 'check');
984
1466
  if (check.expression instanceof Function) {
985
- check.expression = check.expression(map);
1467
+ check.expression = check.expression(columns, table);
986
1468
  }
987
1469
  }
988
- if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
1470
+ if (this.#platform.usesEnumCheckConstraints() && !meta.embeddable) {
989
1471
  for (const prop of meta.props) {
990
- if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => Utils.isString(item))) {
1472
+ if (prop.enum &&
1473
+ prop.persist !== false &&
1474
+ !prop.nativeEnumName &&
1475
+ prop.items?.every(item => typeof item === 'string')) {
991
1476
  this.initFieldName(prop);
992
1477
  meta.checks.push({
993
- name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
1478
+ name: this.#namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
994
1479
  property: prop.name,
995
- expression: `${this.platform.quoteIdentifier(prop.fieldNames[0])} in ('${prop.items.join("', '")}')`,
1480
+ expression: `${this.#platform.quoteIdentifier(prop.fieldNames[0])} in ('${prop.items.join("', '")}')`,
996
1481
  });
997
1482
  }
998
1483
  }
@@ -1000,51 +1485,51 @@ export class MetadataDiscovery {
1000
1485
  }
1001
1486
  initGeneratedColumn(meta, prop) {
1002
1487
  if (!prop.generated && prop.columnTypes) {
1003
- const match = prop.columnTypes[0]?.match(/(.*) generated always as (.*)/i);
1488
+ const match = /(.*) generated always as (.*)/i.exec(prop.columnTypes[0]);
1004
1489
  if (match) {
1005
1490
  prop.columnTypes[0] = match[1];
1006
1491
  prop.generated = match[2];
1007
1492
  return;
1008
1493
  }
1009
- const match2 = prop.columnTypes[0]?.trim().match(/^as (.*)/i);
1494
+ const match2 = /^as (.*)/i.exec(prop.columnTypes[0]?.trim());
1010
1495
  if (match2) {
1011
1496
  prop.generated = match2[1];
1012
1497
  }
1013
1498
  return;
1014
1499
  }
1015
- const map = meta.createColumnMappingObject();
1016
1500
  if (prop.generated instanceof Function) {
1017
- prop.generated = prop.generated(map);
1501
+ const columns = meta.createSchemaColumnMappingObject();
1502
+ prop.generated = prop.generated(columns, this.createSchemaTable(meta));
1018
1503
  }
1019
1504
  }
1020
1505
  getDefaultVersionValue(meta, prop) {
1021
1506
  if (typeof prop.defaultRaw !== 'undefined') {
1022
1507
  return prop.defaultRaw;
1023
1508
  }
1024
- /* v8 ignore next 3 */
1509
+ /* v8 ignore next */
1025
1510
  if (prop.default != null) {
1026
- return '' + this.platform.quoteVersionValue(prop.default, prop);
1511
+ return '' + this.#platform.convertVersionValue(prop.default, prop);
1027
1512
  }
1028
1513
  this.initCustomType(meta, prop, true);
1029
1514
  const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
1030
1515
  if (type === 'Date') {
1031
- prop.length ??= this.platform.getDefaultVersionLength();
1032
- return this.platform.getCurrentTimestampSQL(prop.length);
1516
+ prop.length ??= this.#platform.getDefaultVersionLength();
1517
+ return this.#platform.getCurrentTimestampSQL(prop.length);
1033
1518
  }
1034
1519
  return '1';
1035
1520
  }
1036
1521
  inferDefaultValue(meta, prop) {
1037
- /* v8 ignore next 3 */
1038
- if (!meta.class) {
1039
- return;
1040
- }
1041
1522
  try {
1042
1523
  // try to create two entity instances to detect the value is stable
1043
1524
  const now = Date.now();
1044
1525
  const entity1 = new meta.class();
1045
1526
  const entity2 = new meta.class();
1046
1527
  // we compare the two values by reference, this will discard things like `new Date()` or `Date.now()`
1047
- if (this.config.get('discovery').inferDefaultValues && prop.default === undefined && entity1[prop.name] != null && entity1[prop.name] === entity2[prop.name] && entity1[prop.name] !== now) {
1528
+ if (this.#config.get('discovery').inferDefaultValues &&
1529
+ prop.default === undefined &&
1530
+ entity1[prop.name] != null &&
1531
+ entity1[prop.name] === entity2[prop.name] &&
1532
+ entity1[prop.name] !== now) {
1048
1533
  prop.default ??= entity1[prop.name];
1049
1534
  }
1050
1535
  // if the default value is null, infer nullability
@@ -1065,13 +1550,13 @@ export class MetadataDiscovery {
1065
1550
  return;
1066
1551
  }
1067
1552
  let val = prop.default;
1068
- const raw = RawQueryFragment.getKnownFragment(val);
1553
+ const raw = Raw.getKnownFragment(val);
1069
1554
  if (raw) {
1070
- prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1555
+ prop.defaultRaw = this.#platform.formatQuery(raw.sql, raw.params);
1071
1556
  return;
1072
1557
  }
1073
1558
  if (Array.isArray(prop.default) && prop.customType) {
1074
- val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
1559
+ val = prop.customType.convertToDatabaseValue(prop.default, this.#platform);
1075
1560
  }
1076
1561
  prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
1077
1562
  }
@@ -1111,52 +1596,69 @@ export class MetadataDiscovery {
1111
1596
  prop.type = prop.customType.constructor.name;
1112
1597
  }
1113
1598
  // `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
1114
- if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
1115
- prop.customType = new prop.type();
1116
- prop.type = prop.customType.constructor.name;
1599
+ if (typeof prop.type === 'function' &&
1600
+ Type.isMappedType(prop.type.prototype) &&
1601
+ !prop.customType) {
1602
+ // if the type is an ORM defined mapped type without `ensureComparable: true`,
1603
+ // we use just the type name, to have more performant hydration code
1604
+ const type = Utils.keys(t).find(type => {
1605
+ return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
1606
+ });
1607
+ if (type) {
1608
+ prop.type = type === 'datetime' ? 'Date' : type;
1609
+ }
1610
+ else {
1611
+ prop.customType = new prop.type();
1612
+ prop.type = prop.customType.constructor.name;
1613
+ }
1117
1614
  }
1118
1615
  if (simple) {
1119
1616
  return;
1120
1617
  }
1121
1618
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1122
- prop.customType = new JsonType();
1619
+ prop.customType = new t.json();
1123
1620
  }
1124
- if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1125
- prop.customType = new JsonType();
1621
+ if (prop.kind === ReferenceKind.SCALAR &&
1622
+ !prop.customType &&
1623
+ prop.columnTypes &&
1624
+ ['json', 'jsonb'].includes(prop.columnTypes[0])) {
1625
+ prop.customType = new t.json();
1126
1626
  }
1127
1627
  if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
1128
- prop.customType = new JsonType();
1628
+ prop.customType = new t.json();
1129
1629
  }
1130
1630
  if (!prop.customType && prop.array && prop.items) {
1131
- prop.customType = new EnumArrayType(`${meta.className}.${prop.name}`, prop.items);
1631
+ prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
1132
1632
  }
1133
1633
  const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
1134
1634
  if (objectEmbeddable && !prop.customType && isArray) {
1135
- prop.customType = new JsonType();
1635
+ prop.customType = new t.json();
1136
1636
  }
1137
1637
  // for number arrays we make sure to convert the items to numbers
1138
1638
  if (!prop.customType && prop.type === 'number[]') {
1139
- prop.customType = new ArrayType(i => +i);
1639
+ prop.customType = new t.array(i => +i);
1140
1640
  }
1141
1641
  // `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
1142
1642
  if (!prop.customType && isArray) {
1143
- prop.customType = new ArrayType();
1643
+ prop.customType = new t.array();
1144
1644
  }
1145
1645
  if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
1146
- prop.customType = new BlobType();
1646
+ prop.customType = new t.blob();
1147
1647
  }
1148
1648
  if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
1149
- prop.customType = new Uint8ArrayType();
1649
+ prop.customType = new t.uint8array();
1150
1650
  }
1151
1651
  const mappedType = this.getMappedType(prop);
1152
1652
  if (prop.fieldNames?.length === 1 && !prop.customType) {
1153
- [BigIntType, DoubleType, DecimalType, IntervalType, DateType]
1653
+ [t.bigint, t.double, t.decimal, t.interval, t.date]
1154
1654
  .filter(type => mappedType instanceof type)
1155
- .forEach((type) => prop.customType = new type());
1655
+ .forEach((type) => (prop.customType = new type()));
1156
1656
  }
1157
1657
  if (prop.customType && !prop.columnTypes) {
1158
- const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
1159
- if (prop.customType.compareAsType() === 'any' && ![JsonType].some(t => prop.customType instanceof t)) {
1658
+ const mappedType = this.getMappedType({
1659
+ columnTypes: [prop.customType.getColumnType(prop, this.#platform)],
1660
+ });
1661
+ if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
1160
1662
  prop.runtimeType ??= mappedType.runtimeType;
1161
1663
  }
1162
1664
  else {
@@ -1170,39 +1672,53 @@ export class MetadataDiscovery {
1170
1672
  prop.runtimeType ??= mappedType.runtimeType;
1171
1673
  }
1172
1674
  if (prop.customType) {
1173
- prop.customType.platform = this.platform;
1675
+ prop.customType.platform = this.#platform;
1174
1676
  prop.customType.meta = meta;
1175
1677
  prop.customType.prop = prop;
1176
- prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
1177
- prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
1178
- prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1179
- if (prop.customType instanceof BigIntType && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1678
+ prop.columnTypes ??= [prop.customType.getColumnType(prop, this.#platform)];
1679
+ prop.hasConvertToJSValueSQL =
1680
+ !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.#platform) !== '';
1681
+ prop.hasConvertToDatabaseValueSQL =
1682
+ !!prop.customType.convertToDatabaseValueSQL &&
1683
+ prop.customType.convertToDatabaseValueSQL('', this.#platform) !== '';
1684
+ if (prop.customType instanceof t.bigint &&
1685
+ ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
1180
1686
  prop.customType.mode = prop.runtimeType.toLowerCase();
1181
1687
  }
1182
1688
  }
1183
1689
  if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
1184
1690
  prop.type = prop.customType.name;
1185
1691
  }
1186
- if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && this.metadata.get(prop.type).compositePK) {
1692
+ if (!prop.customType &&
1693
+ [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
1694
+ !prop.polymorphic &&
1695
+ prop.targetMeta.compositePK) {
1187
1696
  prop.customTypes = [];
1188
- for (const pk of this.metadata.get(prop.type).getPrimaryProps()) {
1697
+ for (const pk of prop.targetMeta.getPrimaryProps()) {
1189
1698
  if (pk.customType) {
1190
1699
  prop.customTypes.push(pk.customType);
1191
- prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
1700
+ prop.hasConvertToJSValueSQL ||=
1701
+ !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.#platform) !== '';
1192
1702
  /* v8 ignore next */
1193
- prop.hasConvertToDatabaseValueSQL ||= !!pk.customType.convertToDatabaseValueSQL && pk.customType.convertToDatabaseValueSQL('', this.platform) !== '';
1703
+ prop.hasConvertToDatabaseValueSQL ||=
1704
+ !!pk.customType.convertToDatabaseValueSQL &&
1705
+ pk.customType.convertToDatabaseValueSQL('', this.#platform) !== '';
1194
1706
  }
1195
1707
  else {
1196
1708
  prop.customTypes.push(undefined);
1197
1709
  }
1198
1710
  }
1199
1711
  }
1200
- if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof UnknownType)) {
1201
- if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
1712
+ if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
1713
+ if (!prop.columnTypes &&
1714
+ prop.nativeEnumName &&
1715
+ meta.schema !== this.#platform.getDefaultSchemaName() &&
1716
+ meta.schema &&
1717
+ !prop.nativeEnumName.includes('.')) {
1202
1718
  prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
1203
1719
  }
1204
1720
  else {
1205
- prop.columnTypes ??= [mappedType.getColumnType(prop, this.platform)];
1721
+ prop.columnTypes ??= [mappedType.getColumnType(prop, this.#platform)];
1206
1722
  }
1207
1723
  // use only custom types provided by user, we don't need to use the ones provided by ORM,
1208
1724
  // with exception for ArrayType and JsonType, those two are handled in
@@ -1212,65 +1728,89 @@ export class MetadataDiscovery {
1212
1728
  }
1213
1729
  }
1214
1730
  initRelation(prop) {
1215
- if (prop.kind === ReferenceKind.SCALAR) {
1731
+ if (prop.kind === ReferenceKind.SCALAR || prop.polymorphTargets) {
1216
1732
  return;
1217
1733
  }
1218
- const meta2 = this.discovered.find(m => m.className === prop.type);
1219
- prop.referencedPKs = meta2.primaryKeys;
1734
+ // 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
1735
+ const meta2 = this.#metadata.find(prop.target) ?? this.#metadata.getByClassName(prop.type);
1736
+ // If targetKey is specified, use that property instead of PKs for referencedPKs
1737
+ prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
1220
1738
  prop.targetMeta = meta2;
1221
- if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
1222
- prop.formula = a => `${a}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
1739
+ if (meta2.view) {
1740
+ prop.createForeignKeyConstraint = false;
1741
+ }
1742
+ // Auto-generate formula for persist: false relations, but only for single-column FKs
1743
+ // Composite FK relations need standard JOIN conditions, not formula-based
1744
+ if (!prop.formula &&
1745
+ prop.persist === false &&
1746
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
1747
+ !prop.embedded) {
1748
+ this.initFieldName(prop);
1749
+ if (prop.fieldNames?.length === 1) {
1750
+ prop.formula = table => `${table}.${this.#platform.quoteIdentifier(prop.fieldNames[0])}`;
1751
+ }
1223
1752
  }
1224
1753
  }
1225
1754
  initColumnType(prop) {
1226
1755
  this.initUnsigned(prop);
1227
- this.metadata.find(prop.type)?.getPrimaryProps().map(pk => {
1228
- prop.length ??= pk.length;
1229
- prop.precision ??= pk.precision;
1230
- prop.scale ??= pk.scale;
1756
+ // Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
1757
+ const targetProps = prop.targetMeta
1758
+ ? prop.targetKey
1759
+ ? [prop.targetMeta.properties[prop.targetKey]]
1760
+ : prop.targetMeta.getPrimaryProps()
1761
+ : [];
1762
+ targetProps.map(targetProp => {
1763
+ prop.length ??= targetProp.length;
1764
+ prop.precision ??= targetProp.precision;
1765
+ prop.scale ??= targetProp.scale;
1231
1766
  });
1232
1767
  if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
1233
1768
  delete prop.type;
1234
1769
  const mappedType = this.getMappedType(prop);
1235
1770
  prop.type = mappedType.compareAsType();
1236
1771
  }
1237
- if (prop.columnTypes || !this.schemaHelper) {
1772
+ if (prop.columnTypes || !this.#schemaHelper) {
1238
1773
  return;
1239
1774
  }
1240
1775
  if (prop.kind === ReferenceKind.SCALAR) {
1241
1776
  const mappedType = this.getMappedType(prop);
1242
1777
  const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
1243
- if (mappedType instanceof UnknownType
1244
- && !prop.columnTypes
1778
+ if (mappedType instanceof t.unknown &&
1245
1779
  // it could be a runtime type from reflect-metadata
1246
- && !SCALAR_TYPES.includes(prop.type)
1780
+ !SCALAR_TYPES.includes(prop.type) &&
1247
1781
  // or it might be inferred via ts-morph to some generic type alias
1248
- && !prop.type.match(/[<>:"';{}]/)) {
1782
+ !/[<>:"';{}]/.exec(prop.type)) {
1249
1783
  const type = prop.length != null && !prop.type.endsWith(`(${prop.length})`) ? `${prop.type}(${prop.length})` : prop.type;
1250
1784
  prop.columnTypes = [type];
1251
1785
  }
1252
1786
  else {
1253
- prop.columnTypes = [mappedType.getColumnType(prop, this.platform)];
1787
+ prop.columnTypes = [mappedType.getColumnType(prop, this.#platform)];
1254
1788
  }
1255
1789
  return;
1256
1790
  }
1257
- if (prop.kind === ReferenceKind.EMBEDDED && prop.object && !prop.columnTypes) {
1258
- prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
1791
+ /* v8 ignore next */
1792
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
1793
+ prop.columnTypes = [this.#platform.getJsonDeclarationSQL()];
1259
1794
  return;
1260
1795
  }
1261
- const targetMeta = this.metadata.get(prop.type);
1796
+ const targetMeta = prop.targetMeta;
1262
1797
  prop.columnTypes = [];
1263
- for (const pk of targetMeta.getPrimaryProps()) {
1264
- this.initCustomType(targetMeta, pk);
1265
- this.initColumnType(pk);
1266
- const mappedType = this.getMappedType(pk);
1267
- let columnTypes = pk.columnTypes;
1268
- if (pk.autoincrement) {
1269
- columnTypes = [mappedType.getColumnType({ ...pk, autoincrement: false }, this.platform)];
1798
+ // Use targetKey property if specified, otherwise use primary key properties
1799
+ const referencedProps = prop.targetKey ? [targetMeta.properties[prop.targetKey]] : targetMeta.getPrimaryProps();
1800
+ if (prop.polymorphic && prop.polymorphTargets) {
1801
+ prop.columnTypes.push(this.#platform.getVarcharTypeDeclarationSQL(prop));
1802
+ }
1803
+ for (const referencedProp of referencedProps) {
1804
+ this.initCustomType(targetMeta, referencedProp);
1805
+ this.initColumnType(referencedProp);
1806
+ const mappedType = this.getMappedType(referencedProp);
1807
+ let columnTypes = referencedProp.columnTypes;
1808
+ if (referencedProp.autoincrement) {
1809
+ columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.#platform)];
1270
1810
  }
1271
1811
  prop.columnTypes.push(...columnTypes);
1272
- if (!targetMeta.compositePK) {
1273
- prop.customType = pk.customType;
1812
+ if (!targetMeta.compositePK || prop.targetKey) {
1813
+ prop.customType = referencedProp.customType;
1274
1814
  }
1275
1815
  }
1276
1816
  }
@@ -1284,12 +1824,12 @@ export class MetadataDiscovery {
1284
1824
  t = 'enum';
1285
1825
  }
1286
1826
  else if (prop.enum) {
1287
- t = prop.items?.every(item => Utils.isString(item)) ? 'enum' : 'tinyint';
1827
+ t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
1288
1828
  }
1289
1829
  if (t === 'Date') {
1290
1830
  t = 'datetime';
1291
1831
  }
1292
- return this.platform.getMappedType(t);
1832
+ return this.#platform.getMappedType(t);
1293
1833
  }
1294
1834
  getPrefix(prop, parent) {
1295
1835
  const { embeddedPath = [], fieldNames, prefix = true, prefixMode } = prop;
@@ -1300,7 +1840,7 @@ export class MetadataDiscovery {
1300
1840
  if (prefix === false) {
1301
1841
  return prefixParent;
1302
1842
  }
1303
- const mode = prefixMode ?? this.config.get('embeddables').prefixMode;
1843
+ const mode = prefixMode ?? this.#config.get('embeddables').prefixMode;
1304
1844
  return mode === 'absolute' ? prefix : prefixParent + prefix;
1305
1845
  }
1306
1846
  initUnsigned(prop) {
@@ -1308,47 +1848,23 @@ export class MetadataDiscovery {
1308
1848
  return;
1309
1849
  }
1310
1850
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1311
- const meta2 = this.metadata.get(prop.type);
1312
- prop.unsigned = meta2.getPrimaryProps().some(pk => {
1851
+ prop.unsigned = prop.targetMeta.getPrimaryProps().some(pk => {
1313
1852
  this.initUnsigned(pk);
1314
1853
  return pk.unsigned;
1315
1854
  });
1316
1855
  return;
1317
1856
  }
1318
- prop.unsigned ??= (prop.primary || prop.unsigned) && this.platform.isNumericProperty(prop) && this.platform.supportsUnsigned();
1857
+ prop.unsigned ??=
1858
+ (prop.primary || prop.unsigned) && this.#platform.isNumericProperty(prop) && this.#platform.supportsUnsigned();
1319
1859
  }
1320
1860
  initIndexes(meta, prop) {
1321
1861
  const hasIndex = meta.indexes.some(idx => idx.properties?.length === 1 && idx.properties[0] === prop.name);
1322
- if (prop.kind === ReferenceKind.MANY_TO_ONE && this.platform.indexForeignKeys() && !hasIndex) {
1862
+ if (prop.kind === ReferenceKind.MANY_TO_ONE && this.#platform.indexForeignKeys() && !hasIndex) {
1323
1863
  prop.index ??= true;
1324
1864
  }
1325
1865
  }
1326
- async getEntityClassOrSchema(path, name) {
1327
- const exports = await Utils.dynamicImport(path);
1328
- const targets = Object.values(exports)
1329
- .filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
1330
- // ignore class implementations that are linked from an EntitySchema
1331
- for (const item of targets) {
1332
- if (item instanceof EntitySchema) {
1333
- targets.forEach((item2, idx) => {
1334
- if (item.meta.class === item2) {
1335
- targets.splice(idx, 1);
1336
- }
1337
- });
1338
- }
1339
- }
1340
- if (targets.length > 0) {
1341
- return targets;
1342
- }
1343
- const target = exports.default ?? exports[name];
1344
- /* v8 ignore next 3 */
1345
- if (!target) {
1346
- throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
1347
- }
1348
- return [target];
1349
- }
1350
1866
  shouldForceConstructorUsage(meta) {
1351
- const forceConstructor = this.config.get('forceEntityConstructor');
1867
+ const forceConstructor = this.#config.get('forceEntityConstructor');
1352
1868
  if (Array.isArray(forceConstructor)) {
1353
1869
  return forceConstructor.some(cls => Utils.className(cls) === meta.className);
1354
1870
  }