@mikro-orm/core 7.0.0-dev.23 → 7.0.0-dev.230

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 (209) hide show
  1. package/EntityManager.d.ts +91 -59
  2. package/EntityManager.js +303 -251
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +2 -0
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +17 -8
  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 +12 -5
  13. package/connections/Connection.js +21 -12
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +118 -35
  16. package/drivers/IDatabaseDriver.d.ts +42 -19
  17. package/entity/BaseEntity.d.ts +61 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +101 -29
  20. package/entity/Collection.js +436 -104
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +7 -1
  24. package/entity/EntityFactory.js +83 -54
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +48 -15
  27. package/entity/EntityLoader.d.ts +7 -6
  28. package/entity/EntityLoader.js +215 -89
  29. package/entity/EntityRepository.d.ts +27 -7
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/PolymorphicRef.d.ts +12 -0
  32. package/entity/PolymorphicRef.js +18 -0
  33. package/entity/Reference.d.ts +1 -5
  34. package/entity/Reference.js +21 -12
  35. package/entity/WrappedEntity.d.ts +0 -5
  36. package/entity/WrappedEntity.js +2 -7
  37. package/entity/defineEntity.d.ts +380 -310
  38. package/entity/defineEntity.js +124 -273
  39. package/entity/index.d.ts +2 -2
  40. package/entity/index.js +2 -2
  41. package/entity/utils.js +1 -1
  42. package/entity/validators.d.ts +11 -0
  43. package/entity/validators.js +65 -0
  44. package/enums.d.ts +8 -6
  45. package/enums.js +2 -1
  46. package/errors.d.ts +20 -10
  47. package/errors.js +55 -23
  48. package/events/EventManager.d.ts +2 -1
  49. package/events/EventManager.js +19 -11
  50. package/hydration/Hydrator.js +1 -2
  51. package/hydration/ObjectHydrator.d.ts +4 -4
  52. package/hydration/ObjectHydrator.js +87 -35
  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 +47 -23
  65. package/metadata/EntitySchema.js +92 -33
  66. package/metadata/MetadataDiscovery.d.ts +64 -9
  67. package/metadata/MetadataDiscovery.js +778 -325
  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 +32 -9
  73. package/metadata/MetadataValidator.js +196 -41
  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 +526 -0
  79. package/metadata/types.js +1 -0
  80. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  81. package/naming-strategy/AbstractNamingStrategy.js +20 -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 +28 -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 -14
  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 +3 -3
  100. package/types/ArrayType.d.ts +1 -1
  101. package/types/ArrayType.js +2 -3
  102. package/types/BigIntType.d.ts +8 -6
  103. package/types/BigIntType.js +1 -1
  104. package/types/BlobType.d.ts +0 -1
  105. package/types/BlobType.js +0 -3
  106. package/types/BooleanType.d.ts +1 -0
  107. package/types/BooleanType.js +3 -0
  108. package/types/DecimalType.d.ts +6 -4
  109. package/types/DecimalType.js +2 -2
  110. package/types/DoubleType.js +1 -1
  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 +381 -171
  121. package/typings.js +97 -44
  122. package/unit-of-work/ChangeSet.d.ts +4 -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 +35 -14
  126. package/unit-of-work/ChangeSetPersister.d.ts +7 -3
  127. package/unit-of-work/ChangeSetPersister.js +83 -25
  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 +27 -3
  133. package/unit-of-work/UnitOfWork.js +258 -92
  134. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  135. package/utils/AbstractSchemaGenerator.js +28 -17
  136. package/utils/AsyncContext.d.ts +6 -0
  137. package/utils/AsyncContext.js +42 -0
  138. package/utils/Configuration.d.ts +795 -209
  139. package/utils/Configuration.js +150 -192
  140. package/utils/ConfigurationLoader.d.ts +1 -54
  141. package/utils/ConfigurationLoader.js +1 -352
  142. package/utils/Cursor.d.ts +0 -3
  143. package/utils/Cursor.js +24 -11
  144. package/utils/DataloaderUtils.d.ts +10 -5
  145. package/utils/DataloaderUtils.js +29 -12
  146. package/utils/EntityComparator.d.ts +16 -9
  147. package/utils/EntityComparator.js +158 -58
  148. package/utils/QueryHelper.d.ts +18 -6
  149. package/utils/QueryHelper.js +76 -23
  150. package/utils/RawQueryFragment.d.ts +28 -34
  151. package/utils/RawQueryFragment.js +35 -71
  152. package/utils/RequestContext.js +2 -2
  153. package/utils/TransactionContext.js +2 -2
  154. package/utils/TransactionManager.js +28 -4
  155. package/utils/Utils.d.ts +14 -127
  156. package/utils/Utils.js +85 -397
  157. package/utils/clone.js +8 -23
  158. package/utils/env-vars.d.ts +7 -0
  159. package/utils/env-vars.js +97 -0
  160. package/utils/fs-utils.d.ts +33 -0
  161. package/utils/fs-utils.js +192 -0
  162. package/utils/index.d.ts +1 -1
  163. package/utils/index.js +1 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +46 -3
  166. package/decorators/Check.d.ts +0 -3
  167. package/decorators/Check.js +0 -13
  168. package/decorators/CreateRequestContext.d.ts +0 -3
  169. package/decorators/CreateRequestContext.js +0 -32
  170. package/decorators/Embeddable.d.ts +0 -8
  171. package/decorators/Embeddable.js +0 -11
  172. package/decorators/Embedded.d.ts +0 -12
  173. package/decorators/Embedded.js +0 -18
  174. package/decorators/Entity.d.ts +0 -33
  175. package/decorators/Entity.js +0 -12
  176. package/decorators/Enum.d.ts +0 -9
  177. package/decorators/Enum.js +0 -16
  178. package/decorators/Filter.d.ts +0 -2
  179. package/decorators/Filter.js +0 -8
  180. package/decorators/Formula.d.ts +0 -4
  181. package/decorators/Formula.js +0 -15
  182. package/decorators/Indexed.d.ts +0 -19
  183. package/decorators/Indexed.js +0 -20
  184. package/decorators/ManyToMany.d.ts +0 -42
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -34
  187. package/decorators/ManyToOne.js +0 -14
  188. package/decorators/OneToMany.d.ts +0 -28
  189. package/decorators/OneToMany.js +0 -17
  190. package/decorators/OneToOne.d.ts +0 -28
  191. package/decorators/OneToOne.js +0 -7
  192. package/decorators/PrimaryKey.d.ts +0 -8
  193. package/decorators/PrimaryKey.js +0 -20
  194. package/decorators/Property.d.ts +0 -250
  195. package/decorators/Property.js +0 -32
  196. package/decorators/Transactional.d.ts +0 -14
  197. package/decorators/Transactional.js +0 -28
  198. package/decorators/hooks.d.ts +0 -16
  199. package/decorators/hooks.js +0 -47
  200. package/decorators/index.d.ts +0 -17
  201. package/decorators/index.js +0 -17
  202. package/entity/ArrayCollection.d.ts +0 -118
  203. package/entity/ArrayCollection.js +0 -407
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  207. package/metadata/ReflectMetadataProvider.js +0 -44
  208. package/utils/resolveContextProvider.d.ts +0 -10
  209. package/utils/resolveContextProvider.js +0 -28
@@ -1,4 +1,3 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
1
  import { Collection } from '../entity/Collection.js';
3
2
  import { EntityHelper } from '../entity/EntityHelper.js';
4
3
  import { helper } from '../entity/wrap.js';
@@ -13,8 +12,9 @@ import { Cascade, DeferMode, EventType, LockMode, ReferenceKind } from '../enums
13
12
  import { OptimisticLockError, ValidationError } from '../errors.js';
14
13
  import { TransactionEventBroadcaster } from '../events/TransactionEventBroadcaster.js';
15
14
  import { IdentityMap } from './IdentityMap.js';
15
+ import { createAsyncContext } from '../utils/AsyncContext.js';
16
16
  // to deal with validation for flush inside flush hooks and `Promise.all`
17
- const insideFlush = new AsyncLocalStorage();
17
+ const insideFlush = createAsyncContext();
18
18
  export class UnitOfWork {
19
19
  em;
20
20
  /** map of references to managed entities */
@@ -42,8 +42,8 @@ export class UnitOfWork {
42
42
  this.identityMap = new IdentityMap(this.platform.getDefaultSchemaName());
43
43
  this.eventManager = this.em.getEventManager();
44
44
  this.comparator = this.em.getComparator();
45
- this.changeSetComputer = new ChangeSetComputer(this.em.getValidator(), this.collectionUpdates, this.metadata, this.platform, this.em.config, this.em);
46
- this.changeSetPersister = new ChangeSetPersister(this.em.getDriver(), this.metadata, this.em.config.getHydrator(this.metadata), this.em.getEntityFactory(), this.em.getValidator(), this.em.config, this.em);
45
+ this.changeSetComputer = new ChangeSetComputer(this.collectionUpdates, this.metadata, this.platform, this.em.config, this.em);
46
+ this.changeSetPersister = new ChangeSetPersister(this.em.getDriver(), this.metadata, this.em.config.getHydrator(this.metadata), this.em.getEntityFactory(), this.em.config, this.em);
47
47
  }
48
48
  merge(entity, visited) {
49
49
  const wrapped = helper(entity);
@@ -61,10 +61,46 @@ export class UnitOfWork {
61
61
  // as there can be some entity with already changed state that is not yet flushed
62
62
  if (wrapped.__initialized && (!visited || !wrapped.__originalEntityData)) {
63
63
  wrapped.__originalEntityData = this.comparator.prepareEntity(entity);
64
- wrapped.__touched = false;
65
64
  }
66
65
  this.cascade(entity, Cascade.MERGE, visited ?? new Set());
67
66
  }
67
+ /**
68
+ * Entity data can wary in its shape, e.g. we might get a deep relation graph with joined strategy, but for diffing,
69
+ * we need to normalize the shape, so relation values are only raw FKs. This method handles that.
70
+ * @internal
71
+ */
72
+ normalizeEntityData(meta, data) {
73
+ const forceUndefined = this.em.config.get('forceUndefined');
74
+ for (const key of Utils.keys(data)) {
75
+ const prop = meta.properties[key];
76
+ if (!prop) {
77
+ continue;
78
+ }
79
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
80
+ // Skip polymorphic relations - they use PolymorphicRef wrapper
81
+ if (!prop.polymorphic) {
82
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
83
+ }
84
+ }
85
+ else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
86
+ for (const p of prop.targetMeta.props) {
87
+ /* v8 ignore next */
88
+ const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
89
+ data[prefix + p.name] = data[prop.name][p.name];
90
+ }
91
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
92
+ }
93
+ if (prop.hydrate === false && prop.customType?.ensureComparable(meta, prop)) {
94
+ const converted = prop.customType.convertToJSValue(data[key], this.platform, { key, mode: 'hydration', force: true });
95
+ data[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { key, mode: 'hydration' });
96
+ }
97
+ if (forceUndefined) {
98
+ if (data[key] === null) {
99
+ data[key] = undefined;
100
+ }
101
+ }
102
+ }
103
+ }
68
104
  /**
69
105
  * @internal
70
106
  */
@@ -82,31 +118,14 @@ export class UnitOfWork {
82
118
  wrapped.__em ??= this.em;
83
119
  wrapped.__managed = true;
84
120
  if (data && (options?.refresh || !wrapped.__originalEntityData)) {
121
+ this.normalizeEntityData(wrapped.__meta, data);
85
122
  for (const key of Utils.keys(data)) {
86
123
  const prop = wrapped.__meta.properties[key];
87
- if (!prop) {
88
- continue;
89
- }
90
- wrapped.__loadedProperties.add(key);
91
- if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
92
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
93
- }
94
- else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
95
- for (const p of prop.targetMeta.props) {
96
- /* v8 ignore next */
97
- const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
98
- data[prefix + p.name] = data[prop.name][p.name];
99
- }
100
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
101
- }
102
- if (forceUndefined) {
103
- if (data[key] === null) {
104
- data[key] = undefined;
105
- }
124
+ if (prop) {
125
+ wrapped.__loadedProperties.add(key);
106
126
  }
107
127
  }
108
128
  wrapped.__originalEntityData = data;
109
- wrapped.__touched = false;
110
129
  }
111
130
  return entity;
112
131
  }
@@ -153,6 +172,40 @@ export class UnitOfWork {
153
172
  }
154
173
  return this.identityMap.getByHash(meta, hash);
155
174
  }
175
+ /**
176
+ * Returns entity from the identity map by an alternate key (non-PK property).
177
+ * @param convertCustomTypes - If true, the value is in database format and will be converted to JS format for lookup.
178
+ * If false (default), the value is assumed to be in JS format already.
179
+ */
180
+ getByKey(entityName, key, value, schema, convertCustomTypes) {
181
+ const meta = this.metadata.find(entityName).root;
182
+ schema ??= meta.schema ?? this.em.config.getSchema();
183
+ const prop = meta.properties[key];
184
+ // Convert from DB format to JS format if needed
185
+ if (convertCustomTypes && prop?.customType) {
186
+ value = prop.customType.convertToJSValue(value, this.platform, { mode: 'hydration' });
187
+ }
188
+ const hash = this.identityMap.getKeyHash(key, '' + value, schema);
189
+ return this.identityMap.getByHash(meta, hash);
190
+ }
191
+ /**
192
+ * Stores an entity in the identity map under an alternate key (non-PK property).
193
+ * Also sets the property value on the entity.
194
+ * @param convertCustomTypes - If true, the value is in database format and will be converted to JS format.
195
+ * If false (default), the value is assumed to be in JS format already.
196
+ */
197
+ storeByKey(entity, key, value, schema, convertCustomTypes) {
198
+ const meta = entity.__meta.root;
199
+ schema ??= meta.schema ?? this.em.config.getSchema();
200
+ const prop = meta.properties[key];
201
+ // Convert from DB format to JS format if needed
202
+ if (convertCustomTypes && prop?.customType) {
203
+ value = prop.customType.convertToJSValue(value, this.platform, { mode: 'hydration' });
204
+ }
205
+ // Set the property on the entity
206
+ entity[key] = value;
207
+ this.identityMap.storeByKey(entity, key, '' + value, schema);
208
+ }
156
209
  tryGetById(entityName, where, schema, strict = true) {
157
210
  const pk = Utils.extractPK(where, this.metadata.find(entityName), strict);
158
211
  if (!pk) {
@@ -191,13 +244,11 @@ export class UnitOfWork {
191
244
  if (insideFlush.getStore()) {
192
245
  return false;
193
246
  }
194
- if (this.queuedActions.has(meta.className) || this.queuedActions.has(meta.root.className)) {
247
+ if (this.queuedActions.has(meta.class) || this.queuedActions.has(meta.root.class)) {
195
248
  return true;
196
249
  }
197
- for (const entity of this.identityMap.getStore(meta).values()) {
198
- if (helper(entity).__initialized && helper(entity).isTouched()) {
199
- return true;
200
- }
250
+ if (meta.discriminatorMap && Object.values(meta.discriminatorMap).some(v => this.queuedActions.has(v))) {
251
+ return true;
201
252
  }
202
253
  return false;
203
254
  }
@@ -206,7 +257,7 @@ export class UnitOfWork {
206
257
  }
207
258
  computeChangeSet(entity, type) {
208
259
  const wrapped = helper(entity);
209
- if (type) {
260
+ if (type === ChangeSetType.DELETE || type === ChangeSetType.DELETE_EARLY) {
210
261
  this.changeSets.set(entity, new ChangeSet(entity, type, {}, wrapped.__meta));
211
262
  return;
212
263
  }
@@ -214,11 +265,14 @@ export class UnitOfWork {
214
265
  if (!cs || this.checkUniqueProps(cs)) {
215
266
  return;
216
267
  }
268
+ /* v8 ignore next */
269
+ if (type) {
270
+ cs.type = type;
271
+ }
217
272
  this.initIdentifier(entity);
218
273
  this.changeSets.set(entity, cs);
219
274
  this.persistStack.delete(entity);
220
275
  wrapped.__originalEntityData = this.comparator.prepareEntity(entity);
221
- wrapped.__touched = false;
222
276
  }
223
277
  recomputeSingleChangeSet(entity) {
224
278
  const changeSet = this.changeSets.get(entity);
@@ -229,7 +283,6 @@ export class UnitOfWork {
229
283
  if (cs && !this.checkUniqueProps(cs)) {
230
284
  Object.assign(changeSet.payload, cs.payload);
231
285
  helper(entity).__originalEntityData = this.comparator.prepareEntity(entity);
232
- helper(entity).__touched = false;
233
286
  }
234
287
  }
235
288
  persist(entity, visited, options = {}) {
@@ -239,7 +292,7 @@ export class UnitOfWork {
239
292
  }
240
293
  const wrapped = helper(entity);
241
294
  this.persistStack.add(entity);
242
- this.queuedActions.add(wrapped.__meta.className);
295
+ this.queuedActions.add(wrapped.__meta.class);
243
296
  this.removeStack.delete(entity);
244
297
  if (!wrapped.__managed && wrapped.hasPrimaryKey()) {
245
298
  this.identityMap.store(entity);
@@ -252,7 +305,7 @@ export class UnitOfWork {
252
305
  // allow removing not managed entities if they are not part of the persist stack
253
306
  if (helper(entity).__managed || !this.persistStack.has(entity)) {
254
307
  this.removeStack.add(entity);
255
- this.queuedActions.add(helper(entity).__meta.className);
308
+ this.queuedActions.add(helper(entity).__meta.class);
256
309
  }
257
310
  else {
258
311
  this.persistStack.delete(entity);
@@ -315,7 +368,8 @@ export class UnitOfWork {
315
368
  this.filterCollectionUpdates();
316
369
  // nothing to do, do not start transaction
317
370
  if (this.changeSets.size === 0 && this.collectionUpdates.size === 0 && this.extraUpdates.size === 0) {
318
- return void (await this.eventManager.dispatchEvent(EventType.afterFlush, { em: this.em, uow: this }));
371
+ await this.eventManager.dispatchEvent(EventType.afterFlush, { em: this.em, uow: this });
372
+ return;
319
373
  }
320
374
  const groups = this.getChangeSetGroups();
321
375
  const platform = this.em.getPlatform();
@@ -342,10 +396,10 @@ export class UnitOfWork {
342
396
  }
343
397
  }
344
398
  async lock(entity, options) {
345
- if (!this.getById(entity.constructor.name, helper(entity).__primaryKeys, helper(entity).__schema)) {
399
+ if (!this.getById(entity.constructor, helper(entity).__primaryKeys, helper(entity).__schema)) {
346
400
  throw ValidationError.entityNotManaged(entity);
347
401
  }
348
- const meta = this.metadata.find(entity.constructor.name);
402
+ const meta = this.metadata.find(entity.constructor);
349
403
  if (options.lockMode === LockMode.OPTIMISTIC) {
350
404
  await this.lockOptimistic(entity, meta, options.lockVersion);
351
405
  }
@@ -369,7 +423,7 @@ export class UnitOfWork {
369
423
  if (Utils.isCollection(rel)) {
370
424
  rel.removeWithoutPropagation(entity);
371
425
  }
372
- else if (rel && (prop.mapToPk ? helper(this.em.getReference(prop.type, rel)).getSerializedPrimaryKey() === serializedPK : rel === entity)) {
426
+ else if (rel && (prop.mapToPk ? helper(this.em.getReference(prop.targetMeta.class, rel)).getSerializedPrimaryKey() === serializedPK : rel === entity)) {
373
427
  if (prop.formula) {
374
428
  delete referrer[prop.name];
375
429
  }
@@ -381,7 +435,6 @@ export class UnitOfWork {
381
435
  }
382
436
  delete wrapped.__identifier;
383
437
  delete wrapped.__originalEntityData;
384
- wrapped.__touched = false;
385
438
  wrapped.__managed = false;
386
439
  }
387
440
  computeChangeSets() {
@@ -391,14 +444,14 @@ export class UnitOfWork {
391
444
  this.cascade(entity, Cascade.REMOVE, visited);
392
445
  }
393
446
  visited.clear();
394
- for (const entity of this.persistStack) {
395
- this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
396
- }
397
447
  for (const entity of this.identityMap) {
398
448
  if (!this.removeStack.has(entity) && !this.persistStack.has(entity) && !this.orphanRemoveStack.has(entity)) {
399
449
  this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
400
450
  }
401
451
  }
452
+ for (const entity of this.persistStack) {
453
+ this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
454
+ }
402
455
  visited.clear();
403
456
  for (const entity of this.persistStack) {
404
457
  this.findNewEntities(entity, visited);
@@ -412,24 +465,24 @@ export class UnitOfWork {
412
465
  const inserts = {};
413
466
  for (const cs of this.changeSets.values()) {
414
467
  if (cs.type === ChangeSetType.CREATE) {
415
- inserts[cs.meta.className] ??= [];
416
- inserts[cs.meta.className].push(cs);
468
+ inserts[cs.meta.uniqueName] ??= [];
469
+ inserts[cs.meta.uniqueName].push(cs);
417
470
  }
418
471
  }
419
472
  for (const cs of this.changeSets.values()) {
420
473
  if (cs.type === ChangeSetType.UPDATE) {
421
- this.findEarlyUpdates(cs, inserts[cs.meta.className]);
474
+ this.findEarlyUpdates(cs, inserts[cs.meta.uniqueName]);
422
475
  }
423
476
  }
424
477
  for (const entity of this.removeStack) {
425
478
  const wrapped = helper(entity);
426
- /* v8 ignore next 3 */
479
+ /* v8 ignore next */
427
480
  if (wrapped.__processing) {
428
481
  continue;
429
482
  }
430
483
  const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
431
484
  let type = ChangeSetType.DELETE;
432
- for (const cs of inserts[wrapped.__meta.className] ?? []) {
485
+ for (const cs of inserts[wrapped.__meta.uniqueName] ?? []) {
433
486
  if (deletePkHash.some(hash => hash === cs.getSerializedPrimaryKey() || this.expandUniqueProps(cs.entity).find(child => hash === child))) {
434
487
  type = ChangeSetType.DELETE_EARLY;
435
488
  }
@@ -448,7 +501,7 @@ export class UnitOfWork {
448
501
  }
449
502
  for (const cs of this.changeSets.values()) {
450
503
  for (const prop of props) {
451
- if (prop.name in cs.payload && cs.rootName === changeSet.rootName && cs.type === changeSet.type) {
504
+ if (prop.name in cs.payload && cs.rootMeta === changeSet.rootMeta && cs.type === changeSet.type) {
452
505
  conflicts = true;
453
506
  if (changeSet.payload[prop.name] == null) {
454
507
  type = ChangeSetType.UPDATE_EARLY;
@@ -467,9 +520,10 @@ export class UnitOfWork {
467
520
  }
468
521
  scheduleOrphanRemoval(entity, visited) {
469
522
  if (entity) {
470
- helper(entity).__em = this.em;
523
+ const wrapped = helper(entity);
524
+ wrapped.__em = this.em;
471
525
  this.orphanRemoveStack.add(entity);
472
- this.queuedActions.add(entity.__meta.className);
526
+ this.queuedActions.add(wrapped.__meta.class);
473
527
  this.cascade(entity, Cascade.SCHEDULE_ORPHAN_REMOVAL, visited);
474
528
  }
475
529
  }
@@ -505,7 +559,68 @@ export class UnitOfWork {
505
559
  }
506
560
  const changeSet = this.changeSetComputer.computeChangeSet(entity);
507
561
  if (changeSet && !this.checkUniqueProps(changeSet)) {
508
- this.changeSets.set(entity, changeSet);
562
+ // For TPT child entities, create changesets for each table in hierarchy
563
+ if (wrapped.__meta.inheritanceType === 'tpt' && wrapped.__meta.tptParent) {
564
+ this.createTPTChangeSets(entity, changeSet);
565
+ }
566
+ else {
567
+ this.changeSets.set(entity, changeSet);
568
+ }
569
+ }
570
+ }
571
+ /**
572
+ * For TPT inheritance, creates separate changesets for each table in the hierarchy.
573
+ * Uses the same entity instance for all changesets - only the metadata and payload differ.
574
+ */
575
+ createTPTChangeSets(entity, originalChangeSet) {
576
+ const meta = helper(entity).__meta;
577
+ const isCreate = originalChangeSet.type === ChangeSetType.CREATE;
578
+ let current = meta;
579
+ let leafCs;
580
+ const parentChangeSets = [];
581
+ while (current) {
582
+ const isRoot = !current.tptParent;
583
+ const payload = {};
584
+ for (const prop of current.ownProps) {
585
+ if (prop.name in originalChangeSet.payload) {
586
+ payload[prop.name] = originalChangeSet.payload[prop.name];
587
+ }
588
+ }
589
+ // For CREATE on non-root tables, include the PK (EntityIdentifier for deferred resolution)
590
+ if (isCreate && !isRoot) {
591
+ const wrapped = helper(entity);
592
+ const identifier = wrapped.__identifier;
593
+ const identifiers = Array.isArray(identifier) ? identifier : [identifier];
594
+ for (let i = 0; i < current.primaryKeys.length; i++) {
595
+ const pk = current.primaryKeys[i];
596
+ payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
597
+ }
598
+ }
599
+ if (!isCreate && Object.keys(payload).length === 0) {
600
+ current = current.tptParent;
601
+ continue;
602
+ }
603
+ const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
604
+ if (current === meta) {
605
+ cs.originalEntity = originalChangeSet.originalEntity;
606
+ leafCs = cs;
607
+ }
608
+ else {
609
+ parentChangeSets.push(cs);
610
+ }
611
+ current = current.tptParent;
612
+ }
613
+ // When only parent properties changed (UPDATE), leaf payload is empty—create a stub anchor
614
+ if (!leafCs && parentChangeSets.length > 0) {
615
+ leafCs = new ChangeSet(entity, originalChangeSet.type, {}, meta);
616
+ leafCs.originalEntity = originalChangeSet.originalEntity;
617
+ }
618
+ // Store the leaf changeset in the main map (entity as key), with parent CSs attached
619
+ if (leafCs) {
620
+ if (parentChangeSets.length > 0) {
621
+ leafCs.tptChangeSets = parentChangeSets;
622
+ }
623
+ this.changeSets.set(entity, leafCs);
509
624
  }
510
625
  }
511
626
  /**
@@ -618,7 +733,7 @@ export class UnitOfWork {
618
733
  const copy = this.comparator.prepareEntity(changeSet.entity);
619
734
  await this.eventManager.dispatchEvent(type, { entity: changeSet.entity, meta, em: this.em, changeSet });
620
735
  const current = this.comparator.prepareEntity(changeSet.entity);
621
- const diff = this.comparator.diffEntities(changeSet.name, copy, current);
736
+ const diff = this.comparator.diffEntities(changeSet.meta.class, copy, current);
622
737
  Object.assign(changeSet.payload, diff);
623
738
  const wrapped = helper(changeSet.entity);
624
739
  if (wrapped.__identifier) {
@@ -713,7 +828,7 @@ export class UnitOfWork {
713
828
  if (!meta.versionProperty) {
714
829
  throw OptimisticLockError.notVersioned(meta);
715
830
  }
716
- if (!Utils.isDefined(version)) {
831
+ if (typeof version === 'undefined') {
717
832
  return;
718
833
  }
719
834
  const wrapped = helper(entity);
@@ -727,26 +842,26 @@ export class UnitOfWork {
727
842
  }
728
843
  fixMissingReference(entity, prop) {
729
844
  const reference = entity[prop.name];
730
- const kind = Reference.unwrapReference(reference);
731
- if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && kind && !prop.mapToPk) {
732
- if (!Utils.isEntity(kind)) {
733
- entity[prop.name] = this.em.getReference(prop.type, kind, { wrapped: !!prop.ref });
845
+ const target = Reference.unwrapReference(reference);
846
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && target && !prop.mapToPk) {
847
+ if (!Utils.isEntity(target)) {
848
+ entity[prop.name] = this.em.getReference(prop.targetMeta.class, target, { wrapped: !!prop.ref });
734
849
  }
735
- else if (!helper(kind).__initialized && !helper(kind).__em) {
736
- const pk = helper(kind).getPrimaryKey();
737
- entity[prop.name] = this.em.getReference(prop.type, pk, { wrapped: !!prop.ref });
850
+ else if (!helper(target).__initialized && !helper(target).__em) {
851
+ const pk = helper(target).getPrimaryKey();
852
+ entity[prop.name] = this.em.getReference(prop.targetMeta.class, pk, { wrapped: !!prop.ref });
738
853
  }
739
854
  }
740
- // perf: set the `Collection._property` to skip the getter, as it can be slow when there is a lot of relations
741
- if (Utils.isCollection(kind)) {
742
- kind.property = prop;
855
+ // perf: set the `Collection._property` to skip the getter, as it can be slow when there are a lot of relations
856
+ if (Utils.isCollection(target)) {
857
+ target.property = prop;
743
858
  }
744
859
  const isCollection = [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind);
745
- if (isCollection && Array.isArray(kind)) {
860
+ if (isCollection && Array.isArray(target)) {
746
861
  const collection = new Collection(entity);
747
862
  collection.property = prop;
748
863
  entity[prop.name] = collection;
749
- collection.set(kind);
864
+ collection.set(target);
750
865
  }
751
866
  }
752
867
  async persistToDatabase(groups, ctx) {
@@ -756,30 +871,30 @@ export class UnitOfWork {
756
871
  const commitOrder = this.getCommitOrder();
757
872
  const commitOrderReversed = [...commitOrder].reverse();
758
873
  // early delete - when we recreate entity in the same UoW, we need to issue those delete queries before inserts
759
- for (const name of commitOrderReversed) {
760
- await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE_EARLY].get(name) ?? [], ctx);
874
+ for (const meta of commitOrderReversed) {
875
+ await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE_EARLY].get(meta) ?? [], ctx);
761
876
  }
762
877
  // early update - when we recreate entity in the same UoW, we need to issue those delete queries before inserts
763
- for (const name of commitOrder) {
764
- await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE_EARLY].get(name) ?? [], ctx);
878
+ for (const meta of commitOrder) {
879
+ await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE_EARLY].get(meta) ?? [], ctx);
765
880
  }
766
881
  // extra updates
767
882
  await this.commitExtraUpdates(ChangeSetType.UPDATE_EARLY, ctx);
768
883
  // create
769
- for (const name of commitOrder) {
770
- await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(name) ?? [], ctx);
884
+ for (const meta of commitOrder) {
885
+ await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(meta) ?? [], ctx);
771
886
  }
772
887
  // update
773
- for (const name of commitOrder) {
774
- await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(name) ?? [], ctx);
888
+ for (const meta of commitOrder) {
889
+ await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(meta) ?? [], ctx);
775
890
  }
776
891
  // extra updates
777
892
  await this.commitExtraUpdates(ChangeSetType.UPDATE, ctx);
778
893
  // collection updates
779
894
  await this.commitCollectionUpdates(ctx);
780
895
  // delete - entity deletions need to be in reverse commit order
781
- for (const name of commitOrderReversed) {
782
- await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(name) ?? [], ctx);
896
+ for (const meta of commitOrderReversed) {
897
+ await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(meta) ?? [], ctx);
783
898
  }
784
899
  // take snapshots of all persisted collections
785
900
  const visited = new Set();
@@ -815,16 +930,28 @@ export class UnitOfWork {
815
930
  if (Utils.isCollection(ref)) {
816
931
  ref.getItems(false).some(item => {
817
932
  const cs = this.changeSets.get(Reference.unwrapReference(item));
818
- const isScheduledForInsert = cs && cs.type === ChangeSetType.CREATE && !cs.persisted;
933
+ const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
819
934
  if (isScheduledForInsert) {
820
935
  this.scheduleExtraUpdate(changeSet, [prop]);
821
936
  return true;
822
937
  }
823
938
  return false;
824
939
  });
940
+ continue;
941
+ }
942
+ const refEntity = Reference.unwrapReference(ref);
943
+ // For mapToPk properties, the value is a primitive (string/array), not an entity
944
+ if (!Utils.isEntity(refEntity)) {
945
+ continue;
825
946
  }
826
- const cs = this.changeSets.get(Reference.unwrapReference(ref));
827
- const isScheduledForInsert = cs && cs.type === ChangeSetType.CREATE && !cs.persisted;
947
+ // For TPT entities, check if the ROOT table's changeset has been persisted
948
+ // (since the FK is to the root table, not the concrete entity's table)
949
+ let cs = this.changeSets.get(refEntity);
950
+ if (cs?.tptChangeSets?.length) {
951
+ // Root table changeset is the last one (ordered immediate parent → root)
952
+ cs = cs.tptChangeSets[cs.tptChangeSets.length - 1];
953
+ }
954
+ const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
828
955
  if (isScheduledForInsert) {
829
956
  this.scheduleExtraUpdate(changeSet, [prop]);
830
957
  }
@@ -853,9 +980,16 @@ export class UnitOfWork {
853
980
  }
854
981
  await this.changeSetPersister.executeUpdates(changeSets, batched, { ctx });
855
982
  for (const changeSet of changeSets) {
856
- helper(changeSet.entity).__originalEntityData = this.comparator.prepareEntity(changeSet.entity);
857
- helper(changeSet.entity).__touched = false;
858
- helper(changeSet.entity).__initialized = true;
983
+ const wrapped = helper(changeSet.entity);
984
+ wrapped.__originalEntityData = this.comparator.prepareEntity(changeSet.entity);
985
+ if (!wrapped.__initialized) {
986
+ for (const prop of changeSet.meta.relations) {
987
+ if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) && changeSet.entity[prop.name] == null) {
988
+ changeSet.entity[prop.name] = Collection.create(changeSet.entity, prop.name, undefined, wrapped.isInitialized());
989
+ }
990
+ }
991
+ wrapped.__initialized = true;
992
+ }
859
993
  await this.runHooks(EventType.afterUpdate, changeSet);
860
994
  }
861
995
  }
@@ -939,12 +1073,23 @@ export class UnitOfWork {
939
1073
  [ChangeSetType.UPDATE_EARLY]: new Map(),
940
1074
  [ChangeSetType.DELETE_EARLY]: new Map(),
941
1075
  };
942
- for (const cs of this.changeSets.values()) {
1076
+ const addToGroup = (cs) => {
1077
+ // Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
1078
+ if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) && !Utils.hasObjectKeys(cs.payload)) {
1079
+ return;
1080
+ }
943
1081
  const group = groups[cs.type];
944
- const classGroup = group.get(cs.rootName) ?? [];
1082
+ const groupKey = cs.meta.inheritanceType === 'tpt' ? cs.meta : cs.rootMeta;
1083
+ const classGroup = group.get(groupKey) ?? [];
945
1084
  classGroup.push(cs);
946
- if (!group.has(cs.rootName)) {
947
- group.set(cs.rootName, classGroup);
1085
+ if (!group.has(groupKey)) {
1086
+ group.set(groupKey, classGroup);
1087
+ }
1088
+ };
1089
+ for (const cs of this.changeSets.values()) {
1090
+ addToGroup(cs);
1091
+ for (const parentCs of cs.tptChangeSets ?? []) {
1092
+ addToGroup(parentCs);
948
1093
  }
949
1094
  }
950
1095
  return groups;
@@ -952,14 +1097,35 @@ export class UnitOfWork {
952
1097
  getCommitOrder() {
953
1098
  const calc = new CommitOrderCalculator();
954
1099
  const set = new Set();
955
- this.changeSets.forEach(cs => set.add(cs.rootName));
956
- set.forEach(entityName => calc.addNode(entityName));
957
- for (const entityName of set) {
958
- for (const prop of this.metadata.find(entityName).props) {
959
- calc.discoverProperty(prop, entityName);
1100
+ this.changeSets.forEach(cs => {
1101
+ if (cs.meta.inheritanceType === 'tpt') {
1102
+ set.add(cs.meta);
1103
+ for (const parentCs of cs.tptChangeSets ?? []) {
1104
+ set.add(parentCs.meta);
1105
+ }
1106
+ }
1107
+ else {
1108
+ set.add(cs.rootMeta);
1109
+ }
1110
+ });
1111
+ set.forEach(meta => calc.addNode(meta._id));
1112
+ for (const meta of set) {
1113
+ for (const prop of meta.relations) {
1114
+ if (prop.polymorphTargets) {
1115
+ for (const targetMeta of prop.polymorphTargets) {
1116
+ calc.discoverProperty({ ...prop, targetMeta }, meta._id);
1117
+ }
1118
+ }
1119
+ else {
1120
+ calc.discoverProperty(prop, meta._id);
1121
+ }
1122
+ }
1123
+ // For TPT, parent table must be inserted BEFORE child tables
1124
+ if (meta.inheritanceType === 'tpt' && meta.tptParent && set.has(meta.tptParent)) {
1125
+ calc.addDependency(meta.tptParent._id, meta._id, 1);
960
1126
  }
961
1127
  }
962
- return calc.sort();
1128
+ return calc.sort().map(id => this.metadata.getById(id));
963
1129
  }
964
1130
  resetTransaction(oldTx) {
965
1131
  if (oldTx) {
@@ -10,18 +10,18 @@ export declare abstract class AbstractSchemaGenerator<D extends IDatabaseDriver>
10
10
  protected readonly platform: ReturnType<D['getPlatform']>;
11
11
  protected readonly connection: ReturnType<D['getConnection']>;
12
12
  constructor(em: D | D[typeof EntityManagerType]);
13
- createSchema(options?: CreateSchemaOptions): Promise<void>;
13
+ create(options?: CreateSchemaOptions): Promise<void>;
14
14
  /**
15
15
  * Returns true if the database was created.
16
16
  */
17
17
  ensureDatabase(options?: EnsureDatabaseOptions): Promise<boolean>;
18
- refreshDatabase(options?: RefreshDatabaseOptions): Promise<void>;
19
- clearDatabase(options?: ClearDatabaseOptions): Promise<void>;
18
+ refresh(options?: RefreshDatabaseOptions): Promise<void>;
19
+ clear(options?: ClearDatabaseOptions): Promise<void>;
20
20
  protected clearIdentityMap(): void;
21
21
  getCreateSchemaSQL(options?: CreateSchemaOptions): Promise<string>;
22
- dropSchema(options?: DropSchemaOptions): Promise<void>;
22
+ drop(options?: DropSchemaOptions): Promise<void>;
23
23
  getDropSchemaSQL(options?: Omit<DropSchemaOptions, 'dropDb'>): Promise<string>;
24
- updateSchema(options?: UpdateSchemaOptions): Promise<void>;
24
+ update(options?: UpdateSchemaOptions): Promise<void>;
25
25
  getUpdateSchemaSQL(options?: UpdateSchemaOptions): Promise<string>;
26
26
  getUpdateSchemaMigrationSQL(options?: UpdateSchemaOptions): Promise<{
27
27
  up: string;