@mikro-orm/core 7.0.0-dev.33 → 7.0.0-dev.331
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 +70 -75
- package/EntityManager.js +487 -402
- package/MikroORM.d.ts +45 -38
- package/MikroORM.js +123 -156
- package/README.md +7 -4
- package/cache/FileCacheAdapter.d.ts +2 -7
- package/cache/FileCacheAdapter.js +35 -30
- package/cache/GeneratedCacheAdapter.d.ts +1 -2
- package/cache/GeneratedCacheAdapter.js +6 -8
- package/cache/MemoryCacheAdapter.d.ts +1 -2
- package/cache/MemoryCacheAdapter.js +8 -8
- 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 -18
- package/drivers/DatabaseDriver.js +144 -45
- 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 +95 -31
- package/entity/Collection.js +487 -139
- package/entity/EntityAssigner.js +37 -25
- package/entity/EntityFactory.d.ts +8 -9
- package/entity/EntityFactory.js +152 -100
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +69 -27
- package/entity/EntityLoader.d.ts +12 -13
- package/entity/EntityLoader.js +286 -125
- 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 +3 -8
- package/entity/Reference.js +62 -29
- package/entity/WrappedEntity.d.ts +7 -10
- package/entity/WrappedEntity.js +6 -7
- package/entity/defineEntity.d.ts +472 -313
- 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 +26 -16
- package/errors.js +63 -31
- package/events/EventManager.d.ts +3 -5
- package/events/EventManager.js +37 -26
- package/events/index.d.ts +1 -1
- package/events/index.js +0 -1
- package/exceptions.js +9 -2
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +5 -6
- package/hydration/ObjectHydrator.js +109 -50
- 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 +4 -6
- 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 +65 -18
- package/metadata/MetadataDiscovery.js +940 -424
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +71 -2
- package/metadata/MetadataStorage.d.ts +11 -13
- package/metadata/MetadataStorage.js +79 -48
- 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 +7 -6
- 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 +33 -15
- package/platforms/Platform.js +125 -69
- package/serialization/EntitySerializer.d.ts +6 -3
- package/serialization/EntitySerializer.js +54 -30
- package/serialization/EntityTransformer.js +37 -22
- package/serialization/SerializationContext.d.ts +10 -14
- package/serialization/SerializationContext.js +24 -19
- 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/UuidType.d.ts +2 -0
- package/types/UuidType.js +14 -2
- 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 +2 -12
- package/unit-of-work/ChangeSetComputer.js +61 -38
- package/unit-of-work/ChangeSetPersister.d.ts +10 -17
- package/unit-of-work/ChangeSetPersister.js +136 -73
- package/unit-of-work/CommitOrderCalculator.d.ts +13 -14
- package/unit-of-work/CommitOrderCalculator.js +22 -20
- package/unit-of-work/IdentityMap.d.ts +12 -3
- package/unit-of-work/IdentityMap.js +51 -13
- package/unit-of-work/UnitOfWork.d.ts +39 -23
- package/unit-of-work/UnitOfWork.js +441 -246
- 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 +647 -185
- package/utils/Configuration.js +215 -252
- package/utils/ConfigurationLoader.d.ts +1 -52
- package/utils/ConfigurationLoader.js +1 -330
- package/utils/Cursor.d.ts +3 -6
- package/utils/Cursor.js +32 -17
- package/utils/DataloaderUtils.d.ts +10 -5
- package/utils/DataloaderUtils.js +42 -22
- package/utils/EntityComparator.d.ts +21 -21
- package/utils/EntityComparator.js +224 -118
- package/utils/QueryHelper.d.ts +34 -7
- package/utils/QueryHelper.js +183 -72
- 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 -8
- package/utils/Utils.d.ts +16 -127
- package/utils/Utils.js +104 -402
- package/utils/clone.js +13 -23
- package/utils/env-vars.d.ts +7 -0
- package/utils/env-vars.js +98 -0
- package/utils/fs-utils.d.ts +20 -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,41 +12,42 @@ 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
|
-
em;
|
|
20
19
|
/** map of references to managed entities */
|
|
21
|
-
identityMap;
|
|
22
|
-
persistStack = new Set();
|
|
23
|
-
removeStack = new Set();
|
|
24
|
-
orphanRemoveStack = new Set();
|
|
25
|
-
changeSets = new Map();
|
|
26
|
-
collectionUpdates = new Set();
|
|
27
|
-
extraUpdates = new Set();
|
|
28
|
-
metadata;
|
|
29
|
-
platform;
|
|
30
|
-
eventManager;
|
|
31
|
-
comparator;
|
|
32
|
-
changeSetComputer;
|
|
33
|
-
changeSetPersister;
|
|
34
|
-
queuedActions = new Set();
|
|
35
|
-
loadedEntities = new Set();
|
|
36
|
-
flushQueue = [];
|
|
37
|
-
working = false;
|
|
20
|
+
#identityMap;
|
|
21
|
+
#persistStack = new Set();
|
|
22
|
+
#removeStack = new Set();
|
|
23
|
+
#orphanRemoveStack = new Set();
|
|
24
|
+
#changeSets = new Map();
|
|
25
|
+
#collectionUpdates = new Set();
|
|
26
|
+
#extraUpdates = new Set();
|
|
27
|
+
#metadata;
|
|
28
|
+
#platform;
|
|
29
|
+
#eventManager;
|
|
30
|
+
#comparator;
|
|
31
|
+
#changeSetComputer;
|
|
32
|
+
#changeSetPersister;
|
|
33
|
+
#queuedActions = new Set();
|
|
34
|
+
#loadedEntities = new Set();
|
|
35
|
+
#flushQueue = [];
|
|
36
|
+
#working = false;
|
|
37
|
+
#em;
|
|
38
38
|
constructor(em) {
|
|
39
|
-
this
|
|
40
|
-
this
|
|
41
|
-
this
|
|
42
|
-
this
|
|
43
|
-
this
|
|
44
|
-
this
|
|
45
|
-
this
|
|
46
|
-
this
|
|
39
|
+
this.#em = em;
|
|
40
|
+
this.#metadata = this.#em.getMetadata();
|
|
41
|
+
this.#platform = this.#em.getPlatform();
|
|
42
|
+
this.#identityMap = new IdentityMap(this.#platform.getDefaultSchemaName());
|
|
43
|
+
this.#eventManager = this.#em.getEventManager();
|
|
44
|
+
this.#comparator = this.#em.getComparator();
|
|
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);
|
|
50
|
-
wrapped.__em = this
|
|
50
|
+
wrapped.__em = this.#em;
|
|
51
51
|
if (!wrapped.hasPrimaryKey()) {
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
@@ -56,57 +56,81 @@ export class UnitOfWork {
|
|
|
56
56
|
if (!wrapped.__managed && visited) {
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
this
|
|
59
|
+
this.#identityMap.store(entity);
|
|
60
60
|
// if visited is available, we are cascading, and need to be careful when resetting the entity data
|
|
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
|
-
wrapped.__originalEntityData = this
|
|
64
|
-
wrapped.__touched = false;
|
|
63
|
+
wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
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
|
*/
|
|
71
112
|
register(entity, data, options) {
|
|
72
|
-
this
|
|
113
|
+
this.#identityMap.store(entity);
|
|
73
114
|
EntityHelper.ensurePropagation(entity);
|
|
74
115
|
if (options?.newEntity) {
|
|
75
116
|
return entity;
|
|
76
117
|
}
|
|
77
|
-
const forceUndefined = this
|
|
118
|
+
const forceUndefined = this.#em.config.get('forceUndefined');
|
|
78
119
|
const wrapped = helper(entity);
|
|
79
120
|
if (options?.loaded && wrapped.__initialized && !wrapped.__onLoadFired) {
|
|
80
|
-
this
|
|
121
|
+
this.#loadedEntities.add(entity);
|
|
81
122
|
}
|
|
82
|
-
wrapped.__em ??= this
|
|
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
|
}
|
|
@@ -114,13 +138,19 @@ export class UnitOfWork {
|
|
|
114
138
|
* @internal
|
|
115
139
|
*/
|
|
116
140
|
async dispatchOnLoadEvent() {
|
|
117
|
-
for (const entity of this
|
|
118
|
-
if (this
|
|
119
|
-
await this
|
|
141
|
+
for (const entity of this.#loadedEntities) {
|
|
142
|
+
if (this.#eventManager.hasListeners(EventType.onLoad, entity.__meta)) {
|
|
143
|
+
await this.#eventManager.dispatchEvent(EventType.onLoad, { entity, meta: entity.__meta, em: this.#em });
|
|
120
144
|
helper(entity).__onLoadFired = true;
|
|
121
145
|
}
|
|
122
146
|
}
|
|
123
|
-
this
|
|
147
|
+
this.#loadedEntities.clear();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
unmarkAsLoaded(entity) {
|
|
153
|
+
this.#loadedEntities.delete(entity);
|
|
124
154
|
}
|
|
125
155
|
/**
|
|
126
156
|
* Returns entity from the identity map. For composite keys, you need to pass an array of PKs in the same order as they are defined in `meta.primaryKeys`.
|
|
@@ -129,7 +159,7 @@ export class UnitOfWork {
|
|
|
129
159
|
if (id == null || (Array.isArray(id) && id.length === 0)) {
|
|
130
160
|
return undefined;
|
|
131
161
|
}
|
|
132
|
-
const meta = this
|
|
162
|
+
const meta = this.#metadata.find(entityName).root;
|
|
133
163
|
let hash;
|
|
134
164
|
if (meta.simplePK) {
|
|
135
165
|
hash = '' + id;
|
|
@@ -138,7 +168,7 @@ export class UnitOfWork {
|
|
|
138
168
|
let keys = Array.isArray(id) ? Utils.flatten(id) : [id];
|
|
139
169
|
keys = meta.getPrimaryProps(true).map((p, i) => {
|
|
140
170
|
if (!convertCustomTypes && p.customType) {
|
|
141
|
-
return p.customType.convertToDatabaseValue(keys[i], this
|
|
171
|
+
return p.customType.convertToDatabaseValue(keys[i], this.#platform, {
|
|
142
172
|
key: p.name,
|
|
143
173
|
mode: 'hydration',
|
|
144
174
|
});
|
|
@@ -147,14 +177,48 @@ export class UnitOfWork {
|
|
|
147
177
|
});
|
|
148
178
|
hash = Utils.getPrimaryKeyHash(keys);
|
|
149
179
|
}
|
|
150
|
-
schema ??= meta.schema ?? this
|
|
180
|
+
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
151
181
|
if (schema) {
|
|
152
182
|
hash = `${schema}:${hash}`;
|
|
153
183
|
}
|
|
154
|
-
return this
|
|
184
|
+
return this.#identityMap.getByHash(meta, hash);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Returns entity from the identity map by an alternate key (non-PK property).
|
|
188
|
+
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format for lookup.
|
|
189
|
+
* If false (default), the value is assumed to be in JS format already.
|
|
190
|
+
*/
|
|
191
|
+
getByKey(entityName, key, value, schema, convertCustomTypes) {
|
|
192
|
+
const meta = this.#metadata.find(entityName).root;
|
|
193
|
+
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
194
|
+
const prop = meta.properties[key];
|
|
195
|
+
// Convert from DB format to JS format if needed
|
|
196
|
+
if (convertCustomTypes && prop?.customType) {
|
|
197
|
+
value = prop.customType.convertToJSValue(value, this.#platform, { mode: 'hydration' });
|
|
198
|
+
}
|
|
199
|
+
const hash = this.#identityMap.getKeyHash(key, '' + value, schema);
|
|
200
|
+
return this.#identityMap.getByHash(meta, hash);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Stores an entity in the identity map under an alternate key (non-PK property).
|
|
204
|
+
* Also sets the property value on the entity.
|
|
205
|
+
* @param convertCustomTypes - If true, the value is in database format and will be converted to JS format.
|
|
206
|
+
* If false (default), the value is assumed to be in JS format already.
|
|
207
|
+
*/
|
|
208
|
+
storeByKey(entity, key, value, schema, convertCustomTypes) {
|
|
209
|
+
const meta = entity.__meta.root;
|
|
210
|
+
schema ??= meta.schema ?? this.#em.config.getSchema();
|
|
211
|
+
const prop = meta.properties[key];
|
|
212
|
+
// Convert from DB format to JS format if needed
|
|
213
|
+
if (convertCustomTypes && prop?.customType) {
|
|
214
|
+
value = prop.customType.convertToJSValue(value, this.#platform, { mode: 'hydration' });
|
|
215
|
+
}
|
|
216
|
+
// Set the property on the entity
|
|
217
|
+
entity[key] = value;
|
|
218
|
+
this.#identityMap.storeByKey(entity, key, '' + value, schema);
|
|
155
219
|
}
|
|
156
220
|
tryGetById(entityName, where, schema, strict = true) {
|
|
157
|
-
const pk = Utils.extractPK(where, this
|
|
221
|
+
const pk = Utils.extractPK(where, this.#metadata.find(entityName), strict);
|
|
158
222
|
if (!pk) {
|
|
159
223
|
return null;
|
|
160
224
|
}
|
|
@@ -164,7 +228,7 @@ export class UnitOfWork {
|
|
|
164
228
|
* Returns map of all managed entities.
|
|
165
229
|
*/
|
|
166
230
|
getIdentityMap() {
|
|
167
|
-
return this
|
|
231
|
+
return this.#identityMap;
|
|
168
232
|
}
|
|
169
233
|
/**
|
|
170
234
|
* Returns stored snapshot of entity state that is used for change set computation.
|
|
@@ -173,76 +237,76 @@ export class UnitOfWork {
|
|
|
173
237
|
return helper(entity).__originalEntityData;
|
|
174
238
|
}
|
|
175
239
|
getPersistStack() {
|
|
176
|
-
return this
|
|
240
|
+
return this.#persistStack;
|
|
177
241
|
}
|
|
178
242
|
getRemoveStack() {
|
|
179
|
-
return this
|
|
243
|
+
return this.#removeStack;
|
|
180
244
|
}
|
|
181
245
|
getChangeSets() {
|
|
182
|
-
return [...this
|
|
246
|
+
return [...this.#changeSets.values()];
|
|
183
247
|
}
|
|
184
248
|
getCollectionUpdates() {
|
|
185
|
-
return [...this
|
|
249
|
+
return [...this.#collectionUpdates];
|
|
186
250
|
}
|
|
187
251
|
getExtraUpdates() {
|
|
188
|
-
return this
|
|
252
|
+
return this.#extraUpdates;
|
|
189
253
|
}
|
|
190
254
|
shouldAutoFlush(meta) {
|
|
191
255
|
if (insideFlush.getStore()) {
|
|
192
256
|
return false;
|
|
193
257
|
}
|
|
194
|
-
if (this
|
|
258
|
+
if (this.#queuedActions.has(meta.class) || this.#queuedActions.has(meta.root.class)) {
|
|
195
259
|
return true;
|
|
196
260
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
261
|
+
if (meta.discriminatorMap && Object.values(meta.discriminatorMap).some(v => this.#queuedActions.has(v))) {
|
|
262
|
+
return true;
|
|
201
263
|
}
|
|
202
264
|
return false;
|
|
203
265
|
}
|
|
204
266
|
clearActionsQueue() {
|
|
205
|
-
this
|
|
267
|
+
this.#queuedActions.clear();
|
|
206
268
|
}
|
|
207
269
|
computeChangeSet(entity, type) {
|
|
208
270
|
const wrapped = helper(entity);
|
|
209
|
-
if (type) {
|
|
210
|
-
this
|
|
271
|
+
if (type === ChangeSetType.DELETE || type === ChangeSetType.DELETE_EARLY) {
|
|
272
|
+
this.#changeSets.set(entity, new ChangeSet(entity, type, {}, wrapped.__meta));
|
|
211
273
|
return;
|
|
212
274
|
}
|
|
213
|
-
const cs = this
|
|
275
|
+
const cs = this.#changeSetComputer.computeChangeSet(entity);
|
|
214
276
|
if (!cs || this.checkUniqueProps(cs)) {
|
|
215
277
|
return;
|
|
216
278
|
}
|
|
279
|
+
/* v8 ignore next */
|
|
280
|
+
if (type) {
|
|
281
|
+
cs.type = type;
|
|
282
|
+
}
|
|
217
283
|
this.initIdentifier(entity);
|
|
218
|
-
this
|
|
219
|
-
this
|
|
220
|
-
wrapped.__originalEntityData = this
|
|
221
|
-
wrapped.__touched = false;
|
|
284
|
+
this.#changeSets.set(entity, cs);
|
|
285
|
+
this.#persistStack.delete(entity);
|
|
286
|
+
wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
222
287
|
}
|
|
223
288
|
recomputeSingleChangeSet(entity) {
|
|
224
|
-
const changeSet = this
|
|
289
|
+
const changeSet = this.#changeSets.get(entity);
|
|
225
290
|
if (!changeSet) {
|
|
226
291
|
return;
|
|
227
292
|
}
|
|
228
|
-
const cs = this
|
|
293
|
+
const cs = this.#changeSetComputer.computeChangeSet(entity);
|
|
229
294
|
if (cs && !this.checkUniqueProps(cs)) {
|
|
230
295
|
Object.assign(changeSet.payload, cs.payload);
|
|
231
|
-
helper(entity).__originalEntityData = this
|
|
232
|
-
helper(entity).__touched = false;
|
|
296
|
+
helper(entity).__originalEntityData = this.#comparator.prepareEntity(entity);
|
|
233
297
|
}
|
|
234
298
|
}
|
|
235
299
|
persist(entity, visited, options = {}) {
|
|
236
300
|
EntityHelper.ensurePropagation(entity);
|
|
237
|
-
if (options.checkRemoveStack && this
|
|
301
|
+
if (options.checkRemoveStack && this.#removeStack.has(entity)) {
|
|
238
302
|
return;
|
|
239
303
|
}
|
|
240
304
|
const wrapped = helper(entity);
|
|
241
|
-
this
|
|
242
|
-
this
|
|
243
|
-
this
|
|
305
|
+
this.#persistStack.add(entity);
|
|
306
|
+
this.#queuedActions.add(wrapped.__meta.class);
|
|
307
|
+
this.#removeStack.delete(entity);
|
|
244
308
|
if (!wrapped.__managed && wrapped.hasPrimaryKey()) {
|
|
245
|
-
this
|
|
309
|
+
this.#identityMap.store(entity);
|
|
246
310
|
}
|
|
247
311
|
if (options.cascade ?? true) {
|
|
248
312
|
this.cascade(entity, Cascade.PERSIST, visited, options);
|
|
@@ -250,13 +314,13 @@ export class UnitOfWork {
|
|
|
250
314
|
}
|
|
251
315
|
remove(entity, visited, options = {}) {
|
|
252
316
|
// allow removing not managed entities if they are not part of the persist stack
|
|
253
|
-
if (helper(entity).__managed || !this
|
|
254
|
-
this
|
|
255
|
-
this
|
|
317
|
+
if (helper(entity).__managed || !this.#persistStack.has(entity)) {
|
|
318
|
+
this.#removeStack.add(entity);
|
|
319
|
+
this.#queuedActions.add(helper(entity).__meta.class);
|
|
256
320
|
}
|
|
257
321
|
else {
|
|
258
|
-
this
|
|
259
|
-
this
|
|
322
|
+
this.#persistStack.delete(entity);
|
|
323
|
+
this.#identityMap.delete(entity);
|
|
260
324
|
}
|
|
261
325
|
// remove from referencing relations that are nullable
|
|
262
326
|
for (const prop of helper(entity).__meta.bidirectionalRelations) {
|
|
@@ -269,7 +333,7 @@ export class UnitOfWork {
|
|
|
269
333
|
}
|
|
270
334
|
continue;
|
|
271
335
|
}
|
|
272
|
-
const target = relation
|
|
336
|
+
const target = relation?.[inverseProp];
|
|
273
337
|
if (relation && Utils.isCollection(target)) {
|
|
274
338
|
target.removeWithoutPropagation(entity);
|
|
275
339
|
}
|
|
@@ -279,12 +343,12 @@ export class UnitOfWork {
|
|
|
279
343
|
}
|
|
280
344
|
}
|
|
281
345
|
async commit() {
|
|
282
|
-
if (this
|
|
346
|
+
if (this.#working) {
|
|
283
347
|
if (insideFlush.getStore()) {
|
|
284
348
|
throw ValidationError.cannotCommit();
|
|
285
349
|
}
|
|
286
350
|
return new Promise((resolve, reject) => {
|
|
287
|
-
this
|
|
351
|
+
this.#flushQueue.push(() => {
|
|
288
352
|
return insideFlush.run(true, () => {
|
|
289
353
|
return this.doCommit().then(resolve, reject);
|
|
290
354
|
});
|
|
@@ -292,60 +356,61 @@ export class UnitOfWork {
|
|
|
292
356
|
});
|
|
293
357
|
}
|
|
294
358
|
try {
|
|
295
|
-
this
|
|
359
|
+
this.#working = true;
|
|
296
360
|
await insideFlush.run(true, () => this.doCommit());
|
|
297
|
-
while (this
|
|
298
|
-
await this
|
|
361
|
+
while (this.#flushQueue.length) {
|
|
362
|
+
await this.#flushQueue.shift()();
|
|
299
363
|
}
|
|
300
364
|
}
|
|
301
365
|
finally {
|
|
302
366
|
this.postCommitCleanup();
|
|
303
|
-
this
|
|
367
|
+
this.#working = false;
|
|
304
368
|
}
|
|
305
369
|
}
|
|
306
370
|
async doCommit() {
|
|
307
|
-
const oldTx = this
|
|
371
|
+
const oldTx = this.#em.getTransactionContext();
|
|
308
372
|
try {
|
|
309
|
-
await this
|
|
373
|
+
await this.#eventManager.dispatchEvent(EventType.beforeFlush, { em: this.#em, uow: this });
|
|
310
374
|
this.computeChangeSets();
|
|
311
|
-
for (const cs of this
|
|
375
|
+
for (const cs of this.#changeSets.values()) {
|
|
312
376
|
cs.entity.__helper.__processing = true;
|
|
313
377
|
}
|
|
314
|
-
await this
|
|
378
|
+
await this.#eventManager.dispatchEvent(EventType.onFlush, { em: this.#em, uow: this });
|
|
315
379
|
this.filterCollectionUpdates();
|
|
316
380
|
// nothing to do, do not start transaction
|
|
317
|
-
if (this
|
|
318
|
-
|
|
381
|
+
if (this.#changeSets.size === 0 && this.#collectionUpdates.size === 0 && this.#extraUpdates.size === 0) {
|
|
382
|
+
await this.#eventManager.dispatchEvent(EventType.afterFlush, { em: this.#em, uow: this });
|
|
383
|
+
return;
|
|
319
384
|
}
|
|
320
385
|
const groups = this.getChangeSetGroups();
|
|
321
|
-
const platform = this
|
|
322
|
-
const runInTransaction = !this
|
|
386
|
+
const platform = this.#em.getPlatform();
|
|
387
|
+
const runInTransaction = !this.#em.isInTransaction() && platform.supportsTransactions() && this.#em.config.get('implicitTransactions');
|
|
323
388
|
if (runInTransaction) {
|
|
324
|
-
const loggerContext = Utils.merge({ id: this
|
|
325
|
-
await this
|
|
389
|
+
const loggerContext = Utils.merge({ id: this.#em._id }, this.#em.getLoggerContext({ disableContextResolution: true }));
|
|
390
|
+
await this.#em.getConnection('write').transactional(trx => this.persistToDatabase(groups, trx), {
|
|
326
391
|
ctx: oldTx,
|
|
327
|
-
eventBroadcaster: new TransactionEventBroadcaster(this
|
|
392
|
+
eventBroadcaster: new TransactionEventBroadcaster(this.#em),
|
|
328
393
|
loggerContext,
|
|
329
394
|
});
|
|
330
395
|
}
|
|
331
396
|
else {
|
|
332
|
-
await this.persistToDatabase(groups, this
|
|
397
|
+
await this.persistToDatabase(groups, this.#em.getTransactionContext());
|
|
333
398
|
}
|
|
334
399
|
this.resetTransaction(oldTx);
|
|
335
|
-
for (const cs of this
|
|
400
|
+
for (const cs of this.#changeSets.values()) {
|
|
336
401
|
cs.entity.__helper.__processing = false;
|
|
337
402
|
}
|
|
338
|
-
await this
|
|
403
|
+
await this.#eventManager.dispatchEvent(EventType.afterFlush, { em: this.#em, uow: this });
|
|
339
404
|
}
|
|
340
405
|
finally {
|
|
341
406
|
this.resetTransaction(oldTx);
|
|
342
407
|
}
|
|
343
408
|
}
|
|
344
409
|
async lock(entity, options) {
|
|
345
|
-
if (!this.getById(entity.constructor
|
|
410
|
+
if (!this.getById(entity.constructor, helper(entity).__primaryKeys, helper(entity).__schema)) {
|
|
346
411
|
throw ValidationError.entityNotManaged(entity);
|
|
347
412
|
}
|
|
348
|
-
const meta = this
|
|
413
|
+
const meta = this.#metadata.find(entity.constructor);
|
|
349
414
|
if (options.lockMode === LockMode.OPTIMISTIC) {
|
|
350
415
|
await this.lockOptimistic(entity, meta, options.lockVersion);
|
|
351
416
|
}
|
|
@@ -354,22 +419,25 @@ export class UnitOfWork {
|
|
|
354
419
|
}
|
|
355
420
|
}
|
|
356
421
|
clear() {
|
|
357
|
-
this
|
|
358
|
-
this
|
|
422
|
+
this.#identityMap.clear();
|
|
423
|
+
this.#loadedEntities.clear();
|
|
359
424
|
this.postCommitCleanup();
|
|
360
425
|
}
|
|
361
426
|
unsetIdentity(entity) {
|
|
362
|
-
this
|
|
427
|
+
this.#identityMap.delete(entity);
|
|
363
428
|
const wrapped = helper(entity);
|
|
364
429
|
const serializedPK = wrapped.getSerializedPrimaryKey();
|
|
365
430
|
// remove references of this entity in all managed entities, otherwise flushing could reinsert the entity
|
|
366
431
|
for (const { meta, prop } of wrapped.__meta.referencingProperties) {
|
|
367
|
-
for (const referrer of this
|
|
432
|
+
for (const referrer of this.#identityMap.getStore(meta).values()) {
|
|
368
433
|
const rel = Reference.unwrapReference(referrer[prop.name]);
|
|
369
434
|
if (Utils.isCollection(rel)) {
|
|
370
435
|
rel.removeWithoutPropagation(entity);
|
|
371
436
|
}
|
|
372
|
-
else if (rel &&
|
|
437
|
+
else if (rel &&
|
|
438
|
+
(prop.mapToPk
|
|
439
|
+
? helper(this.#em.getReference(prop.targetMeta.class, rel)).getSerializedPrimaryKey() === serializedPK
|
|
440
|
+
: rel === entity)) {
|
|
373
441
|
if (prop.formula) {
|
|
374
442
|
delete referrer[prop.name];
|
|
375
443
|
}
|
|
@@ -381,55 +449,54 @@ export class UnitOfWork {
|
|
|
381
449
|
}
|
|
382
450
|
delete wrapped.__identifier;
|
|
383
451
|
delete wrapped.__originalEntityData;
|
|
384
|
-
wrapped.__touched = false;
|
|
385
452
|
wrapped.__managed = false;
|
|
386
453
|
}
|
|
387
454
|
computeChangeSets() {
|
|
388
|
-
this
|
|
455
|
+
this.#changeSets.clear();
|
|
389
456
|
const visited = new Set();
|
|
390
|
-
for (const entity of this
|
|
457
|
+
for (const entity of this.#removeStack) {
|
|
391
458
|
this.cascade(entity, Cascade.REMOVE, visited);
|
|
392
459
|
}
|
|
393
460
|
visited.clear();
|
|
394
|
-
for (const entity of this
|
|
395
|
-
this.
|
|
396
|
-
}
|
|
397
|
-
for (const entity of this.identityMap) {
|
|
398
|
-
if (!this.removeStack.has(entity) && !this.persistStack.has(entity) && !this.orphanRemoveStack.has(entity)) {
|
|
461
|
+
for (const entity of this.#identityMap) {
|
|
462
|
+
if (!this.#removeStack.has(entity) && !this.#persistStack.has(entity) && !this.#orphanRemoveStack.has(entity)) {
|
|
399
463
|
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
400
464
|
}
|
|
401
465
|
}
|
|
466
|
+
for (const entity of this.#persistStack) {
|
|
467
|
+
this.cascade(entity, Cascade.PERSIST, visited, { checkRemoveStack: true });
|
|
468
|
+
}
|
|
402
469
|
visited.clear();
|
|
403
|
-
for (const entity of this
|
|
470
|
+
for (const entity of this.#persistStack) {
|
|
404
471
|
this.findNewEntities(entity, visited);
|
|
405
472
|
}
|
|
406
|
-
for (const entity of this
|
|
473
|
+
for (const entity of this.#orphanRemoveStack) {
|
|
407
474
|
if (!helper(entity).__processing) {
|
|
408
|
-
this
|
|
475
|
+
this.#removeStack.add(entity);
|
|
409
476
|
}
|
|
410
477
|
}
|
|
411
478
|
// Check insert stack if there are any entities matching something from delete stack. This can happen when recreating entities.
|
|
412
479
|
const inserts = {};
|
|
413
|
-
for (const cs of this
|
|
480
|
+
for (const cs of this.#changeSets.values()) {
|
|
414
481
|
if (cs.type === ChangeSetType.CREATE) {
|
|
415
|
-
inserts[cs.meta.
|
|
416
|
-
inserts[cs.meta.
|
|
482
|
+
inserts[cs.meta.uniqueName] ??= [];
|
|
483
|
+
inserts[cs.meta.uniqueName].push(cs);
|
|
417
484
|
}
|
|
418
485
|
}
|
|
419
|
-
for (const cs of this
|
|
486
|
+
for (const cs of this.#changeSets.values()) {
|
|
420
487
|
if (cs.type === ChangeSetType.UPDATE) {
|
|
421
|
-
this.findEarlyUpdates(cs, inserts[cs.meta.
|
|
488
|
+
this.findEarlyUpdates(cs, inserts[cs.meta.uniqueName]);
|
|
422
489
|
}
|
|
423
490
|
}
|
|
424
|
-
for (const entity of this
|
|
491
|
+
for (const entity of this.#removeStack) {
|
|
425
492
|
const wrapped = helper(entity);
|
|
426
|
-
/* v8 ignore next
|
|
493
|
+
/* v8 ignore next */
|
|
427
494
|
if (wrapped.__processing) {
|
|
428
495
|
continue;
|
|
429
496
|
}
|
|
430
497
|
const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
|
|
431
498
|
let type = ChangeSetType.DELETE;
|
|
432
|
-
for (const cs of inserts[wrapped.__meta.
|
|
499
|
+
for (const cs of inserts[wrapped.__meta.uniqueName] ?? []) {
|
|
433
500
|
if (deletePkHash.some(hash => hash === cs.getSerializedPrimaryKey() || this.expandUniqueProps(cs.entity).find(child => hash === child))) {
|
|
434
501
|
type = ChangeSetType.DELETE_EARLY;
|
|
435
502
|
}
|
|
@@ -446,9 +513,9 @@ export class UnitOfWork {
|
|
|
446
513
|
if (!props.some(prop => prop.name in changeSet.payload)) {
|
|
447
514
|
return;
|
|
448
515
|
}
|
|
449
|
-
for (const cs of this
|
|
516
|
+
for (const cs of this.#changeSets.values()) {
|
|
450
517
|
for (const prop of props) {
|
|
451
|
-
if (prop.name in cs.payload && cs.
|
|
518
|
+
if (prop.name in cs.payload && cs.rootMeta === changeSet.rootMeta && cs.type === changeSet.type) {
|
|
452
519
|
conflicts = true;
|
|
453
520
|
if (changeSet.payload[prop.name] == null) {
|
|
454
521
|
type = ChangeSetType.UPDATE_EARLY;
|
|
@@ -459,7 +526,13 @@ export class UnitOfWork {
|
|
|
459
526
|
if (!conflicts) {
|
|
460
527
|
return;
|
|
461
528
|
}
|
|
462
|
-
this
|
|
529
|
+
this.#extraUpdates.add([
|
|
530
|
+
changeSet.entity,
|
|
531
|
+
props.map(p => p.name),
|
|
532
|
+
props.map(p => changeSet.entity[p.name]),
|
|
533
|
+
changeSet,
|
|
534
|
+
type,
|
|
535
|
+
]);
|
|
463
536
|
for (const p of props) {
|
|
464
537
|
delete changeSet.entity[p.name];
|
|
465
538
|
delete changeSet.payload[p.name];
|
|
@@ -467,21 +540,22 @@ export class UnitOfWork {
|
|
|
467
540
|
}
|
|
468
541
|
scheduleOrphanRemoval(entity, visited) {
|
|
469
542
|
if (entity) {
|
|
470
|
-
helper(entity)
|
|
471
|
-
this
|
|
472
|
-
this.
|
|
543
|
+
const wrapped = helper(entity);
|
|
544
|
+
wrapped.__em = this.#em;
|
|
545
|
+
this.#orphanRemoveStack.add(entity);
|
|
546
|
+
this.#queuedActions.add(wrapped.__meta.class);
|
|
473
547
|
this.cascade(entity, Cascade.SCHEDULE_ORPHAN_REMOVAL, visited);
|
|
474
548
|
}
|
|
475
549
|
}
|
|
476
550
|
cancelOrphanRemoval(entity, visited) {
|
|
477
|
-
this
|
|
551
|
+
this.#orphanRemoveStack.delete(entity);
|
|
478
552
|
this.cascade(entity, Cascade.CANCEL_ORPHAN_REMOVAL, visited);
|
|
479
553
|
}
|
|
480
554
|
getOrphanRemoveStack() {
|
|
481
|
-
return this
|
|
555
|
+
return this.#orphanRemoveStack;
|
|
482
556
|
}
|
|
483
557
|
getChangeSetPersister() {
|
|
484
|
-
return this
|
|
558
|
+
return this.#changeSetPersister;
|
|
485
559
|
}
|
|
486
560
|
findNewEntities(entity, visited, idx = 0, processed = new Set()) {
|
|
487
561
|
if (visited.has(entity)) {
|
|
@@ -490,11 +564,11 @@ export class UnitOfWork {
|
|
|
490
564
|
visited.add(entity);
|
|
491
565
|
processed.add(entity);
|
|
492
566
|
const wrapped = helper(entity);
|
|
493
|
-
if (wrapped.__processing || this
|
|
567
|
+
if (wrapped.__processing || this.#removeStack.has(entity) || this.#orphanRemoveStack.has(entity)) {
|
|
494
568
|
return;
|
|
495
569
|
}
|
|
496
570
|
// Set entityManager default schema
|
|
497
|
-
wrapped.__schema ??= this
|
|
571
|
+
wrapped.__schema ??= this.#em.schema;
|
|
498
572
|
this.initIdentifier(entity);
|
|
499
573
|
for (const prop of wrapped.__meta.relations) {
|
|
500
574
|
const targets = Utils.unwrapProperty(entity, wrapped.__meta, prop);
|
|
@@ -503,9 +577,70 @@ export class UnitOfWork {
|
|
|
503
577
|
this.processReference(entity, prop, kind, visited, processed, idx);
|
|
504
578
|
}
|
|
505
579
|
}
|
|
506
|
-
const changeSet = this
|
|
580
|
+
const changeSet = this.#changeSetComputer.computeChangeSet(entity);
|
|
507
581
|
if (changeSet && !this.checkUniqueProps(changeSet)) {
|
|
508
|
-
|
|
582
|
+
// For TPT child entities, create changesets for each table in hierarchy
|
|
583
|
+
if (wrapped.__meta.inheritanceType === 'tpt' && wrapped.__meta.tptParent) {
|
|
584
|
+
this.createTPTChangeSets(entity, changeSet);
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
this.#changeSets.set(entity, changeSet);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* For TPT inheritance, creates separate changesets for each table in the hierarchy.
|
|
593
|
+
* Uses the same entity instance for all changesets - only the metadata and payload differ.
|
|
594
|
+
*/
|
|
595
|
+
createTPTChangeSets(entity, originalChangeSet) {
|
|
596
|
+
const meta = helper(entity).__meta;
|
|
597
|
+
const isCreate = originalChangeSet.type === ChangeSetType.CREATE;
|
|
598
|
+
let current = meta;
|
|
599
|
+
let leafCs;
|
|
600
|
+
const parentChangeSets = [];
|
|
601
|
+
while (current) {
|
|
602
|
+
const isRoot = !current.tptParent;
|
|
603
|
+
const payload = {};
|
|
604
|
+
for (const prop of current.ownProps) {
|
|
605
|
+
if (prop.name in originalChangeSet.payload) {
|
|
606
|
+
payload[prop.name] = originalChangeSet.payload[prop.name];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
// For CREATE on non-root tables, include the PK (EntityIdentifier for deferred resolution)
|
|
610
|
+
if (isCreate && !isRoot) {
|
|
611
|
+
const wrapped = helper(entity);
|
|
612
|
+
const identifier = wrapped.__identifier;
|
|
613
|
+
const identifiers = Array.isArray(identifier) ? identifier : [identifier];
|
|
614
|
+
for (let i = 0; i < current.primaryKeys.length; i++) {
|
|
615
|
+
const pk = current.primaryKeys[i];
|
|
616
|
+
payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!isCreate && Object.keys(payload).length === 0) {
|
|
620
|
+
current = current.tptParent;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
|
|
624
|
+
if (current === meta) {
|
|
625
|
+
cs.originalEntity = originalChangeSet.originalEntity;
|
|
626
|
+
leafCs = cs;
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
parentChangeSets.push(cs);
|
|
630
|
+
}
|
|
631
|
+
current = current.tptParent;
|
|
632
|
+
}
|
|
633
|
+
// When only parent properties changed (UPDATE), leaf payload is empty—create a stub anchor
|
|
634
|
+
if (!leafCs && parentChangeSets.length > 0) {
|
|
635
|
+
leafCs = new ChangeSet(entity, originalChangeSet.type, {}, meta);
|
|
636
|
+
leafCs.originalEntity = originalChangeSet.originalEntity;
|
|
637
|
+
}
|
|
638
|
+
// Store the leaf changeset in the main map (entity as key), with parent CSs attached
|
|
639
|
+
if (leafCs) {
|
|
640
|
+
if (parentChangeSets.length > 0) {
|
|
641
|
+
leafCs.tptChangeSets = parentChangeSets;
|
|
642
|
+
}
|
|
643
|
+
this.#changeSets.set(entity, leafCs);
|
|
509
644
|
}
|
|
510
645
|
}
|
|
511
646
|
/**
|
|
@@ -518,7 +653,7 @@ export class UnitOfWork {
|
|
|
518
653
|
// when changing a unique nullable property (or a 1:1 relation), we can't do it in a single
|
|
519
654
|
// query as it would cause unique constraint violations
|
|
520
655
|
const uniqueProps = changeSet.meta.uniqueProps.filter(prop => {
|
|
521
|
-
return
|
|
656
|
+
return prop.nullable || changeSet.type !== ChangeSetType.CREATE;
|
|
522
657
|
});
|
|
523
658
|
this.scheduleExtraUpdate(changeSet, uniqueProps);
|
|
524
659
|
return changeSet.type === ChangeSetType.UPDATE && !Utils.hasObjectKeys(changeSet.payload);
|
|
@@ -528,25 +663,33 @@ export class UnitOfWork {
|
|
|
528
663
|
if (!wrapped.__meta.hasUniqueProps) {
|
|
529
664
|
return [];
|
|
530
665
|
}
|
|
531
|
-
const simpleUniqueHashes = wrapped.__meta.uniqueProps
|
|
666
|
+
const simpleUniqueHashes = wrapped.__meta.uniqueProps
|
|
667
|
+
.map(prop => {
|
|
532
668
|
if (entity[prop.name] != null) {
|
|
533
|
-
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
669
|
+
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
670
|
+
? entity[prop.name]
|
|
671
|
+
: helper(entity[prop.name]).getSerializedPrimaryKey();
|
|
534
672
|
}
|
|
535
673
|
if (wrapped.__originalEntityData?.[prop.name] != null) {
|
|
536
674
|
return Utils.getPrimaryKeyHash(Utils.asArray(wrapped.__originalEntityData[prop.name]));
|
|
537
675
|
}
|
|
538
676
|
return undefined;
|
|
539
|
-
})
|
|
540
|
-
|
|
677
|
+
})
|
|
678
|
+
.filter(i => i);
|
|
679
|
+
const compoundUniqueHashes = wrapped.__meta.uniques
|
|
680
|
+
.map(unique => {
|
|
541
681
|
const props = Utils.asArray(unique.properties);
|
|
542
682
|
if (props.every(prop => entity[prop] != null)) {
|
|
543
683
|
return Utils.getPrimaryKeyHash(props.map(p => {
|
|
544
684
|
const prop = wrapped.__meta.properties[p];
|
|
545
|
-
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
685
|
+
return prop.kind === ReferenceKind.SCALAR || prop.mapToPk
|
|
686
|
+
? entity[prop.name]
|
|
687
|
+
: helper(entity[prop.name]).getSerializedPrimaryKey();
|
|
546
688
|
}));
|
|
547
689
|
}
|
|
548
690
|
return undefined;
|
|
549
|
-
})
|
|
691
|
+
})
|
|
692
|
+
.filter(i => i);
|
|
550
693
|
return simpleUniqueHashes.concat(compoundUniqueHashes);
|
|
551
694
|
}
|
|
552
695
|
initIdentifier(entity) {
|
|
@@ -578,7 +721,8 @@ export class UnitOfWork {
|
|
|
578
721
|
return this.processToOneReference(kind, visited, processed, idx);
|
|
579
722
|
}
|
|
580
723
|
if (Utils.isCollection(kind)) {
|
|
581
|
-
kind
|
|
724
|
+
kind
|
|
725
|
+
.getItems(false)
|
|
582
726
|
.filter(item => !item.__helper.__originalEntityData)
|
|
583
727
|
.forEach(item => {
|
|
584
728
|
// propagate schema from parent
|
|
@@ -596,29 +740,30 @@ export class UnitOfWork {
|
|
|
596
740
|
}
|
|
597
741
|
processToManyReference(collection, visited, processed, parent, prop) {
|
|
598
742
|
if (this.isCollectionSelfReferenced(collection, processed)) {
|
|
599
|
-
this
|
|
743
|
+
this.#extraUpdates.add([parent, prop.name, collection, undefined, ChangeSetType.UPDATE]);
|
|
600
744
|
const coll = new Collection(parent);
|
|
601
745
|
coll.property = prop;
|
|
602
746
|
parent[prop.name] = coll;
|
|
603
747
|
return;
|
|
604
748
|
}
|
|
605
|
-
collection
|
|
749
|
+
collection
|
|
750
|
+
.getItems(false)
|
|
606
751
|
.filter(item => !item.__helper.__originalEntityData)
|
|
607
752
|
.forEach(item => this.findNewEntities(item, visited, 0, processed));
|
|
608
753
|
}
|
|
609
754
|
async runHooks(type, changeSet, sync = false) {
|
|
610
755
|
const meta = changeSet.meta;
|
|
611
|
-
if (!this
|
|
756
|
+
if (!this.#eventManager.hasListeners(type, meta)) {
|
|
612
757
|
return;
|
|
613
758
|
}
|
|
614
759
|
if (!sync) {
|
|
615
|
-
await this
|
|
760
|
+
await this.#eventManager.dispatchEvent(type, { entity: changeSet.entity, meta, em: this.#em, changeSet });
|
|
616
761
|
return;
|
|
617
762
|
}
|
|
618
|
-
const copy = this
|
|
619
|
-
await this
|
|
620
|
-
const current = this
|
|
621
|
-
const diff = this
|
|
763
|
+
const copy = this.#comparator.prepareEntity(changeSet.entity);
|
|
764
|
+
await this.#eventManager.dispatchEvent(type, { entity: changeSet.entity, meta, em: this.#em, changeSet });
|
|
765
|
+
const current = this.#comparator.prepareEntity(changeSet.entity);
|
|
766
|
+
const diff = this.#comparator.diffEntities(changeSet.meta.class, copy, current);
|
|
622
767
|
Object.assign(changeSet.payload, diff);
|
|
623
768
|
const wrapped = helper(changeSet.entity);
|
|
624
769
|
if (wrapped.__identifier) {
|
|
@@ -633,19 +778,19 @@ export class UnitOfWork {
|
|
|
633
778
|
}
|
|
634
779
|
}
|
|
635
780
|
postCommitCleanup() {
|
|
636
|
-
for (const cs of this
|
|
781
|
+
for (const cs of this.#changeSets.values()) {
|
|
637
782
|
const wrapped = helper(cs.entity);
|
|
638
783
|
wrapped.__processing = false;
|
|
639
784
|
delete wrapped.__pk;
|
|
640
785
|
}
|
|
641
|
-
this
|
|
642
|
-
this
|
|
643
|
-
this
|
|
644
|
-
this
|
|
645
|
-
this
|
|
646
|
-
this
|
|
647
|
-
this
|
|
648
|
-
this
|
|
786
|
+
this.#persistStack.clear();
|
|
787
|
+
this.#removeStack.clear();
|
|
788
|
+
this.#orphanRemoveStack.clear();
|
|
789
|
+
this.#changeSets.clear();
|
|
790
|
+
this.#collectionUpdates.clear();
|
|
791
|
+
this.#extraUpdates.clear();
|
|
792
|
+
this.#queuedActions.clear();
|
|
793
|
+
this.#working = false;
|
|
649
794
|
}
|
|
650
795
|
cascade(entity, type, visited = new Set(), options = {}) {
|
|
651
796
|
if (visited.has(entity)) {
|
|
@@ -694,7 +839,8 @@ export class UnitOfWork {
|
|
|
694
839
|
return filtered.some(items => processed.has(items));
|
|
695
840
|
}
|
|
696
841
|
shouldCascade(prop, type) {
|
|
697
|
-
if ([Cascade.REMOVE, Cascade.SCHEDULE_ORPHAN_REMOVAL, Cascade.CANCEL_ORPHAN_REMOVAL, Cascade.ALL].includes(type) &&
|
|
842
|
+
if ([Cascade.REMOVE, Cascade.SCHEDULE_ORPHAN_REMOVAL, Cascade.CANCEL_ORPHAN_REMOVAL, Cascade.ALL].includes(type) &&
|
|
843
|
+
prop.orphanRemoval) {
|
|
698
844
|
return true;
|
|
699
845
|
}
|
|
700
846
|
// ignore user settings for merge, it is kept only for back compatibility, this should have never been configurable
|
|
@@ -704,16 +850,16 @@ export class UnitOfWork {
|
|
|
704
850
|
return prop.cascade && (prop.cascade.includes(type) || prop.cascade.includes(Cascade.ALL));
|
|
705
851
|
}
|
|
706
852
|
async lockPessimistic(entity, options) {
|
|
707
|
-
if (!this
|
|
853
|
+
if (!this.#em.isInTransaction()) {
|
|
708
854
|
throw ValidationError.transactionRequired();
|
|
709
855
|
}
|
|
710
|
-
await this
|
|
856
|
+
await this.#em.getDriver().lockPessimistic(entity, { ctx: this.#em.getTransactionContext(), ...options });
|
|
711
857
|
}
|
|
712
858
|
async lockOptimistic(entity, meta, version) {
|
|
713
859
|
if (!meta.versionProperty) {
|
|
714
860
|
throw OptimisticLockError.notVersioned(meta);
|
|
715
861
|
}
|
|
716
|
-
if (
|
|
862
|
+
if (typeof version === 'undefined') {
|
|
717
863
|
return;
|
|
718
864
|
}
|
|
719
865
|
const wrapped = helper(entity);
|
|
@@ -727,63 +873,67 @@ export class UnitOfWork {
|
|
|
727
873
|
}
|
|
728
874
|
fixMissingReference(entity, prop) {
|
|
729
875
|
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
|
|
876
|
+
const target = Reference.unwrapReference(reference);
|
|
877
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && target && !prop.mapToPk) {
|
|
878
|
+
if (!Utils.isEntity(target)) {
|
|
879
|
+
entity[prop.name] = this.#em.getReference(prop.targetMeta.class, target, {
|
|
880
|
+
wrapped: !!prop.ref,
|
|
881
|
+
});
|
|
734
882
|
}
|
|
735
|
-
else if (!helper(
|
|
736
|
-
const pk = helper(
|
|
737
|
-
entity[prop.name] = this
|
|
883
|
+
else if (!helper(target).__initialized && !helper(target).__em) {
|
|
884
|
+
const pk = helper(target).getPrimaryKey();
|
|
885
|
+
entity[prop.name] = this.#em.getReference(prop.targetMeta.class, pk, {
|
|
886
|
+
wrapped: !!prop.ref,
|
|
887
|
+
});
|
|
738
888
|
}
|
|
739
889
|
}
|
|
740
|
-
// perf: set the `Collection._property` to skip the getter, as it can be slow when there
|
|
741
|
-
if (Utils.isCollection(
|
|
742
|
-
|
|
890
|
+
// perf: set the `Collection._property` to skip the getter, as it can be slow when there are a lot of relations
|
|
891
|
+
if (Utils.isCollection(target)) {
|
|
892
|
+
target.property = prop;
|
|
743
893
|
}
|
|
744
894
|
const isCollection = [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind);
|
|
745
|
-
if (isCollection && Array.isArray(
|
|
895
|
+
if (isCollection && Array.isArray(target)) {
|
|
746
896
|
const collection = new Collection(entity);
|
|
747
897
|
collection.property = prop;
|
|
748
898
|
entity[prop.name] = collection;
|
|
749
|
-
collection.set(
|
|
899
|
+
collection.set(target);
|
|
750
900
|
}
|
|
751
901
|
}
|
|
752
902
|
async persistToDatabase(groups, ctx) {
|
|
753
903
|
if (ctx) {
|
|
754
|
-
this
|
|
904
|
+
this.#em.setTransactionContext(ctx);
|
|
755
905
|
}
|
|
756
906
|
const commitOrder = this.getCommitOrder();
|
|
757
907
|
const commitOrderReversed = [...commitOrder].reverse();
|
|
758
908
|
// 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(
|
|
909
|
+
for (const meta of commitOrderReversed) {
|
|
910
|
+
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE_EARLY].get(meta) ?? [], ctx);
|
|
761
911
|
}
|
|
762
912
|
// 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(
|
|
913
|
+
for (const meta of commitOrder) {
|
|
914
|
+
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE_EARLY].get(meta) ?? [], ctx);
|
|
765
915
|
}
|
|
766
916
|
// extra updates
|
|
767
917
|
await this.commitExtraUpdates(ChangeSetType.UPDATE_EARLY, ctx);
|
|
768
918
|
// create
|
|
769
|
-
for (const
|
|
770
|
-
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(
|
|
919
|
+
for (const meta of commitOrder) {
|
|
920
|
+
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(meta) ?? [], ctx);
|
|
771
921
|
}
|
|
772
922
|
// update
|
|
773
|
-
for (const
|
|
774
|
-
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(
|
|
923
|
+
for (const meta of commitOrder) {
|
|
924
|
+
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(meta) ?? [], ctx);
|
|
775
925
|
}
|
|
776
926
|
// extra updates
|
|
777
927
|
await this.commitExtraUpdates(ChangeSetType.UPDATE, ctx);
|
|
778
928
|
// collection updates
|
|
779
929
|
await this.commitCollectionUpdates(ctx);
|
|
780
930
|
// delete - entity deletions need to be in reverse commit order
|
|
781
|
-
for (const
|
|
782
|
-
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(
|
|
931
|
+
for (const meta of commitOrderReversed) {
|
|
932
|
+
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(meta) ?? [], ctx);
|
|
783
933
|
}
|
|
784
934
|
// take snapshots of all persisted collections
|
|
785
935
|
const visited = new Set();
|
|
786
|
-
for (const changeSet of this
|
|
936
|
+
for (const changeSet of this.#changeSets.values()) {
|
|
787
937
|
this.takeCollectionSnapshots(changeSet.entity, visited);
|
|
788
938
|
}
|
|
789
939
|
}
|
|
@@ -792,15 +942,15 @@ export class UnitOfWork {
|
|
|
792
942
|
return;
|
|
793
943
|
}
|
|
794
944
|
const props = changeSets[0].meta.root.relations.filter(prop => {
|
|
795
|
-
return (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
|
|
796
|
-
|
|
797
|
-
|
|
945
|
+
return ((prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) ||
|
|
946
|
+
prop.kind === ReferenceKind.MANY_TO_ONE ||
|
|
947
|
+
(prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#platform.usesPivotTable()));
|
|
798
948
|
});
|
|
799
949
|
for (const changeSet of changeSets) {
|
|
800
950
|
this.findExtraUpdates(changeSet, props);
|
|
801
951
|
await this.runHooks(EventType.beforeCreate, changeSet, true);
|
|
802
952
|
}
|
|
803
|
-
await this
|
|
953
|
+
await this.#changeSetPersister.executeInserts(changeSets, { ctx });
|
|
804
954
|
for (const changeSet of changeSets) {
|
|
805
955
|
this.register(changeSet.entity, changeSet.payload, { refresh: true });
|
|
806
956
|
await this.runHooks(EventType.afterCreate, changeSet);
|
|
@@ -814,17 +964,29 @@ export class UnitOfWork {
|
|
|
814
964
|
}
|
|
815
965
|
if (Utils.isCollection(ref)) {
|
|
816
966
|
ref.getItems(false).some(item => {
|
|
817
|
-
const cs = this
|
|
818
|
-
const isScheduledForInsert = cs
|
|
967
|
+
const cs = this.#changeSets.get(Reference.unwrapReference(item));
|
|
968
|
+
const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
|
|
819
969
|
if (isScheduledForInsert) {
|
|
820
970
|
this.scheduleExtraUpdate(changeSet, [prop]);
|
|
821
971
|
return true;
|
|
822
972
|
}
|
|
823
973
|
return false;
|
|
824
974
|
});
|
|
975
|
+
continue;
|
|
825
976
|
}
|
|
826
|
-
const
|
|
827
|
-
|
|
977
|
+
const refEntity = Reference.unwrapReference(ref);
|
|
978
|
+
// For mapToPk properties, the value is a primitive (string/array), not an entity
|
|
979
|
+
if (!Utils.isEntity(refEntity)) {
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
// For TPT entities, check if the ROOT table's changeset has been persisted
|
|
983
|
+
// (since the FK is to the root table, not the concrete entity's table)
|
|
984
|
+
let cs = this.#changeSets.get(refEntity);
|
|
985
|
+
if (cs?.tptChangeSets?.length) {
|
|
986
|
+
// Root table changeset is the last one (ordered immediate parent → root)
|
|
987
|
+
cs = cs.tptChangeSets[cs.tptChangeSets.length - 1];
|
|
988
|
+
}
|
|
989
|
+
const isScheduledForInsert = cs?.type === ChangeSetType.CREATE && !cs.persisted;
|
|
828
990
|
if (isScheduledForInsert) {
|
|
829
991
|
this.scheduleExtraUpdate(changeSet, [prop]);
|
|
830
992
|
}
|
|
@@ -851,14 +1013,14 @@ export class UnitOfWork {
|
|
|
851
1013
|
for (const changeSet of changeSets) {
|
|
852
1014
|
await this.runHooks(EventType.beforeUpdate, changeSet, true);
|
|
853
1015
|
}
|
|
854
|
-
await this
|
|
1016
|
+
await this.#changeSetPersister.executeUpdates(changeSets, batched, { ctx });
|
|
855
1017
|
for (const changeSet of changeSets) {
|
|
856
1018
|
const wrapped = helper(changeSet.entity);
|
|
857
|
-
wrapped.__originalEntityData = this
|
|
858
|
-
wrapped.__touched = false;
|
|
1019
|
+
wrapped.__originalEntityData = this.#comparator.prepareEntity(changeSet.entity);
|
|
859
1020
|
if (!wrapped.__initialized) {
|
|
860
1021
|
for (const prop of changeSet.meta.relations) {
|
|
861
|
-
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
1022
|
+
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
1023
|
+
changeSet.entity[prop.name] == null) {
|
|
862
1024
|
changeSet.entity[prop.name] = Collection.create(changeSet.entity, prop.name, undefined, wrapped.isInitialized());
|
|
863
1025
|
}
|
|
864
1026
|
}
|
|
@@ -874,7 +1036,7 @@ export class UnitOfWork {
|
|
|
874
1036
|
for (const changeSet of changeSets) {
|
|
875
1037
|
await this.runHooks(EventType.beforeDelete, changeSet, true);
|
|
876
1038
|
}
|
|
877
|
-
await this
|
|
1039
|
+
await this.#changeSetPersister.executeDeletes(changeSets, { ctx });
|
|
878
1040
|
for (const changeSet of changeSets) {
|
|
879
1041
|
this.unsetIdentity(changeSet.entity);
|
|
880
1042
|
await this.runHooks(EventType.afterDelete, changeSet);
|
|
@@ -882,17 +1044,17 @@ export class UnitOfWork {
|
|
|
882
1044
|
}
|
|
883
1045
|
async commitExtraUpdates(type, ctx) {
|
|
884
1046
|
const extraUpdates = [];
|
|
885
|
-
for (const extraUpdate of this
|
|
1047
|
+
for (const extraUpdate of this.#extraUpdates) {
|
|
886
1048
|
if (extraUpdate[4] !== type) {
|
|
887
1049
|
continue;
|
|
888
1050
|
}
|
|
889
1051
|
if (Array.isArray(extraUpdate[1])) {
|
|
890
|
-
extraUpdate[1].forEach((p, i) => extraUpdate[0][p] = extraUpdate[2][i]);
|
|
1052
|
+
extraUpdate[1].forEach((p, i) => (extraUpdate[0][p] = extraUpdate[2][i]));
|
|
891
1053
|
}
|
|
892
1054
|
else {
|
|
893
1055
|
extraUpdate[0][extraUpdate[1]] = extraUpdate[2];
|
|
894
1056
|
}
|
|
895
|
-
const changeSet = this
|
|
1057
|
+
const changeSet = this.#changeSetComputer.computeChangeSet(extraUpdate[0]);
|
|
896
1058
|
if (changeSet) {
|
|
897
1059
|
extraUpdates.push([changeSet, extraUpdate[3]]);
|
|
898
1060
|
}
|
|
@@ -907,21 +1069,21 @@ export class UnitOfWork {
|
|
|
907
1069
|
}
|
|
908
1070
|
async commitCollectionUpdates(ctx) {
|
|
909
1071
|
this.filterCollectionUpdates();
|
|
910
|
-
const loggerContext = Utils.merge({ id: this
|
|
911
|
-
await this
|
|
1072
|
+
const loggerContext = Utils.merge({ id: this.#em._id }, this.#em.getLoggerContext({ disableContextResolution: true }));
|
|
1073
|
+
await this.#em.getDriver().syncCollections(this.#collectionUpdates, {
|
|
912
1074
|
ctx,
|
|
913
|
-
schema: this
|
|
1075
|
+
schema: this.#em.schema,
|
|
914
1076
|
loggerContext,
|
|
915
1077
|
});
|
|
916
|
-
for (const coll of this
|
|
1078
|
+
for (const coll of this.#collectionUpdates) {
|
|
917
1079
|
coll.takeSnapshot();
|
|
918
1080
|
}
|
|
919
1081
|
}
|
|
920
1082
|
filterCollectionUpdates() {
|
|
921
|
-
for (const coll of this
|
|
1083
|
+
for (const coll of this.#collectionUpdates) {
|
|
922
1084
|
let skip = true;
|
|
923
1085
|
if (coll.property.owner || coll.getItems(false).filter(item => !item.__helper.__initialized).length > 0) {
|
|
924
|
-
if (this
|
|
1086
|
+
if (this.#platform.usesPivotTable()) {
|
|
925
1087
|
skip = false;
|
|
926
1088
|
}
|
|
927
1089
|
}
|
|
@@ -932,7 +1094,7 @@ export class UnitOfWork {
|
|
|
932
1094
|
skip = false;
|
|
933
1095
|
}
|
|
934
1096
|
if (skip) {
|
|
935
|
-
this
|
|
1097
|
+
this.#collectionUpdates.delete(coll);
|
|
936
1098
|
}
|
|
937
1099
|
}
|
|
938
1100
|
}
|
|
@@ -947,12 +1109,24 @@ export class UnitOfWork {
|
|
|
947
1109
|
[ChangeSetType.UPDATE_EARLY]: new Map(),
|
|
948
1110
|
[ChangeSetType.DELETE_EARLY]: new Map(),
|
|
949
1111
|
};
|
|
950
|
-
|
|
1112
|
+
const addToGroup = (cs) => {
|
|
1113
|
+
// Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
|
|
1114
|
+
if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) &&
|
|
1115
|
+
!Utils.hasObjectKeys(cs.payload)) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
951
1118
|
const group = groups[cs.type];
|
|
952
|
-
const
|
|
1119
|
+
const groupKey = cs.meta.inheritanceType === 'tpt' ? cs.meta : cs.rootMeta;
|
|
1120
|
+
const classGroup = group.get(groupKey) ?? [];
|
|
953
1121
|
classGroup.push(cs);
|
|
954
|
-
if (!group.has(
|
|
955
|
-
group.set(
|
|
1122
|
+
if (!group.has(groupKey)) {
|
|
1123
|
+
group.set(groupKey, classGroup);
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
for (const cs of this.#changeSets.values()) {
|
|
1127
|
+
addToGroup(cs);
|
|
1128
|
+
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1129
|
+
addToGroup(parentCs);
|
|
956
1130
|
}
|
|
957
1131
|
}
|
|
958
1132
|
return groups;
|
|
@@ -960,21 +1134,42 @@ export class UnitOfWork {
|
|
|
960
1134
|
getCommitOrder() {
|
|
961
1135
|
const calc = new CommitOrderCalculator();
|
|
962
1136
|
const set = new Set();
|
|
963
|
-
this
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1137
|
+
this.#changeSets.forEach(cs => {
|
|
1138
|
+
if (cs.meta.inheritanceType === 'tpt') {
|
|
1139
|
+
set.add(cs.meta);
|
|
1140
|
+
for (const parentCs of cs.tptChangeSets ?? []) {
|
|
1141
|
+
set.add(parentCs.meta);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
set.add(cs.rootMeta);
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
set.forEach(meta => calc.addNode(meta._id));
|
|
1149
|
+
for (const meta of set) {
|
|
1150
|
+
for (const prop of meta.relations) {
|
|
1151
|
+
if (prop.polymorphTargets) {
|
|
1152
|
+
for (const targetMeta of prop.polymorphTargets) {
|
|
1153
|
+
calc.discoverProperty({ ...prop, targetMeta }, meta._id);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
calc.discoverProperty(prop, meta._id);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
// For TPT, parent table must be inserted BEFORE child tables
|
|
1161
|
+
if (meta.inheritanceType === 'tpt' && meta.tptParent && set.has(meta.tptParent)) {
|
|
1162
|
+
calc.addDependency(meta.tptParent._id, meta._id, 1);
|
|
968
1163
|
}
|
|
969
1164
|
}
|
|
970
|
-
return calc.sort();
|
|
1165
|
+
return calc.sort().map(id => this.#metadata.getById(id));
|
|
971
1166
|
}
|
|
972
1167
|
resetTransaction(oldTx) {
|
|
973
1168
|
if (oldTx) {
|
|
974
|
-
this
|
|
1169
|
+
this.#em.setTransactionContext(oldTx);
|
|
975
1170
|
}
|
|
976
1171
|
else {
|
|
977
|
-
this
|
|
1172
|
+
this.#em.resetTransactionContext();
|
|
978
1173
|
}
|
|
979
1174
|
}
|
|
980
1175
|
/**
|