@mikro-orm/core 7.0.14 → 7.0.15-dev.1

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 (206) hide show
  1. package/EntityManager.d.ts +584 -885
  2. package/EntityManager.js +1908 -1935
  3. package/MikroORM.d.ts +74 -103
  4. package/MikroORM.js +178 -177
  5. package/README.md +1 -1
  6. package/cache/CacheAdapter.d.ts +36 -36
  7. package/cache/FileCacheAdapter.d.ts +24 -30
  8. package/cache/FileCacheAdapter.js +78 -80
  9. package/cache/GeneratedCacheAdapter.d.ts +20 -18
  10. package/cache/GeneratedCacheAdapter.js +30 -30
  11. package/cache/MemoryCacheAdapter.d.ts +20 -18
  12. package/cache/MemoryCacheAdapter.js +36 -35
  13. package/cache/NullCacheAdapter.d.ts +16 -16
  14. package/cache/NullCacheAdapter.js +24 -24
  15. package/connections/Connection.d.ts +84 -95
  16. package/connections/Connection.js +168 -165
  17. package/drivers/DatabaseDriver.d.ts +80 -186
  18. package/drivers/DatabaseDriver.js +443 -450
  19. package/drivers/IDatabaseDriver.d.ts +305 -444
  20. package/entity/BaseEntity.d.ts +83 -120
  21. package/entity/BaseEntity.js +43 -43
  22. package/entity/Collection.d.ts +181 -215
  23. package/entity/Collection.js +724 -730
  24. package/entity/EntityAssigner.d.ts +77 -88
  25. package/entity/EntityAssigner.js +230 -231
  26. package/entity/EntityFactory.d.ts +55 -67
  27. package/entity/EntityFactory.js +413 -454
  28. package/entity/EntityHelper.d.ts +23 -35
  29. package/entity/EntityHelper.js +290 -302
  30. package/entity/EntityIdentifier.d.ts +4 -4
  31. package/entity/EntityIdentifier.js +10 -10
  32. package/entity/EntityLoader.d.ts +72 -98
  33. package/entity/EntityLoader.js +786 -815
  34. package/entity/EntityRepository.d.ts +201 -316
  35. package/entity/EntityRepository.js +213 -213
  36. package/entity/PolymorphicRef.d.ts +5 -5
  37. package/entity/PolymorphicRef.js +10 -10
  38. package/entity/Reference.d.ts +83 -127
  39. package/entity/Reference.js +277 -281
  40. package/entity/WrappedEntity.d.ts +72 -115
  41. package/entity/WrappedEntity.js +166 -168
  42. package/entity/defineEntity.d.ts +654 -1359
  43. package/entity/defineEntity.js +518 -527
  44. package/entity/utils.d.ts +3 -13
  45. package/entity/utils.js +73 -71
  46. package/entity/validators.js +43 -43
  47. package/entity/wrap.js +8 -8
  48. package/enums.d.ts +253 -258
  49. package/enums.js +252 -251
  50. package/errors.d.ts +72 -114
  51. package/errors.js +253 -350
  52. package/events/EventManager.d.ts +14 -26
  53. package/events/EventManager.js +77 -79
  54. package/events/EventSubscriber.d.ts +29 -29
  55. package/events/TransactionEventBroadcaster.d.ts +8 -15
  56. package/events/TransactionEventBroadcaster.js +14 -14
  57. package/exceptions.d.ts +40 -23
  58. package/exceptions.js +52 -35
  59. package/hydration/Hydrator.d.ts +17 -42
  60. package/hydration/Hydrator.js +43 -43
  61. package/hydration/ObjectHydrator.d.ts +17 -50
  62. package/hydration/ObjectHydrator.js +418 -483
  63. package/index.d.ts +2 -118
  64. package/index.js +1 -10
  65. package/logging/DefaultLogger.d.ts +32 -34
  66. package/logging/DefaultLogger.js +86 -86
  67. package/logging/Logger.d.ts +41 -41
  68. package/logging/SimpleLogger.d.ts +11 -13
  69. package/logging/SimpleLogger.js +22 -22
  70. package/logging/colors.d.ts +6 -6
  71. package/logging/colors.js +10 -11
  72. package/logging/inspect.js +7 -7
  73. package/metadata/EntitySchema.d.ts +130 -214
  74. package/metadata/EntitySchema.js +412 -411
  75. package/metadata/MetadataDiscovery.d.ts +115 -115
  76. package/metadata/MetadataDiscovery.js +1931 -2013
  77. package/metadata/MetadataProvider.d.ts +26 -29
  78. package/metadata/MetadataProvider.js +97 -95
  79. package/metadata/MetadataStorage.d.ts +32 -38
  80. package/metadata/MetadataStorage.js +118 -118
  81. package/metadata/MetadataValidator.d.ts +39 -39
  82. package/metadata/MetadataValidator.js +340 -385
  83. package/metadata/discover-entities.d.ts +2 -5
  84. package/metadata/discover-entities.js +37 -35
  85. package/metadata/types.d.ts +531 -615
  86. package/naming-strategy/AbstractNamingStrategy.d.ts +39 -54
  87. package/naming-strategy/AbstractNamingStrategy.js +85 -90
  88. package/naming-strategy/EntityCaseNamingStrategy.d.ts +6 -6
  89. package/naming-strategy/EntityCaseNamingStrategy.js +22 -22
  90. package/naming-strategy/MongoNamingStrategy.d.ts +6 -6
  91. package/naming-strategy/MongoNamingStrategy.js +18 -18
  92. package/naming-strategy/NamingStrategy.d.ts +99 -109
  93. package/naming-strategy/UnderscoreNamingStrategy.d.ts +7 -7
  94. package/naming-strategy/UnderscoreNamingStrategy.js +21 -21
  95. package/not-supported.js +4 -7
  96. package/package.json +1 -1
  97. package/platforms/ExceptionConverter.d.ts +1 -1
  98. package/platforms/ExceptionConverter.js +4 -4
  99. package/platforms/Platform.d.ts +303 -312
  100. package/platforms/Platform.js +695 -715
  101. package/serialization/EntitySerializer.d.ts +26 -49
  102. package/serialization/EntitySerializer.js +218 -224
  103. package/serialization/EntityTransformer.d.ts +6 -10
  104. package/serialization/EntityTransformer.js +217 -219
  105. package/serialization/SerializationContext.d.ts +23 -27
  106. package/serialization/SerializationContext.js +105 -105
  107. package/types/ArrayType.d.ts +8 -8
  108. package/types/ArrayType.js +33 -33
  109. package/types/BigIntType.d.ts +10 -17
  110. package/types/BigIntType.js +37 -37
  111. package/types/BlobType.d.ts +3 -3
  112. package/types/BlobType.js +13 -13
  113. package/types/BooleanType.d.ts +4 -4
  114. package/types/BooleanType.js +12 -12
  115. package/types/CharacterType.d.ts +2 -2
  116. package/types/CharacterType.js +6 -6
  117. package/types/DateTimeType.d.ts +5 -5
  118. package/types/DateTimeType.js +15 -15
  119. package/types/DateType.d.ts +5 -5
  120. package/types/DateType.js +15 -15
  121. package/types/DecimalType.d.ts +7 -7
  122. package/types/DecimalType.js +26 -26
  123. package/types/DoubleType.d.ts +3 -3
  124. package/types/DoubleType.js +12 -12
  125. package/types/EnumArrayType.d.ts +5 -5
  126. package/types/EnumArrayType.js +24 -24
  127. package/types/EnumType.d.ts +3 -3
  128. package/types/EnumType.js +11 -11
  129. package/types/FloatType.d.ts +3 -3
  130. package/types/FloatType.js +9 -9
  131. package/types/IntegerType.d.ts +3 -3
  132. package/types/IntegerType.js +9 -9
  133. package/types/IntervalType.d.ts +4 -4
  134. package/types/IntervalType.js +12 -12
  135. package/types/JsonType.d.ts +8 -8
  136. package/types/JsonType.js +32 -32
  137. package/types/MediumIntType.d.ts +1 -1
  138. package/types/MediumIntType.js +3 -3
  139. package/types/SmallIntType.d.ts +3 -3
  140. package/types/SmallIntType.js +9 -9
  141. package/types/StringType.d.ts +4 -4
  142. package/types/StringType.js +12 -12
  143. package/types/TextType.d.ts +3 -3
  144. package/types/TextType.js +9 -9
  145. package/types/TimeType.d.ts +5 -5
  146. package/types/TimeType.js +17 -17
  147. package/types/TinyIntType.d.ts +3 -3
  148. package/types/TinyIntType.js +10 -10
  149. package/types/Type.d.ts +79 -83
  150. package/types/Type.js +82 -82
  151. package/types/Uint8ArrayType.d.ts +4 -4
  152. package/types/Uint8ArrayType.js +21 -21
  153. package/types/UnknownType.d.ts +4 -4
  154. package/types/UnknownType.js +12 -12
  155. package/types/UuidType.d.ts +5 -5
  156. package/types/UuidType.js +19 -19
  157. package/types/index.d.ts +49 -75
  158. package/types/index.js +26 -52
  159. package/typings.d.ts +743 -1264
  160. package/typings.js +233 -244
  161. package/unit-of-work/ChangeSet.d.ts +26 -26
  162. package/unit-of-work/ChangeSet.js +56 -56
  163. package/unit-of-work/ChangeSetComputer.d.ts +12 -12
  164. package/unit-of-work/ChangeSetComputer.js +179 -187
  165. package/unit-of-work/ChangeSetPersister.d.ts +50 -69
  166. package/unit-of-work/ChangeSetPersister.js +446 -469
  167. package/unit-of-work/CommitOrderCalculator.d.ts +40 -40
  168. package/unit-of-work/CommitOrderCalculator.js +88 -89
  169. package/unit-of-work/IdentityMap.d.ts +31 -31
  170. package/unit-of-work/IdentityMap.js +105 -105
  171. package/unit-of-work/UnitOfWork.d.ts +141 -181
  172. package/unit-of-work/UnitOfWork.js +1226 -1240
  173. package/utils/AbstractMigrator.d.ts +92 -112
  174. package/utils/AbstractMigrator.js +287 -285
  175. package/utils/AbstractSchemaGenerator.d.ts +34 -43
  176. package/utils/AbstractSchemaGenerator.js +122 -121
  177. package/utils/AsyncContext.d.ts +3 -3
  178. package/utils/AsyncContext.js +35 -34
  179. package/utils/Configuration.d.ts +808 -852
  180. package/utils/Configuration.js +344 -359
  181. package/utils/Cursor.d.ts +22 -40
  182. package/utils/Cursor.js +127 -135
  183. package/utils/DataloaderUtils.d.ts +43 -58
  184. package/utils/DataloaderUtils.js +198 -203
  185. package/utils/EntityComparator.d.ts +82 -99
  186. package/utils/EntityComparator.js +744 -836
  187. package/utils/NullHighlighter.d.ts +1 -1
  188. package/utils/NullHighlighter.js +3 -3
  189. package/utils/QueryHelper.d.ts +57 -85
  190. package/utils/QueryHelper.js +392 -404
  191. package/utils/RawQueryFragment.d.ts +34 -50
  192. package/utils/RawQueryFragment.js +121 -122
  193. package/utils/RequestContext.d.ts +32 -32
  194. package/utils/RequestContext.js +53 -52
  195. package/utils/TransactionContext.d.ts +16 -16
  196. package/utils/TransactionContext.js +27 -27
  197. package/utils/TransactionManager.d.ts +58 -58
  198. package/utils/TransactionManager.js +200 -202
  199. package/utils/Utils.d.ts +145 -204
  200. package/utils/Utils.js +826 -826
  201. package/utils/clone.js +114 -105
  202. package/utils/env-vars.js +88 -90
  203. package/utils/fs-utils.d.ts +15 -15
  204. package/utils/fs-utils.js +182 -181
  205. package/utils/upsert-utils.d.ts +5 -20
  206. package/utils/upsert-utils.js +116 -114
@@ -8,482 +8,441 @@ import { JsonType } from '../types/JsonType.js';
8
8
  import { isRaw } from '../utils/RawQueryFragment.js';
9
9
  /** @internal Factory responsible for creating, merging, and hydrating entity instances. */
10
10
  export class EntityFactory {
11
- #driver;
12
- #platform;
13
- #config;
14
- #metadata;
15
- #hydrator;
16
- #eventManager;
17
- #comparator;
18
- #em;
19
- constructor(em) {
20
- this.#em = em;
21
- this.#driver = this.#em.getDriver();
22
- this.#platform = this.#driver.getPlatform();
23
- this.#config = this.#em.config;
24
- this.#metadata = this.#em.getMetadata();
25
- this.#hydrator = this.#config.getHydrator(this.#metadata);
26
- this.#eventManager = this.#em.getEventManager();
27
- this.#comparator = this.#em.getComparator();
28
- }
29
- /** Creates a new entity instance or returns an existing one from the identity map, hydrating it with the provided data. */
30
- create(entityName, data, options = {}) {
31
- data = Reference.unwrapReference(data);
32
- options.initialized ??= true;
33
- if (EntityHelper.isEntity(data)) {
34
- return data;
11
+ #driver;
12
+ #platform;
13
+ #config;
14
+ #metadata;
15
+ #hydrator;
16
+ #eventManager;
17
+ #comparator;
18
+ #em;
19
+ constructor(em) {
20
+ this.#em = em;
21
+ this.#driver = this.#em.getDriver();
22
+ this.#platform = this.#driver.getPlatform();
23
+ this.#config = this.#em.config;
24
+ this.#metadata = this.#em.getMetadata();
25
+ this.#hydrator = this.#config.getHydrator(this.#metadata);
26
+ this.#eventManager = this.#em.getEventManager();
27
+ this.#comparator = this.#em.getComparator();
35
28
  }
36
- const meta = this.#metadata.get(entityName);
37
- if (meta.virtual) {
38
- data = { ...data };
39
- const entity = this.createEntity(data, meta, options);
40
- this.hydrate(entity, meta, data, options);
41
- return entity;
42
- }
43
- if (meta.serializedPrimaryKey) {
44
- this.denormalizePrimaryKey(meta, data);
45
- }
46
- const meta2 = this.processDiscriminatorColumn(meta, data);
47
- const exists = this.findEntity(data, meta2, options);
48
- let wrapped = exists && helper(exists);
49
- if (wrapped && !options.refresh) {
50
- const wasInitialized = wrapped.isInitialized();
51
- wrapped.__processing = true;
52
- Utils.dropUndefinedProperties(data);
53
- this.mergeData(meta2, exists, data, options);
54
- wrapped.__processing = false;
55
- wrapped.__initialized ||= !!options.initialized;
56
- if (wrapped.isInitialized()) {
57
- if (!wasInitialized) {
58
- if (meta.root.inheritanceType && !(exists instanceof meta2.class)) {
59
- Object.setPrototypeOf(exists, meta2.prototype);
60
- }
61
- if (options.merge && wrapped.hasPrimaryKey()) {
62
- this.unitOfWork.register(exists, data, { loaded: options.initialized });
63
- // ensure all data keys are tracked as loaded for shouldRefresh checks,
64
- // but don't overwrite __originalEntityData — mergeData already set it
65
- // with DB values for non-user-modified keys, leaving user changes detectable
66
- for (const key of Utils.keys(data)) {
67
- if (meta2.properties[key]) {
68
- wrapped.__loadedProperties.add(key);
69
- }
70
- }
71
- }
72
- if (this.#eventManager.hasListeners(EventType.onInit, meta2)) {
73
- this.#eventManager.dispatchEvent(EventType.onInit, { entity: exists, meta: meta2, em: this.#em });
74
- }
29
+ /** Creates a new entity instance or returns an existing one from the identity map, hydrating it with the provided data. */
30
+ create(entityName, data, options = {}) {
31
+ data = Reference.unwrapReference(data);
32
+ options.initialized ??= true;
33
+ if (EntityHelper.isEntity(data)) {
34
+ return data;
75
35
  }
76
- return exists;
77
- }
78
- }
79
- data = { ...data };
80
- const entity = exists ?? this.createEntity(data, meta2, options);
81
- wrapped = helper(entity);
82
- wrapped.__processing = true;
83
- wrapped.__initialized = options.initialized;
84
- if (options.newEntity || meta.forceConstructor || meta.virtual) {
85
- const tmp = { ...data };
86
- meta.constructorParams?.forEach(prop => delete tmp[prop]);
87
- this.hydrate(entity, meta2, tmp, options);
88
- // since we now process only a copy of the `data` via hydrator, but later we register the state with the full snapshot,
89
- // we need to go through all props with custom types that have `ensureComparable: true` and ensure they are comparable
90
- // even if they are not part of constructor parameters (as this is otherwise normalized during hydration, here only in `tmp`)
91
- if (options.convertCustomTypes) {
92
- for (const prop of meta.props) {
93
- if (prop.customType?.ensureComparable(meta, prop) && data[prop.name]) {
94
- if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
95
- continue;
36
+ const meta = this.#metadata.get(entityName);
37
+ if (meta.virtual) {
38
+ data = { ...data };
39
+ const entity = this.createEntity(data, meta, options);
40
+ this.hydrate(entity, meta, data, options);
41
+ return entity;
42
+ }
43
+ if (meta.serializedPrimaryKey) {
44
+ this.denormalizePrimaryKey(meta, data);
45
+ }
46
+ const meta2 = this.processDiscriminatorColumn(meta, data);
47
+ const exists = this.findEntity(data, meta2, options);
48
+ let wrapped = exists && helper(exists);
49
+ if (wrapped && !options.refresh) {
50
+ const wasInitialized = wrapped.isInitialized();
51
+ wrapped.__processing = true;
52
+ Utils.dropUndefinedProperties(data);
53
+ this.mergeData(meta2, exists, data, options);
54
+ wrapped.__processing = false;
55
+ wrapped.__initialized ||= !!options.initialized;
56
+ if (wrapped.isInitialized()) {
57
+ if (!wasInitialized) {
58
+ if (meta.root.inheritanceType && !(exists instanceof meta2.class)) {
59
+ Object.setPrototypeOf(exists, meta2.prototype);
60
+ }
61
+ if (options.merge && wrapped.hasPrimaryKey()) {
62
+ this.unitOfWork.register(exists, data, { loaded: options.initialized });
63
+ // ensure all data keys are tracked as loaded for shouldRefresh checks,
64
+ // but don't overwrite __originalEntityData — mergeData already set it
65
+ // with DB values for non-user-modified keys, leaving user changes detectable
66
+ for (const key of Utils.keys(data)) {
67
+ if (meta2.properties[key]) {
68
+ wrapped.__loadedProperties.add(key);
69
+ }
70
+ }
71
+ }
72
+ if (this.#eventManager.hasListeners(EventType.onInit, meta2)) {
73
+ this.#eventManager.dispatchEvent(EventType.onInit, { entity: exists, meta: meta2, em: this.#em });
74
+ }
75
+ }
76
+ return exists;
96
77
  }
97
- if (
98
- [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
99
- Utils.isPlainObject(data[prop.name])
100
- ) {
101
- data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
78
+ }
79
+ data = { ...data };
80
+ const entity = exists ?? this.createEntity(data, meta2, options);
81
+ wrapped = helper(entity);
82
+ wrapped.__processing = true;
83
+ wrapped.__initialized = options.initialized;
84
+ if (options.newEntity || meta.forceConstructor || meta.virtual) {
85
+ const tmp = { ...data };
86
+ meta.constructorParams?.forEach(prop => delete tmp[prop]);
87
+ this.hydrate(entity, meta2, tmp, options);
88
+ // since we now process only a copy of the `data` via hydrator, but later we register the state with the full snapshot,
89
+ // we need to go through all props with custom types that have `ensureComparable: true` and ensure they are comparable
90
+ // even if they are not part of constructor parameters (as this is otherwise normalized during hydration, here only in `tmp`)
91
+ if (options.convertCustomTypes) {
92
+ for (const prop of meta.props) {
93
+ if (prop.customType?.ensureComparable(meta, prop) && data[prop.name]) {
94
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
95
+ continue;
96
+ }
97
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
98
+ Utils.isPlainObject(data[prop.name])) {
99
+ data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
100
+ }
101
+ if (prop.customType instanceof JsonType && this.#platform.convertsJsonAutomatically()) {
102
+ data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.#platform, {
103
+ key: prop.name,
104
+ mode: 'hydration',
105
+ });
106
+ }
107
+ }
108
+ }
102
109
  }
103
- if (prop.customType instanceof JsonType && this.#platform.convertsJsonAutomatically()) {
104
- data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.#platform, {
105
- key: prop.name,
106
- mode: 'hydration',
107
- });
110
+ }
111
+ else {
112
+ this.hydrate(entity, meta2, data, options);
113
+ }
114
+ if (exists && meta.root.inheritanceType && !(entity instanceof meta2.class)) {
115
+ Object.setPrototypeOf(entity, meta2.prototype);
116
+ }
117
+ if (options.merge && wrapped.hasPrimaryKey()) {
118
+ this.unitOfWork.register(entity, data, {
119
+ // Always refresh to ensure the payload is in correct shape for joined strategy. When loading nested relations,
120
+ // they will be created early without `Type.ensureComparable` being properly handled, resulting in extra updates.
121
+ refresh: options.initialized,
122
+ newEntity: options.newEntity,
123
+ loaded: options.initialized,
124
+ });
125
+ if (options.recomputeSnapshot) {
126
+ wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
108
127
  }
109
- }
110
128
  }
111
- }
112
- } else {
113
- this.hydrate(entity, meta2, data, options);
114
- }
115
- if (exists && meta.root.inheritanceType && !(entity instanceof meta2.class)) {
116
- Object.setPrototypeOf(entity, meta2.prototype);
117
- }
118
- if (options.merge && wrapped.hasPrimaryKey()) {
119
- this.unitOfWork.register(entity, data, {
120
- // Always refresh to ensure the payload is in correct shape for joined strategy. When loading nested relations,
121
- // they will be created early without `Type.ensureComparable` being properly handled, resulting in extra updates.
122
- refresh: options.initialized,
123
- newEntity: options.newEntity,
124
- loaded: options.initialized,
125
- });
126
- if (options.recomputeSnapshot) {
127
- wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
128
- }
129
- }
130
- if (this.#eventManager.hasListeners(EventType.onInit, meta2)) {
131
- this.#eventManager.dispatchEvent(EventType.onInit, { entity, meta: meta2, em: this.#em });
132
- }
133
- wrapped.__processing = false;
134
- return entity;
135
- }
136
- /** Merges new data into an existing entity, preserving user-modified properties. */
137
- mergeData(meta, entity, data, options = {}) {
138
- // merge unchanged properties automatically
139
- data = QueryHelper.processParams(data);
140
- const existsData = this.#comparator.prepareEntity(entity);
141
- const originalEntityData = helper(entity).__originalEntityData ?? {};
142
- const diff = this.#comparator.diffEntities(meta.class, originalEntityData, existsData);
143
- const diff2 = this.#comparator.diffEntities(meta.class, existsData, data, { includeInverseSides: true });
144
- // do not override values changed by user; for uninitialized entities,
145
- // the diff may include stale snapshot entries (value is undefined) — skip those
146
- if (helper(entity).__initialized) {
147
- Utils.keys(diff).forEach(key => delete diff2[key]);
148
- } else {
149
- Utils.keys(diff).forEach(key => {
150
- if (diff[key] !== undefined) {
151
- delete diff2[key];
129
+ if (this.#eventManager.hasListeners(EventType.onInit, meta2)) {
130
+ this.#eventManager.dispatchEvent(EventType.onInit, { entity, meta: meta2, em: this.#em });
152
131
  }
153
- });
132
+ wrapped.__processing = false;
133
+ return entity;
154
134
  }
155
- Utils.keys(diff2)
156
- .filter(key => {
157
- // ignore null values if there is already present non-null value
158
- if (existsData[key] != null) {
159
- return diff2[key] == null;
135
+ /** Merges new data into an existing entity, preserving user-modified properties. */
136
+ mergeData(meta, entity, data, options = {}) {
137
+ // merge unchanged properties automatically
138
+ data = QueryHelper.processParams(data);
139
+ const existsData = this.#comparator.prepareEntity(entity);
140
+ const originalEntityData = helper(entity).__originalEntityData ?? {};
141
+ const diff = this.#comparator.diffEntities(meta.class, originalEntityData, existsData);
142
+ const diff2 = this.#comparator.diffEntities(meta.class, existsData, data, { includeInverseSides: true });
143
+ // do not override values changed by user; for uninitialized entities,
144
+ // the diff may include stale snapshot entries (value is undefined) — skip those
145
+ if (helper(entity).__initialized) {
146
+ Utils.keys(diff).forEach(key => delete diff2[key]);
160
147
  }
161
- return diff2[key] === undefined;
162
- })
163
- .forEach(key => delete diff2[key]);
164
- // but always add collection properties, formulas, generated columns, and version properties if they
165
- // are part of the `data`, as these are excluded from `comparableProps` and won't appear in the diff
166
- Utils.keys(data)
167
- .filter(
168
- key =>
169
- meta.properties[key]?.formula ||
170
- meta.properties[key]?.version ||
171
- (meta.properties[key]?.generated && !meta.properties[key]?.primary) ||
172
- [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(meta.properties[key]?.kind),
173
- )
174
- .forEach(key => (diff2[key] = data[key]));
175
- // rehydrated with the new values, skip those changed by user
176
- // use full hydration if the entity is already initialized, even if the caller used `initialized: false`
177
- // (e.g. from createReference), otherwise scalar properties in diff2 won't be applied
178
- const initialized = options.initialized || helper(entity).__initialized;
179
- this.hydrate(entity, meta, diff2, initialized ? { ...options, initialized } : options);
180
- // we need to update the entity data only with keys that were not present before
181
- const nullVal = this.#config.get('forceUndefined') ? undefined : null;
182
- Utils.keys(diff2).forEach(key => {
183
- const prop = meta.properties[key];
184
- if (
185
- [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
186
- Utils.isPlainObject(data[prop.name])
187
- ) {
188
- // oxfmt-ignore
189
- diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
190
- }
191
- if (
192
- !options.convertCustomTypes &&
193
- [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE, ReferenceKind.SCALAR].includes(prop.kind) &&
194
- prop.customType?.ensureComparable(meta, prop) &&
195
- diff2[key] != null
196
- ) {
197
- const converted = prop.customType.convertToJSValue(diff2[key], this.#platform, { force: true });
198
- diff2[key] = prop.customType.convertToDatabaseValue(converted, this.#platform, { fromQuery: true });
199
- }
200
- originalEntityData[key] = diff2[key] === null ? nullVal : diff2[key];
201
- helper(entity).__loadedProperties.add(key);
202
- });
203
- // in case of joined loading strategy, we need to cascade the merging to possibly loaded relations manually
204
- meta.relations.forEach(prop => {
205
- if (
206
- [ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
207
- Array.isArray(data[prop.name])
208
- ) {
209
- // instead of trying to match the collection items (which could easily fail if the collection was loaded with different ordering),
210
- // we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
211
- data[prop.name]
212
- .filter(child => Utils.isPlainObject(child)) // objects with prototype can be PKs (e.g. `ObjectId`)
213
- .forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
214
- return;
215
- }
216
- if (
217
- [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
218
- Utils.isPlainObject(data[prop.name]) &&
219
- entity[prop.name] &&
220
- helper(entity[prop.name]).__initialized
221
- ) {
222
- this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
223
- }
224
- });
225
- this.unitOfWork.normalizeEntityData(meta, originalEntityData);
226
- }
227
- /** Creates or retrieves an uninitialized entity reference by its primary key or alternate key. */
228
- createReference(entityName, id, options = {}) {
229
- options.convertCustomTypes ??= true;
230
- const meta = this.#metadata.get(entityName);
231
- const schema = this.#driver.getSchemaName(meta, options);
232
- // Handle alternate key lookup
233
- if (options.key) {
234
- const value = '' + (Array.isArray(id) ? id[0] : Utils.isPlainObject(id) ? id[options.key] : id);
235
- const exists = this.unitOfWork.getByKey(entityName, options.key, value, schema, options.convertCustomTypes);
236
- if (exists) {
237
- return exists;
238
- }
239
- // Create entity stub - storeByKey will set the alternate key property and store in identity map
240
- const entity = this.create(entityName, {}, { ...options, initialized: false });
241
- this.unitOfWork.storeByKey(entity, options.key, value, schema, options.convertCustomTypes);
242
- return entity;
243
- }
244
- if (meta.simplePK) {
245
- const exists = this.unitOfWork.getById(entityName, id, schema);
246
- if (exists) {
247
- return exists;
248
- }
249
- const data = Utils.isPlainObject(id) ? id : { [meta.primaryKeys[0]]: Array.isArray(id) ? id[0] : id };
250
- return this.create(entityName, data, { ...options, initialized: false });
148
+ else {
149
+ Utils.keys(diff).forEach(key => {
150
+ if (diff[key] !== undefined) {
151
+ delete diff2[key];
152
+ }
153
+ });
154
+ }
155
+ Utils.keys(diff2)
156
+ .filter(key => {
157
+ // ignore null values if there is already present non-null value
158
+ if (existsData[key] != null) {
159
+ return diff2[key] == null;
160
+ }
161
+ return diff2[key] === undefined;
162
+ })
163
+ .forEach(key => delete diff2[key]);
164
+ // but always add collection properties, formulas, generated columns, and version properties if they
165
+ // are part of the `data`, as these are excluded from `comparableProps` and won't appear in the diff
166
+ Utils.keys(data)
167
+ .filter(key => meta.properties[key]?.formula ||
168
+ meta.properties[key]?.version ||
169
+ (meta.properties[key]?.generated && !meta.properties[key]?.primary) ||
170
+ [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(meta.properties[key]?.kind))
171
+ .forEach(key => (diff2[key] = data[key]));
172
+ // rehydrated with the new values, skip those changed by user
173
+ // use full hydration if the entity is already initialized, even if the caller used `initialized: false`
174
+ // (e.g. from createReference), otherwise scalar properties in diff2 won't be applied
175
+ const initialized = options.initialized || helper(entity).__initialized;
176
+ this.hydrate(entity, meta, diff2, initialized ? { ...options, initialized } : options);
177
+ // we need to update the entity data only with keys that were not present before
178
+ const nullVal = this.#config.get('forceUndefined') ? undefined : null;
179
+ Utils.keys(diff2).forEach(key => {
180
+ const prop = meta.properties[key];
181
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
182
+ Utils.isPlainObject(data[prop.name])) {
183
+ // oxfmt-ignore
184
+ diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
185
+ }
186
+ if (!options.convertCustomTypes &&
187
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE, ReferenceKind.SCALAR].includes(prop.kind) &&
188
+ prop.customType?.ensureComparable(meta, prop) &&
189
+ diff2[key] != null) {
190
+ const converted = prop.customType.convertToJSValue(diff2[key], this.#platform, { force: true });
191
+ diff2[key] = prop.customType.convertToDatabaseValue(converted, this.#platform, { fromQuery: true });
192
+ }
193
+ originalEntityData[key] = diff2[key] === null ? nullVal : diff2[key];
194
+ helper(entity).__loadedProperties.add(key);
195
+ });
196
+ // in case of joined loading strategy, we need to cascade the merging to possibly loaded relations manually
197
+ meta.relations.forEach(prop => {
198
+ if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
199
+ Array.isArray(data[prop.name])) {
200
+ // instead of trying to match the collection items (which could easily fail if the collection was loaded with different ordering),
201
+ // we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
202
+ data[prop.name]
203
+ .filter(child => Utils.isPlainObject(child)) // objects with prototype can be PKs (e.g. `ObjectId`)
204
+ .forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
205
+ return;
206
+ }
207
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
208
+ Utils.isPlainObject(data[prop.name]) &&
209
+ entity[prop.name] &&
210
+ helper(entity[prop.name]).__initialized) {
211
+ this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
212
+ }
213
+ });
214
+ this.unitOfWork.normalizeEntityData(meta, originalEntityData);
251
215
  }
252
- if (Array.isArray(id)) {
253
- id = Utils.getPrimaryKeyCondFromArray(id, meta);
216
+ /** Creates or retrieves an uninitialized entity reference by its primary key or alternate key. */
217
+ createReference(entityName, id, options = {}) {
218
+ options.convertCustomTypes ??= true;
219
+ const meta = this.#metadata.get(entityName);
220
+ const schema = this.#driver.getSchemaName(meta, options);
221
+ // Handle alternate key lookup
222
+ if (options.key) {
223
+ const value = '' + (Array.isArray(id) ? id[0] : Utils.isPlainObject(id) ? id[options.key] : id);
224
+ const exists = this.unitOfWork.getByKey(entityName, options.key, value, schema, options.convertCustomTypes);
225
+ if (exists) {
226
+ return exists;
227
+ }
228
+ // Create entity stub - storeByKey will set the alternate key property and store in identity map
229
+ const entity = this.create(entityName, {}, { ...options, initialized: false });
230
+ this.unitOfWork.storeByKey(entity, options.key, value, schema, options.convertCustomTypes);
231
+ return entity;
232
+ }
233
+ if (meta.simplePK) {
234
+ const exists = this.unitOfWork.getById(entityName, id, schema);
235
+ if (exists) {
236
+ return exists;
237
+ }
238
+ const data = Utils.isPlainObject(id) ? id : { [meta.primaryKeys[0]]: Array.isArray(id) ? id[0] : id };
239
+ return this.create(entityName, data, { ...options, initialized: false });
240
+ }
241
+ if (Array.isArray(id)) {
242
+ id = Utils.getPrimaryKeyCondFromArray(id, meta);
243
+ }
244
+ const pks = Utils.getOrderedPrimaryKeys(id, meta, this.#platform);
245
+ const exists = this.unitOfWork.getById(entityName, pks, schema, options.convertCustomTypes);
246
+ if (exists) {
247
+ return exists;
248
+ }
249
+ if (Utils.isPrimaryKey(id)) {
250
+ id = { [meta.primaryKeys[0]]: id };
251
+ }
252
+ return this.create(entityName, id, { ...options, initialized: false });
254
253
  }
255
- const pks = Utils.getOrderedPrimaryKeys(id, meta, this.#platform);
256
- const exists = this.unitOfWork.getById(entityName, pks, schema, options.convertCustomTypes);
257
- if (exists) {
258
- return exists;
254
+ /** Creates an embeddable entity instance from the provided data. */
255
+ createEmbeddable(entityName, data, options = {}) {
256
+ data = { ...data };
257
+ const meta = this.#metadata.get(entityName);
258
+ const meta2 = this.processDiscriminatorColumn(meta, data);
259
+ return this.createEntity(data, meta2, options);
259
260
  }
260
- if (Utils.isPrimaryKey(id)) {
261
- id = { [meta.primaryKeys[0]]: id };
261
+ /** Returns the EntityComparator instance used for diffing entities. */
262
+ getComparator() {
263
+ return this.#comparator;
262
264
  }
263
- return this.create(entityName, id, { ...options, initialized: false });
264
- }
265
- /** Creates an embeddable entity instance from the provided data. */
266
- createEmbeddable(entityName, data, options = {}) {
267
- data = { ...data };
268
- const meta = this.#metadata.get(entityName);
269
- const meta2 = this.processDiscriminatorColumn(meta, data);
270
- return this.createEntity(data, meta2, options);
271
- }
272
- /** Returns the EntityComparator instance used for diffing entities. */
273
- getComparator() {
274
- return this.#comparator;
275
- }
276
- createEntity(data, meta, options) {
277
- const schema = this.#driver.getSchemaName(meta, options);
278
- if (options.newEntity || meta.forceConstructor || meta.virtual) {
279
- if (meta.polymorphs) {
280
- throw new Error(`Cannot create entity ${meta.className}, class prototype is unknown`);
281
- }
282
- const params = this.extractConstructorParams(meta, data, options);
283
- const Entity = meta.class;
284
- // creates new instance via constructor as this is the new entity
285
- const entity = new Entity(...params);
286
- // creating managed entity instance when `forceEntityConstructor` is enabled,
287
- // we need to wipe all the values as they would cause update queries on next flush
288
- if (!options.newEntity && (meta.forceConstructor || this.#config.get('forceEntityConstructor'))) {
289
- meta.props
290
- .filter(prop => prop.persist !== false && !prop.primary && data[prop.name] === undefined)
291
- .forEach(prop => delete entity[prop.name]);
292
- }
293
- if (meta.virtual) {
265
+ createEntity(data, meta, options) {
266
+ const schema = this.#driver.getSchemaName(meta, options);
267
+ if (options.newEntity || meta.forceConstructor || meta.virtual) {
268
+ if (meta.polymorphs) {
269
+ throw new Error(`Cannot create entity ${meta.className}, class prototype is unknown`);
270
+ }
271
+ const params = this.extractConstructorParams(meta, data, options);
272
+ const Entity = meta.class;
273
+ // creates new instance via constructor as this is the new entity
274
+ const entity = new Entity(...params);
275
+ // creating managed entity instance when `forceEntityConstructor` is enabled,
276
+ // we need to wipe all the values as they would cause update queries on next flush
277
+ if (!options.newEntity && (meta.forceConstructor || this.#config.get('forceEntityConstructor'))) {
278
+ meta.props
279
+ .filter(prop => prop.persist !== false && !prop.primary && data[prop.name] === undefined)
280
+ .forEach(prop => delete entity[prop.name]);
281
+ }
282
+ if (meta.virtual) {
283
+ return entity;
284
+ }
285
+ helper(entity).__schema = schema;
286
+ if (options.initialized) {
287
+ EntityHelper.ensurePropagation(entity);
288
+ }
289
+ return entity;
290
+ }
291
+ // creates new entity instance, bypassing constructor call as its already persisted entity
292
+ const entity = Object.create(meta.class.prototype);
293
+ helper(entity).__managed = true;
294
+ helper(entity).__processing = !meta.embeddable && !meta.virtual;
295
+ helper(entity).__schema = schema;
296
+ if (options.merge && !options.newEntity) {
297
+ this.#hydrator.hydrateReference(entity, meta, data, this, options.convertCustomTypes, options.schema, options.parentSchema);
298
+ this.unitOfWork.register(entity);
299
+ }
300
+ if (options.initialized) {
301
+ EntityHelper.ensurePropagation(entity);
302
+ }
294
303
  return entity;
295
- }
296
- helper(entity).__schema = schema;
297
- if (options.initialized) {
298
- EntityHelper.ensurePropagation(entity);
299
- }
300
- return entity;
301
- }
302
- // creates new entity instance, bypassing constructor call as its already persisted entity
303
- const entity = Object.create(meta.class.prototype);
304
- helper(entity).__managed = true;
305
- helper(entity).__processing = !meta.embeddable && !meta.virtual;
306
- helper(entity).__schema = schema;
307
- if (options.merge && !options.newEntity) {
308
- this.#hydrator.hydrateReference(
309
- entity,
310
- meta,
311
- data,
312
- this,
313
- options.convertCustomTypes,
314
- options.schema,
315
- options.parentSchema,
316
- );
317
- this.unitOfWork.register(entity);
318
304
  }
319
- if (options.initialized) {
320
- EntityHelper.ensurePropagation(entity);
321
- }
322
- return entity;
323
- }
324
- /** @internal */
325
- assignDefaultValues(entity, meta, onCreateOnly) {
326
- for (const prop of meta.props) {
327
- if (
328
- prop.embedded ||
329
- [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
330
- (prop.getter && !prop.setter)
331
- ) {
332
- continue;
333
- }
334
- if (prop.onCreate) {
335
- entity[prop.name] ??= prop.onCreate(entity, this.#em);
336
- } else if (!onCreateOnly && prop.default != null && !isRaw(prop.default) && entity[prop.name] === undefined) {
337
- entity[prop.name] = prop.default;
338
- }
339
- if (prop.kind === ReferenceKind.EMBEDDED && entity[prop.name]) {
340
- const items = prop.array ? entity[prop.name] : [entity[prop.name]];
341
- for (const item of items) {
342
- // Embedded sub-properties need all defaults since the DB can't apply them within JSON columns.
343
- this.assignDefaultValues(item, prop.targetMeta);
305
+ /** @internal */
306
+ assignDefaultValues(entity, meta, onCreateOnly) {
307
+ for (const prop of meta.props) {
308
+ if (prop.embedded ||
309
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
310
+ (prop.getter && !prop.setter)) {
311
+ continue;
312
+ }
313
+ if (prop.onCreate) {
314
+ entity[prop.name] ??= prop.onCreate(entity, this.#em);
315
+ }
316
+ else if (!onCreateOnly && prop.default != null && !isRaw(prop.default) && entity[prop.name] === undefined) {
317
+ entity[prop.name] = prop.default;
318
+ }
319
+ if (prop.kind === ReferenceKind.EMBEDDED && entity[prop.name]) {
320
+ const items = prop.array ? entity[prop.name] : [entity[prop.name]];
321
+ for (const item of items) {
322
+ // Embedded sub-properties need all defaults since the DB can't apply them within JSON columns.
323
+ this.assignDefaultValues(item, prop.targetMeta);
324
+ }
325
+ }
344
326
  }
345
- }
346
- }
347
- }
348
- hydrate(entity, meta, data, options) {
349
- if (options.initialized) {
350
- this.#hydrator.hydrate(
351
- entity,
352
- meta,
353
- data,
354
- this,
355
- 'full',
356
- options.newEntity,
357
- options.convertCustomTypes,
358
- options.schema,
359
- this.#driver.getSchemaName(meta, options),
360
- options.normalizeAccessors,
361
- );
362
- } else {
363
- this.#hydrator.hydrateReference(
364
- entity,
365
- meta,
366
- data,
367
- this,
368
- options.convertCustomTypes,
369
- options.schema,
370
- this.#driver.getSchemaName(meta, options),
371
- options.normalizeAccessors,
372
- );
373
- }
374
- Utils.keys(data).forEach(key => {
375
- helper(entity)?.__loadedProperties.add(key);
376
- helper(entity)?.__serializationContext.fields?.add(key);
377
- });
378
- const processOnCreateHooksEarly =
379
- options.processOnCreateHooksEarly ?? this.#config.get('processOnCreateHooksEarly');
380
- if (options.newEntity && processOnCreateHooksEarly) {
381
- this.assignDefaultValues(entity, meta);
382
- }
383
- }
384
- findEntity(data, meta, options) {
385
- const schema = this.#driver.getSchemaName(meta, options);
386
- if (meta.simplePK) {
387
- return this.unitOfWork.getById(meta.class, data[meta.primaryKeys[0]], schema);
388
- }
389
- if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
390
- return undefined;
391
- }
392
- const pks = Utils.getOrderedPrimaryKeys(data, meta, this.#platform, options.convertCustomTypes);
393
- return this.unitOfWork.getById(meta.class, pks, schema);
394
- }
395
- processDiscriminatorColumn(meta, data) {
396
- // Handle STI discriminator (persisted column)
397
- if (meta.root.inheritanceType === 'sti') {
398
- const prop = meta.properties[meta.root.discriminatorColumn];
399
- const value = data[prop.name];
400
- const type = meta.root.discriminatorMap[value];
401
- meta = type ? this.#metadata.get(type) : meta;
402
- return meta;
403
- }
404
- // Handle TPT discriminator (computed at query time)
405
- if (meta.root.inheritanceType === 'tpt' && meta.root.discriminatorMap) {
406
- const value = data[meta.root.tptDiscriminatorColumn];
407
- if (value) {
408
- const type = meta.root.discriminatorMap[value];
409
- meta = type ? this.#metadata.get(type) : meta;
410
- }
411
327
  }
412
- return meta;
413
- }
414
- /**
415
- * denormalize PK to value required by driver (e.g. ObjectId)
416
- */
417
- denormalizePrimaryKey(meta, data) {
418
- const pk = meta.getPrimaryProp();
419
- const spk = meta.properties[meta.serializedPrimaryKey];
420
- if (!spk?.serializedPrimaryKey) {
421
- return;
422
- }
423
- if (pk.type === 'ObjectId' && (data[pk.name] != null || data[spk.name] != null)) {
424
- data[pk.name] = this.#platform.denormalizePrimaryKey(data[spk.name] || data[pk.name]);
425
- delete data[spk.name];
328
+ hydrate(entity, meta, data, options) {
329
+ if (options.initialized) {
330
+ this.#hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.#driver.getSchemaName(meta, options), options.normalizeAccessors);
331
+ }
332
+ else {
333
+ this.#hydrator.hydrateReference(entity, meta, data, this, options.convertCustomTypes, options.schema, this.#driver.getSchemaName(meta, options), options.normalizeAccessors);
334
+ }
335
+ Utils.keys(data).forEach(key => {
336
+ helper(entity)?.__loadedProperties.add(key);
337
+ helper(entity)?.__serializationContext.fields?.add(key);
338
+ });
339
+ const processOnCreateHooksEarly = options.processOnCreateHooksEarly ?? this.#config.get('processOnCreateHooksEarly');
340
+ if (options.newEntity && processOnCreateHooksEarly) {
341
+ this.assignDefaultValues(entity, meta);
342
+ }
426
343
  }
427
- }
428
- /**
429
- * returns parameters for entity constructor, creating references from plain ids
430
- */
431
- extractConstructorParams(meta, data, options) {
432
- if (!meta.constructorParams) {
433
- return [data];
344
+ findEntity(data, meta, options) {
345
+ const schema = this.#driver.getSchemaName(meta, options);
346
+ if (meta.simplePK) {
347
+ return this.unitOfWork.getById(meta.class, data[meta.primaryKeys[0]], schema);
348
+ }
349
+ if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
350
+ return undefined;
351
+ }
352
+ const pks = Utils.getOrderedPrimaryKeys(data, meta, this.#platform, options.convertCustomTypes);
353
+ return this.unitOfWork.getById(meta.class, pks, schema);
434
354
  }
435
- return meta.constructorParams.map(k => {
436
- const prop = meta.properties[k];
437
- const value = data[k];
438
- if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
439
- const pk = Reference.unwrapReference(value);
440
- const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, true);
441
- if (entity) {
442
- return entity;
355
+ processDiscriminatorColumn(meta, data) {
356
+ // Handle STI discriminator (persisted column)
357
+ if (meta.root.inheritanceType === 'sti') {
358
+ const prop = meta.properties[meta.root.discriminatorColumn];
359
+ const value = data[prop.name];
360
+ const type = meta.root.discriminatorMap[value];
361
+ meta = type ? this.#metadata.get(type) : meta;
362
+ return meta;
443
363
  }
444
- if (Utils.isEntity(value)) {
445
- return value;
364
+ // Handle TPT discriminator (computed at query time)
365
+ if (meta.root.inheritanceType === 'tpt' && meta.root.discriminatorMap) {
366
+ const value = data[meta.root.tptDiscriminatorColumn];
367
+ if (value) {
368
+ const type = meta.root.discriminatorMap[value];
369
+ meta = type ? this.#metadata.get(type) : meta;
370
+ }
446
371
  }
447
- const nakedPk = Utils.extractPK(value, prop.targetMeta, true);
448
- if (Utils.isObject(value) && !nakedPk) {
449
- return this.create(prop.targetMeta.class, value, options);
372
+ return meta;
373
+ }
374
+ /**
375
+ * denormalize PK to value required by driver (e.g. ObjectId)
376
+ */
377
+ denormalizePrimaryKey(meta, data) {
378
+ const pk = meta.getPrimaryProp();
379
+ const spk = meta.properties[meta.serializedPrimaryKey];
380
+ if (!spk?.serializedPrimaryKey) {
381
+ return;
450
382
  }
451
- const { newEntity, initialized, ...rest } = options;
452
- const target = this.createReference(prop.targetMeta.class, nakedPk, rest);
453
- return Reference.wrapReference(target, prop);
454
- }
455
- if (prop?.kind === ReferenceKind.EMBEDDED && value) {
456
- /* v8 ignore next */
457
- if (Utils.isEntity(value)) {
458
- return value;
383
+ if (pk.type === 'ObjectId' && (data[pk.name] != null || data[spk.name] != null)) {
384
+ data[pk.name] = this.#platform.denormalizePrimaryKey((data[spk.name] || data[pk.name]));
385
+ delete data[spk.name];
459
386
  }
460
- return this.createEmbeddable(prop.targetMeta.class, value, options);
461
- }
462
- if (!prop) {
463
- const tmp = { ...data };
464
- for (const prop of meta.props) {
465
- if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
466
- continue;
467
- }
468
- if (
469
- [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
470
- Utils.isPlainObject(tmp[prop.name]) &&
471
- !Utils.extractPK(tmp[prop.name], prop.targetMeta, true)
472
- ) {
473
- tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
474
- } else if (prop.kind === ReferenceKind.SCALAR) {
475
- tmp[prop.name] = prop.customType.convertToJSValue(tmp[prop.name], this.#platform);
476
- }
387
+ }
388
+ /**
389
+ * returns parameters for entity constructor, creating references from plain ids
390
+ */
391
+ extractConstructorParams(meta, data, options) {
392
+ if (!meta.constructorParams) {
393
+ return [data];
477
394
  }
478
- return tmp;
479
- }
480
- if (options.convertCustomTypes && prop.customType && value != null) {
481
- return prop.customType.convertToJSValue(value, this.#platform);
482
- }
483
- return value;
484
- });
485
- }
486
- get unitOfWork() {
487
- return this.#em.getUnitOfWork(false);
488
- }
395
+ return meta.constructorParams.map(k => {
396
+ const prop = meta.properties[k];
397
+ const value = data[k];
398
+ if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
399
+ const pk = Reference.unwrapReference(value);
400
+ const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, true);
401
+ if (entity) {
402
+ return entity;
403
+ }
404
+ if (Utils.isEntity(value)) {
405
+ return value;
406
+ }
407
+ const nakedPk = Utils.extractPK(value, prop.targetMeta, true);
408
+ if (Utils.isObject(value) && !nakedPk) {
409
+ return this.create(prop.targetMeta.class, value, options);
410
+ }
411
+ const { newEntity, initialized, ...rest } = options;
412
+ const target = this.createReference(prop.targetMeta.class, nakedPk, rest);
413
+ return Reference.wrapReference(target, prop);
414
+ }
415
+ if (prop?.kind === ReferenceKind.EMBEDDED && value) {
416
+ /* v8 ignore next */
417
+ if (Utils.isEntity(value)) {
418
+ return value;
419
+ }
420
+ return this.createEmbeddable(prop.targetMeta.class, value, options);
421
+ }
422
+ if (!prop) {
423
+ const tmp = { ...data };
424
+ for (const prop of meta.props) {
425
+ if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
426
+ continue;
427
+ }
428
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
429
+ Utils.isPlainObject(tmp[prop.name]) &&
430
+ !Utils.extractPK(tmp[prop.name], prop.targetMeta, true)) {
431
+ tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
432
+ }
433
+ else if (prop.kind === ReferenceKind.SCALAR) {
434
+ tmp[prop.name] = prop.customType.convertToJSValue(tmp[prop.name], this.#platform);
435
+ }
436
+ }
437
+ return tmp;
438
+ }
439
+ if (options.convertCustomTypes && prop.customType && value != null) {
440
+ return prop.customType.convertToJSValue(value, this.#platform);
441
+ }
442
+ return value;
443
+ });
444
+ }
445
+ get unitOfWork() {
446
+ return this.#em.getUnitOfWork(false);
447
+ }
489
448
  }