@mikro-orm/core 7.0.0-dev.32 → 7.0.0-dev.321

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