@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.300
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 +114 -63
- package/EntityManager.js +385 -310
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +109 -143
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +17 -8
- package/cache/GeneratedCacheAdapter.d.ts +0 -1
- package/cache/GeneratedCacheAdapter.js +0 -2
- package/cache/index.d.ts +0 -1
- package/cache/index.js +0 -1
- package/connections/Connection.d.ts +16 -7
- package/connections/Connection.js +23 -14
- package/drivers/DatabaseDriver.d.ts +25 -16
- package/drivers/DatabaseDriver.js +119 -36
- package/drivers/IDatabaseDriver.d.ts +125 -23
- package/entity/BaseEntity.d.ts +63 -4
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +102 -31
- package/entity/Collection.js +446 -108
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +13 -1
- package/entity/EntityFactory.js +106 -60
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +65 -20
- package/entity/EntityLoader.d.ts +13 -11
- package/entity/EntityLoader.js +257 -107
- 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 +9 -12
- package/entity/Reference.js +34 -9
- package/entity/WrappedEntity.d.ts +3 -8
- package/entity/WrappedEntity.js +3 -8
- package/entity/defineEntity.d.ts +753 -0
- package/entity/defineEntity.js +537 -0
- package/entity/index.d.ts +4 -2
- package/entity/index.js +4 -2
- package/entity/utils.d.ts +13 -1
- package/entity/utils.js +49 -4
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +65 -0
- package/enums.d.ts +23 -8
- package/enums.js +15 -1
- package/errors.d.ts +25 -9
- package/errors.js +67 -21
- package/events/EventManager.d.ts +2 -1
- package/events/EventManager.js +19 -11
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +89 -36
- package/index.d.ts +2 -2
- package/index.js +1 -2
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/DefaultLogger.js +1 -0
- package/logging/SimpleLogger.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +7 -6
- package/logging/index.d.ts +1 -0
- package/logging/index.js +1 -0
- package/logging/inspect.d.ts +2 -0
- package/logging/inspect.js +11 -0
- package/metadata/EntitySchema.d.ts +53 -27
- package/metadata/EntitySchema.js +125 -52
- package/metadata/MetadataDiscovery.d.ts +64 -10
- package/metadata/MetadataDiscovery.js +823 -344
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +66 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +71 -38
- package/metadata/MetadataValidator.d.ts +32 -9
- package/metadata/MetadataValidator.js +198 -42
- 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 +1 -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 +20 -2
- 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/not-supported.d.ts +2 -0
- package/not-supported.js +4 -0
- package/package.json +22 -11
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +14 -16
- package/platforms/Platform.js +24 -44
- package/serialization/EntitySerializer.d.ts +8 -3
- package/serialization/EntitySerializer.js +47 -27
- package/serialization/EntityTransformer.js +33 -21
- package/serialization/SerializationContext.d.ts +6 -6
- package/serialization/SerializationContext.js +16 -13
- package/types/ArrayType.d.ts +1 -1
- package/types/ArrayType.js +2 -3
- package/types/BigIntType.d.ts +9 -6
- package/types/BigIntType.js +4 -1
- package/types/BlobType.d.ts +0 -1
- package/types/BlobType.js +0 -3
- package/types/BooleanType.d.ts +2 -1
- package/types/BooleanType.js +3 -0
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +3 -3
- package/types/DoubleType.js +2 -2
- 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 +1 -1
- package/typings.d.ts +469 -175
- package/typings.js +120 -45
- package/unit-of-work/ChangeSet.d.ts +4 -6
- package/unit-of-work/ChangeSet.js +4 -5
- package/unit-of-work/ChangeSetComputer.d.ts +3 -8
- package/unit-of-work/ChangeSetComputer.js +44 -21
- package/unit-of-work/ChangeSetPersister.d.ts +15 -12
- package/unit-of-work/ChangeSetPersister.js +113 -45
- package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
- package/unit-of-work/CommitOrderCalculator.js +13 -13
- package/unit-of-work/IdentityMap.d.ts +12 -0
- package/unit-of-work/IdentityMap.js +39 -1
- package/unit-of-work/UnitOfWork.d.ts +28 -3
- package/unit-of-work/UnitOfWork.js +315 -110
- package/utils/AbstractMigrator.d.ts +101 -0
- package/utils/AbstractMigrator.js +305 -0
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +32 -18
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +801 -207
- package/utils/Configuration.js +150 -191
- package/utils/ConfigurationLoader.d.ts +1 -54
- package/utils/ConfigurationLoader.js +1 -352
- package/utils/Cursor.d.ts +3 -6
- package/utils/Cursor.js +27 -11
- package/utils/DataloaderUtils.d.ts +15 -5
- package/utils/DataloaderUtils.js +65 -17
- package/utils/EntityComparator.d.ts +21 -10
- package/utils/EntityComparator.js +243 -106
- package/utils/QueryHelper.d.ts +24 -6
- package/utils/QueryHelper.js +122 -26
- package/utils/RawQueryFragment.d.ts +60 -32
- package/utils/RawQueryFragment.js +69 -66
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +223 -0
- package/utils/Utils.d.ts +15 -122
- package/utils/Utils.js +108 -376
- package/utils/clone.js +8 -23
- package/utils/env-vars.d.ts +7 -0
- package/utils/env-vars.js +97 -0
- package/utils/fs-utils.d.ts +34 -0
- package/utils/fs-utils.js +196 -0
- package/utils/index.d.ts +2 -3
- package/utils/index.js +2 -3
- package/utils/upsert-utils.d.ts +9 -4
- package/utils/upsert-utils.js +55 -4
- 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 -18
- package/decorators/Embedded.js +0 -18
- package/decorators/Entity.d.ts +0 -18
- package/decorators/Entity.js +0 -13
- 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 -5
- package/decorators/Formula.js +0 -15
- package/decorators/Indexed.d.ts +0 -17
- package/decorators/Indexed.js +0 -20
- package/decorators/ManyToMany.d.ts +0 -40
- package/decorators/ManyToMany.js +0 -14
- package/decorators/ManyToOne.d.ts +0 -30
- 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 -24
- package/decorators/OneToOne.js +0 -7
- package/decorators/PrimaryKey.d.ts +0 -9
- 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 -13
- 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 -116
- package/entity/ArrayCollection.js +0 -395
- 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
package/EntityManager.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import DataLoader from 'dataloader';
|
|
3
|
-
import { getOnConflictReturningFields } from './utils/upsert-utils.js';
|
|
1
|
+
import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
|
|
4
2
|
import { Utils } from './utils/Utils.js';
|
|
5
3
|
import { Cursor } from './utils/Cursor.js';
|
|
6
|
-
import { DataloaderUtils } from './utils/DataloaderUtils.js';
|
|
7
4
|
import { QueryHelper } from './utils/QueryHelper.js';
|
|
8
5
|
import { TransactionContext } from './utils/TransactionContext.js';
|
|
9
|
-
import { isRaw,
|
|
6
|
+
import { isRaw, Raw } from './utils/RawQueryFragment.js';
|
|
10
7
|
import { EntityFactory } from './entity/EntityFactory.js';
|
|
11
8
|
import { EntityAssigner } from './entity/EntityAssigner.js';
|
|
12
|
-
import {
|
|
9
|
+
import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
|
|
13
10
|
import { EntityLoader } from './entity/EntityLoader.js';
|
|
14
11
|
import { Reference } from './entity/Reference.js';
|
|
15
12
|
import { helper } from './entity/wrap.js';
|
|
@@ -19,6 +16,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
|
|
|
19
16
|
import { EventManager } from './events/EventManager.js';
|
|
20
17
|
import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
|
|
21
18
|
import { OptimisticLockError, ValidationError } from './errors.js';
|
|
19
|
+
import { applyPopulateHints, getLoadingStrategy } from './entity/utils.js';
|
|
20
|
+
import { TransactionManager } from './utils/TransactionManager.js';
|
|
22
21
|
/**
|
|
23
22
|
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
|
|
24
23
|
* such as UnitOfWork, Query Language, and Repository API.
|
|
@@ -34,10 +33,8 @@ export class EntityManager {
|
|
|
34
33
|
_id = EntityManager.counter++;
|
|
35
34
|
global = false;
|
|
36
35
|
name;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
validator;
|
|
40
|
-
repositoryMap = {};
|
|
36
|
+
loaders = {};
|
|
37
|
+
repositoryMap = new Map();
|
|
41
38
|
entityLoader;
|
|
42
39
|
comparator;
|
|
43
40
|
entityFactory;
|
|
@@ -61,7 +58,6 @@ export class EntityManager {
|
|
|
61
58
|
this.eventManager = eventManager;
|
|
62
59
|
this.entityLoader = new EntityLoader(this);
|
|
63
60
|
this.name = this.config.get('contextName');
|
|
64
|
-
this.validator = new EntityValidator(this.config.get('strict'));
|
|
65
61
|
this.comparator = this.config.getComparator(this.metadata);
|
|
66
62
|
this.resultCache = this.config.getResultCacheAdapter();
|
|
67
63
|
this.disableTransactions = this.config.get('disableTransactions');
|
|
@@ -91,13 +87,12 @@ export class EntityManager {
|
|
|
91
87
|
* Gets repository for given entity. You can pass either string name or entity class reference.
|
|
92
88
|
*/
|
|
93
89
|
getRepository(entityName) {
|
|
94
|
-
|
|
95
|
-
if (!this.repositoryMap
|
|
96
|
-
const meta = this.metadata.get(entityName);
|
|
90
|
+
const meta = this.metadata.get(entityName);
|
|
91
|
+
if (!this.repositoryMap.has(meta)) {
|
|
97
92
|
const RepositoryClass = this.config.getRepositoryClass(meta.repository);
|
|
98
|
-
this.repositoryMap
|
|
93
|
+
this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
|
|
99
94
|
}
|
|
100
|
-
return this.repositoryMap
|
|
95
|
+
return this.repositoryMap.get(meta);
|
|
101
96
|
}
|
|
102
97
|
/**
|
|
103
98
|
* Shortcut for `em.getRepository()`.
|
|
@@ -105,12 +100,6 @@ export class EntityManager {
|
|
|
105
100
|
repo(entityName) {
|
|
106
101
|
return this.getRepository(entityName);
|
|
107
102
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Gets EntityValidator instance
|
|
110
|
-
*/
|
|
111
|
-
getValidator() {
|
|
112
|
-
return this.validator;
|
|
113
|
-
}
|
|
114
103
|
/**
|
|
115
104
|
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
116
105
|
*/
|
|
@@ -125,10 +114,15 @@ export class EntityManager {
|
|
|
125
114
|
const em = this.getContext();
|
|
126
115
|
em.prepareOptions(options);
|
|
127
116
|
await em.tryFlush(entityName, options);
|
|
128
|
-
entityName = Utils.className(entityName);
|
|
129
117
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
validateParams(where);
|
|
119
|
+
const meta = this.metadata.get(entityName);
|
|
120
|
+
if (meta.orderBy) {
|
|
121
|
+
options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
options.orderBy ??= {};
|
|
125
|
+
}
|
|
132
126
|
options.populate = await em.preparePopulate(entityName, options);
|
|
133
127
|
const populate = options.populate;
|
|
134
128
|
const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
|
|
@@ -137,19 +131,18 @@ export class EntityManager {
|
|
|
137
131
|
await em.entityLoader.populate(entityName, cached.data, populate, {
|
|
138
132
|
...options,
|
|
139
133
|
...em.getPopulateWhere(where, options),
|
|
140
|
-
convertCustomTypes: false,
|
|
141
134
|
ignoreLazyScalarProperties: true,
|
|
142
135
|
lookup: false,
|
|
143
136
|
});
|
|
144
137
|
return cached.data;
|
|
145
138
|
}
|
|
146
|
-
const meta = this.metadata.get(entityName);
|
|
147
139
|
options = { ...options };
|
|
148
140
|
// save the original hint value so we know it was infer/all
|
|
149
141
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
150
142
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
151
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
152
|
-
|
|
143
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
144
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
145
|
+
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
153
146
|
if (results.length === 0) {
|
|
154
147
|
await em.storeCache(options.cache, cached, []);
|
|
155
148
|
return [];
|
|
@@ -168,7 +161,6 @@ export class EntityManager {
|
|
|
168
161
|
await em.entityLoader.populate(entityName, unique, populate, {
|
|
169
162
|
...options,
|
|
170
163
|
...em.getPopulateWhere(where, options),
|
|
171
|
-
convertCustomTypes: false,
|
|
172
164
|
ignoreLazyScalarProperties: true,
|
|
173
165
|
lookup: false,
|
|
174
166
|
});
|
|
@@ -181,6 +173,60 @@ export class EntityManager {
|
|
|
181
173
|
}
|
|
182
174
|
return unique;
|
|
183
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Finds all entities and returns an async iterable (async generator) that yields results one by one.
|
|
178
|
+
* The results are merged and mapped to entity instances, without adding them to the identity map.
|
|
179
|
+
* You can disable merging by passing the options `{ mergeResults: false }`.
|
|
180
|
+
* With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
|
|
181
|
+
* root entities when there are multiple items in the populated collection.
|
|
182
|
+
* This is useful for processing large datasets without loading everything into memory at once.
|
|
183
|
+
*
|
|
184
|
+
* ```ts
|
|
185
|
+
* const stream = em.stream(Book, { populate: ['author'] });
|
|
186
|
+
*
|
|
187
|
+
* for await (const book of stream) {
|
|
188
|
+
* // book is an instance of Book entity
|
|
189
|
+
* console.log(book.title, book.author.name);
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
async *stream(entityName, options = {}) {
|
|
194
|
+
const em = this.getContext();
|
|
195
|
+
em.prepareOptions(options);
|
|
196
|
+
options.strategy = 'joined';
|
|
197
|
+
await em.tryFlush(entityName, options);
|
|
198
|
+
const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
|
|
199
|
+
validateParams(where);
|
|
200
|
+
options.orderBy = options.orderBy || {};
|
|
201
|
+
options.populate = await em.preparePopulate(entityName, options);
|
|
202
|
+
const meta = this.metadata.get(entityName);
|
|
203
|
+
options = { ...options };
|
|
204
|
+
// save the original hint value so we know it was infer/all
|
|
205
|
+
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
206
|
+
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
207
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
208
|
+
const stream = em.driver.stream(entityName, where, {
|
|
209
|
+
ctx: em.transactionContext,
|
|
210
|
+
mapResults: false,
|
|
211
|
+
...options,
|
|
212
|
+
});
|
|
213
|
+
for await (const data of stream) {
|
|
214
|
+
const fork = em.fork();
|
|
215
|
+
const entity = fork.entityFactory.create(entityName, data, {
|
|
216
|
+
refresh: options.refresh,
|
|
217
|
+
schema: options.schema,
|
|
218
|
+
convertCustomTypes: true,
|
|
219
|
+
});
|
|
220
|
+
helper(entity).setSerializationContext({
|
|
221
|
+
populate: options.populate,
|
|
222
|
+
fields: options.fields,
|
|
223
|
+
exclude: options.exclude,
|
|
224
|
+
});
|
|
225
|
+
await fork.unitOfWork.dispatchOnLoadEvent();
|
|
226
|
+
fork.clear();
|
|
227
|
+
yield entity;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
184
230
|
/**
|
|
185
231
|
* Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
|
|
186
232
|
*/
|
|
@@ -194,7 +240,7 @@ export class EntityManager {
|
|
|
194
240
|
if (options.populateWhere === PopulateHint.ALL) {
|
|
195
241
|
return { where: {}, populateWhere: options.populateWhere };
|
|
196
242
|
}
|
|
197
|
-
/* v8 ignore next
|
|
243
|
+
/* v8 ignore next */
|
|
198
244
|
if (options.populateWhere === PopulateHint.INFER) {
|
|
199
245
|
return { where, populateWhere: options.populateWhere };
|
|
200
246
|
}
|
|
@@ -203,12 +249,12 @@ export class EntityManager {
|
|
|
203
249
|
/**
|
|
204
250
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
205
251
|
*/
|
|
206
|
-
addFilter(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
|
|
252
|
+
addFilter(options) {
|
|
253
|
+
if (options.entity) {
|
|
254
|
+
options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
|
|
210
255
|
}
|
|
211
|
-
|
|
256
|
+
options.default ??= true;
|
|
257
|
+
this.getContext(false).filters[options.name] = options;
|
|
212
258
|
}
|
|
213
259
|
/**
|
|
214
260
|
* Sets filter parameter values globally inside context defined by this entity manager.
|
|
@@ -232,8 +278,8 @@ export class EntityManager {
|
|
|
232
278
|
/**
|
|
233
279
|
* Gets logger context for this entity manager.
|
|
234
280
|
*/
|
|
235
|
-
getLoggerContext() {
|
|
236
|
-
const em = this.getContext();
|
|
281
|
+
getLoggerContext(options) {
|
|
282
|
+
const em = options?.disableContextResolution ? this : this.getContext();
|
|
237
283
|
em.loggerContext ??= {};
|
|
238
284
|
return em.loggerContext;
|
|
239
285
|
}
|
|
@@ -253,21 +299,29 @@ export class EntityManager {
|
|
|
253
299
|
where = this.applyDiscriminatorCondition(entityName, where);
|
|
254
300
|
return where;
|
|
255
301
|
}
|
|
302
|
+
async processUnionWhere(entityName, options, type) {
|
|
303
|
+
if (options.unionWhere?.length) {
|
|
304
|
+
if (!this.driver.getPlatform().supportsUnionWhere()) {
|
|
305
|
+
throw new Error(`unionWhere is only supported on SQL drivers`);
|
|
306
|
+
}
|
|
307
|
+
options.unionWhere = await Promise.all(options.unionWhere.map(branch => this.processWhere(entityName, branch, options, type)));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
256
310
|
// this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
|
|
257
311
|
applyDiscriminatorCondition(entityName, where) {
|
|
258
312
|
const meta = this.metadata.find(entityName);
|
|
259
|
-
if (!meta?.discriminatorValue) {
|
|
313
|
+
if (meta?.root.inheritanceType !== 'sti' || !meta?.discriminatorValue) {
|
|
260
314
|
return where;
|
|
261
315
|
}
|
|
262
|
-
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.
|
|
316
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
263
317
|
const children = [];
|
|
264
318
|
const lookUpChildren = (ret, type) => {
|
|
265
319
|
const children = types.filter(meta2 => meta2.extends === type);
|
|
266
|
-
children.forEach(m => lookUpChildren(ret, m.
|
|
320
|
+
children.forEach(m => lookUpChildren(ret, m.class));
|
|
267
321
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
268
322
|
return children;
|
|
269
323
|
};
|
|
270
|
-
lookUpChildren(children, meta.
|
|
324
|
+
lookUpChildren(children, meta.class);
|
|
271
325
|
/* v8 ignore next */
|
|
272
326
|
where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
|
|
273
327
|
return where;
|
|
@@ -283,72 +337,104 @@ export class EntityManager {
|
|
|
283
337
|
}
|
|
284
338
|
return ret;
|
|
285
339
|
}
|
|
286
|
-
async getJoinedFilters(meta,
|
|
340
|
+
async getJoinedFilters(meta, options) {
|
|
341
|
+
// If user provided populateFilter, merge it with computed filters
|
|
342
|
+
const userFilter = options.populateFilter;
|
|
343
|
+
if (!this.config.get('filtersOnRelations') || !options.populate) {
|
|
344
|
+
return userFilter;
|
|
345
|
+
}
|
|
287
346
|
const ret = {};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
347
|
+
for (const hint of options.populate) {
|
|
348
|
+
const field = hint.field.split(':')[0];
|
|
349
|
+
const prop = meta.properties[field];
|
|
350
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
351
|
+
const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
|
|
352
|
+
if (!joined && !hint.filter) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
356
|
+
const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
|
|
357
|
+
...options,
|
|
358
|
+
populate: hint.children,
|
|
359
|
+
});
|
|
360
|
+
const where2 = await this.getJoinedFilters(prop.targetMeta, {
|
|
361
|
+
...options,
|
|
362
|
+
filters,
|
|
363
|
+
populate: hint.children,
|
|
364
|
+
populateWhere: PopulateHint.ALL,
|
|
365
|
+
});
|
|
366
|
+
if (Utils.hasObjectKeys(where)) {
|
|
367
|
+
ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
|
|
368
|
+
}
|
|
369
|
+
if (where2 && Utils.hasObjectKeys(where2)) {
|
|
370
|
+
if (ret[field]) {
|
|
371
|
+
Utils.merge(ret[field], where2);
|
|
300
372
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Utils.merge(ret[field], where2);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
ret[field] = where2;
|
|
307
|
-
}
|
|
373
|
+
else {
|
|
374
|
+
ret[field] = where2;
|
|
308
375
|
}
|
|
309
376
|
}
|
|
310
377
|
}
|
|
311
|
-
|
|
378
|
+
// Merge user-provided populateFilter with computed filters
|
|
379
|
+
if (userFilter) {
|
|
380
|
+
Utils.merge(ret, userFilter);
|
|
381
|
+
}
|
|
382
|
+
return Utils.hasObjectKeys(ret) ? ret : undefined;
|
|
312
383
|
}
|
|
313
384
|
/**
|
|
314
385
|
* When filters are active on M:1 or 1:1 relations, we need to ref join them eagerly as they might affect the FK value.
|
|
315
386
|
*/
|
|
316
|
-
async autoJoinRefsForFilters(meta, options) {
|
|
317
|
-
if (!meta || !this.config.get('autoJoinRefsForFilters')) {
|
|
387
|
+
async autoJoinRefsForFilters(meta, options, parent) {
|
|
388
|
+
if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
|
|
318
389
|
return;
|
|
319
390
|
}
|
|
320
|
-
const props = meta.relations.filter(prop => {
|
|
321
|
-
return !prop.object && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
|
|
322
|
-
&& ((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)));
|
|
323
|
-
});
|
|
324
391
|
const ret = options.populate;
|
|
325
|
-
for (const prop of
|
|
326
|
-
|
|
392
|
+
for (const prop of meta.relations) {
|
|
393
|
+
if (prop.object
|
|
394
|
+
|| ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
|
|
395
|
+
|| !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
396
|
+
|| (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
400
|
+
const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
|
|
327
401
|
if (!Utils.isEmpty(cond)) {
|
|
328
402
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
329
|
-
|
|
330
|
-
|
|
403
|
+
let found = false;
|
|
404
|
+
for (const hint of populated) {
|
|
405
|
+
if (!hint.all) {
|
|
406
|
+
hint.filter = true;
|
|
407
|
+
}
|
|
408
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
409
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
410
|
+
found = true;
|
|
411
|
+
}
|
|
331
412
|
}
|
|
332
|
-
|
|
413
|
+
if (!found) {
|
|
333
414
|
ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
|
|
334
415
|
}
|
|
335
416
|
}
|
|
336
417
|
}
|
|
418
|
+
for (const hint of ret) {
|
|
419
|
+
const [field, ref] = hint.field.split(':');
|
|
420
|
+
const prop = meta?.properties[field];
|
|
421
|
+
if (prop && !ref) {
|
|
422
|
+
hint.children ??= [];
|
|
423
|
+
await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
337
426
|
}
|
|
338
427
|
/**
|
|
339
428
|
* @internal
|
|
340
429
|
*/
|
|
341
430
|
async applyFilters(entityName, where, options, type, findOptions) {
|
|
342
|
-
const meta = this.metadata.
|
|
431
|
+
const meta = this.metadata.get(entityName);
|
|
343
432
|
const filters = [];
|
|
344
433
|
const ret = [];
|
|
345
|
-
if (!meta) {
|
|
346
|
-
return where;
|
|
347
|
-
}
|
|
348
434
|
const active = new Set();
|
|
349
435
|
const push = (source) => {
|
|
350
436
|
const activeFilters = QueryHelper
|
|
351
|
-
.getActiveFilters(
|
|
437
|
+
.getActiveFilters(meta, options, source)
|
|
352
438
|
.filter(f => !active.has(f.name));
|
|
353
439
|
filters.push(...activeFilters);
|
|
354
440
|
activeFilters.forEach(f => active.add(f.name));
|
|
@@ -363,24 +449,28 @@ export class EntityManager {
|
|
|
363
449
|
let cond;
|
|
364
450
|
if (filter.cond instanceof Function) {
|
|
365
451
|
// @ts-ignore
|
|
366
|
-
const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
452
|
+
const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
367
453
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
368
454
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
369
455
|
}
|
|
370
|
-
cond = await filter.cond(args, type, this, findOptions);
|
|
456
|
+
cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
|
|
371
457
|
}
|
|
372
458
|
else {
|
|
373
459
|
cond = filter.cond;
|
|
374
460
|
}
|
|
375
|
-
|
|
461
|
+
cond = QueryHelper.processWhere({
|
|
376
462
|
where: cond,
|
|
377
463
|
entityName,
|
|
378
464
|
metadata: this.metadata,
|
|
379
465
|
platform: this.driver.getPlatform(),
|
|
380
466
|
aliased: type === 'read',
|
|
381
|
-
})
|
|
467
|
+
});
|
|
468
|
+
if (filter.strict) {
|
|
469
|
+
Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
|
|
470
|
+
}
|
|
471
|
+
ret.push(cond);
|
|
382
472
|
}
|
|
383
|
-
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
|
|
473
|
+
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
|
|
384
474
|
return conds.length > 1 ? { $and: conds } : conds[0];
|
|
385
475
|
}
|
|
386
476
|
/**
|
|
@@ -391,12 +481,10 @@ export class EntityManager {
|
|
|
391
481
|
const em = this.getContext(false);
|
|
392
482
|
await em.tryFlush(entityName, options);
|
|
393
483
|
options.flushMode = 'commit'; // do not try to auto flush again
|
|
394
|
-
return
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
]);
|
|
399
|
-
});
|
|
484
|
+
return Promise.all([
|
|
485
|
+
em.find(entityName, where, options),
|
|
486
|
+
em.count(entityName, where, options),
|
|
487
|
+
]);
|
|
400
488
|
}
|
|
401
489
|
/**
|
|
402
490
|
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
|
|
@@ -412,27 +500,31 @@ export class EntityManager {
|
|
|
412
500
|
* - POJO/entity instance
|
|
413
501
|
*
|
|
414
502
|
* ```ts
|
|
415
|
-
* const currentCursor = await em.findByCursor(User, {
|
|
503
|
+
* const currentCursor = await em.findByCursor(User, {
|
|
416
504
|
* first: 10,
|
|
417
505
|
* after: previousCursor, // cursor instance
|
|
418
506
|
* orderBy: { id: 'desc' },
|
|
419
507
|
* });
|
|
420
508
|
*
|
|
421
509
|
* // to fetch next page
|
|
422
|
-
* const nextCursor = await em.findByCursor(User, {
|
|
510
|
+
* const nextCursor = await em.findByCursor(User, {
|
|
423
511
|
* first: 10,
|
|
424
512
|
* after: currentCursor.endCursor, // opaque string
|
|
425
513
|
* orderBy: { id: 'desc' },
|
|
426
514
|
* });
|
|
427
515
|
*
|
|
428
516
|
* // to fetch next page
|
|
429
|
-
* const nextCursor2 = await em.findByCursor(User, {
|
|
517
|
+
* const nextCursor2 = await em.findByCursor(User, {
|
|
430
518
|
* first: 10,
|
|
431
519
|
* after: { id: lastSeenId }, // entity-like POJO
|
|
432
520
|
* orderBy: { id: 'desc' },
|
|
433
521
|
* });
|
|
434
522
|
* ```
|
|
435
523
|
*
|
|
524
|
+
* The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
|
|
525
|
+
* returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
|
|
526
|
+
* of pages.
|
|
527
|
+
*
|
|
436
528
|
* The `Cursor` object provides the following interface:
|
|
437
529
|
*
|
|
438
530
|
* ```ts
|
|
@@ -442,7 +534,7 @@ export class EntityManager {
|
|
|
442
534
|
* User { ... },
|
|
443
535
|
* User { ... },
|
|
444
536
|
* ],
|
|
445
|
-
* totalCount: 50,
|
|
537
|
+
* totalCount: 50, // not included if `includeCount: false`
|
|
446
538
|
* startCursor: 'WzRd',
|
|
447
539
|
* endCursor: 'WzZd',
|
|
448
540
|
* hasPrevPage: true,
|
|
@@ -450,14 +542,16 @@ export class EntityManager {
|
|
|
450
542
|
* }
|
|
451
543
|
* ```
|
|
452
544
|
*/
|
|
453
|
-
async findByCursor(entityName,
|
|
545
|
+
async findByCursor(entityName, options) {
|
|
454
546
|
const em = this.getContext(false);
|
|
455
|
-
entityName = Utils.className(entityName);
|
|
456
547
|
options.overfetch ??= true;
|
|
457
|
-
|
|
548
|
+
options.where ??= {};
|
|
549
|
+
if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
|
|
458
550
|
throw new Error('Explicit `orderBy` option required');
|
|
459
551
|
}
|
|
460
|
-
const [entities, count] =
|
|
552
|
+
const [entities, count] = options.includeCount !== false
|
|
553
|
+
? await em.findAndCount(entityName, options.where, options)
|
|
554
|
+
: [await em.find(entityName, options.where, options)];
|
|
461
555
|
return new Cursor(entities, count, options, this.metadata.get(entityName));
|
|
462
556
|
}
|
|
463
557
|
/**
|
|
@@ -469,9 +563,9 @@ export class EntityManager {
|
|
|
469
563
|
const ret = await this.refresh(entity, options);
|
|
470
564
|
if (!ret) {
|
|
471
565
|
options.failHandler ??= this.config.get('findOneOrFailHandler');
|
|
472
|
-
const
|
|
473
|
-
const where =
|
|
474
|
-
throw options.failHandler(
|
|
566
|
+
const wrapped = helper(entity);
|
|
567
|
+
const where = wrapped.getPrimaryKey();
|
|
568
|
+
throw options.failHandler(wrapped.__meta.className, where);
|
|
475
569
|
}
|
|
476
570
|
return ret;
|
|
477
571
|
}
|
|
@@ -482,19 +576,31 @@ export class EntityManager {
|
|
|
482
576
|
*/
|
|
483
577
|
async refresh(entity, options = {}) {
|
|
484
578
|
const fork = this.fork({ keepTransactionContext: true });
|
|
485
|
-
const
|
|
486
|
-
const reloaded = await fork.findOne(
|
|
487
|
-
schema:
|
|
579
|
+
const wrapped = helper(entity);
|
|
580
|
+
const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
|
|
581
|
+
schema: wrapped.__schema,
|
|
488
582
|
...options,
|
|
489
583
|
flushMode: FlushMode.COMMIT,
|
|
490
584
|
});
|
|
491
|
-
|
|
492
|
-
|
|
585
|
+
const em = this.getContext();
|
|
586
|
+
if (!reloaded) {
|
|
587
|
+
em.unitOfWork.unsetIdentity(entity);
|
|
588
|
+
return null;
|
|
493
589
|
}
|
|
494
|
-
|
|
495
|
-
|
|
590
|
+
let found = false;
|
|
591
|
+
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
592
|
+
const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
|
|
593
|
+
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
|
|
594
|
+
em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
|
|
595
|
+
Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
|
|
596
|
+
found ||= ref === entity;
|
|
597
|
+
}
|
|
598
|
+
if (!found) {
|
|
599
|
+
const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
|
|
600
|
+
em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
|
|
601
|
+
Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
|
|
496
602
|
}
|
|
497
|
-
return
|
|
603
|
+
return entity;
|
|
498
604
|
}
|
|
499
605
|
/**
|
|
500
606
|
* Finds first entity matching your `where` query.
|
|
@@ -508,7 +614,6 @@ export class EntityManager {
|
|
|
508
614
|
return ret;
|
|
509
615
|
}
|
|
510
616
|
const em = this.getContext();
|
|
511
|
-
entityName = Utils.className(entityName);
|
|
512
617
|
em.prepareOptions(options);
|
|
513
618
|
let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
|
|
514
619
|
// query for a not managed entity which is already in the identity map as it
|
|
@@ -520,33 +625,36 @@ export class EntityManager {
|
|
|
520
625
|
await em.tryFlush(entityName, options);
|
|
521
626
|
const meta = em.metadata.get(entityName);
|
|
522
627
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
523
|
-
|
|
628
|
+
validateEmptyWhere(where);
|
|
524
629
|
em.checkLockRequirements(options.lockMode, meta);
|
|
525
|
-
const isOptimisticLocking =
|
|
630
|
+
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
|
|
526
631
|
if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
|
|
527
632
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
528
633
|
}
|
|
529
|
-
|
|
634
|
+
validateParams(where);
|
|
530
635
|
options.populate = await em.preparePopulate(entityName, options);
|
|
531
636
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
532
637
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
533
|
-
if (cached?.data) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
638
|
+
if (cached?.data !== undefined) {
|
|
639
|
+
if (cached.data) {
|
|
640
|
+
await em.entityLoader.populate(entityName, [cached.data], options.populate, {
|
|
641
|
+
...options,
|
|
642
|
+
...em.getPopulateWhere(where, options),
|
|
643
|
+
ignoreLazyScalarProperties: true,
|
|
644
|
+
lookup: false,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
541
647
|
return cached.data;
|
|
542
648
|
}
|
|
543
649
|
options = { ...options };
|
|
544
650
|
// save the original hint value so we know it was infer/all
|
|
545
651
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
546
652
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
547
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
653
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
654
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
548
655
|
const data = await em.driver.findOne(entityName, where, {
|
|
549
656
|
ctx: em.transactionContext,
|
|
657
|
+
em,
|
|
550
658
|
...options,
|
|
551
659
|
});
|
|
552
660
|
if (!data) {
|
|
@@ -584,10 +692,10 @@ export class EntityManager {
|
|
|
584
692
|
if (!entity || isStrictViolation) {
|
|
585
693
|
const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
|
|
586
694
|
options.failHandler ??= this.config.get(key);
|
|
587
|
-
|
|
695
|
+
const name = Utils.className(entityName);
|
|
588
696
|
/* v8 ignore next */
|
|
589
697
|
where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
|
|
590
|
-
throw options.failHandler(
|
|
698
|
+
throw options.failHandler(name, where);
|
|
591
699
|
}
|
|
592
700
|
return entity;
|
|
593
701
|
}
|
|
@@ -627,11 +735,11 @@ export class EntityManager {
|
|
|
627
735
|
let where;
|
|
628
736
|
let entity = null;
|
|
629
737
|
if (data === undefined) {
|
|
630
|
-
entityName = entityNameOrEntity.constructor
|
|
738
|
+
entityName = entityNameOrEntity.constructor;
|
|
631
739
|
data = entityNameOrEntity;
|
|
632
740
|
}
|
|
633
741
|
else {
|
|
634
|
-
entityName =
|
|
742
|
+
entityName = entityNameOrEntity;
|
|
635
743
|
}
|
|
636
744
|
const meta = this.metadata.get(entityName);
|
|
637
745
|
const convertCustomTypes = !Utils.isEntity(data);
|
|
@@ -654,26 +762,9 @@ export class EntityManager {
|
|
|
654
762
|
}
|
|
655
763
|
}
|
|
656
764
|
}
|
|
657
|
-
|
|
658
|
-
const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
|
|
659
|
-
if (options.onConflictFields || where == null) {
|
|
660
|
-
if (propIndex !== false && propIndex >= 0) {
|
|
661
|
-
where = { [unique[propIndex]]: data[unique[propIndex]] };
|
|
662
|
-
}
|
|
663
|
-
else if (meta.uniques.length > 0) {
|
|
664
|
-
for (const u of meta.uniques) {
|
|
665
|
-
if (Utils.asArray(u.properties).every(p => data[p] != null)) {
|
|
666
|
-
where = Utils.asArray(u.properties).reduce((o, key) => {
|
|
667
|
-
o[key] = data[key];
|
|
668
|
-
return o;
|
|
669
|
-
}, {});
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
765
|
+
where = getWhereCondition(meta, options.onConflictFields, data, where).where;
|
|
675
766
|
data = QueryHelper.processObjectParams(data);
|
|
676
|
-
|
|
767
|
+
validateParams(data, 'insert data');
|
|
677
768
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
678
769
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
|
|
679
770
|
}
|
|
@@ -711,13 +802,14 @@ export class EntityManager {
|
|
|
711
802
|
where[meta.primaryKeys[0]] = ret.insertId;
|
|
712
803
|
}
|
|
713
804
|
}
|
|
714
|
-
const data2 = await this.driver.findOne(meta.
|
|
805
|
+
const data2 = await this.driver.findOne(meta.class, where, {
|
|
715
806
|
fields: returning,
|
|
716
807
|
ctx: em.transactionContext,
|
|
717
808
|
convertCustomTypes: true,
|
|
718
809
|
connectionType: 'write',
|
|
810
|
+
schema: options.schema,
|
|
719
811
|
});
|
|
720
|
-
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full');
|
|
812
|
+
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
|
|
721
813
|
}
|
|
722
814
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
723
815
|
const snapshot = this.comparator.prepareEntity(entity);
|
|
@@ -765,11 +857,11 @@ export class EntityManager {
|
|
|
765
857
|
let entityName;
|
|
766
858
|
let propIndex;
|
|
767
859
|
if (data === undefined) {
|
|
768
|
-
entityName = entityNameOrEntity[0].constructor
|
|
860
|
+
entityName = entityNameOrEntity[0].constructor;
|
|
769
861
|
data = entityNameOrEntity;
|
|
770
862
|
}
|
|
771
863
|
else {
|
|
772
|
-
entityName =
|
|
864
|
+
entityName = entityNameOrEntity;
|
|
773
865
|
}
|
|
774
866
|
const batchSize = options.batchSize ?? this.config.get('batchSize');
|
|
775
867
|
if (data.length > batchSize) {
|
|
@@ -813,32 +905,18 @@ export class EntityManager {
|
|
|
813
905
|
}
|
|
814
906
|
}
|
|
815
907
|
}
|
|
816
|
-
const unique = meta.props.filter(p => p.unique).map(p => p.name);
|
|
817
|
-
propIndex = unique.findIndex(p =>
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
where = { [unique[propIndex]]: row[unique[propIndex]] };
|
|
821
|
-
}
|
|
822
|
-
else if (meta.uniques.length > 0) {
|
|
823
|
-
for (const u of meta.uniques) {
|
|
824
|
-
if (Utils.asArray(u.properties).every(p => row[p] != null)) {
|
|
825
|
-
where = Utils.asArray(u.properties).reduce((o, key) => {
|
|
826
|
-
o[key] = row[key];
|
|
827
|
-
return o;
|
|
828
|
-
}, {});
|
|
829
|
-
break;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
row = QueryHelper.processObjectParams(row);
|
|
908
|
+
const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
|
|
909
|
+
propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
910
|
+
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
911
|
+
propIndex = tmp.propIndex;
|
|
835
912
|
where = QueryHelper.processWhere({
|
|
836
|
-
where,
|
|
913
|
+
where: tmp.where,
|
|
837
914
|
entityName,
|
|
838
915
|
metadata: this.metadata,
|
|
839
916
|
platform: this.getPlatform(),
|
|
840
917
|
});
|
|
841
|
-
|
|
918
|
+
row = QueryHelper.processObjectParams(row);
|
|
919
|
+
validateParams(row, 'insert data');
|
|
842
920
|
allData.push(row);
|
|
843
921
|
allWhere.push(where);
|
|
844
922
|
}
|
|
@@ -879,7 +957,7 @@ export class EntityManager {
|
|
|
879
957
|
const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
|
|
880
958
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
881
959
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
882
|
-
const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
|
|
960
|
+
const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
|
|
883
961
|
for (const cond of loadPK.values()) {
|
|
884
962
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
885
963
|
}
|
|
@@ -891,11 +969,12 @@ export class EntityManager {
|
|
|
891
969
|
where.$or[idx][prop] = item[prop];
|
|
892
970
|
});
|
|
893
971
|
});
|
|
894
|
-
const data2 = await this.driver.find(meta.
|
|
972
|
+
const data2 = await this.driver.find(meta.class, where, {
|
|
895
973
|
fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
|
|
896
974
|
ctx: em.transactionContext,
|
|
897
975
|
convertCustomTypes: true,
|
|
898
976
|
connectionType: 'write',
|
|
977
|
+
schema: options.schema,
|
|
899
978
|
});
|
|
900
979
|
for (const [entity, cond] of loadPK.entries()) {
|
|
901
980
|
const row = data2.find(row => {
|
|
@@ -907,11 +986,11 @@ export class EntityManager {
|
|
|
907
986
|
});
|
|
908
987
|
return this.comparator.matching(entityName, cond, tmp);
|
|
909
988
|
});
|
|
910
|
-
/* v8 ignore next
|
|
989
|
+
/* v8 ignore next */
|
|
911
990
|
if (!row) {
|
|
912
991
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
913
992
|
}
|
|
914
|
-
em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full');
|
|
993
|
+
em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full', false, true);
|
|
915
994
|
}
|
|
916
995
|
if (loadPK.size !== data2.length && Array.isArray(uniqueFields)) {
|
|
917
996
|
for (let i = 0; i < allData.length; i++) {
|
|
@@ -930,7 +1009,7 @@ export class EntityManager {
|
|
|
930
1009
|
}, {});
|
|
931
1010
|
return this.comparator.matching(entityName, cond, pk);
|
|
932
1011
|
});
|
|
933
|
-
/* v8 ignore next
|
|
1012
|
+
/* v8 ignore next */
|
|
934
1013
|
if (!row) {
|
|
935
1014
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
936
1015
|
}
|
|
@@ -952,45 +1031,37 @@ export class EntityManager {
|
|
|
952
1031
|
}
|
|
953
1032
|
/**
|
|
954
1033
|
* Runs your callback wrapped inside a database transaction.
|
|
1034
|
+
*
|
|
1035
|
+
* If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
|
|
1036
|
+
* can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
|
|
1037
|
+
* should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
|
|
1038
|
+
* method automatically creates an async context for the transaction.
|
|
1039
|
+
*
|
|
1040
|
+
* **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
|
|
1041
|
+
* `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
|
|
1042
|
+
* between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
|
|
1043
|
+
* the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
|
|
1044
|
+
*
|
|
1045
|
+
* **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
|
|
1046
|
+
* parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
|
|
1047
|
+
* and then call this method on the fork.
|
|
1048
|
+
*
|
|
1049
|
+
* **Example:**
|
|
1050
|
+
* ```ts
|
|
1051
|
+
* await em.transactional(async (em) => {
|
|
1052
|
+
* const author = new Author('Jon');
|
|
1053
|
+
* em.persist(author);
|
|
1054
|
+
* // flush is called automatically at the end of the callback
|
|
1055
|
+
* });
|
|
1056
|
+
* ```
|
|
955
1057
|
*/
|
|
956
1058
|
async transactional(cb, options = {}) {
|
|
957
1059
|
const em = this.getContext(false);
|
|
958
1060
|
if (this.disableTransactions || em.disableTransactions) {
|
|
959
1061
|
return cb(em);
|
|
960
1062
|
}
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
flushMode: options.flushMode,
|
|
964
|
-
cloneEventManager: true,
|
|
965
|
-
disableTransactions: options.ignoreNestedTransactions,
|
|
966
|
-
loggerContext: options.loggerContext,
|
|
967
|
-
});
|
|
968
|
-
options.ctx ??= em.transactionContext;
|
|
969
|
-
const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
|
|
970
|
-
return TransactionContext.create(fork, async () => {
|
|
971
|
-
return fork.getConnection().transactional(async (trx) => {
|
|
972
|
-
fork.transactionContext = trx;
|
|
973
|
-
if (propagateToUpperContext) {
|
|
974
|
-
fork.eventManager.registerSubscriber({
|
|
975
|
-
afterFlush(args) {
|
|
976
|
-
args.uow.getChangeSets()
|
|
977
|
-
.filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
|
|
978
|
-
.forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
|
|
979
|
-
},
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
const ret = await cb(fork);
|
|
983
|
-
await fork.flush();
|
|
984
|
-
if (propagateToUpperContext) {
|
|
985
|
-
// ensure all entities from inner context are merged to the upper one
|
|
986
|
-
for (const entity of fork.unitOfWork.getIdentityMap()) {
|
|
987
|
-
em.unitOfWork.register(entity);
|
|
988
|
-
entity.__helper.__em = em;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return ret;
|
|
992
|
-
}, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
|
|
993
|
-
});
|
|
1063
|
+
const manager = new TransactionManager(this);
|
|
1064
|
+
return manager.handle(cb, options);
|
|
994
1065
|
}
|
|
995
1066
|
/**
|
|
996
1067
|
* Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
|
|
@@ -1051,11 +1122,11 @@ export class EntityManager {
|
|
|
1051
1122
|
em.prepareOptions(options);
|
|
1052
1123
|
let entityName;
|
|
1053
1124
|
if (data === undefined) {
|
|
1054
|
-
entityName = entityNameOrEntity.constructor
|
|
1125
|
+
entityName = entityNameOrEntity.constructor;
|
|
1055
1126
|
data = entityNameOrEntity;
|
|
1056
1127
|
}
|
|
1057
1128
|
else {
|
|
1058
|
-
entityName =
|
|
1129
|
+
entityName = entityNameOrEntity;
|
|
1059
1130
|
}
|
|
1060
1131
|
if (Utils.isEntity(data)) {
|
|
1061
1132
|
if (options.schema && helper(data).getSchema() == null) {
|
|
@@ -1074,7 +1145,7 @@ export class EntityManager {
|
|
|
1074
1145
|
return cs.getPrimaryKey();
|
|
1075
1146
|
}
|
|
1076
1147
|
data = QueryHelper.processObjectParams(data);
|
|
1077
|
-
|
|
1148
|
+
validateParams(data, 'insert data');
|
|
1078
1149
|
const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
|
|
1079
1150
|
return res.insertId;
|
|
1080
1151
|
}
|
|
@@ -1086,11 +1157,11 @@ export class EntityManager {
|
|
|
1086
1157
|
em.prepareOptions(options);
|
|
1087
1158
|
let entityName;
|
|
1088
1159
|
if (data === undefined) {
|
|
1089
|
-
entityName = entityNameOrEntities[0].constructor
|
|
1160
|
+
entityName = entityNameOrEntities[0].constructor;
|
|
1090
1161
|
data = entityNameOrEntities;
|
|
1091
1162
|
}
|
|
1092
1163
|
else {
|
|
1093
|
-
entityName =
|
|
1164
|
+
entityName = entityNameOrEntities;
|
|
1094
1165
|
}
|
|
1095
1166
|
if (data.length === 0) {
|
|
1096
1167
|
return [];
|
|
@@ -1114,7 +1185,7 @@ export class EntityManager {
|
|
|
1114
1185
|
return css.map(cs => cs.getPrimaryKey());
|
|
1115
1186
|
}
|
|
1116
1187
|
data = data.map(row => QueryHelper.processObjectParams(row));
|
|
1117
|
-
data.forEach(row =>
|
|
1188
|
+
data.forEach(row => validateParams(row, 'insert data'));
|
|
1118
1189
|
const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
|
|
1119
1190
|
if (res.insertedIds) {
|
|
1120
1191
|
return res.insertedIds;
|
|
@@ -1127,12 +1198,12 @@ export class EntityManager {
|
|
|
1127
1198
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
1128
1199
|
const em = this.getContext(false);
|
|
1129
1200
|
em.prepareOptions(options);
|
|
1130
|
-
|
|
1201
|
+
await em.processUnionWhere(entityName, options, 'update');
|
|
1131
1202
|
data = QueryHelper.processObjectParams(data);
|
|
1132
1203
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
|
|
1204
|
+
validateParams(data, 'update data');
|
|
1205
|
+
validateParams(where, 'update condition');
|
|
1206
|
+
const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, em, ...options });
|
|
1136
1207
|
return res.affectedRows;
|
|
1137
1208
|
}
|
|
1138
1209
|
/**
|
|
@@ -1141,28 +1212,29 @@ export class EntityManager {
|
|
|
1141
1212
|
async nativeDelete(entityName, where, options = {}) {
|
|
1142
1213
|
const em = this.getContext(false);
|
|
1143
1214
|
em.prepareOptions(options);
|
|
1144
|
-
|
|
1215
|
+
await em.processUnionWhere(entityName, options, 'delete');
|
|
1145
1216
|
where = await em.processWhere(entityName, where, options, 'delete');
|
|
1146
|
-
|
|
1147
|
-
const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1217
|
+
validateParams(where, 'delete condition');
|
|
1218
|
+
const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
1148
1219
|
return res.affectedRows;
|
|
1149
1220
|
}
|
|
1150
1221
|
/**
|
|
1151
1222
|
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
1152
1223
|
*/
|
|
1153
1224
|
map(entityName, result, options = {}) {
|
|
1154
|
-
entityName = Utils.className(entityName);
|
|
1155
1225
|
const meta = this.metadata.get(entityName);
|
|
1156
1226
|
const data = this.driver.mapResult(result, meta);
|
|
1157
|
-
Object.keys(data)
|
|
1227
|
+
for (const k of Object.keys(data)) {
|
|
1158
1228
|
const prop = meta.properties[k];
|
|
1159
|
-
if (prop
|
|
1160
|
-
|
|
1229
|
+
if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
|
|
1230
|
+
validateProperty(prop, data[k], data);
|
|
1161
1231
|
}
|
|
1162
|
-
}
|
|
1232
|
+
}
|
|
1163
1233
|
return this.merge(entityName, data, {
|
|
1164
1234
|
convertCustomTypes: true,
|
|
1165
|
-
refresh: true,
|
|
1235
|
+
refresh: true,
|
|
1236
|
+
validate: false,
|
|
1237
|
+
...options,
|
|
1166
1238
|
});
|
|
1167
1239
|
}
|
|
1168
1240
|
/**
|
|
@@ -1170,22 +1242,22 @@ export class EntityManager {
|
|
|
1170
1242
|
* via second parameter. By default, it will return already loaded entities without modifying them.
|
|
1171
1243
|
*/
|
|
1172
1244
|
merge(entityName, data, options = {}) {
|
|
1173
|
-
const em = this.getContext();
|
|
1174
1245
|
if (Utils.isEntity(entityName)) {
|
|
1175
|
-
return
|
|
1246
|
+
return this.merge(entityName.constructor, entityName, data);
|
|
1176
1247
|
}
|
|
1248
|
+
const em = options.disableContextResolution ? this : this.getContext();
|
|
1177
1249
|
options.schema ??= em._schema;
|
|
1178
|
-
|
|
1179
|
-
|
|
1250
|
+
options.validate ??= true;
|
|
1251
|
+
options.cascade ??= true;
|
|
1252
|
+
validatePrimaryKey(data, em.metadata.get(entityName));
|
|
1180
1253
|
let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
|
|
1181
1254
|
if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
|
|
1182
1255
|
return entity;
|
|
1183
1256
|
}
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
em.
|
|
1188
|
-
em.unitOfWork.merge(entity);
|
|
1257
|
+
const dataIsEntity = Utils.isEntity(data);
|
|
1258
|
+
entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1259
|
+
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1260
|
+
em.unitOfWork.merge(entity, visited);
|
|
1189
1261
|
return entity;
|
|
1190
1262
|
}
|
|
1191
1263
|
/**
|
|
@@ -1210,6 +1282,7 @@ export class EntityManager {
|
|
|
1210
1282
|
...options,
|
|
1211
1283
|
newEntity: !options.managed,
|
|
1212
1284
|
merge: options.managed,
|
|
1285
|
+
normalizeAccessors: true,
|
|
1213
1286
|
});
|
|
1214
1287
|
options.persist ??= em.config.get('persistOnCreate');
|
|
1215
1288
|
if (options.persist && !this.getMetadata(entityName).embeddable) {
|
|
@@ -1229,7 +1302,7 @@ export class EntityManager {
|
|
|
1229
1302
|
getReference(entityName, id, options = {}) {
|
|
1230
1303
|
options.schema ??= this.schema;
|
|
1231
1304
|
options.convertCustomTypes ??= false;
|
|
1232
|
-
const meta = this.metadata.get(
|
|
1305
|
+
const meta = this.metadata.get(entityName);
|
|
1233
1306
|
if (Utils.isPrimaryKey(id)) {
|
|
1234
1307
|
if (meta.compositePK) {
|
|
1235
1308
|
throw ValidationError.invalidCompositeIdentifier(meta);
|
|
@@ -1248,11 +1321,8 @@ export class EntityManager {
|
|
|
1248
1321
|
async count(entityName, where = {}, options = {}) {
|
|
1249
1322
|
const em = this.getContext(false);
|
|
1250
1323
|
// Shallow copy options since the object will be modified when deleting orderBy
|
|
1251
|
-
options = {
|
|
1252
|
-
|
|
1253
|
-
...options,
|
|
1254
|
-
};
|
|
1255
|
-
entityName = Utils.className(entityName);
|
|
1324
|
+
options = { ...options };
|
|
1325
|
+
em.prepareOptions(options);
|
|
1256
1326
|
await em.tryFlush(entityName, options);
|
|
1257
1327
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1258
1328
|
options.populate = await em.preparePopulate(entityName, options);
|
|
@@ -1261,15 +1331,16 @@ export class EntityManager {
|
|
|
1261
1331
|
const meta = em.metadata.find(entityName);
|
|
1262
1332
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
1263
1333
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
1264
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
1265
|
-
|
|
1334
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
1335
|
+
validateParams(where);
|
|
1266
1336
|
delete options.orderBy;
|
|
1337
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
1267
1338
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1268
1339
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1269
|
-
if (cached?.data) {
|
|
1340
|
+
if (cached?.data !== undefined) {
|
|
1270
1341
|
return cached.data;
|
|
1271
1342
|
}
|
|
1272
|
-
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1343
|
+
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
1273
1344
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1274
1345
|
return +count;
|
|
1275
1346
|
}
|
|
@@ -1288,7 +1359,7 @@ export class EntityManager {
|
|
|
1288
1359
|
for (const ent of entities) {
|
|
1289
1360
|
if (!Utils.isEntity(ent, true)) {
|
|
1290
1361
|
/* v8 ignore next */
|
|
1291
|
-
const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor
|
|
1362
|
+
const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
|
|
1292
1363
|
throw ValidationError.notDiscoveredEntity(ent, meta);
|
|
1293
1364
|
}
|
|
1294
1365
|
// do not cascade just yet, cascading of entities in persist stack is done when flushing
|
|
@@ -1296,13 +1367,6 @@ export class EntityManager {
|
|
|
1296
1367
|
}
|
|
1297
1368
|
return this;
|
|
1298
1369
|
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Persists your entity immediately, flushing all not yet persisted changes to the database too.
|
|
1301
|
-
* Equivalent to `em.persist(e).flush()`.
|
|
1302
|
-
*/
|
|
1303
|
-
async persistAndFlush(entity) {
|
|
1304
|
-
await this.persist(entity).flush();
|
|
1305
|
-
}
|
|
1306
1370
|
/**
|
|
1307
1371
|
* Marks entity for removal.
|
|
1308
1372
|
* A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -1326,13 +1390,6 @@ export class EntityManager {
|
|
|
1326
1390
|
}
|
|
1327
1391
|
return em;
|
|
1328
1392
|
}
|
|
1329
|
-
/**
|
|
1330
|
-
* Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
|
|
1331
|
-
* Equivalent to `em.remove(e).flush()`
|
|
1332
|
-
*/
|
|
1333
|
-
async removeAndFlush(entity) {
|
|
1334
|
-
await this.remove(entity).flush();
|
|
1335
|
-
}
|
|
1336
1393
|
/**
|
|
1337
1394
|
* Flushes all changes to objects that have been queued up to now to the database.
|
|
1338
1395
|
* This effectively synchronizes the in-memory state of managed objects with the database.
|
|
@@ -1346,7 +1403,6 @@ export class EntityManager {
|
|
|
1346
1403
|
async tryFlush(entityName, options) {
|
|
1347
1404
|
const em = this.getContext();
|
|
1348
1405
|
const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
|
|
1349
|
-
entityName = Utils.className(entityName);
|
|
1350
1406
|
const meta = em.metadata.get(entityName);
|
|
1351
1407
|
if (flushMode === FlushMode.COMMIT) {
|
|
1352
1408
|
return;
|
|
@@ -1365,7 +1421,6 @@ export class EntityManager {
|
|
|
1365
1421
|
* Checks whether given property can be populated on the entity.
|
|
1366
1422
|
*/
|
|
1367
1423
|
canPopulate(entityName, property) {
|
|
1368
|
-
entityName = Utils.className(entityName);
|
|
1369
1424
|
// eslint-disable-next-line prefer-const
|
|
1370
1425
|
let [p, ...parts] = property.split('.');
|
|
1371
1426
|
const meta = this.metadata.find(entityName);
|
|
@@ -1375,12 +1430,11 @@ export class EntityManager {
|
|
|
1375
1430
|
if (p.includes(':')) {
|
|
1376
1431
|
p = p.split(':', 2)[0];
|
|
1377
1432
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}
|
|
1433
|
+
// For TPT inheritance, check the entity's own properties, not just the root's
|
|
1434
|
+
// For STI, meta.properties includes all properties anyway
|
|
1435
|
+
const ret = p in meta.properties;
|
|
1382
1436
|
if (parts.length > 0) {
|
|
1383
|
-
return this.canPopulate(
|
|
1437
|
+
return this.canPopulate(meta.properties[p].targetMeta.class, parts.join('.'));
|
|
1384
1438
|
}
|
|
1385
1439
|
return ret;
|
|
1386
1440
|
}
|
|
@@ -1394,8 +1448,8 @@ export class EntityManager {
|
|
|
1394
1448
|
}
|
|
1395
1449
|
const em = this.getContext();
|
|
1396
1450
|
em.prepareOptions(options);
|
|
1397
|
-
const entityName = arr[0].constructor
|
|
1398
|
-
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
|
|
1451
|
+
const entityName = arr[0].constructor;
|
|
1452
|
+
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
|
|
1399
1453
|
await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1400
1454
|
return entities;
|
|
1401
1455
|
}
|
|
@@ -1431,6 +1485,9 @@ export class EntityManager {
|
|
|
1431
1485
|
for (const entity of em.unitOfWork.getIdentityMap()) {
|
|
1432
1486
|
fork.unitOfWork.register(entity);
|
|
1433
1487
|
}
|
|
1488
|
+
for (const entity of em.unitOfWork.getPersistStack()) {
|
|
1489
|
+
fork.unitOfWork.persist(entity);
|
|
1490
|
+
}
|
|
1434
1491
|
for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
|
|
1435
1492
|
fork.unitOfWork.getOrphanRemoveStack().add(entity);
|
|
1436
1493
|
}
|
|
@@ -1452,6 +1509,12 @@ export class EntityManager {
|
|
|
1452
1509
|
getEntityFactory() {
|
|
1453
1510
|
return this.getContext().entityFactory;
|
|
1454
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
|
|
1514
|
+
*/
|
|
1515
|
+
getEntityLoader() {
|
|
1516
|
+
return this.getContext().entityLoader;
|
|
1517
|
+
}
|
|
1455
1518
|
/**
|
|
1456
1519
|
* Gets the Hydrator used by the EntityManager.
|
|
1457
1520
|
*/
|
|
@@ -1514,7 +1577,6 @@ export class EntityManager {
|
|
|
1514
1577
|
*/
|
|
1515
1578
|
getMetadata(entityName) {
|
|
1516
1579
|
if (entityName) {
|
|
1517
|
-
entityName = Utils.className(entityName);
|
|
1518
1580
|
return this.metadata.get(entityName);
|
|
1519
1581
|
}
|
|
1520
1582
|
return this.metadata;
|
|
@@ -1543,12 +1605,11 @@ export class EntityManager {
|
|
|
1543
1605
|
lockTableAliases: options.lockTableAliases,
|
|
1544
1606
|
});
|
|
1545
1607
|
}
|
|
1546
|
-
const preparedPopulate = await this.preparePopulate(meta.
|
|
1547
|
-
await this.entityLoader.populate(meta.
|
|
1608
|
+
const preparedPopulate = await this.preparePopulate(meta.class, options);
|
|
1609
|
+
await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
|
|
1548
1610
|
...options,
|
|
1549
1611
|
...this.getPopulateWhere(where, options),
|
|
1550
1612
|
orderBy: options.populateOrderBy ?? options.orderBy,
|
|
1551
|
-
convertCustomTypes: false,
|
|
1552
1613
|
ignoreLazyScalarProperties: true,
|
|
1553
1614
|
lookup: false,
|
|
1554
1615
|
});
|
|
@@ -1565,6 +1626,7 @@ export class EntityManager {
|
|
|
1565
1626
|
return ret;
|
|
1566
1627
|
}, []);
|
|
1567
1628
|
}
|
|
1629
|
+
/** @internal */
|
|
1568
1630
|
async preparePopulate(entityName, options, validate = true) {
|
|
1569
1631
|
if (options.populate === false) {
|
|
1570
1632
|
return [];
|
|
@@ -1605,13 +1667,13 @@ export class EntityManager {
|
|
|
1605
1667
|
options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
|
|
1606
1668
|
}
|
|
1607
1669
|
if (!options.populate) {
|
|
1608
|
-
const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
|
|
1670
|
+
const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
|
|
1609
1671
|
await this.autoJoinRefsForFilters(meta, { ...options, populate });
|
|
1610
1672
|
return populate;
|
|
1611
1673
|
}
|
|
1612
1674
|
if (typeof options.populate !== 'boolean') {
|
|
1613
1675
|
options.populate = Utils.asArray(options.populate).map(field => {
|
|
1614
|
-
/* v8 ignore next
|
|
1676
|
+
/* v8 ignore next */
|
|
1615
1677
|
if (typeof field === 'boolean' || field === PopulatePath.ALL) {
|
|
1616
1678
|
return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
|
|
1617
1679
|
}
|
|
@@ -1621,24 +1683,27 @@ export class EntityManager {
|
|
|
1621
1683
|
options.flags.push(QueryFlag.INFER_POPULATE);
|
|
1622
1684
|
return [];
|
|
1623
1685
|
}
|
|
1624
|
-
if (
|
|
1686
|
+
if (typeof field === 'string') {
|
|
1625
1687
|
return [{ field, strategy: options.strategy }];
|
|
1626
1688
|
}
|
|
1627
1689
|
return [field];
|
|
1628
1690
|
}).flat();
|
|
1629
1691
|
}
|
|
1630
|
-
const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
|
|
1692
|
+
const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
|
|
1631
1693
|
const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
|
|
1632
1694
|
if (validate && invalid) {
|
|
1633
1695
|
throw ValidationError.invalidPropertyName(entityName, invalid.field);
|
|
1634
1696
|
}
|
|
1635
1697
|
await this.autoJoinRefsForFilters(meta, { ...options, populate });
|
|
1636
|
-
|
|
1698
|
+
for (const field of populate) {
|
|
1637
1699
|
// force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
|
|
1638
1700
|
const all = field.all ?? (Array.isArray(options.populate) && options.populate.includes('*'));
|
|
1639
1701
|
field.strategy = all ? LoadStrategy.SELECT_IN : (options.strategy ?? field.strategy);
|
|
1640
|
-
|
|
1641
|
-
|
|
1702
|
+
}
|
|
1703
|
+
if (options.populateHints) {
|
|
1704
|
+
applyPopulateHints(populate, options.populateHints);
|
|
1705
|
+
}
|
|
1706
|
+
return populate;
|
|
1642
1707
|
}
|
|
1643
1708
|
/**
|
|
1644
1709
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
@@ -1658,7 +1723,7 @@ export class EntityManager {
|
|
|
1658
1723
|
return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
|
|
1659
1724
|
});
|
|
1660
1725
|
}
|
|
1661
|
-
if (autoRefresh) {
|
|
1726
|
+
if (autoRefresh || options.filters) {
|
|
1662
1727
|
return true;
|
|
1663
1728
|
}
|
|
1664
1729
|
if (Array.isArray(options.populate)) {
|
|
@@ -1671,7 +1736,7 @@ export class EntityManager {
|
|
|
1671
1736
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1672
1737
|
}
|
|
1673
1738
|
options.schema ??= this._schema;
|
|
1674
|
-
options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1739
|
+
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1675
1740
|
}
|
|
1676
1741
|
/**
|
|
1677
1742
|
* @internal
|
|
@@ -1682,7 +1747,7 @@ export class EntityManager {
|
|
|
1682
1747
|
for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
|
|
1683
1748
|
delete opts[k];
|
|
1684
1749
|
}
|
|
1685
|
-
return [entityName, method, opts, where];
|
|
1750
|
+
return [Utils.className(entityName), method, opts, where];
|
|
1686
1751
|
}
|
|
1687
1752
|
/**
|
|
1688
1753
|
* @internal
|
|
@@ -1695,31 +1760,27 @@ export class EntityManager {
|
|
|
1695
1760
|
const em = this.getContext();
|
|
1696
1761
|
const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
|
|
1697
1762
|
const cached = await em.resultCache.get(cacheKey);
|
|
1698
|
-
if (cached) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
else {
|
|
1717
|
-
data = cached;
|
|
1718
|
-
}
|
|
1719
|
-
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1720
|
-
return { key: cacheKey, data };
|
|
1763
|
+
if (!cached) {
|
|
1764
|
+
return { key: cacheKey, data: cached };
|
|
1765
|
+
}
|
|
1766
|
+
let data;
|
|
1767
|
+
const createOptions = {
|
|
1768
|
+
merge: true,
|
|
1769
|
+
convertCustomTypes: false,
|
|
1770
|
+
refresh,
|
|
1771
|
+
recomputeSnapshot: true,
|
|
1772
|
+
};
|
|
1773
|
+
if (Array.isArray(cached) && merge) {
|
|
1774
|
+
data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
|
|
1775
|
+
}
|
|
1776
|
+
else if (Utils.isObject(cached) && merge) {
|
|
1777
|
+
data = em.entityFactory.create(entityName, cached, createOptions);
|
|
1778
|
+
}
|
|
1779
|
+
else {
|
|
1780
|
+
data = cached;
|
|
1721
1781
|
}
|
|
1722
|
-
|
|
1782
|
+
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1783
|
+
return { key: cacheKey, data };
|
|
1723
1784
|
}
|
|
1724
1785
|
/**
|
|
1725
1786
|
* @internal
|
|
@@ -1728,7 +1789,7 @@ export class EntityManager {
|
|
|
1728
1789
|
config ??= this.config.get('resultCache').global;
|
|
1729
1790
|
if (config) {
|
|
1730
1791
|
const em = this.getContext();
|
|
1731
|
-
const expiration = Array.isArray(config) ? config[1] : (
|
|
1792
|
+
const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
|
|
1732
1793
|
await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
|
|
1733
1794
|
}
|
|
1734
1795
|
}
|
|
@@ -1761,6 +1822,20 @@ export class EntityManager {
|
|
|
1761
1822
|
set schema(schema) {
|
|
1762
1823
|
this.getContext(false)._schema = schema ?? undefined;
|
|
1763
1824
|
}
|
|
1825
|
+
/** @internal */
|
|
1826
|
+
async getDataLoader(type) {
|
|
1827
|
+
const em = this.getContext();
|
|
1828
|
+
if (em.loaders[type]) {
|
|
1829
|
+
return em.loaders[type];
|
|
1830
|
+
}
|
|
1831
|
+
const { DataloaderUtils } = await import('@mikro-orm/core/dataloader');
|
|
1832
|
+
const DataLoader = await DataloaderUtils.getDataLoader();
|
|
1833
|
+
switch (type) {
|
|
1834
|
+
case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
|
|
1835
|
+
case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
1836
|
+
case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1764
1839
|
/**
|
|
1765
1840
|
* Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
|
|
1766
1841
|
* if executed inside request context handler.
|
|
@@ -1769,7 +1844,7 @@ export class EntityManager {
|
|
|
1769
1844
|
return this.getContext(false)._id;
|
|
1770
1845
|
}
|
|
1771
1846
|
/** @ignore */
|
|
1772
|
-
[inspect.custom]() {
|
|
1847
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
1773
1848
|
return `[EntityManager<${this.id}>]`;
|
|
1774
1849
|
}
|
|
1775
1850
|
}
|