@mikro-orm/core 7.0.0-dev.2 → 7.0.0-dev.200
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 +111 -61
- package/EntityManager.js +346 -300
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +103 -143
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +8 -7
- 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 +80 -35
- package/drivers/IDatabaseDriver.d.ts +47 -17
- package/entity/BaseEntity.d.ts +2 -2
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +95 -31
- package/entity/Collection.js +444 -102
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +13 -1
- package/entity/EntityFactory.js +88 -54
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +38 -15
- package/entity/EntityLoader.d.ts +8 -7
- package/entity/EntityLoader.js +134 -80
- package/entity/EntityRepository.d.ts +24 -4
- package/entity/EntityRepository.js +8 -2
- package/entity/Reference.d.ts +9 -12
- package/entity/Reference.js +34 -9
- package/entity/WrappedEntity.d.ts +2 -7
- package/entity/WrappedEntity.js +3 -8
- package/entity/defineEntity.d.ts +585 -0
- package/entity/defineEntity.js +533 -0
- package/entity/index.d.ts +3 -2
- package/entity/index.js +3 -2
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +16 -4
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +65 -0
- package/enums.d.ts +22 -6
- package/enums.js +15 -1
- package/errors.d.ts +23 -9
- package/errors.js +59 -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 +53 -33
- 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 +26 -26
- package/metadata/EntitySchema.js +82 -51
- package/metadata/MetadataDiscovery.d.ts +7 -10
- package/metadata/MetadataDiscovery.js +408 -335
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +46 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +70 -37
- package/metadata/MetadataValidator.d.ts +17 -9
- package/metadata/MetadataValidator.js +100 -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 +502 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
- package/naming-strategy/AbstractNamingStrategy.js +14 -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 +24 -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 +19 -11
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +7 -13
- package/platforms/Platform.js +20 -43
- package/serialization/EntitySerializer.d.ts +5 -0
- package/serialization/EntitySerializer.js +47 -27
- package/serialization/EntityTransformer.js +28 -18
- 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 +300 -140
- package/typings.js +62 -44
- package/unit-of-work/ChangeSet.d.ts +2 -6
- package/unit-of-work/ChangeSet.js +4 -5
- package/unit-of-work/ChangeSetComputer.d.ts +1 -3
- package/unit-of-work/ChangeSetComputer.js +26 -13
- package/unit-of-work/ChangeSetPersister.d.ts +5 -4
- package/unit-of-work/ChangeSetPersister.js +77 -35
- 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 +23 -3
- package/unit-of-work/UnitOfWork.js +199 -106
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +22 -17
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +779 -207
- package/utils/Configuration.js +146 -190
- 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 +13 -9
- package/utils/EntityComparator.js +164 -89
- package/utils/QueryHelper.d.ts +14 -6
- package/utils/QueryHelper.js +88 -26
- package/utils/RawQueryFragment.d.ts +48 -25
- package/utils/RawQueryFragment.js +67 -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 +13 -120
- package/utils/Utils.js +104 -375
- 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 +32 -0
- package/utils/fs-utils.js +178 -0
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- 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 -29
- 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,13 @@
|
|
|
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
4
|
import { DataloaderUtils } from './utils/DataloaderUtils.js';
|
|
7
5
|
import { QueryHelper } from './utils/QueryHelper.js';
|
|
8
6
|
import { TransactionContext } from './utils/TransactionContext.js';
|
|
9
|
-
import { isRaw,
|
|
7
|
+
import { isRaw, Raw } from './utils/RawQueryFragment.js';
|
|
10
8
|
import { EntityFactory } from './entity/EntityFactory.js';
|
|
11
9
|
import { EntityAssigner } from './entity/EntityAssigner.js';
|
|
12
|
-
import {
|
|
10
|
+
import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
|
|
13
11
|
import { EntityLoader } from './entity/EntityLoader.js';
|
|
14
12
|
import { Reference } from './entity/Reference.js';
|
|
15
13
|
import { helper } from './entity/wrap.js';
|
|
@@ -19,6 +17,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
|
|
|
19
17
|
import { EventManager } from './events/EventManager.js';
|
|
20
18
|
import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
|
|
21
19
|
import { OptimisticLockError, ValidationError } from './errors.js';
|
|
20
|
+
import { getLoadingStrategy } from './entity/utils.js';
|
|
21
|
+
import { TransactionManager } from './utils/TransactionManager.js';
|
|
22
22
|
/**
|
|
23
23
|
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
|
|
24
24
|
* such as UnitOfWork, Query Language, and Repository API.
|
|
@@ -34,10 +34,8 @@ export class EntityManager {
|
|
|
34
34
|
_id = EntityManager.counter++;
|
|
35
35
|
global = false;
|
|
36
36
|
name;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
validator;
|
|
40
|
-
repositoryMap = {};
|
|
37
|
+
loaders = {};
|
|
38
|
+
repositoryMap = new Map();
|
|
41
39
|
entityLoader;
|
|
42
40
|
comparator;
|
|
43
41
|
entityFactory;
|
|
@@ -61,7 +59,6 @@ export class EntityManager {
|
|
|
61
59
|
this.eventManager = eventManager;
|
|
62
60
|
this.entityLoader = new EntityLoader(this);
|
|
63
61
|
this.name = this.config.get('contextName');
|
|
64
|
-
this.validator = new EntityValidator(this.config.get('strict'));
|
|
65
62
|
this.comparator = this.config.getComparator(this.metadata);
|
|
66
63
|
this.resultCache = this.config.getResultCacheAdapter();
|
|
67
64
|
this.disableTransactions = this.config.get('disableTransactions');
|
|
@@ -91,13 +88,12 @@ export class EntityManager {
|
|
|
91
88
|
* Gets repository for given entity. You can pass either string name or entity class reference.
|
|
92
89
|
*/
|
|
93
90
|
getRepository(entityName) {
|
|
94
|
-
|
|
95
|
-
if (!this.repositoryMap
|
|
96
|
-
const meta = this.metadata.get(entityName);
|
|
91
|
+
const meta = this.metadata.get(entityName);
|
|
92
|
+
if (!this.repositoryMap.has(meta)) {
|
|
97
93
|
const RepositoryClass = this.config.getRepositoryClass(meta.repository);
|
|
98
|
-
this.repositoryMap
|
|
94
|
+
this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
|
|
99
95
|
}
|
|
100
|
-
return this.repositoryMap
|
|
96
|
+
return this.repositoryMap.get(meta);
|
|
101
97
|
}
|
|
102
98
|
/**
|
|
103
99
|
* Shortcut for `em.getRepository()`.
|
|
@@ -105,12 +101,6 @@ export class EntityManager {
|
|
|
105
101
|
repo(entityName) {
|
|
106
102
|
return this.getRepository(entityName);
|
|
107
103
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Gets EntityValidator instance
|
|
110
|
-
*/
|
|
111
|
-
getValidator() {
|
|
112
|
-
return this.validator;
|
|
113
|
-
}
|
|
114
104
|
/**
|
|
115
105
|
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
116
106
|
*/
|
|
@@ -125,9 +115,8 @@ export class EntityManager {
|
|
|
125
115
|
const em = this.getContext();
|
|
126
116
|
em.prepareOptions(options);
|
|
127
117
|
await em.tryFlush(entityName, options);
|
|
128
|
-
entityName = Utils.className(entityName);
|
|
129
118
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
130
|
-
|
|
119
|
+
validateParams(where);
|
|
131
120
|
options.orderBy = options.orderBy || {};
|
|
132
121
|
options.populate = await em.preparePopulate(entityName, options);
|
|
133
122
|
const populate = options.populate;
|
|
@@ -137,7 +126,6 @@ export class EntityManager {
|
|
|
137
126
|
await em.entityLoader.populate(entityName, cached.data, populate, {
|
|
138
127
|
...options,
|
|
139
128
|
...em.getPopulateWhere(where, options),
|
|
140
|
-
convertCustomTypes: false,
|
|
141
129
|
ignoreLazyScalarProperties: true,
|
|
142
130
|
lookup: false,
|
|
143
131
|
});
|
|
@@ -148,8 +136,8 @@ export class EntityManager {
|
|
|
148
136
|
// save the original hint value so we know it was infer/all
|
|
149
137
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
150
138
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
151
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
152
|
-
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, ...options });
|
|
139
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
140
|
+
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
153
141
|
if (results.length === 0) {
|
|
154
142
|
await em.storeCache(options.cache, cached, []);
|
|
155
143
|
return [];
|
|
@@ -168,7 +156,6 @@ export class EntityManager {
|
|
|
168
156
|
await em.entityLoader.populate(entityName, unique, populate, {
|
|
169
157
|
...options,
|
|
170
158
|
...em.getPopulateWhere(where, options),
|
|
171
|
-
convertCustomTypes: false,
|
|
172
159
|
ignoreLazyScalarProperties: true,
|
|
173
160
|
lookup: false,
|
|
174
161
|
});
|
|
@@ -181,6 +168,60 @@ export class EntityManager {
|
|
|
181
168
|
}
|
|
182
169
|
return unique;
|
|
183
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Finds all entities and returns an async iterable (async generator) that yields results one by one.
|
|
173
|
+
* The results are merged and mapped to entity instances, without adding them to the identity map.
|
|
174
|
+
* You can disable merging by passing the options `{ mergeResults: false }`.
|
|
175
|
+
* With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
|
|
176
|
+
* root entities when there are multiple items in the populated collection.
|
|
177
|
+
* This is useful for processing large datasets without loading everything into memory at once.
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* const stream = em.stream(Book, { populate: ['author'] });
|
|
181
|
+
*
|
|
182
|
+
* for await (const book of stream) {
|
|
183
|
+
* // book is an instance of Book entity
|
|
184
|
+
* console.log(book.title, book.author.name);
|
|
185
|
+
* }
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
async *stream(entityName, options = {}) {
|
|
189
|
+
const em = this.getContext();
|
|
190
|
+
em.prepareOptions(options);
|
|
191
|
+
options.strategy = 'joined';
|
|
192
|
+
await em.tryFlush(entityName, options);
|
|
193
|
+
const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
|
|
194
|
+
validateParams(where);
|
|
195
|
+
options.orderBy = options.orderBy || {};
|
|
196
|
+
options.populate = await em.preparePopulate(entityName, options);
|
|
197
|
+
const meta = this.metadata.get(entityName);
|
|
198
|
+
options = { ...options };
|
|
199
|
+
// save the original hint value so we know it was infer/all
|
|
200
|
+
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
201
|
+
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
202
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
203
|
+
const stream = em.driver.stream(entityName, where, {
|
|
204
|
+
ctx: em.transactionContext,
|
|
205
|
+
mapResults: false,
|
|
206
|
+
...options,
|
|
207
|
+
});
|
|
208
|
+
for await (const data of stream) {
|
|
209
|
+
const fork = em.fork();
|
|
210
|
+
const entity = fork.entityFactory.create(entityName, data, {
|
|
211
|
+
refresh: options.refresh,
|
|
212
|
+
schema: options.schema,
|
|
213
|
+
convertCustomTypes: true,
|
|
214
|
+
});
|
|
215
|
+
helper(entity).setSerializationContext({
|
|
216
|
+
populate: options.populate,
|
|
217
|
+
fields: options.fields,
|
|
218
|
+
exclude: options.exclude,
|
|
219
|
+
});
|
|
220
|
+
await fork.unitOfWork.dispatchOnLoadEvent();
|
|
221
|
+
fork.clear();
|
|
222
|
+
yield entity;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
184
225
|
/**
|
|
185
226
|
* Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
|
|
186
227
|
*/
|
|
@@ -194,7 +235,7 @@ export class EntityManager {
|
|
|
194
235
|
if (options.populateWhere === PopulateHint.ALL) {
|
|
195
236
|
return { where: {}, populateWhere: options.populateWhere };
|
|
196
237
|
}
|
|
197
|
-
/* v8 ignore next
|
|
238
|
+
/* v8 ignore next */
|
|
198
239
|
if (options.populateWhere === PopulateHint.INFER) {
|
|
199
240
|
return { where, populateWhere: options.populateWhere };
|
|
200
241
|
}
|
|
@@ -203,12 +244,12 @@ export class EntityManager {
|
|
|
203
244
|
/**
|
|
204
245
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
205
246
|
*/
|
|
206
|
-
addFilter(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
|
|
247
|
+
addFilter(options) {
|
|
248
|
+
if (options.entity) {
|
|
249
|
+
options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
|
|
210
250
|
}
|
|
211
|
-
|
|
251
|
+
options.default ??= true;
|
|
252
|
+
this.getContext(false).filters[options.name] = options;
|
|
212
253
|
}
|
|
213
254
|
/**
|
|
214
255
|
* Sets filter parameter values globally inside context defined by this entity manager.
|
|
@@ -232,8 +273,8 @@ export class EntityManager {
|
|
|
232
273
|
/**
|
|
233
274
|
* Gets logger context for this entity manager.
|
|
234
275
|
*/
|
|
235
|
-
getLoggerContext() {
|
|
236
|
-
const em = this.getContext();
|
|
276
|
+
getLoggerContext(options) {
|
|
277
|
+
const em = options?.disableContextResolution ? this : this.getContext();
|
|
237
278
|
em.loggerContext ??= {};
|
|
238
279
|
return em.loggerContext;
|
|
239
280
|
}
|
|
@@ -250,7 +291,7 @@ export class EntityManager {
|
|
|
250
291
|
aliased: type === 'read',
|
|
251
292
|
});
|
|
252
293
|
where = (await this.applyFilters(entityName, where, options.filters ?? {}, type, options));
|
|
253
|
-
where =
|
|
294
|
+
where = this.applyDiscriminatorCondition(entityName, where);
|
|
254
295
|
return where;
|
|
255
296
|
}
|
|
256
297
|
// this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
|
|
@@ -259,15 +300,15 @@ export class EntityManager {
|
|
|
259
300
|
if (!meta?.discriminatorValue) {
|
|
260
301
|
return where;
|
|
261
302
|
}
|
|
262
|
-
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.
|
|
303
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
263
304
|
const children = [];
|
|
264
305
|
const lookUpChildren = (ret, type) => {
|
|
265
306
|
const children = types.filter(meta2 => meta2.extends === type);
|
|
266
|
-
children.forEach(m => lookUpChildren(ret, m.
|
|
307
|
+
children.forEach(m => lookUpChildren(ret, m.class));
|
|
267
308
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
268
309
|
return children;
|
|
269
310
|
};
|
|
270
|
-
lookUpChildren(children, meta.
|
|
311
|
+
lookUpChildren(children, meta.class);
|
|
271
312
|
/* v8 ignore next */
|
|
272
313
|
where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
|
|
273
314
|
return where;
|
|
@@ -283,28 +324,39 @@ export class EntityManager {
|
|
|
283
324
|
}
|
|
284
325
|
return ret;
|
|
285
326
|
}
|
|
286
|
-
async getJoinedFilters(meta,
|
|
327
|
+
async getJoinedFilters(meta, options) {
|
|
328
|
+
if (!this.config.get('filtersOnRelations') || !options.populate) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
287
331
|
const ret = {};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
332
|
+
for (const hint of options.populate) {
|
|
333
|
+
const field = hint.field.split(':')[0];
|
|
334
|
+
const prop = meta.properties[field];
|
|
335
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
336
|
+
const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
|
|
337
|
+
if (!joined && !hint.filter) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
341
|
+
const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
|
|
342
|
+
...options,
|
|
343
|
+
populate: hint.children,
|
|
344
|
+
});
|
|
345
|
+
const where2 = await this.getJoinedFilters(prop.targetMeta, {
|
|
346
|
+
...options,
|
|
347
|
+
filters,
|
|
348
|
+
populate: hint.children,
|
|
349
|
+
populateWhere: PopulateHint.ALL,
|
|
350
|
+
});
|
|
351
|
+
if (Utils.hasObjectKeys(where)) {
|
|
352
|
+
ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
|
|
353
|
+
}
|
|
354
|
+
if (where2 && Utils.hasObjectKeys(where2)) {
|
|
355
|
+
if (ret[field]) {
|
|
356
|
+
Utils.merge(ret[field], where2);
|
|
300
357
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Utils.merge(ret[field], where2);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
ret[field] = where2;
|
|
307
|
-
}
|
|
358
|
+
else {
|
|
359
|
+
ret[field] = where2;
|
|
308
360
|
}
|
|
309
361
|
}
|
|
310
362
|
}
|
|
@@ -313,42 +365,57 @@ export class EntityManager {
|
|
|
313
365
|
/**
|
|
314
366
|
* 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
367
|
*/
|
|
316
|
-
async autoJoinRefsForFilters(meta, options) {
|
|
317
|
-
if (!meta || !this.config.get('autoJoinRefsForFilters')) {
|
|
368
|
+
async autoJoinRefsForFilters(meta, options, parent) {
|
|
369
|
+
if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
|
|
318
370
|
return;
|
|
319
371
|
}
|
|
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
372
|
const ret = options.populate;
|
|
325
|
-
for (const prop of
|
|
326
|
-
|
|
373
|
+
for (const prop of meta.relations) {
|
|
374
|
+
if (prop.object
|
|
375
|
+
|| ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
|
|
376
|
+
|| !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
377
|
+
|| (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
381
|
+
const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
|
|
327
382
|
if (!Utils.isEmpty(cond)) {
|
|
328
383
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
329
|
-
|
|
330
|
-
|
|
384
|
+
let found = false;
|
|
385
|
+
for (const hint of populated) {
|
|
386
|
+
if (!hint.all) {
|
|
387
|
+
hint.filter = true;
|
|
388
|
+
}
|
|
389
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
390
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
391
|
+
found = true;
|
|
392
|
+
}
|
|
331
393
|
}
|
|
332
|
-
|
|
394
|
+
if (!found) {
|
|
333
395
|
ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
|
|
334
396
|
}
|
|
335
397
|
}
|
|
336
398
|
}
|
|
399
|
+
for (const hint of ret) {
|
|
400
|
+
const [field, ref] = hint.field.split(':');
|
|
401
|
+
const prop = meta?.properties[field];
|
|
402
|
+
if (prop && !ref) {
|
|
403
|
+
hint.children ??= [];
|
|
404
|
+
await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
337
407
|
}
|
|
338
408
|
/**
|
|
339
409
|
* @internal
|
|
340
410
|
*/
|
|
341
411
|
async applyFilters(entityName, where, options, type, findOptions) {
|
|
342
|
-
const meta = this.metadata.
|
|
412
|
+
const meta = this.metadata.get(entityName);
|
|
343
413
|
const filters = [];
|
|
344
414
|
const ret = [];
|
|
345
|
-
if (!meta) {
|
|
346
|
-
return where;
|
|
347
|
-
}
|
|
348
415
|
const active = new Set();
|
|
349
416
|
const push = (source) => {
|
|
350
417
|
const activeFilters = QueryHelper
|
|
351
|
-
.getActiveFilters(
|
|
418
|
+
.getActiveFilters(meta, options, source)
|
|
352
419
|
.filter(f => !active.has(f.name));
|
|
353
420
|
filters.push(...activeFilters);
|
|
354
421
|
activeFilters.forEach(f => active.add(f.name));
|
|
@@ -363,24 +430,28 @@ export class EntityManager {
|
|
|
363
430
|
let cond;
|
|
364
431
|
if (filter.cond instanceof Function) {
|
|
365
432
|
// @ts-ignore
|
|
366
|
-
const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
433
|
+
const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
367
434
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
368
435
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
369
436
|
}
|
|
370
|
-
cond = await filter.cond(args, type, this, findOptions);
|
|
437
|
+
cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
|
|
371
438
|
}
|
|
372
439
|
else {
|
|
373
440
|
cond = filter.cond;
|
|
374
441
|
}
|
|
375
|
-
|
|
442
|
+
cond = QueryHelper.processWhere({
|
|
376
443
|
where: cond,
|
|
377
444
|
entityName,
|
|
378
445
|
metadata: this.metadata,
|
|
379
446
|
platform: this.driver.getPlatform(),
|
|
380
447
|
aliased: type === 'read',
|
|
381
|
-
})
|
|
448
|
+
});
|
|
449
|
+
if (filter.strict) {
|
|
450
|
+
Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
|
|
451
|
+
}
|
|
452
|
+
ret.push(cond);
|
|
382
453
|
}
|
|
383
|
-
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
|
|
454
|
+
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
|
|
384
455
|
return conds.length > 1 ? { $and: conds } : conds[0];
|
|
385
456
|
}
|
|
386
457
|
/**
|
|
@@ -391,12 +462,10 @@ export class EntityManager {
|
|
|
391
462
|
const em = this.getContext(false);
|
|
392
463
|
await em.tryFlush(entityName, options);
|
|
393
464
|
options.flushMode = 'commit'; // do not try to auto flush again
|
|
394
|
-
return
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
]);
|
|
399
|
-
});
|
|
465
|
+
return Promise.all([
|
|
466
|
+
em.find(entityName, where, options),
|
|
467
|
+
em.count(entityName, where, options),
|
|
468
|
+
]);
|
|
400
469
|
}
|
|
401
470
|
/**
|
|
402
471
|
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
|
|
@@ -412,27 +481,31 @@ export class EntityManager {
|
|
|
412
481
|
* - POJO/entity instance
|
|
413
482
|
*
|
|
414
483
|
* ```ts
|
|
415
|
-
* const currentCursor = await em.findByCursor(User, {
|
|
484
|
+
* const currentCursor = await em.findByCursor(User, {
|
|
416
485
|
* first: 10,
|
|
417
486
|
* after: previousCursor, // cursor instance
|
|
418
487
|
* orderBy: { id: 'desc' },
|
|
419
488
|
* });
|
|
420
489
|
*
|
|
421
490
|
* // to fetch next page
|
|
422
|
-
* const nextCursor = await em.findByCursor(User, {
|
|
491
|
+
* const nextCursor = await em.findByCursor(User, {
|
|
423
492
|
* first: 10,
|
|
424
493
|
* after: currentCursor.endCursor, // opaque string
|
|
425
494
|
* orderBy: { id: 'desc' },
|
|
426
495
|
* });
|
|
427
496
|
*
|
|
428
497
|
* // to fetch next page
|
|
429
|
-
* const nextCursor2 = await em.findByCursor(User, {
|
|
498
|
+
* const nextCursor2 = await em.findByCursor(User, {
|
|
430
499
|
* first: 10,
|
|
431
500
|
* after: { id: lastSeenId }, // entity-like POJO
|
|
432
501
|
* orderBy: { id: 'desc' },
|
|
433
502
|
* });
|
|
434
503
|
* ```
|
|
435
504
|
*
|
|
505
|
+
* The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
|
|
506
|
+
* returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
|
|
507
|
+
* of pages.
|
|
508
|
+
*
|
|
436
509
|
* The `Cursor` object provides the following interface:
|
|
437
510
|
*
|
|
438
511
|
* ```ts
|
|
@@ -442,7 +515,7 @@ export class EntityManager {
|
|
|
442
515
|
* User { ... },
|
|
443
516
|
* User { ... },
|
|
444
517
|
* ],
|
|
445
|
-
* totalCount: 50,
|
|
518
|
+
* totalCount: 50, // not included if `includeCount: false`
|
|
446
519
|
* startCursor: 'WzRd',
|
|
447
520
|
* endCursor: 'WzZd',
|
|
448
521
|
* hasPrevPage: true,
|
|
@@ -450,14 +523,16 @@ export class EntityManager {
|
|
|
450
523
|
* }
|
|
451
524
|
* ```
|
|
452
525
|
*/
|
|
453
|
-
async findByCursor(entityName,
|
|
526
|
+
async findByCursor(entityName, options) {
|
|
454
527
|
const em = this.getContext(false);
|
|
455
|
-
entityName = Utils.className(entityName);
|
|
456
528
|
options.overfetch ??= true;
|
|
457
|
-
|
|
529
|
+
options.where ??= {};
|
|
530
|
+
if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
|
|
458
531
|
throw new Error('Explicit `orderBy` option required');
|
|
459
532
|
}
|
|
460
|
-
const [entities, count] =
|
|
533
|
+
const [entities, count] = options.includeCount !== false
|
|
534
|
+
? await em.findAndCount(entityName, options.where, options)
|
|
535
|
+
: [await em.find(entityName, options.where, options)];
|
|
461
536
|
return new Cursor(entities, count, options, this.metadata.get(entityName));
|
|
462
537
|
}
|
|
463
538
|
/**
|
|
@@ -469,9 +544,9 @@ export class EntityManager {
|
|
|
469
544
|
const ret = await this.refresh(entity, options);
|
|
470
545
|
if (!ret) {
|
|
471
546
|
options.failHandler ??= this.config.get('findOneOrFailHandler');
|
|
472
|
-
const
|
|
473
|
-
const where =
|
|
474
|
-
throw options.failHandler(
|
|
547
|
+
const wrapped = helper(entity);
|
|
548
|
+
const where = wrapped.getPrimaryKey();
|
|
549
|
+
throw options.failHandler(wrapped.__meta.className, where);
|
|
475
550
|
}
|
|
476
551
|
return ret;
|
|
477
552
|
}
|
|
@@ -482,19 +557,31 @@ export class EntityManager {
|
|
|
482
557
|
*/
|
|
483
558
|
async refresh(entity, options = {}) {
|
|
484
559
|
const fork = this.fork({ keepTransactionContext: true });
|
|
485
|
-
const
|
|
486
|
-
const reloaded = await fork.findOne(
|
|
487
|
-
schema:
|
|
560
|
+
const wrapped = helper(entity);
|
|
561
|
+
const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
|
|
562
|
+
schema: wrapped.__schema,
|
|
488
563
|
...options,
|
|
489
564
|
flushMode: FlushMode.COMMIT,
|
|
490
565
|
});
|
|
491
|
-
|
|
492
|
-
|
|
566
|
+
const em = this.getContext();
|
|
567
|
+
if (!reloaded) {
|
|
568
|
+
em.unitOfWork.unsetIdentity(entity);
|
|
569
|
+
return null;
|
|
493
570
|
}
|
|
494
|
-
|
|
495
|
-
|
|
571
|
+
let found = false;
|
|
572
|
+
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
573
|
+
const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
|
|
574
|
+
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
|
|
575
|
+
em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
|
|
576
|
+
Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
|
|
577
|
+
found ||= ref === entity;
|
|
578
|
+
}
|
|
579
|
+
if (!found) {
|
|
580
|
+
const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
|
|
581
|
+
em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
|
|
582
|
+
Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
|
|
496
583
|
}
|
|
497
|
-
return
|
|
584
|
+
return entity;
|
|
498
585
|
}
|
|
499
586
|
/**
|
|
500
587
|
* Finds first entity matching your `where` query.
|
|
@@ -508,7 +595,6 @@ export class EntityManager {
|
|
|
508
595
|
return ret;
|
|
509
596
|
}
|
|
510
597
|
const em = this.getContext();
|
|
511
|
-
entityName = Utils.className(entityName);
|
|
512
598
|
em.prepareOptions(options);
|
|
513
599
|
let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
|
|
514
600
|
// query for a not managed entity which is already in the identity map as it
|
|
@@ -520,33 +606,35 @@ export class EntityManager {
|
|
|
520
606
|
await em.tryFlush(entityName, options);
|
|
521
607
|
const meta = em.metadata.get(entityName);
|
|
522
608
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
523
|
-
|
|
609
|
+
validateEmptyWhere(where);
|
|
524
610
|
em.checkLockRequirements(options.lockMode, meta);
|
|
525
|
-
const isOptimisticLocking =
|
|
611
|
+
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
|
|
526
612
|
if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
|
|
527
613
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
528
614
|
}
|
|
529
|
-
|
|
615
|
+
validateParams(where);
|
|
530
616
|
options.populate = await em.preparePopulate(entityName, options);
|
|
531
617
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
532
618
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
533
|
-
if (cached?.data) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
619
|
+
if (cached?.data !== undefined) {
|
|
620
|
+
if (cached.data) {
|
|
621
|
+
await em.entityLoader.populate(entityName, [cached.data], options.populate, {
|
|
622
|
+
...options,
|
|
623
|
+
...em.getPopulateWhere(where, options),
|
|
624
|
+
ignoreLazyScalarProperties: true,
|
|
625
|
+
lookup: false,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
541
628
|
return cached.data;
|
|
542
629
|
}
|
|
543
630
|
options = { ...options };
|
|
544
631
|
// save the original hint value so we know it was infer/all
|
|
545
632
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
546
633
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
547
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
634
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
548
635
|
const data = await em.driver.findOne(entityName, where, {
|
|
549
636
|
ctx: em.transactionContext,
|
|
637
|
+
em,
|
|
550
638
|
...options,
|
|
551
639
|
});
|
|
552
640
|
if (!data) {
|
|
@@ -584,10 +672,10 @@ export class EntityManager {
|
|
|
584
672
|
if (!entity || isStrictViolation) {
|
|
585
673
|
const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
|
|
586
674
|
options.failHandler ??= this.config.get(key);
|
|
587
|
-
|
|
675
|
+
const name = Utils.className(entityName);
|
|
588
676
|
/* v8 ignore next */
|
|
589
677
|
where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
|
|
590
|
-
throw options.failHandler(
|
|
678
|
+
throw options.failHandler(name, where);
|
|
591
679
|
}
|
|
592
680
|
return entity;
|
|
593
681
|
}
|
|
@@ -627,11 +715,11 @@ export class EntityManager {
|
|
|
627
715
|
let where;
|
|
628
716
|
let entity = null;
|
|
629
717
|
if (data === undefined) {
|
|
630
|
-
entityName = entityNameOrEntity.constructor
|
|
718
|
+
entityName = entityNameOrEntity.constructor;
|
|
631
719
|
data = entityNameOrEntity;
|
|
632
720
|
}
|
|
633
721
|
else {
|
|
634
|
-
entityName =
|
|
722
|
+
entityName = entityNameOrEntity;
|
|
635
723
|
}
|
|
636
724
|
const meta = this.metadata.get(entityName);
|
|
637
725
|
const convertCustomTypes = !Utils.isEntity(data);
|
|
@@ -654,26 +742,9 @@ export class EntityManager {
|
|
|
654
742
|
}
|
|
655
743
|
}
|
|
656
744
|
}
|
|
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
|
-
}
|
|
745
|
+
where = getWhereCondition(meta, options.onConflictFields, data, where).where;
|
|
675
746
|
data = QueryHelper.processObjectParams(data);
|
|
676
|
-
|
|
747
|
+
validateParams(data, 'insert data');
|
|
677
748
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
678
749
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
|
|
679
750
|
}
|
|
@@ -711,13 +782,14 @@ export class EntityManager {
|
|
|
711
782
|
where[meta.primaryKeys[0]] = ret.insertId;
|
|
712
783
|
}
|
|
713
784
|
}
|
|
714
|
-
const data2 = await this.driver.findOne(meta.
|
|
785
|
+
const data2 = await this.driver.findOne(meta.class, where, {
|
|
715
786
|
fields: returning,
|
|
716
787
|
ctx: em.transactionContext,
|
|
717
788
|
convertCustomTypes: true,
|
|
718
789
|
connectionType: 'write',
|
|
790
|
+
schema: options.schema,
|
|
719
791
|
});
|
|
720
|
-
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full');
|
|
792
|
+
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
|
|
721
793
|
}
|
|
722
794
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
723
795
|
const snapshot = this.comparator.prepareEntity(entity);
|
|
@@ -765,11 +837,11 @@ export class EntityManager {
|
|
|
765
837
|
let entityName;
|
|
766
838
|
let propIndex;
|
|
767
839
|
if (data === undefined) {
|
|
768
|
-
entityName = entityNameOrEntity[0].constructor
|
|
840
|
+
entityName = entityNameOrEntity[0].constructor;
|
|
769
841
|
data = entityNameOrEntity;
|
|
770
842
|
}
|
|
771
843
|
else {
|
|
772
|
-
entityName =
|
|
844
|
+
entityName = entityNameOrEntity;
|
|
773
845
|
}
|
|
774
846
|
const batchSize = options.batchSize ?? this.config.get('batchSize');
|
|
775
847
|
if (data.length > batchSize) {
|
|
@@ -813,32 +885,18 @@ export class EntityManager {
|
|
|
813
885
|
}
|
|
814
886
|
}
|
|
815
887
|
}
|
|
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);
|
|
888
|
+
const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
|
|
889
|
+
propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
890
|
+
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
891
|
+
propIndex = tmp.propIndex;
|
|
835
892
|
where = QueryHelper.processWhere({
|
|
836
|
-
where,
|
|
893
|
+
where: tmp.where,
|
|
837
894
|
entityName,
|
|
838
895
|
metadata: this.metadata,
|
|
839
896
|
platform: this.getPlatform(),
|
|
840
897
|
});
|
|
841
|
-
|
|
898
|
+
row = QueryHelper.processObjectParams(row);
|
|
899
|
+
validateParams(row, 'insert data');
|
|
842
900
|
allData.push(row);
|
|
843
901
|
allWhere.push(where);
|
|
844
902
|
}
|
|
@@ -879,7 +937,7 @@ export class EntityManager {
|
|
|
879
937
|
const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
|
|
880
938
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
881
939
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
882
|
-
const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
|
|
940
|
+
const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
|
|
883
941
|
for (const cond of loadPK.values()) {
|
|
884
942
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
885
943
|
}
|
|
@@ -891,11 +949,12 @@ export class EntityManager {
|
|
|
891
949
|
where.$or[idx][prop] = item[prop];
|
|
892
950
|
});
|
|
893
951
|
});
|
|
894
|
-
const data2 = await this.driver.find(meta.
|
|
952
|
+
const data2 = await this.driver.find(meta.class, where, {
|
|
895
953
|
fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
|
|
896
954
|
ctx: em.transactionContext,
|
|
897
955
|
convertCustomTypes: true,
|
|
898
956
|
connectionType: 'write',
|
|
957
|
+
schema: options.schema,
|
|
899
958
|
});
|
|
900
959
|
for (const [entity, cond] of loadPK.entries()) {
|
|
901
960
|
const row = data2.find(row => {
|
|
@@ -907,11 +966,11 @@ export class EntityManager {
|
|
|
907
966
|
});
|
|
908
967
|
return this.comparator.matching(entityName, cond, tmp);
|
|
909
968
|
});
|
|
910
|
-
/* v8 ignore next
|
|
969
|
+
/* v8 ignore next */
|
|
911
970
|
if (!row) {
|
|
912
971
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
913
972
|
}
|
|
914
|
-
em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full');
|
|
973
|
+
em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full', false, true);
|
|
915
974
|
}
|
|
916
975
|
if (loadPK.size !== data2.length && Array.isArray(uniqueFields)) {
|
|
917
976
|
for (let i = 0; i < allData.length; i++) {
|
|
@@ -930,7 +989,7 @@ export class EntityManager {
|
|
|
930
989
|
}, {});
|
|
931
990
|
return this.comparator.matching(entityName, cond, pk);
|
|
932
991
|
});
|
|
933
|
-
/* v8 ignore next
|
|
992
|
+
/* v8 ignore next */
|
|
934
993
|
if (!row) {
|
|
935
994
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
936
995
|
}
|
|
@@ -952,45 +1011,37 @@ export class EntityManager {
|
|
|
952
1011
|
}
|
|
953
1012
|
/**
|
|
954
1013
|
* Runs your callback wrapped inside a database transaction.
|
|
1014
|
+
*
|
|
1015
|
+
* If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
|
|
1016
|
+
* can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
|
|
1017
|
+
* should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
|
|
1018
|
+
* method automatically creates an async context for the transaction.
|
|
1019
|
+
*
|
|
1020
|
+
* **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
|
|
1021
|
+
* `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
|
|
1022
|
+
* between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
|
|
1023
|
+
* the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
|
|
1024
|
+
*
|
|
1025
|
+
* **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
|
|
1026
|
+
* parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
|
|
1027
|
+
* and then call this method on the fork.
|
|
1028
|
+
*
|
|
1029
|
+
* **Example:**
|
|
1030
|
+
* ```ts
|
|
1031
|
+
* await em.transactional(async (em) => {
|
|
1032
|
+
* const author = new Author('Jon');
|
|
1033
|
+
* em.persist(author);
|
|
1034
|
+
* // flush is called automatically at the end of the callback
|
|
1035
|
+
* });
|
|
1036
|
+
* ```
|
|
955
1037
|
*/
|
|
956
1038
|
async transactional(cb, options = {}) {
|
|
957
1039
|
const em = this.getContext(false);
|
|
958
1040
|
if (this.disableTransactions || em.disableTransactions) {
|
|
959
1041
|
return cb(em);
|
|
960
1042
|
}
|
|
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
|
-
});
|
|
1043
|
+
const manager = new TransactionManager(this);
|
|
1044
|
+
return manager.handle(cb, options);
|
|
994
1045
|
}
|
|
995
1046
|
/**
|
|
996
1047
|
* Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
|
|
@@ -1051,11 +1102,11 @@ export class EntityManager {
|
|
|
1051
1102
|
em.prepareOptions(options);
|
|
1052
1103
|
let entityName;
|
|
1053
1104
|
if (data === undefined) {
|
|
1054
|
-
entityName = entityNameOrEntity.constructor
|
|
1105
|
+
entityName = entityNameOrEntity.constructor;
|
|
1055
1106
|
data = entityNameOrEntity;
|
|
1056
1107
|
}
|
|
1057
1108
|
else {
|
|
1058
|
-
entityName =
|
|
1109
|
+
entityName = entityNameOrEntity;
|
|
1059
1110
|
}
|
|
1060
1111
|
if (Utils.isEntity(data)) {
|
|
1061
1112
|
if (options.schema && helper(data).getSchema() == null) {
|
|
@@ -1074,7 +1125,7 @@ export class EntityManager {
|
|
|
1074
1125
|
return cs.getPrimaryKey();
|
|
1075
1126
|
}
|
|
1076
1127
|
data = QueryHelper.processObjectParams(data);
|
|
1077
|
-
|
|
1128
|
+
validateParams(data, 'insert data');
|
|
1078
1129
|
const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
|
|
1079
1130
|
return res.insertId;
|
|
1080
1131
|
}
|
|
@@ -1086,11 +1137,11 @@ export class EntityManager {
|
|
|
1086
1137
|
em.prepareOptions(options);
|
|
1087
1138
|
let entityName;
|
|
1088
1139
|
if (data === undefined) {
|
|
1089
|
-
entityName = entityNameOrEntities[0].constructor
|
|
1140
|
+
entityName = entityNameOrEntities[0].constructor;
|
|
1090
1141
|
data = entityNameOrEntities;
|
|
1091
1142
|
}
|
|
1092
1143
|
else {
|
|
1093
|
-
entityName =
|
|
1144
|
+
entityName = entityNameOrEntities;
|
|
1094
1145
|
}
|
|
1095
1146
|
if (data.length === 0) {
|
|
1096
1147
|
return [];
|
|
@@ -1114,7 +1165,7 @@ export class EntityManager {
|
|
|
1114
1165
|
return css.map(cs => cs.getPrimaryKey());
|
|
1115
1166
|
}
|
|
1116
1167
|
data = data.map(row => QueryHelper.processObjectParams(row));
|
|
1117
|
-
data.forEach(row =>
|
|
1168
|
+
data.forEach(row => validateParams(row, 'insert data'));
|
|
1118
1169
|
const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
|
|
1119
1170
|
if (res.insertedIds) {
|
|
1120
1171
|
return res.insertedIds;
|
|
@@ -1127,11 +1178,10 @@ export class EntityManager {
|
|
|
1127
1178
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
1128
1179
|
const em = this.getContext(false);
|
|
1129
1180
|
em.prepareOptions(options);
|
|
1130
|
-
entityName = Utils.className(entityName);
|
|
1131
1181
|
data = QueryHelper.processObjectParams(data);
|
|
1132
1182
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
1133
|
-
|
|
1134
|
-
|
|
1183
|
+
validateParams(data, 'update data');
|
|
1184
|
+
validateParams(where, 'update condition');
|
|
1135
1185
|
const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
|
|
1136
1186
|
return res.affectedRows;
|
|
1137
1187
|
}
|
|
@@ -1141,9 +1191,8 @@ export class EntityManager {
|
|
|
1141
1191
|
async nativeDelete(entityName, where, options = {}) {
|
|
1142
1192
|
const em = this.getContext(false);
|
|
1143
1193
|
em.prepareOptions(options);
|
|
1144
|
-
entityName = Utils.className(entityName);
|
|
1145
1194
|
where = await em.processWhere(entityName, where, options, 'delete');
|
|
1146
|
-
|
|
1195
|
+
validateParams(where, 'delete condition');
|
|
1147
1196
|
const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1148
1197
|
return res.affectedRows;
|
|
1149
1198
|
}
|
|
@@ -1151,18 +1200,19 @@ export class EntityManager {
|
|
|
1151
1200
|
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
1152
1201
|
*/
|
|
1153
1202
|
map(entityName, result, options = {}) {
|
|
1154
|
-
entityName = Utils.className(entityName);
|
|
1155
1203
|
const meta = this.metadata.get(entityName);
|
|
1156
1204
|
const data = this.driver.mapResult(result, meta);
|
|
1157
|
-
Object.keys(data)
|
|
1205
|
+
for (const k of Object.keys(data)) {
|
|
1158
1206
|
const prop = meta.properties[k];
|
|
1159
|
-
if (prop
|
|
1160
|
-
|
|
1207
|
+
if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
|
|
1208
|
+
validateProperty(prop, data[k], data);
|
|
1161
1209
|
}
|
|
1162
|
-
}
|
|
1210
|
+
}
|
|
1163
1211
|
return this.merge(entityName, data, {
|
|
1164
1212
|
convertCustomTypes: true,
|
|
1165
|
-
refresh: true,
|
|
1213
|
+
refresh: true,
|
|
1214
|
+
validate: false,
|
|
1215
|
+
...options,
|
|
1166
1216
|
});
|
|
1167
1217
|
}
|
|
1168
1218
|
/**
|
|
@@ -1170,22 +1220,22 @@ export class EntityManager {
|
|
|
1170
1220
|
* via second parameter. By default, it will return already loaded entities without modifying them.
|
|
1171
1221
|
*/
|
|
1172
1222
|
merge(entityName, data, options = {}) {
|
|
1173
|
-
const em = this.getContext();
|
|
1174
1223
|
if (Utils.isEntity(entityName)) {
|
|
1175
|
-
return
|
|
1224
|
+
return this.merge(entityName.constructor, entityName, data);
|
|
1176
1225
|
}
|
|
1226
|
+
const em = options.disableContextResolution ? this : this.getContext();
|
|
1177
1227
|
options.schema ??= em._schema;
|
|
1178
|
-
|
|
1179
|
-
|
|
1228
|
+
options.validate ??= true;
|
|
1229
|
+
options.cascade ??= true;
|
|
1230
|
+
validatePrimaryKey(data, em.metadata.get(entityName));
|
|
1180
1231
|
let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
|
|
1181
1232
|
if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
|
|
1182
1233
|
return entity;
|
|
1183
1234
|
}
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
em.
|
|
1188
|
-
em.unitOfWork.merge(entity);
|
|
1235
|
+
const dataIsEntity = Utils.isEntity(data);
|
|
1236
|
+
entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1237
|
+
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1238
|
+
em.unitOfWork.merge(entity, visited);
|
|
1189
1239
|
return entity;
|
|
1190
1240
|
}
|
|
1191
1241
|
/**
|
|
@@ -1210,6 +1260,7 @@ export class EntityManager {
|
|
|
1210
1260
|
...options,
|
|
1211
1261
|
newEntity: !options.managed,
|
|
1212
1262
|
merge: options.managed,
|
|
1263
|
+
normalizeAccessors: true,
|
|
1213
1264
|
});
|
|
1214
1265
|
options.persist ??= em.config.get('persistOnCreate');
|
|
1215
1266
|
if (options.persist && !this.getMetadata(entityName).embeddable) {
|
|
@@ -1229,7 +1280,7 @@ export class EntityManager {
|
|
|
1229
1280
|
getReference(entityName, id, options = {}) {
|
|
1230
1281
|
options.schema ??= this.schema;
|
|
1231
1282
|
options.convertCustomTypes ??= false;
|
|
1232
|
-
const meta = this.metadata.get(
|
|
1283
|
+
const meta = this.metadata.get(entityName);
|
|
1233
1284
|
if (Utils.isPrimaryKey(id)) {
|
|
1234
1285
|
if (meta.compositePK) {
|
|
1235
1286
|
throw ValidationError.invalidCompositeIdentifier(meta);
|
|
@@ -1248,11 +1299,8 @@ export class EntityManager {
|
|
|
1248
1299
|
async count(entityName, where = {}, options = {}) {
|
|
1249
1300
|
const em = this.getContext(false);
|
|
1250
1301
|
// Shallow copy options since the object will be modified when deleting orderBy
|
|
1251
|
-
options = {
|
|
1252
|
-
|
|
1253
|
-
...options,
|
|
1254
|
-
};
|
|
1255
|
-
entityName = Utils.className(entityName);
|
|
1302
|
+
options = { ...options };
|
|
1303
|
+
em.prepareOptions(options);
|
|
1256
1304
|
await em.tryFlush(entityName, options);
|
|
1257
1305
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1258
1306
|
options.populate = await em.preparePopulate(entityName, options);
|
|
@@ -1261,15 +1309,15 @@ export class EntityManager {
|
|
|
1261
1309
|
const meta = em.metadata.find(entityName);
|
|
1262
1310
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
1263
1311
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
1264
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
1265
|
-
|
|
1312
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
1313
|
+
validateParams(where);
|
|
1266
1314
|
delete options.orderBy;
|
|
1267
1315
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1268
1316
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1269
|
-
if (cached?.data) {
|
|
1317
|
+
if (cached?.data !== undefined) {
|
|
1270
1318
|
return cached.data;
|
|
1271
1319
|
}
|
|
1272
|
-
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1320
|
+
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
1273
1321
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1274
1322
|
return +count;
|
|
1275
1323
|
}
|
|
@@ -1288,7 +1336,7 @@ export class EntityManager {
|
|
|
1288
1336
|
for (const ent of entities) {
|
|
1289
1337
|
if (!Utils.isEntity(ent, true)) {
|
|
1290
1338
|
/* v8 ignore next */
|
|
1291
|
-
const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor
|
|
1339
|
+
const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
|
|
1292
1340
|
throw ValidationError.notDiscoveredEntity(ent, meta);
|
|
1293
1341
|
}
|
|
1294
1342
|
// do not cascade just yet, cascading of entities in persist stack is done when flushing
|
|
@@ -1296,13 +1344,6 @@ export class EntityManager {
|
|
|
1296
1344
|
}
|
|
1297
1345
|
return this;
|
|
1298
1346
|
}
|
|
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
1347
|
/**
|
|
1307
1348
|
* Marks entity for removal.
|
|
1308
1349
|
* A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -1326,13 +1367,6 @@ export class EntityManager {
|
|
|
1326
1367
|
}
|
|
1327
1368
|
return em;
|
|
1328
1369
|
}
|
|
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
1370
|
/**
|
|
1337
1371
|
* Flushes all changes to objects that have been queued up to now to the database.
|
|
1338
1372
|
* This effectively synchronizes the in-memory state of managed objects with the database.
|
|
@@ -1346,7 +1380,6 @@ export class EntityManager {
|
|
|
1346
1380
|
async tryFlush(entityName, options) {
|
|
1347
1381
|
const em = this.getContext();
|
|
1348
1382
|
const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
|
|
1349
|
-
entityName = Utils.className(entityName);
|
|
1350
1383
|
const meta = em.metadata.get(entityName);
|
|
1351
1384
|
if (flushMode === FlushMode.COMMIT) {
|
|
1352
1385
|
return;
|
|
@@ -1365,7 +1398,6 @@ export class EntityManager {
|
|
|
1365
1398
|
* Checks whether given property can be populated on the entity.
|
|
1366
1399
|
*/
|
|
1367
1400
|
canPopulate(entityName, property) {
|
|
1368
|
-
entityName = Utils.className(entityName);
|
|
1369
1401
|
// eslint-disable-next-line prefer-const
|
|
1370
1402
|
let [p, ...parts] = property.split('.');
|
|
1371
1403
|
const meta = this.metadata.find(entityName);
|
|
@@ -1376,11 +1408,8 @@ export class EntityManager {
|
|
|
1376
1408
|
p = p.split(':', 2)[0];
|
|
1377
1409
|
}
|
|
1378
1410
|
const ret = p in meta.root.properties;
|
|
1379
|
-
if (!ret) {
|
|
1380
|
-
return !!this.metadata.find(property)?.pivotTable;
|
|
1381
|
-
}
|
|
1382
1411
|
if (parts.length > 0) {
|
|
1383
|
-
return this.canPopulate(
|
|
1412
|
+
return this.canPopulate(meta.root.properties[p].targetMeta.class, parts.join('.'));
|
|
1384
1413
|
}
|
|
1385
1414
|
return ret;
|
|
1386
1415
|
}
|
|
@@ -1394,8 +1423,8 @@ export class EntityManager {
|
|
|
1394
1423
|
}
|
|
1395
1424
|
const em = this.getContext();
|
|
1396
1425
|
em.prepareOptions(options);
|
|
1397
|
-
const entityName = arr[0].constructor
|
|
1398
|
-
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
|
|
1426
|
+
const entityName = arr[0].constructor;
|
|
1427
|
+
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
|
|
1399
1428
|
await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1400
1429
|
return entities;
|
|
1401
1430
|
}
|
|
@@ -1431,6 +1460,9 @@ export class EntityManager {
|
|
|
1431
1460
|
for (const entity of em.unitOfWork.getIdentityMap()) {
|
|
1432
1461
|
fork.unitOfWork.register(entity);
|
|
1433
1462
|
}
|
|
1463
|
+
for (const entity of em.unitOfWork.getPersistStack()) {
|
|
1464
|
+
fork.unitOfWork.persist(entity);
|
|
1465
|
+
}
|
|
1434
1466
|
for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
|
|
1435
1467
|
fork.unitOfWork.getOrphanRemoveStack().add(entity);
|
|
1436
1468
|
}
|
|
@@ -1452,6 +1484,12 @@ export class EntityManager {
|
|
|
1452
1484
|
getEntityFactory() {
|
|
1453
1485
|
return this.getContext().entityFactory;
|
|
1454
1486
|
}
|
|
1487
|
+
/**
|
|
1488
|
+
* @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
|
|
1489
|
+
*/
|
|
1490
|
+
getEntityLoader() {
|
|
1491
|
+
return this.getContext().entityLoader;
|
|
1492
|
+
}
|
|
1455
1493
|
/**
|
|
1456
1494
|
* Gets the Hydrator used by the EntityManager.
|
|
1457
1495
|
*/
|
|
@@ -1514,7 +1552,6 @@ export class EntityManager {
|
|
|
1514
1552
|
*/
|
|
1515
1553
|
getMetadata(entityName) {
|
|
1516
1554
|
if (entityName) {
|
|
1517
|
-
entityName = Utils.className(entityName);
|
|
1518
1555
|
return this.metadata.get(entityName);
|
|
1519
1556
|
}
|
|
1520
1557
|
return this.metadata;
|
|
@@ -1543,12 +1580,11 @@ export class EntityManager {
|
|
|
1543
1580
|
lockTableAliases: options.lockTableAliases,
|
|
1544
1581
|
});
|
|
1545
1582
|
}
|
|
1546
|
-
const preparedPopulate = await this.preparePopulate(meta.
|
|
1547
|
-
await this.entityLoader.populate(meta.
|
|
1583
|
+
const preparedPopulate = await this.preparePopulate(meta.class, options);
|
|
1584
|
+
await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
|
|
1548
1585
|
...options,
|
|
1549
1586
|
...this.getPopulateWhere(where, options),
|
|
1550
1587
|
orderBy: options.populateOrderBy ?? options.orderBy,
|
|
1551
|
-
convertCustomTypes: false,
|
|
1552
1588
|
ignoreLazyScalarProperties: true,
|
|
1553
1589
|
lookup: false,
|
|
1554
1590
|
});
|
|
@@ -1565,6 +1601,7 @@ export class EntityManager {
|
|
|
1565
1601
|
return ret;
|
|
1566
1602
|
}, []);
|
|
1567
1603
|
}
|
|
1604
|
+
/** @internal */
|
|
1568
1605
|
async preparePopulate(entityName, options, validate = true) {
|
|
1569
1606
|
if (options.populate === false) {
|
|
1570
1607
|
return [];
|
|
@@ -1605,13 +1642,13 @@ export class EntityManager {
|
|
|
1605
1642
|
options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
|
|
1606
1643
|
}
|
|
1607
1644
|
if (!options.populate) {
|
|
1608
|
-
const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
|
|
1645
|
+
const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
|
|
1609
1646
|
await this.autoJoinRefsForFilters(meta, { ...options, populate });
|
|
1610
1647
|
return populate;
|
|
1611
1648
|
}
|
|
1612
1649
|
if (typeof options.populate !== 'boolean') {
|
|
1613
1650
|
options.populate = Utils.asArray(options.populate).map(field => {
|
|
1614
|
-
/* v8 ignore next
|
|
1651
|
+
/* v8 ignore next */
|
|
1615
1652
|
if (typeof field === 'boolean' || field === PopulatePath.ALL) {
|
|
1616
1653
|
return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
|
|
1617
1654
|
}
|
|
@@ -1621,13 +1658,13 @@ export class EntityManager {
|
|
|
1621
1658
|
options.flags.push(QueryFlag.INFER_POPULATE);
|
|
1622
1659
|
return [];
|
|
1623
1660
|
}
|
|
1624
|
-
if (
|
|
1661
|
+
if (typeof field === 'string') {
|
|
1625
1662
|
return [{ field, strategy: options.strategy }];
|
|
1626
1663
|
}
|
|
1627
1664
|
return [field];
|
|
1628
1665
|
}).flat();
|
|
1629
1666
|
}
|
|
1630
|
-
const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
|
|
1667
|
+
const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
|
|
1631
1668
|
const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
|
|
1632
1669
|
if (validate && invalid) {
|
|
1633
1670
|
throw ValidationError.invalidPropertyName(entityName, invalid.field);
|
|
@@ -1658,7 +1695,7 @@ export class EntityManager {
|
|
|
1658
1695
|
return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
|
|
1659
1696
|
});
|
|
1660
1697
|
}
|
|
1661
|
-
if (autoRefresh) {
|
|
1698
|
+
if (autoRefresh || options.filters) {
|
|
1662
1699
|
return true;
|
|
1663
1700
|
}
|
|
1664
1701
|
if (Array.isArray(options.populate)) {
|
|
@@ -1671,7 +1708,7 @@ export class EntityManager {
|
|
|
1671
1708
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1672
1709
|
}
|
|
1673
1710
|
options.schema ??= this._schema;
|
|
1674
|
-
options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1711
|
+
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1675
1712
|
}
|
|
1676
1713
|
/**
|
|
1677
1714
|
* @internal
|
|
@@ -1682,7 +1719,7 @@ export class EntityManager {
|
|
|
1682
1719
|
for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
|
|
1683
1720
|
delete opts[k];
|
|
1684
1721
|
}
|
|
1685
|
-
return [entityName, method, opts, where];
|
|
1722
|
+
return [Utils.className(entityName), method, opts, where];
|
|
1686
1723
|
}
|
|
1687
1724
|
/**
|
|
1688
1725
|
* @internal
|
|
@@ -1695,31 +1732,27 @@ export class EntityManager {
|
|
|
1695
1732
|
const em = this.getContext();
|
|
1696
1733
|
const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
|
|
1697
1734
|
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
|
-
recomputeSnapshot: true,
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
else {
|
|
1717
|
-
data = cached;
|
|
1718
|
-
}
|
|
1719
|
-
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1720
|
-
return { key: cacheKey, data };
|
|
1735
|
+
if (!cached) {
|
|
1736
|
+
return { key: cacheKey, data: cached };
|
|
1737
|
+
}
|
|
1738
|
+
let data;
|
|
1739
|
+
const createOptions = {
|
|
1740
|
+
merge: true,
|
|
1741
|
+
convertCustomTypes: false,
|
|
1742
|
+
refresh,
|
|
1743
|
+
recomputeSnapshot: true,
|
|
1744
|
+
};
|
|
1745
|
+
if (Array.isArray(cached) && merge) {
|
|
1746
|
+
data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
|
|
1747
|
+
}
|
|
1748
|
+
else if (Utils.isObject(cached) && merge) {
|
|
1749
|
+
data = em.entityFactory.create(entityName, cached, createOptions);
|
|
1721
1750
|
}
|
|
1722
|
-
|
|
1751
|
+
else {
|
|
1752
|
+
data = cached;
|
|
1753
|
+
}
|
|
1754
|
+
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1755
|
+
return { key: cacheKey, data };
|
|
1723
1756
|
}
|
|
1724
1757
|
/**
|
|
1725
1758
|
* @internal
|
|
@@ -1728,7 +1761,7 @@ export class EntityManager {
|
|
|
1728
1761
|
config ??= this.config.get('resultCache').global;
|
|
1729
1762
|
if (config) {
|
|
1730
1763
|
const em = this.getContext();
|
|
1731
|
-
const expiration = Array.isArray(config) ? config[1] : (
|
|
1764
|
+
const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
|
|
1732
1765
|
await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
|
|
1733
1766
|
}
|
|
1734
1767
|
}
|
|
@@ -1761,6 +1794,19 @@ export class EntityManager {
|
|
|
1761
1794
|
set schema(schema) {
|
|
1762
1795
|
this.getContext(false)._schema = schema ?? undefined;
|
|
1763
1796
|
}
|
|
1797
|
+
/** @internal */
|
|
1798
|
+
async getDataLoader(type) {
|
|
1799
|
+
const em = this.getContext();
|
|
1800
|
+
if (em.loaders[type]) {
|
|
1801
|
+
return em.loaders[type];
|
|
1802
|
+
}
|
|
1803
|
+
const DataLoader = await DataloaderUtils.getDataLoader();
|
|
1804
|
+
switch (type) {
|
|
1805
|
+
case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
|
|
1806
|
+
case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
1807
|
+
case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1764
1810
|
/**
|
|
1765
1811
|
* Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
|
|
1766
1812
|
* if executed inside request context handler.
|
|
@@ -1769,7 +1815,7 @@ export class EntityManager {
|
|
|
1769
1815
|
return this.getContext(false)._id;
|
|
1770
1816
|
}
|
|
1771
1817
|
/** @ignore */
|
|
1772
|
-
[inspect.custom]() {
|
|
1818
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
1773
1819
|
return `[EntityManager<${this.id}>]`;
|
|
1774
1820
|
}
|
|
1775
1821
|
}
|