@mikro-orm/core 7.0.0-dev.2 → 7.0.0-dev.200

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