@mikro-orm/core 7.0.0-dev.31 → 7.0.0-dev.310
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.
- package/EntityManager.d.ts +69 -61
- package/EntityManager.js +365 -283
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +109 -142
- package/README.md +5 -3
- package/cache/FileCacheAdapter.d.ts +1 -2
- package/cache/FileCacheAdapter.js +19 -14
- package/cache/GeneratedCacheAdapter.d.ts +0 -1
- package/cache/GeneratedCacheAdapter.js +0 -2
- package/cache/index.d.ts +1 -2
- package/cache/index.js +0 -2
- package/connections/Connection.d.ts +12 -5
- package/connections/Connection.js +37 -15
- package/drivers/DatabaseDriver.d.ts +25 -16
- package/drivers/DatabaseDriver.js +144 -43
- package/drivers/IDatabaseDriver.d.ts +118 -23
- package/entity/BaseEntity.d.ts +63 -4
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +101 -29
- package/entity/Collection.js +473 -115
- package/entity/EntityAssigner.js +37 -25
- package/entity/EntityFactory.d.ts +7 -1
- package/entity/EntityFactory.js +116 -64
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +69 -27
- package/entity/EntityLoader.d.ts +11 -10
- package/entity/EntityLoader.js +262 -98
- package/entity/EntityRepository.d.ts +28 -8
- package/entity/EntityRepository.js +8 -2
- package/entity/PolymorphicRef.d.ts +12 -0
- package/entity/PolymorphicRef.js +18 -0
- package/entity/Reference.d.ts +2 -6
- package/entity/Reference.js +52 -19
- package/entity/WrappedEntity.d.ts +3 -8
- package/entity/WrappedEntity.js +6 -7
- package/entity/defineEntity.d.ts +525 -311
- package/entity/defineEntity.js +134 -290
- package/entity/index.d.ts +2 -2
- package/entity/index.js +2 -2
- package/entity/utils.d.ts +6 -1
- package/entity/utils.js +46 -11
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +66 -0
- package/enums.d.ts +8 -6
- package/enums.js +13 -17
- package/errors.d.ts +20 -10
- package/errors.js +63 -31
- package/events/EventManager.d.ts +2 -1
- package/events/EventManager.js +24 -13
- package/events/index.d.ts +1 -1
- package/events/index.js +0 -1
- package/exceptions.js +7 -2
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +105 -46
- package/index.d.ts +2 -2
- package/index.js +1 -2
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/DefaultLogger.js +3 -4
- package/logging/SimpleLogger.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +5 -7
- package/logging/index.d.ts +2 -1
- package/logging/index.js +1 -1
- package/logging/inspect.d.ts +2 -0
- package/logging/inspect.js +11 -0
- package/metadata/EntitySchema.d.ts +47 -23
- package/metadata/EntitySchema.js +103 -34
- package/metadata/MetadataDiscovery.d.ts +64 -9
- package/metadata/MetadataDiscovery.js +866 -354
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +71 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +72 -41
- package/metadata/MetadataValidator.d.ts +32 -9
- package/metadata/MetadataValidator.js +214 -44
- package/metadata/discover-entities.d.ts +5 -0
- package/metadata/discover-entities.js +40 -0
- package/metadata/index.d.ts +1 -1
- package/metadata/index.js +0 -1
- package/metadata/types.d.ts +577 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
- package/naming-strategy/AbstractNamingStrategy.js +26 -5
- package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
- package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
- package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
- package/naming-strategy/MongoNamingStrategy.js +6 -6
- package/naming-strategy/NamingStrategy.d.ts +28 -4
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
- package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
- package/naming-strategy/index.d.ts +1 -1
- package/naming-strategy/index.js +0 -1
- package/not-supported.d.ts +2 -0
- package/not-supported.js +8 -0
- package/package.json +47 -36
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +11 -15
- package/platforms/Platform.js +72 -69
- package/serialization/EntitySerializer.d.ts +6 -3
- package/serialization/EntitySerializer.js +53 -29
- package/serialization/EntityTransformer.js +33 -21
- package/serialization/SerializationContext.d.ts +6 -6
- package/serialization/SerializationContext.js +4 -4
- package/types/ArrayType.d.ts +1 -1
- package/types/ArrayType.js +2 -3
- package/types/BigIntType.js +1 -1
- package/types/BlobType.d.ts +0 -1
- package/types/BlobType.js +0 -3
- package/types/BooleanType.d.ts +1 -0
- package/types/BooleanType.js +3 -0
- package/types/DecimalType.js +2 -2
- package/types/DoubleType.js +1 -1
- package/types/EnumArrayType.js +1 -2
- package/types/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/TinyIntType.js +1 -1
- package/types/Type.d.ts +2 -4
- package/types/Type.js +3 -3
- package/types/Uint8ArrayType.d.ts +0 -1
- package/types/Uint8ArrayType.js +1 -4
- package/types/index.d.ts +3 -2
- package/typings.d.ts +427 -170
- package/typings.js +100 -45
- package/unit-of-work/ChangeSet.d.ts +4 -6
- package/unit-of-work/ChangeSet.js +8 -9
- package/unit-of-work/ChangeSetComputer.d.ts +3 -8
- package/unit-of-work/ChangeSetComputer.js +49 -26
- package/unit-of-work/ChangeSetPersister.d.ts +13 -12
- package/unit-of-work/ChangeSetPersister.js +106 -43
- package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
- package/unit-of-work/CommitOrderCalculator.js +17 -15
- package/unit-of-work/IdentityMap.d.ts +12 -0
- package/unit-of-work/IdentityMap.js +39 -1
- package/unit-of-work/UnitOfWork.d.ts +34 -4
- package/unit-of-work/UnitOfWork.js +294 -107
- package/utils/AbstractMigrator.d.ts +101 -0
- package/utils/AbstractMigrator.js +303 -0
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +30 -18
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +795 -211
- package/utils/Configuration.js +160 -197
- package/utils/ConfigurationLoader.d.ts +1 -52
- package/utils/ConfigurationLoader.js +1 -330
- package/utils/Cursor.d.ts +0 -3
- package/utils/Cursor.js +29 -14
- package/utils/DataloaderUtils.d.ts +10 -5
- package/utils/DataloaderUtils.js +42 -22
- package/utils/EntityComparator.d.ts +16 -9
- package/utils/EntityComparator.js +202 -96
- package/utils/QueryHelper.d.ts +34 -6
- package/utils/QueryHelper.js +181 -48
- package/utils/RawQueryFragment.d.ts +28 -34
- package/utils/RawQueryFragment.js +37 -72
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.js +11 -7
- package/utils/Utils.d.ts +16 -127
- package/utils/Utils.js +106 -401
- package/utils/clone.js +8 -23
- package/utils/env-vars.d.ts +7 -0
- package/utils/env-vars.js +98 -0
- package/utils/fs-utils.d.ts +34 -0
- package/utils/fs-utils.js +193 -0
- package/utils/index.d.ts +1 -3
- package/utils/index.js +1 -3
- package/utils/upsert-utils.d.ts +9 -4
- package/utils/upsert-utils.js +51 -5
- package/decorators/Check.d.ts +0 -3
- package/decorators/Check.js +0 -13
- package/decorators/CreateRequestContext.d.ts +0 -3
- package/decorators/CreateRequestContext.js +0 -32
- package/decorators/Embeddable.d.ts +0 -8
- package/decorators/Embeddable.js +0 -11
- package/decorators/Embedded.d.ts +0 -12
- package/decorators/Embedded.js +0 -18
- package/decorators/Entity.d.ts +0 -33
- package/decorators/Entity.js +0 -12
- package/decorators/Enum.d.ts +0 -9
- package/decorators/Enum.js +0 -16
- package/decorators/Filter.d.ts +0 -2
- package/decorators/Filter.js +0 -8
- package/decorators/Formula.d.ts +0 -4
- package/decorators/Formula.js +0 -15
- package/decorators/Indexed.d.ts +0 -19
- package/decorators/Indexed.js +0 -20
- package/decorators/ManyToMany.d.ts +0 -42
- package/decorators/ManyToMany.js +0 -14
- package/decorators/ManyToOne.d.ts +0 -34
- package/decorators/ManyToOne.js +0 -14
- package/decorators/OneToMany.d.ts +0 -28
- package/decorators/OneToMany.js +0 -17
- package/decorators/OneToOne.d.ts +0 -28
- package/decorators/OneToOne.js +0 -7
- package/decorators/PrimaryKey.d.ts +0 -8
- package/decorators/PrimaryKey.js +0 -20
- package/decorators/Property.d.ts +0 -250
- package/decorators/Property.js +0 -32
- package/decorators/Transactional.d.ts +0 -14
- package/decorators/Transactional.js +0 -28
- package/decorators/hooks.d.ts +0 -16
- package/decorators/hooks.js +0 -47
- package/decorators/index.d.ts +0 -17
- package/decorators/index.js +0 -17
- package/entity/ArrayCollection.d.ts +0 -118
- package/entity/ArrayCollection.js +0 -407
- package/entity/EntityValidator.d.ts +0 -19
- package/entity/EntityValidator.js +0 -150
- package/metadata/ReflectMetadataProvider.d.ts +0 -8
- package/metadata/ReflectMetadataProvider.js +0 -44
- package/utils/resolveContextProvider.d.ts +0 -10
- 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 =
|
|
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
|
|
46
|
-
this.changeSetPersister = new ChangeSetPersister(this.em
|
|
45
|
+
this.changeSetComputer = new ChangeSetComputer(this.em, this.collectionUpdates);
|
|
46
|
+
this.changeSetPersister = new ChangeSetPersister(this.em);
|
|
47
47
|
}
|
|
48
48
|
merge(entity, visited) {
|
|
49
49
|
const wrapped = helper(entity);
|
|
@@ -61,10 +61,51 @@ 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) &&
|
|
80
|
+
Utils.isPlainObject(data[prop.name])) {
|
|
81
|
+
// Skip polymorphic relations - they use PolymorphicRef wrapper
|
|
82
|
+
if (!prop.polymorphic) {
|
|
83
|
+
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (prop.kind === ReferenceKind.EMBEDDED && !prop.object && Utils.isPlainObject(data[prop.name])) {
|
|
87
|
+
for (const p of prop.targetMeta.props) {
|
|
88
|
+
/* v8 ignore next */
|
|
89
|
+
const prefix = prop.prefix === false ? '' : prop.prefix === true ? prop.name + '_' : prop.prefix;
|
|
90
|
+
data[(prefix + p.name)] = data[prop.name][p.name];
|
|
91
|
+
}
|
|
92
|
+
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
|
93
|
+
}
|
|
94
|
+
if (prop.hydrate === false && prop.customType?.ensureComparable(meta, prop)) {
|
|
95
|
+
const converted = prop.customType.convertToJSValue(data[key], this.platform, {
|
|
96
|
+
key,
|
|
97
|
+
mode: 'hydration',
|
|
98
|
+
force: true,
|
|
99
|
+
});
|
|
100
|
+
data[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { key, mode: 'hydration' });
|
|
101
|
+
}
|
|
102
|
+
if (forceUndefined) {
|
|
103
|
+
if (data[key] === null) {
|
|
104
|
+
data[key] = undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
68
109
|
/**
|
|
69
110
|
* @internal
|
|
70
111
|
*/
|
|
@@ -82,31 +123,14 @@ export class UnitOfWork {
|
|
|
82
123
|
wrapped.__em ??= this.em;
|
|
83
124
|
wrapped.__managed = true;
|
|
84
125
|
if (data && (options?.refresh || !wrapped.__originalEntityData)) {
|
|
126
|
+
this.normalizeEntityData(wrapped.__meta, data);
|
|
85
127
|
for (const key of Utils.keys(data)) {
|
|
86
128
|
const prop = wrapped.__meta.properties[key];
|
|
87
|
-
if (
|
|
88
|
-
|
|
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
|
-
}
|
|
129
|
+
if (prop) {
|
|
130
|
+
wrapped.__loadedProperties.add(key);
|
|
106
131
|
}
|
|
107
132
|
}
|
|
108
133
|
wrapped.__originalEntityData = data;
|
|
109
|
-
wrapped.__touched = false;
|
|
110
134
|
}
|
|
111
135
|
return entity;
|
|
112
136
|
}
|
|
@@ -153,6 +177,40 @@ export class UnitOfWork {
|
|
|
153
177
|
}
|
|
154
178
|
return this.identityMap.getByHash(meta, hash);
|
|
155
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Returns entity from the identity map by an alternate key (non-PK property).
|
|
182
|
+
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format for lookup.
|
|
183
|
+
* If false (default), the value is assumed to be in JS format already.
|
|
184
|
+
*/
|
|
185
|
+
getByKey(entityName, key, value, schema, convertCustomTypes) {
|
|
186
|
+
const meta = this.metadata.find(entityName).root;
|
|
187
|
+
schema ??= meta.schema ?? this.em.config.getSchema();
|
|
188
|
+
const prop = meta.properties[key];
|
|
189
|
+
// Convert from DB format to JS format if needed
|
|
190
|
+
if (convertCustomTypes && prop?.customType) {
|
|
191
|
+
value = prop.customType.convertToJSValue(value, this.platform, { mode: 'hydration' });
|
|
192
|
+
}
|
|
193
|
+
const hash = this.identityMap.getKeyHash(key, '' + value, schema);
|
|
194
|
+
return this.identityMap.getByHash(meta, hash);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Stores an entity in the identity map under an alternate key (non-PK property).
|
|
198
|
+
* Also sets the property value on the entity.
|
|
199
|
+
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format.
|
|
200
|
+
* If false (default), the value is assumed to be in JS format already.
|
|
201
|
+
*/
|
|
202
|
+
storeByKey(entity, key, value, schema, convertCustomTypes) {
|
|
203
|
+
const meta = entity.__meta.root;
|
|
204
|
+
schema ??= meta.schema ?? this.em.config.getSchema();
|
|
205
|
+
const prop = meta.properties[key];
|
|
206
|
+
// Convert from DB format to JS format if needed
|
|
207
|
+
if (convertCustomTypes && prop?.customType) {
|
|
208
|
+
value = prop.customType.convertToJSValue(value, this.platform, { mode: 'hydration' });
|
|
209
|
+
}
|
|
210
|
+
// Set the property on the entity
|
|
211
|
+
entity[key] = value;
|
|
212
|
+
this.identityMap.storeByKey(entity, key, '' + value, schema);
|
|
213
|
+
}
|
|
156
214
|
tryGetById(entityName, where, schema, strict = true) {
|
|
157
215
|
const pk = Utils.extractPK(where, this.metadata.find(entityName), strict);
|
|
158
216
|
if (!pk) {
|
|
@@ -191,13 +249,11 @@ export class UnitOfWork {
|
|
|
191
249
|
if (insideFlush.getStore()) {
|
|
192
250
|
return false;
|
|
193
251
|
}
|
|
194
|
-
if (this.queuedActions.has(meta.
|
|
252
|
+
if (this.queuedActions.has(meta.class) || this.queuedActions.has(meta.root.class)) {
|
|
195
253
|
return true;
|
|
196
254
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
255
|
+
if (meta.discriminatorMap && Object.values(meta.discriminatorMap).some(v => this.queuedActions.has(v))) {
|
|
256
|
+
return true;
|
|
201
257
|
}
|
|
202
258
|
return false;
|
|
203
259
|
}
|
|
@@ -206,7 +262,7 @@ export class UnitOfWork {
|
|
|
206
262
|
}
|
|
207
263
|
computeChangeSet(entity, type) {
|
|
208
264
|
const wrapped = helper(entity);
|
|
209
|
-
if (type) {
|
|
265
|
+
if (type === ChangeSetType.DELETE || type === ChangeSetType.DELETE_EARLY) {
|
|
210
266
|
this.changeSets.set(entity, new ChangeSet(entity, type, {}, wrapped.__meta));
|
|
211
267
|
return;
|
|
212
268
|
}
|
|
@@ -214,11 +270,14 @@ export class UnitOfWork {
|
|
|
214
270
|
if (!cs || this.checkUniqueProps(cs)) {
|
|
215
271
|
return;
|
|
216
272
|
}
|
|
273
|
+
/* v8 ignore next */
|
|
274
|
+
if (type) {
|
|
275
|
+
cs.type = type;
|
|
276
|
+
}
|
|
217
277
|
this.initIdentifier(entity);
|
|
218
278
|
this.changeSets.set(entity, cs);
|
|
219
279
|
this.persistStack.delete(entity);
|
|
220
280
|
wrapped.__originalEntityData = this.comparator.prepareEntity(entity);
|
|
221
|
-
wrapped.__touched = false;
|
|
222
281
|
}
|
|
223
282
|
recomputeSingleChangeSet(entity) {
|
|
224
283
|
const changeSet = this.changeSets.get(entity);
|
|
@@ -229,7 +288,6 @@ export class UnitOfWork {
|
|
|
229
288
|
if (cs && !this.checkUniqueProps(cs)) {
|
|
230
289
|
Object.assign(changeSet.payload, cs.payload);
|
|
231
290
|
helper(entity).__originalEntityData = this.comparator.prepareEntity(entity);
|
|
232
|
-
helper(entity).__touched = false;
|
|
233
291
|
}
|
|
234
292
|
}
|
|
235
293
|
persist(entity, visited, options = {}) {
|
|
@@ -239,7 +297,7 @@ export class UnitOfWork {
|
|
|
239
297
|
}
|
|
240
298
|
const wrapped = helper(entity);
|
|
241
299
|
this.persistStack.add(entity);
|
|
242
|
-
this.queuedActions.add(wrapped.__meta.
|
|
300
|
+
this.queuedActions.add(wrapped.__meta.class);
|
|
243
301
|
this.removeStack.delete(entity);
|
|
244
302
|
if (!wrapped.__managed && wrapped.hasPrimaryKey()) {
|
|
245
303
|
this.identityMap.store(entity);
|
|
@@ -252,7 +310,7 @@ export class UnitOfWork {
|
|
|
252
310
|
// allow removing not managed entities if they are not part of the persist stack
|
|
253
311
|
if (helper(entity).__managed || !this.persistStack.has(entity)) {
|
|
254
312
|
this.removeStack.add(entity);
|
|
255
|
-
this.queuedActions.add(helper(entity).__meta.
|
|
313
|
+
this.queuedActions.add(helper(entity).__meta.class);
|
|
256
314
|
}
|
|
257
315
|
else {
|
|
258
316
|
this.persistStack.delete(entity);
|
|
@@ -269,7 +327,7 @@ export class UnitOfWork {
|
|
|
269
327
|
}
|
|
270
328
|
continue;
|
|
271
329
|
}
|
|
272
|
-
const target = relation
|
|
330
|
+
const target = relation?.[inverseProp];
|
|
273
331
|
if (relation && Utils.isCollection(target)) {
|
|
274
332
|
target.removeWithoutPropagation(entity);
|
|
275
333
|
}
|
|
@@ -315,7 +373,8 @@ export class UnitOfWork {
|
|
|
315
373
|
this.filterCollectionUpdates();
|
|
316
374
|
// nothing to do, do not start transaction
|
|
317
375
|
if (this.changeSets.size === 0 && this.collectionUpdates.size === 0 && this.extraUpdates.size === 0) {
|
|
318
|
-
|
|
376
|
+
await this.eventManager.dispatchEvent(EventType.afterFlush, { em: this.em, uow: this });
|
|
377
|
+
return;
|
|
319
378
|
}
|
|
320
379
|
const groups = this.getChangeSetGroups();
|
|
321
380
|
const platform = this.em.getPlatform();
|
|
@@ -342,10 +401,10 @@ export class UnitOfWork {
|
|
|
342
401
|
}
|
|
343
402
|
}
|
|
344
403
|
async lock(entity, options) {
|
|
345
|
-
if (!this.getById(entity.constructor
|
|
404
|
+
if (!this.getById(entity.constructor, helper(entity).__primaryKeys, helper(entity).__schema)) {
|
|
346
405
|
throw ValidationError.entityNotManaged(entity);
|
|
347
406
|
}
|
|
348
|
-
const meta = this.metadata.find(entity.constructor
|
|
407
|
+
const meta = this.metadata.find(entity.constructor);
|
|
349
408
|
if (options.lockMode === LockMode.OPTIMISTIC) {
|
|
350
409
|
await this.lockOptimistic(entity, meta, options.lockVersion);
|
|
351
410
|
}
|
|
@@ -369,7 +428,10 @@ export class UnitOfWork {
|
|
|
369
428
|
if (Utils.isCollection(rel)) {
|
|
370
429
|
rel.removeWithoutPropagation(entity);
|
|
371
430
|
}
|
|
372
|
-
else if (rel &&
|
|
431
|
+
else if (rel &&
|
|
432
|
+
(prop.mapToPk
|
|
433
|
+
? helper(this.em.getReference(prop.targetMeta.class, rel)).getSerializedPrimaryKey() === serializedPK
|
|
434
|
+
: rel === entity)) {
|
|
373
435
|
if (prop.formula) {
|
|
374
436
|
delete referrer[prop.name];
|
|
375
437
|
}
|
|
@@ -381,7 +443,6 @@ export class UnitOfWork {
|
|
|
381
443
|
}
|
|
382
444
|
delete wrapped.__identifier;
|
|
383
445
|
delete wrapped.__originalEntityData;
|
|
384
|
-
wrapped.__touched = false;
|
|
385
446
|
wrapped.__managed = false;
|
|
386
447
|
}
|
|
387
448
|
computeChangeSets() {
|
|
@@ -391,14 +452,14 @@ export class UnitOfWork {
|
|
|
391
452
|
this.cascade(entity, Cascade.REMOVE, visited);
|
|
392
453
|
}
|
|
393
454
|
visited.clear();
|
|
394
|
-
for (const entity of this.persistStack) {
|
|
395
|
-
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
396
|
-
}
|
|
397
455
|
for (const entity of this.identityMap) {
|
|
398
456
|
if (!this.removeStack.has(entity) && !this.persistStack.has(entity) && !this.orphanRemoveStack.has(entity)) {
|
|
399
457
|
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
400
458
|
}
|
|
401
459
|
}
|
|
460
|
+
for (const entity of this.persistStack) {
|
|
461
|
+
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
462
|
+
}
|
|
402
463
|
visited.clear();
|
|
403
464
|
for (const entity of this.persistStack) {
|
|
404
465
|
this.findNewEntities(entity, visited);
|
|
@@ -412,24 +473,24 @@ export class UnitOfWork {
|
|
|
412
473
|
const inserts = {};
|
|
413
474
|
for (const cs of this.changeSets.values()) {
|
|
414
475
|
if (cs.type === ChangeSetType.CREATE) {
|
|
415
|
-
inserts[cs.meta.
|
|
416
|
-
inserts[cs.meta.
|
|
476
|
+
inserts[cs.meta.uniqueName] ??= [];
|
|
477
|
+
inserts[cs.meta.uniqueName].push(cs);
|
|
417
478
|
}
|
|
418
479
|
}
|
|
419
480
|
for (const cs of this.changeSets.values()) {
|
|
420
481
|
if (cs.type === ChangeSetType.UPDATE) {
|
|
421
|
-
this.findEarlyUpdates(cs, inserts[cs.meta.
|
|
482
|
+
this.findEarlyUpdates(cs, inserts[cs.meta.uniqueName]);
|
|
422
483
|
}
|
|
423
484
|
}
|
|
424
485
|
for (const entity of this.removeStack) {
|
|
425
486
|
const wrapped = helper(entity);
|
|
426
|
-
/* v8 ignore next
|
|
487
|
+
/* v8 ignore next */
|
|
427
488
|
if (wrapped.__processing) {
|
|
428
489
|
continue;
|
|
429
490
|
}
|
|
430
491
|
const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
|
|
431
492
|
let type = ChangeSetType.DELETE;
|
|
432
|
-
for (const cs of inserts[wrapped.__meta.
|
|
493
|
+
for (const cs of inserts[wrapped.__meta.uniqueName] ?? []) {
|
|
433
494
|
if (deletePkHash.some(hash => hash === cs.getSerializedPrimaryKey() || this.expandUniqueProps(cs.entity).find(child => hash === child))) {
|
|
434
495
|
type = ChangeSetType.DELETE_EARLY;
|
|
435
496
|
}
|
|
@@ -448,7 +509,7 @@ export class UnitOfWork {
|
|
|
448
509
|
}
|
|
449
510
|
for (const cs of this.changeSets.values()) {
|
|
450
511
|
for (const prop of props) {
|
|
451
|
-
if (prop.name in cs.payload && cs.
|
|
512
|
+
if (prop.name in cs.payload && cs.rootMeta === changeSet.rootMeta && cs.type === changeSet.type) {
|
|
452
513
|
conflicts = true;
|
|
453
514
|
if (changeSet.payload[prop.name] == null) {
|
|
454
515
|
type = ChangeSetType.UPDATE_EARLY;
|
|
@@ -459,7 +520,13 @@ export class UnitOfWork {
|
|
|
459
520
|
if (!conflicts) {
|
|
460
521
|
return;
|
|
461
522
|
}
|
|
462
|
-
this.extraUpdates.add([
|
|
523
|
+
this.extraUpdates.add([
|
|
524
|
+
changeSet.entity,
|
|
525
|
+
props.map(p => p.name),
|
|
526
|
+
props.map(p => changeSet.entity[p.name]),
|
|
527
|
+
changeSet,
|
|
528
|
+
type,
|
|
529
|
+
]);
|
|
463
530
|
for (const p of props) {
|
|
464
531
|
delete changeSet.entity[p.name];
|
|
465
532
|
delete changeSet.payload[p.name];
|
|
@@ -467,9 +534,10 @@ export class UnitOfWork {
|
|
|
467
534
|
}
|
|
468
535
|
scheduleOrphanRemoval(entity, visited) {
|
|
469
536
|
if (entity) {
|
|
470
|
-
helper(entity)
|
|
537
|
+
const wrapped = helper(entity);
|
|
538
|
+
wrapped.__em = this.em;
|
|
471
539
|
this.orphanRemoveStack.add(entity);
|
|
472
|
-
this.queuedActions.add(
|
|
540
|
+
this.queuedActions.add(wrapped.__meta.class);
|
|
473
541
|
this.cascade(entity, Cascade.SCHEDULE_ORPHAN_REMOVAL, visited);
|
|
474
542
|
}
|
|
475
543
|
}
|
|
@@ -505,7 +573,68 @@ export class UnitOfWork {
|
|
|
505
573
|
}
|
|
506
574
|
const changeSet = this.changeSetComputer.computeChangeSet(entity);
|
|
507
575
|
if (changeSet && !this.checkUniqueProps(changeSet)) {
|
|
508
|
-
|
|
576
|
+
// For TPT child entities, create changesets for each table in hierarchy
|
|
577
|
+
if (wrapped.__meta.inheritanceType === 'tpt' && wrapped.__meta.tptParent) {
|
|
578
|
+
this.createTPTChangeSets(entity, changeSet);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
this.changeSets.set(entity, changeSet);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* For TPT inheritance, creates separate changesets for each table in the hierarchy.
|
|
587
|
+
* Uses the same entity instance for all changesets - only the metadata and payload differ.
|
|
588
|
+
*/
|
|
589
|
+
createTPTChangeSets(entity, originalChangeSet) {
|
|
590
|
+
const meta = helper(entity).__meta;
|
|
591
|
+
const isCreate = originalChangeSet.type === ChangeSetType.CREATE;
|
|
592
|
+
let current = meta;
|
|
593
|
+
let leafCs;
|
|
594
|
+
const parentChangeSets = [];
|
|
595
|
+
while (current) {
|
|
596
|
+
const isRoot = !current.tptParent;
|
|
597
|
+
const payload = {};
|
|
598
|
+
for (const prop of current.ownProps) {
|
|
599
|
+
if (prop.name in originalChangeSet.payload) {
|
|
600
|
+
payload[prop.name] = originalChangeSet.payload[prop.name];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// For CREATE on non-root tables, include the PK (EntityIdentifier for deferred resolution)
|
|
604
|
+
if (isCreate && !isRoot) {
|
|
605
|
+
const wrapped = helper(entity);
|
|
606
|
+
const identifier = wrapped.__identifier;
|
|
607
|
+
const identifiers = Array.isArray(identifier) ? identifier : [identifier];
|
|
608
|
+
for (let i = 0; i < current.primaryKeys.length; i++) {
|
|
609
|
+
const pk = current.primaryKeys[i];
|
|
610
|
+
payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (!isCreate && Object.keys(payload).length === 0) {
|
|
614
|
+
current = current.tptParent;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
|
|
618
|
+
if (current === meta) {
|
|
619
|
+
cs.originalEntity = originalChangeSet.originalEntity;
|
|
620
|
+
leafCs = cs;
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
parentChangeSets.push(cs);
|
|
624
|
+
}
|
|
625
|
+
current = current.tptParent;
|
|
626
|
+
}
|
|
627
|
+
// When only parent properties changed (UPDATE), leaf payload is empty—create a stub anchor
|
|
628
|
+
if (!leafCs && parentChangeSets.length > 0) {
|
|
629
|
+
leafCs = new ChangeSet(entity, originalChangeSet.type, {}, meta);
|
|
630
|
+
leafCs.originalEntity = originalChangeSet.originalEntity;
|
|
631
|
+
}
|
|
632
|
+
// Store the leaf changeset in the main map (entity as key), with parent CSs attached
|
|
633
|
+
if (leafCs) {
|
|
634
|
+
if (parentChangeSets.length > 0) {
|
|
635
|
+
leafCs.tptChangeSets = parentChangeSets;
|
|
636
|
+
}
|
|
637
|
+
this.changeSets.set(entity, leafCs);
|
|
509
638
|
}
|
|
510
639
|
}
|
|
511
640
|
/**
|
|
@@ -518,7 +647,7 @@ export class UnitOfWork {
|
|
|
518
647
|
// when changing a unique nullable property (or a 1:1 relation), we can't do it in a single
|
|
519
648
|
// query as it would cause unique constraint violations
|
|
520
649
|
const uniqueProps = changeSet.meta.uniqueProps.filter(prop => {
|
|
521
|
-
return
|
|
650
|
+
return prop.nullable || changeSet.type !== ChangeSetType.CREATE;
|
|
522
651
|
});
|
|
523
652
|
this.scheduleExtraUpdate(changeSet, uniqueProps);
|
|
524
653
|
return changeSet.type === ChangeSetType.UPDATE && !Utils.hasObjectKeys(changeSet.payload);
|
|
@@ -528,25 +657,33 @@ export class UnitOfWork {
|
|
|
528
657
|
if (!wrapped.__meta.hasUniqueProps) {
|
|
529
658
|
return [];
|
|
530
659
|
}
|
|
531
|
-
const simpleUniqueHashes = wrapped.__meta.uniqueProps
|
|
660
|
+
const simpleUniqueHashes = wrapped.__meta.uniqueProps
|
|
661
|
+
.map(prop => {
|
|
532
662
|
if (entity[prop.name] != null) {
|
|
533
|
-
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
663
|
+
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
664
|
+
? entity[prop.name]
|
|
665
|
+
: helper(entity[prop.name]).getSerializedPrimaryKey();
|
|
534
666
|
}
|
|
535
667
|
if (wrapped.__originalEntityData?.[prop.name] != null) {
|
|
536
668
|
return Utils.getPrimaryKeyHash(Utils.asArray(wrapped.__originalEntityData[prop.name]));
|
|
537
669
|
}
|
|
538
670
|
return undefined;
|
|
539
|
-
})
|
|
540
|
-
|
|
671
|
+
})
|
|
672
|
+
.filter(i => i);
|
|
673
|
+
const compoundUniqueHashes = wrapped.__meta.uniques
|
|
674
|
+
.map(unique => {
|
|
541
675
|
const props = Utils.asArray(unique.properties);
|
|
542
676
|
if (props.every(prop => entity[prop] != null)) {
|
|
543
677
|
return Utils.getPrimaryKeyHash(props.map(p => {
|
|
544
678
|
const prop = wrapped.__meta.properties[p];
|
|
545
|
-
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
679
|
+
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
680
|
+
? entity[prop.name]
|
|
681
|
+
: helper(entity[prop.name]).getSerializedPrimaryKey();
|
|
546
682
|
}));
|
|
547
683
|
}
|
|
548
684
|
return undefined;
|
|
549
|
-
})
|
|
685
|
+
})
|
|
686
|
+
.filter(i => i);
|
|
550
687
|
return simpleUniqueHashes.concat(compoundUniqueHashes);
|
|
551
688
|
}
|
|
552
689
|
initIdentifier(entity) {
|
|
@@ -578,7 +715,8 @@ export class UnitOfWork {
|
|
|
578
715
|
return this.processToOneReference(kind, visited, processed, idx);
|
|
579
716
|
}
|
|
580
717
|
if (Utils.isCollection(kind)) {
|
|
581
|
-
kind
|
|
718
|
+
kind
|
|
719
|
+
.getItems(false)
|
|
582
720
|
.filter(item => !item.__helper.__originalEntityData)
|
|
583
721
|
.forEach(item => {
|
|
584
722
|
// propagate schema from parent
|
|
@@ -602,7 +740,8 @@ export class UnitOfWork {
|
|
|
602
740
|
parent[prop.name] = coll;
|
|
603
741
|
return;
|
|
604
742
|
}
|
|
605
|
-
collection
|
|
743
|
+
collection
|
|
744
|
+
.getItems(false)
|
|
606
745
|
.filter(item => !item.__helper.__originalEntityData)
|
|
607
746
|
.forEach(item => this.findNewEntities(item, visited, 0, processed));
|
|
608
747
|
}
|
|
@@ -618,7 +757,7 @@ export class UnitOfWork {
|
|
|
618
757
|
const copy = this.comparator.prepareEntity(changeSet.entity);
|
|
619
758
|
await this.eventManager.dispatchEvent(type, { entity: changeSet.entity, meta, em: this.em, changeSet });
|
|
620
759
|
const current = this.comparator.prepareEntity(changeSet.entity);
|
|
621
|
-
const diff = this.comparator.diffEntities(changeSet.
|
|
760
|
+
const diff = this.comparator.diffEntities(changeSet.meta.class, copy, current);
|
|
622
761
|
Object.assign(changeSet.payload, diff);
|
|
623
762
|
const wrapped = helper(changeSet.entity);
|
|
624
763
|
if (wrapped.__identifier) {
|
|
@@ -694,7 +833,8 @@ export class UnitOfWork {
|
|
|
694
833
|
return filtered.some(items => processed.has(items));
|
|
695
834
|
}
|
|
696
835
|
shouldCascade(prop, type) {
|
|
697
|
-
if ([Cascade.REMOVE, Cascade.SCHEDULE_ORPHAN_REMOVAL, Cascade.CANCEL_ORPHAN_REMOVAL, Cascade.ALL].includes(type) &&
|
|
836
|
+
if ([Cascade.REMOVE, Cascade.SCHEDULE_ORPHAN_REMOVAL, Cascade.CANCEL_ORPHAN_REMOVAL, Cascade.ALL].includes(type) &&
|
|
837
|
+
prop.orphanRemoval) {
|
|
698
838
|
return true;
|
|
699
839
|
}
|
|
700
840
|
// ignore user settings for merge, it is kept only for back compatibility, this should have never been configurable
|
|
@@ -713,7 +853,7 @@ export class UnitOfWork {
|
|
|
713
853
|
if (!meta.versionProperty) {
|
|
714
854
|
throw OptimisticLockError.notVersioned(meta);
|
|
715
855
|
}
|
|
716
|
-
if (
|
|
856
|
+
if (typeof version === 'undefined') {
|
|
717
857
|
return;
|
|
718
858
|
}
|
|
719
859
|
const wrapped = helper(entity);
|
|
@@ -727,26 +867,28 @@ export class UnitOfWork {
|
|
|
727
867
|
}
|
|
728
868
|
fixMissingReference(entity, prop) {
|
|
729
869
|
const reference = entity[prop.name];
|
|
730
|
-
const
|
|
731
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
732
|
-
if (!Utils.isEntity(
|
|
733
|
-
entity[prop.name] = this.em.getReference(prop.
|
|
870
|
+
const target = Reference.unwrapReference(reference);
|
|
871
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && target && !prop.mapToPk) {
|
|
872
|
+
if (!Utils.isEntity(target)) {
|
|
873
|
+
entity[prop.name] = this.em.getReference(prop.targetMeta.class, target, {
|
|
874
|
+
wrapped: !!prop.ref,
|
|
875
|
+
});
|
|
734
876
|
}
|
|
735
|
-
else if (!helper(
|
|
736
|
-
const pk = helper(
|
|
737
|
-
entity[prop.name] = this.em.getReference(prop.
|
|
877
|
+
else if (!helper(target).__initialized && !helper(target).__em) {
|
|
878
|
+
const pk = helper(target).getPrimaryKey();
|
|
879
|
+
entity[prop.name] = this.em.getReference(prop.targetMeta.class, pk, { wrapped: !!prop.ref });
|
|
738
880
|
}
|
|
739
881
|
}
|
|
740
|
-
// perf: set the `Collection._property` to skip the getter, as it can be slow when there
|
|
741
|
-
if (Utils.isCollection(
|
|
742
|
-
|
|
882
|
+
// perf: set the `Collection._property` to skip the getter, as it can be slow when there are a lot of relations
|
|
883
|
+
if (Utils.isCollection(target)) {
|
|
884
|
+
target.property = prop;
|
|
743
885
|
}
|
|
744
886
|
const isCollection = [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind);
|
|
745
|
-
if (isCollection && Array.isArray(
|
|
887
|
+
if (isCollection && Array.isArray(target)) {
|
|
746
888
|
const collection = new Collection(entity);
|
|
747
889
|
collection.property = prop;
|
|
748
890
|
entity[prop.name] = collection;
|
|
749
|
-
collection.set(
|
|
891
|
+
collection.set(target);
|
|
750
892
|
}
|
|
751
893
|
}
|
|
752
894
|
async persistToDatabase(groups, ctx) {
|
|
@@ -756,30 +898,30 @@ export class UnitOfWork {
|
|
|
756
898
|
const commitOrder = this.getCommitOrder();
|
|
757
899
|
const commitOrderReversed = [...commitOrder].reverse();
|
|
758
900
|
// early delete - when we recreate entity in the same UoW, we need to issue those delete queries before inserts
|
|
759
|
-
for (const
|
|
760
|
-
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE_EARLY].get(
|
|
901
|
+
for (const meta of commitOrderReversed) {
|
|
902
|
+
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE_EARLY].get(meta) ?? [], ctx);
|
|
761
903
|
}
|
|
762
904
|
// early update - when we recreate entity in the same UoW, we need to issue those delete queries before inserts
|
|
763
|
-
for (const
|
|
764
|
-
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE_EARLY].get(
|
|
905
|
+
for (const meta of commitOrder) {
|
|
906
|
+
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE_EARLY].get(meta) ?? [], ctx);
|
|
765
907
|
}
|
|
766
908
|
// extra updates
|
|
767
909
|
await this.commitExtraUpdates(ChangeSetType.UPDATE_EARLY, ctx);
|
|
768
910
|
// create
|
|
769
|
-
for (const
|
|
770
|
-
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(
|
|
911
|
+
for (const meta of commitOrder) {
|
|
912
|
+
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(meta) ?? [], ctx);
|
|
771
913
|
}
|
|
772
914
|
// update
|
|
773
|
-
for (const
|
|
774
|
-
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(
|
|
915
|
+
for (const meta of commitOrder) {
|
|
916
|
+
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(meta) ?? [], ctx);
|
|
775
917
|
}
|
|
776
918
|
// extra updates
|
|
777
919
|
await this.commitExtraUpdates(ChangeSetType.UPDATE, ctx);
|
|
778
920
|
// collection updates
|
|
779
921
|
await this.commitCollectionUpdates(ctx);
|
|
780
922
|
// delete - entity deletions need to be in reverse commit order
|
|
781
|
-
for (const
|
|
782
|
-
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(
|
|
923
|
+
for (const meta of commitOrderReversed) {
|
|
924
|
+
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(meta) ?? [], ctx);
|
|
783
925
|
}
|
|
784
926
|
// take snapshots of all persisted collections
|
|
785
927
|
const visited = new Set();
|
|
@@ -792,9 +934,9 @@ export class UnitOfWork {
|
|
|
792
934
|
return;
|
|
793
935
|
}
|
|
794
936
|
const props = changeSets[0].meta.root.relations.filter(prop => {
|
|
795
|
-
return (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
|
|
796
|
-
|
|
797
|
-
|
|
937
|
+
return ((prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) ||
|
|
938
|
+
prop.kind === ReferenceKind.MANY_TO_ONE ||
|
|
939
|
+
(prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.platform.usesPivotTable()));
|
|
798
940
|
});
|
|
799
941
|
for (const changeSet of changeSets) {
|
|
800
942
|
this.findExtraUpdates(changeSet, props);
|
|
@@ -815,16 +957,28 @@ export class UnitOfWork {
|
|
|
815
957
|
if (Utils.isCollection(ref)) {
|
|
816
958
|
ref.getItems(false).some(item => {
|
|
817
959
|
const cs = this.changeSets.get(Reference.unwrapReference(item));
|
|
818
|
-
const isScheduledForInsert = cs
|
|
960
|
+
const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
|
|
819
961
|
if (isScheduledForInsert) {
|
|
820
962
|
this.scheduleExtraUpdate(changeSet, [prop]);
|
|
821
963
|
return true;
|
|
822
964
|
}
|
|
823
965
|
return false;
|
|
824
966
|
});
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
const refEntity = Reference.unwrapReference(ref);
|
|
970
|
+
// For mapToPk properties, the value is a primitive (string/array), not an entity
|
|
971
|
+
if (!Utils.isEntity(refEntity)) {
|
|
972
|
+
continue;
|
|
825
973
|
}
|
|
826
|
-
|
|
827
|
-
|
|
974
|
+
// For TPT entities, check if the ROOT table's changeset has been persisted
|
|
975
|
+
// (since the FK is to the root table, not the concrete entity's table)
|
|
976
|
+
let cs = this.changeSets.get(refEntity);
|
|
977
|
+
if (cs?.tptChangeSets?.length) {
|
|
978
|
+
// Root table changeset is the last one (ordered immediate parent → root)
|
|
979
|
+
cs = cs.tptChangeSets[cs.tptChangeSets.length - 1];
|
|
980
|
+
}
|
|
981
|
+
const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
|
|
828
982
|
if (isScheduledForInsert) {
|
|
829
983
|
this.scheduleExtraUpdate(changeSet, [prop]);
|
|
830
984
|
}
|
|
@@ -855,10 +1009,10 @@ export class UnitOfWork {
|
|
|
855
1009
|
for (const changeSet of changeSets) {
|
|
856
1010
|
const wrapped = helper(changeSet.entity);
|
|
857
1011
|
wrapped.__originalEntityData = this.comparator.prepareEntity(changeSet.entity);
|
|
858
|
-
wrapped.__touched = false;
|
|
859
1012
|
if (!wrapped.__initialized) {
|
|
860
1013
|
for (const prop of changeSet.meta.relations) {
|
|
861
|
-
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
1014
|
+
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
1015
|
+
changeSet.entity[prop.name] == null) {
|
|
862
1016
|
changeSet.entity[prop.name] = Collection.create(changeSet.entity, prop.name, undefined, wrapped.isInitialized());
|
|
863
1017
|
}
|
|
864
1018
|
}
|
|
@@ -887,7 +1041,7 @@ export class UnitOfWork {
|
|
|
887
1041
|
continue;
|
|
888
1042
|
}
|
|
889
1043
|
if (Array.isArray(extraUpdate[1])) {
|
|
890
|
-
extraUpdate[1].forEach((p, i) => extraUpdate[0][p] = extraUpdate[2][i]);
|
|
1044
|
+
extraUpdate[1].forEach((p, i) => (extraUpdate[0][p] = extraUpdate[2][i]));
|
|
891
1045
|
}
|
|
892
1046
|
else {
|
|
893
1047
|
extraUpdate[0][extraUpdate[1]] = extraUpdate[2];
|
|
@@ -947,12 +1101,24 @@ export class UnitOfWork {
|
|
|
947
1101
|
[ChangeSetType.UPDATE_EARLY]: new Map(),
|
|
948
1102
|
[ChangeSetType.DELETE_EARLY]: new Map(),
|
|
949
1103
|
};
|
|
950
|
-
|
|
1104
|
+
const addToGroup = (cs) => {
|
|
1105
|
+
// Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
|
|
1106
|
+
if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) &&
|
|
1107
|
+
!Utils.hasObjectKeys(cs.payload)) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
951
1110
|
const group = groups[cs.type];
|
|
952
|
-
const
|
|
1111
|
+
const groupKey = cs.meta.inheritanceType === 'tpt' ? cs.meta : cs.rootMeta;
|
|
1112
|
+
const classGroup = group.get(groupKey) ?? [];
|
|
953
1113
|
classGroup.push(cs);
|
|
954
|
-
if (!group.has(
|
|
955
|
-
group.set(
|
|
1114
|
+
if (!group.has(groupKey)) {
|
|
1115
|
+
group.set(groupKey, classGroup);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
for (const cs of this.changeSets.values()) {
|
|
1119
|
+
addToGroup(cs);
|
|
1120
|
+
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1121
|
+
addToGroup(parentCs);
|
|
956
1122
|
}
|
|
957
1123
|
}
|
|
958
1124
|
return groups;
|
|
@@ -960,14 +1126,35 @@ export class UnitOfWork {
|
|
|
960
1126
|
getCommitOrder() {
|
|
961
1127
|
const calc = new CommitOrderCalculator();
|
|
962
1128
|
const set = new Set();
|
|
963
|
-
this.changeSets.forEach(cs =>
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1129
|
+
this.changeSets.forEach(cs => {
|
|
1130
|
+
if (cs.meta.inheritanceType === 'tpt') {
|
|
1131
|
+
set.add(cs.meta);
|
|
1132
|
+
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1133
|
+
set.add(parentCs.meta);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
set.add(cs.rootMeta);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
set.forEach(meta => calc.addNode(meta._id));
|
|
1141
|
+
for (const meta of set) {
|
|
1142
|
+
for (const prop of meta.relations) {
|
|
1143
|
+
if (prop.polymorphTargets) {
|
|
1144
|
+
for (const targetMeta of prop.polymorphTargets) {
|
|
1145
|
+
calc.discoverProperty({ ...prop, targetMeta }, meta._id);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
calc.discoverProperty(prop, meta._id);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
// For TPT, parent table must be inserted BEFORE child tables
|
|
1153
|
+
if (meta.inheritanceType === 'tpt' && meta.tptParent && set.has(meta.tptParent)) {
|
|
1154
|
+
calc.addDependency(meta.tptParent._id, meta._id, 1);
|
|
968
1155
|
}
|
|
969
1156
|
}
|
|
970
|
-
return calc.sort();
|
|
1157
|
+
return calc.sort().map(id => this.metadata.getById(id));
|
|
971
1158
|
}
|
|
972
1159
|
resetTransaction(oldTx) {
|
|
973
1160
|
if (oldTx) {
|