@mikro-orm/core 7.0.0-dev.8 → 7.0.0-dev.81
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 +85 -48
- package/EntityManager.js +300 -225
- package/MikroORM.d.ts +40 -31
- package/MikroORM.js +98 -137
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +6 -5
- 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 +11 -7
- package/connections/Connection.js +16 -14
- package/drivers/DatabaseDriver.d.ts +11 -5
- package/drivers/DatabaseDriver.js +23 -11
- package/drivers/IDatabaseDriver.d.ts +27 -5
- package/entity/BaseEntity.d.ts +0 -1
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +98 -30
- package/entity/Collection.js +432 -93
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +15 -7
- package/entity/EntityFactory.d.ts +7 -0
- package/entity/EntityFactory.js +64 -41
- package/entity/EntityHelper.js +26 -9
- package/entity/EntityLoader.d.ts +5 -4
- package/entity/EntityLoader.js +73 -40
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/Reference.d.ts +9 -7
- package/entity/Reference.js +33 -6
- package/entity/WrappedEntity.d.ts +2 -4
- package/entity/WrappedEntity.js +1 -5
- package/entity/defineEntity.d.ts +549 -0
- package/entity/defineEntity.js +529 -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 +21 -6
- package/enums.js +14 -1
- package/errors.d.ts +6 -2
- package/errors.js +14 -9
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +36 -25
- package/index.d.ts +2 -2
- package/index.js +1 -2
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/SimpleLogger.d.ts +1 -1
- package/metadata/EntitySchema.d.ts +9 -13
- package/metadata/EntitySchema.js +44 -26
- package/metadata/MetadataDiscovery.d.ts +6 -9
- package/metadata/MetadataDiscovery.js +167 -206
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +44 -2
- package/metadata/MetadataStorage.d.ts +1 -6
- package/metadata/MetadataStorage.js +6 -18
- package/metadata/MetadataValidator.d.ts +0 -7
- package/metadata/MetadataValidator.js +4 -13
- 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 +480 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
- package/naming-strategy/AbstractNamingStrategy.js +8 -2
- package/naming-strategy/NamingStrategy.d.ts +11 -1
- package/not-supported.d.ts +2 -0
- package/not-supported.js +4 -0
- package/package.json +18 -10
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +6 -10
- package/platforms/Platform.js +14 -39
- package/serialization/EntitySerializer.d.ts +2 -0
- package/serialization/EntitySerializer.js +32 -14
- package/serialization/EntityTransformer.js +22 -12
- package/serialization/SerializationContext.js +16 -13
- package/types/ArrayType.d.ts +1 -1
- package/types/ArrayType.js +2 -3
- package/types/BigIntType.d.ts +8 -6
- package/types/BigIntType.js +1 -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/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/TinyIntType.js +1 -1
- package/types/Type.d.ts +2 -1
- package/types/Type.js +1 -1
- package/types/Uint8ArrayType.d.ts +0 -1
- package/types/Uint8ArrayType.js +1 -4
- package/types/index.d.ts +1 -1
- package/typings.d.ts +113 -77
- package/typings.js +41 -35
- package/unit-of-work/ChangeSetComputer.d.ts +1 -3
- package/unit-of-work/ChangeSetComputer.js +11 -9
- package/unit-of-work/ChangeSetPersister.d.ts +5 -4
- package/unit-of-work/ChangeSetPersister.js +58 -20
- package/unit-of-work/UnitOfWork.d.ts +8 -1
- package/unit-of-work/UnitOfWork.js +115 -57
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +11 -9
- package/utils/Configuration.d.ts +757 -206
- package/utils/Configuration.js +139 -187
- package/utils/ConfigurationLoader.d.ts +1 -54
- package/utils/ConfigurationLoader.js +1 -352
- package/utils/Cursor.d.ts +3 -3
- package/utils/Cursor.js +4 -1
- package/utils/DataloaderUtils.d.ts +15 -5
- package/utils/DataloaderUtils.js +54 -8
- package/utils/EntityComparator.d.ts +8 -4
- package/utils/EntityComparator.js +111 -64
- package/utils/QueryHelper.d.ts +9 -1
- package/utils/QueryHelper.js +70 -9
- package/utils/RawQueryFragment.d.ts +36 -4
- package/utils/RawQueryFragment.js +35 -14
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +223 -0
- package/utils/Utils.d.ts +8 -97
- package/utils/Utils.js +88 -303
- package/utils/clone.js +2 -3
- package/utils/env-vars.d.ts +3 -0
- package/utils/env-vars.js +87 -0
- package/utils/fs-utils.d.ts +12 -0
- package/utils/fs-utils.js +96 -0
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- package/utils/upsert-utils.d.ts +7 -2
- 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 -12
- package/decorators/Enum.d.ts +0 -9
- package/decorators/Enum.js +0 -16
- package/decorators/Filter.d.ts +0 -2
- package/decorators/Filter.js +0 -8
- package/decorators/Formula.d.ts +0 -4
- package/decorators/Formula.js +0 -15
- package/decorators/Indexed.d.ts +0 -19
- package/decorators/Indexed.js +0 -20
- package/decorators/ManyToMany.d.ts +0 -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 -8
- package/decorators/PrimaryKey.js +0 -20
- package/decorators/Property.d.ts +0 -250
- package/decorators/Property.js +0 -32
- package/decorators/Transactional.d.ts +0 -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 -402
- 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,6 +1,5 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import
|
|
3
|
-
import { getOnConflictReturningFields } from './utils/upsert-utils.js';
|
|
2
|
+
import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
|
|
4
3
|
import { Utils } from './utils/Utils.js';
|
|
5
4
|
import { Cursor } from './utils/Cursor.js';
|
|
6
5
|
import { DataloaderUtils } from './utils/DataloaderUtils.js';
|
|
@@ -9,7 +8,7 @@ import { TransactionContext } from './utils/TransactionContext.js';
|
|
|
9
8
|
import { isRaw, RawQueryFragment } from './utils/RawQueryFragment.js';
|
|
10
9
|
import { EntityFactory } from './entity/EntityFactory.js';
|
|
11
10
|
import { EntityAssigner } from './entity/EntityAssigner.js';
|
|
12
|
-
import {
|
|
11
|
+
import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
|
|
13
12
|
import { EntityLoader } from './entity/EntityLoader.js';
|
|
14
13
|
import { Reference } from './entity/Reference.js';
|
|
15
14
|
import { helper } from './entity/wrap.js';
|
|
@@ -19,6 +18,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
|
|
|
19
18
|
import { EventManager } from './events/EventManager.js';
|
|
20
19
|
import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
|
|
21
20
|
import { OptimisticLockError, ValidationError } from './errors.js';
|
|
21
|
+
import { getLoadingStrategy } from './entity/utils.js';
|
|
22
|
+
import { TransactionManager } from './utils/TransactionManager.js';
|
|
22
23
|
/**
|
|
23
24
|
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
|
|
24
25
|
* such as UnitOfWork, Query Language, and Repository API.
|
|
@@ -34,9 +35,7 @@ export class EntityManager {
|
|
|
34
35
|
_id = EntityManager.counter++;
|
|
35
36
|
global = false;
|
|
36
37
|
name;
|
|
37
|
-
|
|
38
|
-
colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
|
|
39
|
-
validator;
|
|
38
|
+
loaders = {};
|
|
40
39
|
repositoryMap = {};
|
|
41
40
|
entityLoader;
|
|
42
41
|
comparator;
|
|
@@ -61,7 +60,6 @@ export class EntityManager {
|
|
|
61
60
|
this.eventManager = eventManager;
|
|
62
61
|
this.entityLoader = new EntityLoader(this);
|
|
63
62
|
this.name = this.config.get('contextName');
|
|
64
|
-
this.validator = new EntityValidator(this.config.get('strict'));
|
|
65
63
|
this.comparator = this.config.getComparator(this.metadata);
|
|
66
64
|
this.resultCache = this.config.getResultCacheAdapter();
|
|
67
65
|
this.disableTransactions = this.config.get('disableTransactions');
|
|
@@ -105,12 +103,6 @@ export class EntityManager {
|
|
|
105
103
|
repo(entityName) {
|
|
106
104
|
return this.getRepository(entityName);
|
|
107
105
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Gets EntityValidator instance
|
|
110
|
-
*/
|
|
111
|
-
getValidator() {
|
|
112
|
-
return this.validator;
|
|
113
|
-
}
|
|
114
106
|
/**
|
|
115
107
|
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
116
108
|
*/
|
|
@@ -127,7 +119,7 @@ export class EntityManager {
|
|
|
127
119
|
await em.tryFlush(entityName, options);
|
|
128
120
|
entityName = Utils.className(entityName);
|
|
129
121
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
130
|
-
|
|
122
|
+
validateParams(where);
|
|
131
123
|
options.orderBy = options.orderBy || {};
|
|
132
124
|
options.populate = await em.preparePopulate(entityName, options);
|
|
133
125
|
const populate = options.populate;
|
|
@@ -137,7 +129,6 @@ export class EntityManager {
|
|
|
137
129
|
await em.entityLoader.populate(entityName, cached.data, populate, {
|
|
138
130
|
...options,
|
|
139
131
|
...em.getPopulateWhere(where, options),
|
|
140
|
-
convertCustomTypes: false,
|
|
141
132
|
ignoreLazyScalarProperties: true,
|
|
142
133
|
lookup: false,
|
|
143
134
|
});
|
|
@@ -148,7 +139,7 @@ export class EntityManager {
|
|
|
148
139
|
// save the original hint value so we know it was infer/all
|
|
149
140
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
150
141
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
151
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
142
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
152
143
|
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
153
144
|
if (results.length === 0) {
|
|
154
145
|
await em.storeCache(options.cache, cached, []);
|
|
@@ -168,7 +159,6 @@ export class EntityManager {
|
|
|
168
159
|
await em.entityLoader.populate(entityName, unique, populate, {
|
|
169
160
|
...options,
|
|
170
161
|
...em.getPopulateWhere(where, options),
|
|
171
|
-
convertCustomTypes: false,
|
|
172
162
|
ignoreLazyScalarProperties: true,
|
|
173
163
|
lookup: false,
|
|
174
164
|
});
|
|
@@ -181,6 +171,61 @@ export class EntityManager {
|
|
|
181
171
|
}
|
|
182
172
|
return unique;
|
|
183
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Finds all entities and returns an async iterable (async generator) that yields results one by one.
|
|
176
|
+
* The results are merged and mapped to entity instances, without adding them to the identity map.
|
|
177
|
+
* You can disable merging by passing the options `{ mergeResults: false }`.
|
|
178
|
+
* With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
|
|
179
|
+
* root entities when there are multiple items in the populated collection.
|
|
180
|
+
* This is useful for processing large datasets without loading everything into memory at once.
|
|
181
|
+
*
|
|
182
|
+
* ```ts
|
|
183
|
+
* const stream = em.stream(Book, { populate: ['author'] });
|
|
184
|
+
*
|
|
185
|
+
* for await (const book of stream) {
|
|
186
|
+
* // book is an instance of Book entity
|
|
187
|
+
* console.log(book.title, book.author.name);
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
async *stream(entityName, options = {}) {
|
|
192
|
+
const em = this.getContext();
|
|
193
|
+
em.prepareOptions(options);
|
|
194
|
+
options.strategy = 'joined';
|
|
195
|
+
await em.tryFlush(entityName, options);
|
|
196
|
+
entityName = Utils.className(entityName);
|
|
197
|
+
const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
|
|
198
|
+
validateParams(where);
|
|
199
|
+
options.orderBy = options.orderBy || {};
|
|
200
|
+
options.populate = await em.preparePopulate(entityName, options);
|
|
201
|
+
const meta = this.metadata.get(entityName);
|
|
202
|
+
options = { ...options };
|
|
203
|
+
// save the original hint value so we know it was infer/all
|
|
204
|
+
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
205
|
+
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
206
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
207
|
+
const stream = em.driver.stream(entityName, where, {
|
|
208
|
+
ctx: em.transactionContext,
|
|
209
|
+
mapResults: false,
|
|
210
|
+
...options,
|
|
211
|
+
});
|
|
212
|
+
for await (const data of stream) {
|
|
213
|
+
const fork = em.fork();
|
|
214
|
+
const entity = fork.entityFactory.create(entityName, data, {
|
|
215
|
+
refresh: options.refresh,
|
|
216
|
+
schema: options.schema,
|
|
217
|
+
convertCustomTypes: true,
|
|
218
|
+
});
|
|
219
|
+
helper(entity).setSerializationContext({
|
|
220
|
+
populate: options.populate,
|
|
221
|
+
fields: options.fields,
|
|
222
|
+
exclude: options.exclude,
|
|
223
|
+
});
|
|
224
|
+
await fork.unitOfWork.dispatchOnLoadEvent();
|
|
225
|
+
fork.clear();
|
|
226
|
+
yield entity;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
184
229
|
/**
|
|
185
230
|
* Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
|
|
186
231
|
*/
|
|
@@ -194,7 +239,7 @@ export class EntityManager {
|
|
|
194
239
|
if (options.populateWhere === PopulateHint.ALL) {
|
|
195
240
|
return { where: {}, populateWhere: options.populateWhere };
|
|
196
241
|
}
|
|
197
|
-
/* v8 ignore next
|
|
242
|
+
/* v8 ignore next */
|
|
198
243
|
if (options.populateWhere === PopulateHint.INFER) {
|
|
199
244
|
return { where, populateWhere: options.populateWhere };
|
|
200
245
|
}
|
|
@@ -203,12 +248,12 @@ export class EntityManager {
|
|
|
203
248
|
/**
|
|
204
249
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
205
250
|
*/
|
|
206
|
-
addFilter(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
|
|
251
|
+
addFilter(options) {
|
|
252
|
+
if (options.entity) {
|
|
253
|
+
options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
|
|
210
254
|
}
|
|
211
|
-
|
|
255
|
+
options.default ??= true;
|
|
256
|
+
this.getContext(false).filters[options.name] = options;
|
|
212
257
|
}
|
|
213
258
|
/**
|
|
214
259
|
* Sets filter parameter values globally inside context defined by this entity manager.
|
|
@@ -232,8 +277,8 @@ export class EntityManager {
|
|
|
232
277
|
/**
|
|
233
278
|
* Gets logger context for this entity manager.
|
|
234
279
|
*/
|
|
235
|
-
getLoggerContext() {
|
|
236
|
-
const em = this.getContext();
|
|
280
|
+
getLoggerContext(options) {
|
|
281
|
+
const em = options?.disableContextResolution ? this : this.getContext();
|
|
237
282
|
em.loggerContext ??= {};
|
|
238
283
|
return em.loggerContext;
|
|
239
284
|
}
|
|
@@ -283,28 +328,39 @@ export class EntityManager {
|
|
|
283
328
|
}
|
|
284
329
|
return ret;
|
|
285
330
|
}
|
|
286
|
-
async getJoinedFilters(meta,
|
|
331
|
+
async getJoinedFilters(meta, options) {
|
|
332
|
+
if (!this.config.get('filtersOnRelations') || !options.populate) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
287
335
|
const ret = {};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
336
|
+
for (const hint of options.populate) {
|
|
337
|
+
const field = hint.field.split(':')[0];
|
|
338
|
+
const prop = meta.properties[field];
|
|
339
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
340
|
+
const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
|
|
341
|
+
if (!joined && !hint.filter) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
345
|
+
const where = await this.applyFilters(prop.type, {}, filters, 'read', {
|
|
346
|
+
...options,
|
|
347
|
+
populate: hint.children,
|
|
348
|
+
});
|
|
349
|
+
const where2 = await this.getJoinedFilters(prop.targetMeta, {
|
|
350
|
+
...options,
|
|
351
|
+
filters,
|
|
352
|
+
populate: hint.children,
|
|
353
|
+
populateWhere: PopulateHint.ALL,
|
|
354
|
+
});
|
|
355
|
+
if (Utils.hasObjectKeys(where)) {
|
|
356
|
+
ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
|
|
357
|
+
}
|
|
358
|
+
if (where2 && Utils.hasObjectKeys(where2)) {
|
|
359
|
+
if (ret[field]) {
|
|
360
|
+
Utils.merge(ret[field], where2);
|
|
300
361
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Utils.merge(ret[field], where2);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
ret[field] = where2;
|
|
307
|
-
}
|
|
362
|
+
else {
|
|
363
|
+
ret[field] = where2;
|
|
308
364
|
}
|
|
309
365
|
}
|
|
310
366
|
}
|
|
@@ -313,27 +369,45 @@ export class EntityManager {
|
|
|
313
369
|
/**
|
|
314
370
|
* 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
371
|
*/
|
|
316
|
-
async autoJoinRefsForFilters(meta, options) {
|
|
317
|
-
if (!meta || !this.config.get('autoJoinRefsForFilters')) {
|
|
372
|
+
async autoJoinRefsForFilters(meta, options, parent) {
|
|
373
|
+
if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
|
|
318
374
|
return;
|
|
319
375
|
}
|
|
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
376
|
const ret = options.populate;
|
|
325
|
-
for (const prop of
|
|
326
|
-
|
|
377
|
+
for (const prop of meta.relations) {
|
|
378
|
+
if (prop.object
|
|
379
|
+
|| ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
|
|
380
|
+
|| !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
381
|
+
|| (parent?.className === prop.targetMeta.root.className && parent.propName === prop.inversedBy)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
385
|
+
const cond = await this.applyFilters(prop.type, {}, options.filters, 'read', options);
|
|
327
386
|
if (!Utils.isEmpty(cond)) {
|
|
328
387
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
329
|
-
|
|
330
|
-
|
|
388
|
+
let found = false;
|
|
389
|
+
for (const hint of populated) {
|
|
390
|
+
if (!hint.all) {
|
|
391
|
+
hint.filter = true;
|
|
392
|
+
}
|
|
393
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
394
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
395
|
+
found = true;
|
|
396
|
+
}
|
|
331
397
|
}
|
|
332
|
-
|
|
398
|
+
if (!found) {
|
|
333
399
|
ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
|
|
334
400
|
}
|
|
335
401
|
}
|
|
336
402
|
}
|
|
403
|
+
for (const hint of ret) {
|
|
404
|
+
const [field, ref] = hint.field.split(':');
|
|
405
|
+
const prop = meta?.properties[field];
|
|
406
|
+
if (prop && !ref) {
|
|
407
|
+
hint.children ??= [];
|
|
408
|
+
await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { className: meta.root.className, propName: prop.name });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
337
411
|
}
|
|
338
412
|
/**
|
|
339
413
|
* @internal
|
|
@@ -363,7 +437,7 @@ export class EntityManager {
|
|
|
363
437
|
let cond;
|
|
364
438
|
if (filter.cond instanceof Function) {
|
|
365
439
|
// @ts-ignore
|
|
366
|
-
const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
440
|
+
const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
367
441
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
368
442
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
369
443
|
}
|
|
@@ -372,13 +446,17 @@ export class EntityManager {
|
|
|
372
446
|
else {
|
|
373
447
|
cond = filter.cond;
|
|
374
448
|
}
|
|
375
|
-
|
|
449
|
+
cond = QueryHelper.processWhere({
|
|
376
450
|
where: cond,
|
|
377
451
|
entityName,
|
|
378
452
|
metadata: this.metadata,
|
|
379
453
|
platform: this.driver.getPlatform(),
|
|
380
454
|
aliased: type === 'read',
|
|
381
|
-
})
|
|
455
|
+
});
|
|
456
|
+
if (filter.strict) {
|
|
457
|
+
Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
|
|
458
|
+
}
|
|
459
|
+
ret.push(cond);
|
|
382
460
|
}
|
|
383
461
|
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
|
|
384
462
|
return conds.length > 1 ? { $and: conds } : conds[0];
|
|
@@ -433,6 +511,10 @@ export class EntityManager {
|
|
|
433
511
|
* });
|
|
434
512
|
* ```
|
|
435
513
|
*
|
|
514
|
+
* The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
|
|
515
|
+
* returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
|
|
516
|
+
* of pages.
|
|
517
|
+
*
|
|
436
518
|
* The `Cursor` object provides the following interface:
|
|
437
519
|
*
|
|
438
520
|
* ```ts
|
|
@@ -442,7 +524,7 @@ export class EntityManager {
|
|
|
442
524
|
* User { ... },
|
|
443
525
|
* User { ... },
|
|
444
526
|
* ],
|
|
445
|
-
* totalCount: 50,
|
|
527
|
+
* totalCount: 50, // not included if `includeCount: false`
|
|
446
528
|
* startCursor: 'WzRd',
|
|
447
529
|
* endCursor: 'WzZd',
|
|
448
530
|
* hasPrevPage: true,
|
|
@@ -457,7 +539,9 @@ export class EntityManager {
|
|
|
457
539
|
if (Utils.isEmpty(options.orderBy)) {
|
|
458
540
|
throw new Error('Explicit `orderBy` option required');
|
|
459
541
|
}
|
|
460
|
-
const [entities, count] =
|
|
542
|
+
const [entities, count] = options.includeCount !== false
|
|
543
|
+
? await em.findAndCount(entityName, where, options)
|
|
544
|
+
: [await em.find(entityName, where, options)];
|
|
461
545
|
return new Cursor(entities, count, options, this.metadata.get(entityName));
|
|
462
546
|
}
|
|
463
547
|
/**
|
|
@@ -483,18 +567,31 @@ export class EntityManager {
|
|
|
483
567
|
async refresh(entity, options = {}) {
|
|
484
568
|
const fork = this.fork({ keepTransactionContext: true });
|
|
485
569
|
const entityName = entity.constructor.name;
|
|
570
|
+
const wrapped = helper(entity);
|
|
486
571
|
const reloaded = await fork.findOne(entityName, entity, {
|
|
487
|
-
schema:
|
|
572
|
+
schema: wrapped.__schema,
|
|
488
573
|
...options,
|
|
489
574
|
flushMode: FlushMode.COMMIT,
|
|
490
575
|
});
|
|
491
|
-
|
|
492
|
-
|
|
576
|
+
const em = this.getContext();
|
|
577
|
+
if (!reloaded) {
|
|
578
|
+
em.unitOfWork.unsetIdentity(entity);
|
|
579
|
+
return null;
|
|
493
580
|
}
|
|
494
|
-
|
|
495
|
-
|
|
581
|
+
let found = false;
|
|
582
|
+
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
583
|
+
const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
|
|
584
|
+
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true });
|
|
585
|
+
em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, true);
|
|
586
|
+
Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
|
|
587
|
+
found ||= ref === entity;
|
|
496
588
|
}
|
|
497
|
-
|
|
589
|
+
if (!found) {
|
|
590
|
+
const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true });
|
|
591
|
+
em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
|
|
592
|
+
Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
|
|
593
|
+
}
|
|
594
|
+
return entity;
|
|
498
595
|
}
|
|
499
596
|
/**
|
|
500
597
|
* Finds first entity matching your `where` query.
|
|
@@ -520,31 +617,32 @@ export class EntityManager {
|
|
|
520
617
|
await em.tryFlush(entityName, options);
|
|
521
618
|
const meta = em.metadata.get(entityName);
|
|
522
619
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
523
|
-
|
|
620
|
+
validateEmptyWhere(where);
|
|
524
621
|
em.checkLockRequirements(options.lockMode, meta);
|
|
525
|
-
const isOptimisticLocking =
|
|
622
|
+
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
|
|
526
623
|
if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
|
|
527
624
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
528
625
|
}
|
|
529
|
-
|
|
626
|
+
validateParams(where);
|
|
530
627
|
options.populate = await em.preparePopulate(entityName, options);
|
|
531
628
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
532
629
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
533
|
-
if (cached?.data) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
630
|
+
if (cached?.data !== undefined) {
|
|
631
|
+
if (cached.data) {
|
|
632
|
+
await em.entityLoader.populate(entityName, [cached.data], options.populate, {
|
|
633
|
+
...options,
|
|
634
|
+
...em.getPopulateWhere(where, options),
|
|
635
|
+
ignoreLazyScalarProperties: true,
|
|
636
|
+
lookup: false,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
541
639
|
return cached.data;
|
|
542
640
|
}
|
|
543
641
|
options = { ...options };
|
|
544
642
|
// save the original hint value so we know it was infer/all
|
|
545
643
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
546
644
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
547
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
645
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
548
646
|
const data = await em.driver.findOne(entityName, where, {
|
|
549
647
|
ctx: em.transactionContext,
|
|
550
648
|
em,
|
|
@@ -655,26 +753,9 @@ export class EntityManager {
|
|
|
655
753
|
}
|
|
656
754
|
}
|
|
657
755
|
}
|
|
658
|
-
|
|
659
|
-
const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
|
|
660
|
-
if (options.onConflictFields || where == null) {
|
|
661
|
-
if (propIndex !== false && propIndex >= 0) {
|
|
662
|
-
where = { [unique[propIndex]]: data[unique[propIndex]] };
|
|
663
|
-
}
|
|
664
|
-
else if (meta.uniques.length > 0) {
|
|
665
|
-
for (const u of meta.uniques) {
|
|
666
|
-
if (Utils.asArray(u.properties).every(p => data[p] != null)) {
|
|
667
|
-
where = Utils.asArray(u.properties).reduce((o, key) => {
|
|
668
|
-
o[key] = data[key];
|
|
669
|
-
return o;
|
|
670
|
-
}, {});
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
756
|
+
where = getWhereCondition(meta, options.onConflictFields, data, where).where;
|
|
676
757
|
data = QueryHelper.processObjectParams(data);
|
|
677
|
-
|
|
758
|
+
validateParams(data, 'insert data');
|
|
678
759
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
679
760
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
|
|
680
761
|
}
|
|
@@ -717,6 +798,7 @@ export class EntityManager {
|
|
|
717
798
|
ctx: em.transactionContext,
|
|
718
799
|
convertCustomTypes: true,
|
|
719
800
|
connectionType: 'write',
|
|
801
|
+
schema: options.schema,
|
|
720
802
|
});
|
|
721
803
|
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
|
|
722
804
|
}
|
|
@@ -814,32 +896,18 @@ export class EntityManager {
|
|
|
814
896
|
}
|
|
815
897
|
}
|
|
816
898
|
}
|
|
817
|
-
const unique = meta.props.filter(p => p.unique).map(p => p.name);
|
|
818
|
-
propIndex = unique.findIndex(p =>
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
where = { [unique[propIndex]]: row[unique[propIndex]] };
|
|
822
|
-
}
|
|
823
|
-
else if (meta.uniques.length > 0) {
|
|
824
|
-
for (const u of meta.uniques) {
|
|
825
|
-
if (Utils.asArray(u.properties).every(p => row[p] != null)) {
|
|
826
|
-
where = Utils.asArray(u.properties).reduce((o, key) => {
|
|
827
|
-
o[key] = row[key];
|
|
828
|
-
return o;
|
|
829
|
-
}, {});
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
row = QueryHelper.processObjectParams(row);
|
|
899
|
+
const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
|
|
900
|
+
propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
901
|
+
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
902
|
+
propIndex = tmp.propIndex;
|
|
836
903
|
where = QueryHelper.processWhere({
|
|
837
|
-
where,
|
|
904
|
+
where: tmp.where,
|
|
838
905
|
entityName,
|
|
839
906
|
metadata: this.metadata,
|
|
840
907
|
platform: this.getPlatform(),
|
|
841
908
|
});
|
|
842
|
-
|
|
909
|
+
row = QueryHelper.processObjectParams(row);
|
|
910
|
+
validateParams(row, 'insert data');
|
|
843
911
|
allData.push(row);
|
|
844
912
|
allWhere.push(where);
|
|
845
913
|
}
|
|
@@ -880,7 +948,7 @@ export class EntityManager {
|
|
|
880
948
|
const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
|
|
881
949
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
882
950
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
883
|
-
const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
|
|
951
|
+
const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
|
|
884
952
|
for (const cond of loadPK.values()) {
|
|
885
953
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
886
954
|
}
|
|
@@ -897,6 +965,7 @@ export class EntityManager {
|
|
|
897
965
|
ctx: em.transactionContext,
|
|
898
966
|
convertCustomTypes: true,
|
|
899
967
|
connectionType: 'write',
|
|
968
|
+
schema: options.schema,
|
|
900
969
|
});
|
|
901
970
|
for (const [entity, cond] of loadPK.entries()) {
|
|
902
971
|
const row = data2.find(row => {
|
|
@@ -908,7 +977,7 @@ export class EntityManager {
|
|
|
908
977
|
});
|
|
909
978
|
return this.comparator.matching(entityName, cond, tmp);
|
|
910
979
|
});
|
|
911
|
-
/* v8 ignore next
|
|
980
|
+
/* v8 ignore next */
|
|
912
981
|
if (!row) {
|
|
913
982
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
914
983
|
}
|
|
@@ -931,7 +1000,7 @@ export class EntityManager {
|
|
|
931
1000
|
}, {});
|
|
932
1001
|
return this.comparator.matching(entityName, cond, pk);
|
|
933
1002
|
});
|
|
934
|
-
/* v8 ignore next
|
|
1003
|
+
/* v8 ignore next */
|
|
935
1004
|
if (!row) {
|
|
936
1005
|
throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
|
|
937
1006
|
}
|
|
@@ -953,45 +1022,37 @@ export class EntityManager {
|
|
|
953
1022
|
}
|
|
954
1023
|
/**
|
|
955
1024
|
* Runs your callback wrapped inside a database transaction.
|
|
1025
|
+
*
|
|
1026
|
+
* If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
|
|
1027
|
+
* can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
|
|
1028
|
+
* should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
|
|
1029
|
+
* method automatically creates an async context for the transaction.
|
|
1030
|
+
*
|
|
1031
|
+
* **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
|
|
1032
|
+
* `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
|
|
1033
|
+
* between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
|
|
1034
|
+
* the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
|
|
1035
|
+
*
|
|
1036
|
+
* **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
|
|
1037
|
+
* parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
|
|
1038
|
+
* and then call this method on the fork.
|
|
1039
|
+
*
|
|
1040
|
+
* **Example:**
|
|
1041
|
+
* ```ts
|
|
1042
|
+
* await em.transactional(async (em) => {
|
|
1043
|
+
* const author = new Author('Jon');
|
|
1044
|
+
* em.persist(author);
|
|
1045
|
+
* // flush is called automatically at the end of the callback
|
|
1046
|
+
* });
|
|
1047
|
+
* ```
|
|
956
1048
|
*/
|
|
957
1049
|
async transactional(cb, options = {}) {
|
|
958
1050
|
const em = this.getContext(false);
|
|
959
1051
|
if (this.disableTransactions || em.disableTransactions) {
|
|
960
1052
|
return cb(em);
|
|
961
1053
|
}
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
flushMode: options.flushMode,
|
|
965
|
-
cloneEventManager: true,
|
|
966
|
-
disableTransactions: options.ignoreNestedTransactions,
|
|
967
|
-
loggerContext: options.loggerContext,
|
|
968
|
-
});
|
|
969
|
-
options.ctx ??= em.transactionContext;
|
|
970
|
-
const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
|
|
971
|
-
return TransactionContext.create(fork, async () => {
|
|
972
|
-
return fork.getConnection().transactional(async (trx) => {
|
|
973
|
-
fork.transactionContext = trx;
|
|
974
|
-
if (propagateToUpperContext) {
|
|
975
|
-
fork.eventManager.registerSubscriber({
|
|
976
|
-
afterFlush(args) {
|
|
977
|
-
args.uow.getChangeSets()
|
|
978
|
-
.filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
|
|
979
|
-
.forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
|
|
980
|
-
},
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
const ret = await cb(fork);
|
|
984
|
-
await fork.flush();
|
|
985
|
-
if (propagateToUpperContext) {
|
|
986
|
-
// ensure all entities from inner context are merged to the upper one
|
|
987
|
-
for (const entity of fork.unitOfWork.getIdentityMap()) {
|
|
988
|
-
em.unitOfWork.register(entity);
|
|
989
|
-
entity.__helper.__em = em;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
return ret;
|
|
993
|
-
}, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
|
|
994
|
-
});
|
|
1054
|
+
const manager = new TransactionManager(this);
|
|
1055
|
+
return manager.handle(cb, options);
|
|
995
1056
|
}
|
|
996
1057
|
/**
|
|
997
1058
|
* Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
|
|
@@ -1075,7 +1136,7 @@ export class EntityManager {
|
|
|
1075
1136
|
return cs.getPrimaryKey();
|
|
1076
1137
|
}
|
|
1077
1138
|
data = QueryHelper.processObjectParams(data);
|
|
1078
|
-
|
|
1139
|
+
validateParams(data, 'insert data');
|
|
1079
1140
|
const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
|
|
1080
1141
|
return res.insertId;
|
|
1081
1142
|
}
|
|
@@ -1115,7 +1176,7 @@ export class EntityManager {
|
|
|
1115
1176
|
return css.map(cs => cs.getPrimaryKey());
|
|
1116
1177
|
}
|
|
1117
1178
|
data = data.map(row => QueryHelper.processObjectParams(row));
|
|
1118
|
-
data.forEach(row =>
|
|
1179
|
+
data.forEach(row => validateParams(row, 'insert data'));
|
|
1119
1180
|
const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
|
|
1120
1181
|
if (res.insertedIds) {
|
|
1121
1182
|
return res.insertedIds;
|
|
@@ -1131,8 +1192,8 @@ export class EntityManager {
|
|
|
1131
1192
|
entityName = Utils.className(entityName);
|
|
1132
1193
|
data = QueryHelper.processObjectParams(data);
|
|
1133
1194
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
1134
|
-
|
|
1135
|
-
|
|
1195
|
+
validateParams(data, 'update data');
|
|
1196
|
+
validateParams(where, 'update condition');
|
|
1136
1197
|
const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
|
|
1137
1198
|
return res.affectedRows;
|
|
1138
1199
|
}
|
|
@@ -1144,7 +1205,7 @@ export class EntityManager {
|
|
|
1144
1205
|
em.prepareOptions(options);
|
|
1145
1206
|
entityName = Utils.className(entityName);
|
|
1146
1207
|
where = await em.processWhere(entityName, where, options, 'delete');
|
|
1147
|
-
|
|
1208
|
+
validateParams(where, 'delete condition');
|
|
1148
1209
|
const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1149
1210
|
return res.affectedRows;
|
|
1150
1211
|
}
|
|
@@ -1155,15 +1216,17 @@ export class EntityManager {
|
|
|
1155
1216
|
entityName = Utils.className(entityName);
|
|
1156
1217
|
const meta = this.metadata.get(entityName);
|
|
1157
1218
|
const data = this.driver.mapResult(result, meta);
|
|
1158
|
-
Object.keys(data)
|
|
1219
|
+
for (const k of Object.keys(data)) {
|
|
1159
1220
|
const prop = meta.properties[k];
|
|
1160
|
-
if (prop
|
|
1161
|
-
|
|
1221
|
+
if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
|
|
1222
|
+
validateProperty(prop, data[k], data);
|
|
1162
1223
|
}
|
|
1163
|
-
}
|
|
1224
|
+
}
|
|
1164
1225
|
return this.merge(entityName, data, {
|
|
1165
1226
|
convertCustomTypes: true,
|
|
1166
|
-
refresh: true,
|
|
1227
|
+
refresh: true,
|
|
1228
|
+
validate: false,
|
|
1229
|
+
...options,
|
|
1167
1230
|
});
|
|
1168
1231
|
}
|
|
1169
1232
|
/**
|
|
@@ -1171,22 +1234,28 @@ export class EntityManager {
|
|
|
1171
1234
|
* via second parameter. By default, it will return already loaded entities without modifying them.
|
|
1172
1235
|
*/
|
|
1173
1236
|
merge(entityName, data, options = {}) {
|
|
1174
|
-
const em = this.getContext();
|
|
1175
1237
|
if (Utils.isEntity(entityName)) {
|
|
1176
|
-
return
|
|
1238
|
+
return this.merge(entityName.constructor.name, entityName, data);
|
|
1177
1239
|
}
|
|
1240
|
+
const em = options.disableContextResolution ? this : this.getContext();
|
|
1178
1241
|
options.schema ??= em._schema;
|
|
1242
|
+
options.validate ??= true;
|
|
1243
|
+
options.cascade ??= true;
|
|
1179
1244
|
entityName = Utils.className(entityName);
|
|
1180
|
-
|
|
1245
|
+
validatePrimaryKey(data, em.metadata.get(entityName));
|
|
1181
1246
|
let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
|
|
1182
1247
|
if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
|
|
1183
1248
|
return entity;
|
|
1184
1249
|
}
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1250
|
+
const dataIsEntity = Utils.isEntity(data);
|
|
1251
|
+
if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
|
|
1252
|
+
helper(entity).__data = helper(data).__data;
|
|
1253
|
+
helper(entity).__originalEntityData = helper(data).__originalEntityData;
|
|
1254
|
+
return entity;
|
|
1255
|
+
}
|
|
1256
|
+
entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1257
|
+
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1258
|
+
em.unitOfWork.merge(entity, visited);
|
|
1190
1259
|
return entity;
|
|
1191
1260
|
}
|
|
1192
1261
|
/**
|
|
@@ -1211,6 +1280,7 @@ export class EntityManager {
|
|
|
1211
1280
|
...options,
|
|
1212
1281
|
newEntity: !options.managed,
|
|
1213
1282
|
merge: options.managed,
|
|
1283
|
+
normalizeAccessors: true,
|
|
1214
1284
|
});
|
|
1215
1285
|
options.persist ??= em.config.get('persistOnCreate');
|
|
1216
1286
|
if (options.persist && !this.getMetadata(entityName).embeddable) {
|
|
@@ -1249,10 +1319,8 @@ export class EntityManager {
|
|
|
1249
1319
|
async count(entityName, where = {}, options = {}) {
|
|
1250
1320
|
const em = this.getContext(false);
|
|
1251
1321
|
// Shallow copy options since the object will be modified when deleting orderBy
|
|
1252
|
-
options = {
|
|
1253
|
-
|
|
1254
|
-
...options,
|
|
1255
|
-
};
|
|
1322
|
+
options = { ...options };
|
|
1323
|
+
em.prepareOptions(options);
|
|
1256
1324
|
entityName = Utils.className(entityName);
|
|
1257
1325
|
await em.tryFlush(entityName, options);
|
|
1258
1326
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
@@ -1262,15 +1330,15 @@ export class EntityManager {
|
|
|
1262
1330
|
const meta = em.metadata.find(entityName);
|
|
1263
1331
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
1264
1332
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
1265
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
1266
|
-
|
|
1333
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
1334
|
+
validateParams(where);
|
|
1267
1335
|
delete options.orderBy;
|
|
1268
1336
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1269
1337
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1270
|
-
if (cached?.data) {
|
|
1338
|
+
if (cached?.data !== undefined) {
|
|
1271
1339
|
return cached.data;
|
|
1272
1340
|
}
|
|
1273
|
-
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1341
|
+
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
1274
1342
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1275
1343
|
return +count;
|
|
1276
1344
|
}
|
|
@@ -1297,13 +1365,6 @@ export class EntityManager {
|
|
|
1297
1365
|
}
|
|
1298
1366
|
return this;
|
|
1299
1367
|
}
|
|
1300
|
-
/**
|
|
1301
|
-
* Persists your entity immediately, flushing all not yet persisted changes to the database too.
|
|
1302
|
-
* Equivalent to `em.persist(e).flush()`.
|
|
1303
|
-
*/
|
|
1304
|
-
async persistAndFlush(entity) {
|
|
1305
|
-
await this.persist(entity).flush();
|
|
1306
|
-
}
|
|
1307
1368
|
/**
|
|
1308
1369
|
* Marks entity for removal.
|
|
1309
1370
|
* A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -1327,13 +1388,6 @@ export class EntityManager {
|
|
|
1327
1388
|
}
|
|
1328
1389
|
return em;
|
|
1329
1390
|
}
|
|
1330
|
-
/**
|
|
1331
|
-
* Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
|
|
1332
|
-
* Equivalent to `em.remove(e).flush()`
|
|
1333
|
-
*/
|
|
1334
|
-
async removeAndFlush(entity) {
|
|
1335
|
-
await this.remove(entity).flush();
|
|
1336
|
-
}
|
|
1337
1391
|
/**
|
|
1338
1392
|
* Flushes all changes to objects that have been queued up to now to the database.
|
|
1339
1393
|
* This effectively synchronizes the in-memory state of managed objects with the database.
|
|
@@ -1396,7 +1450,7 @@ export class EntityManager {
|
|
|
1396
1450
|
const em = this.getContext();
|
|
1397
1451
|
em.prepareOptions(options);
|
|
1398
1452
|
const entityName = arr[0].constructor.name;
|
|
1399
|
-
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
|
|
1453
|
+
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
|
|
1400
1454
|
await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1401
1455
|
return entities;
|
|
1402
1456
|
}
|
|
@@ -1432,6 +1486,9 @@ export class EntityManager {
|
|
|
1432
1486
|
for (const entity of em.unitOfWork.getIdentityMap()) {
|
|
1433
1487
|
fork.unitOfWork.register(entity);
|
|
1434
1488
|
}
|
|
1489
|
+
for (const entity of em.unitOfWork.getPersistStack()) {
|
|
1490
|
+
fork.unitOfWork.persist(entity);
|
|
1491
|
+
}
|
|
1435
1492
|
for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
|
|
1436
1493
|
fork.unitOfWork.getOrphanRemoveStack().add(entity);
|
|
1437
1494
|
}
|
|
@@ -1453,6 +1510,12 @@ export class EntityManager {
|
|
|
1453
1510
|
getEntityFactory() {
|
|
1454
1511
|
return this.getContext().entityFactory;
|
|
1455
1512
|
}
|
|
1513
|
+
/**
|
|
1514
|
+
* @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
|
|
1515
|
+
*/
|
|
1516
|
+
getEntityLoader() {
|
|
1517
|
+
return this.getContext().entityLoader;
|
|
1518
|
+
}
|
|
1456
1519
|
/**
|
|
1457
1520
|
* Gets the Hydrator used by the EntityManager.
|
|
1458
1521
|
*/
|
|
@@ -1549,7 +1612,6 @@ export class EntityManager {
|
|
|
1549
1612
|
...options,
|
|
1550
1613
|
...this.getPopulateWhere(where, options),
|
|
1551
1614
|
orderBy: options.populateOrderBy ?? options.orderBy,
|
|
1552
|
-
convertCustomTypes: false,
|
|
1553
1615
|
ignoreLazyScalarProperties: true,
|
|
1554
1616
|
lookup: false,
|
|
1555
1617
|
});
|
|
@@ -1612,7 +1674,7 @@ export class EntityManager {
|
|
|
1612
1674
|
}
|
|
1613
1675
|
if (typeof options.populate !== 'boolean') {
|
|
1614
1676
|
options.populate = Utils.asArray(options.populate).map(field => {
|
|
1615
|
-
/* v8 ignore next
|
|
1677
|
+
/* v8 ignore next */
|
|
1616
1678
|
if (typeof field === 'boolean' || field === PopulatePath.ALL) {
|
|
1617
1679
|
return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
|
|
1618
1680
|
}
|
|
@@ -1622,7 +1684,7 @@ export class EntityManager {
|
|
|
1622
1684
|
options.flags.push(QueryFlag.INFER_POPULATE);
|
|
1623
1685
|
return [];
|
|
1624
1686
|
}
|
|
1625
|
-
if (
|
|
1687
|
+
if (typeof field === 'string') {
|
|
1626
1688
|
return [{ field, strategy: options.strategy }];
|
|
1627
1689
|
}
|
|
1628
1690
|
return [field];
|
|
@@ -1672,7 +1734,7 @@ export class EntityManager {
|
|
|
1672
1734
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1673
1735
|
}
|
|
1674
1736
|
options.schema ??= this._schema;
|
|
1675
|
-
options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1737
|
+
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1676
1738
|
}
|
|
1677
1739
|
/**
|
|
1678
1740
|
* @internal
|
|
@@ -1696,31 +1758,31 @@ export class EntityManager {
|
|
|
1696
1758
|
const em = this.getContext();
|
|
1697
1759
|
const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
|
|
1698
1760
|
const cached = await em.resultCache.get(cacheKey);
|
|
1699
|
-
if (cached) {
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
data = cached;
|
|
1719
|
-
}
|
|
1720
|
-
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1721
|
-
return { key: cacheKey, data };
|
|
1761
|
+
if (!cached) {
|
|
1762
|
+
return { key: cacheKey, data: cached };
|
|
1763
|
+
}
|
|
1764
|
+
let data;
|
|
1765
|
+
if (Array.isArray(cached) && merge) {
|
|
1766
|
+
data = cached.map(item => em.entityFactory.create(entityName, item, {
|
|
1767
|
+
merge: true,
|
|
1768
|
+
convertCustomTypes: true,
|
|
1769
|
+
refresh,
|
|
1770
|
+
recomputeSnapshot: true,
|
|
1771
|
+
}));
|
|
1772
|
+
}
|
|
1773
|
+
else if (Utils.isObject(cached) && merge) {
|
|
1774
|
+
data = em.entityFactory.create(entityName, cached, {
|
|
1775
|
+
merge: true,
|
|
1776
|
+
convertCustomTypes: true,
|
|
1777
|
+
refresh,
|
|
1778
|
+
recomputeSnapshot: true,
|
|
1779
|
+
});
|
|
1722
1780
|
}
|
|
1723
|
-
|
|
1781
|
+
else {
|
|
1782
|
+
data = cached;
|
|
1783
|
+
}
|
|
1784
|
+
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1785
|
+
return { key: cacheKey, data };
|
|
1724
1786
|
}
|
|
1725
1787
|
/**
|
|
1726
1788
|
* @internal
|
|
@@ -1729,7 +1791,7 @@ export class EntityManager {
|
|
|
1729
1791
|
config ??= this.config.get('resultCache').global;
|
|
1730
1792
|
if (config) {
|
|
1731
1793
|
const em = this.getContext();
|
|
1732
|
-
const expiration = Array.isArray(config) ? config[1] : (
|
|
1794
|
+
const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
|
|
1733
1795
|
await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
|
|
1734
1796
|
}
|
|
1735
1797
|
}
|
|
@@ -1762,6 +1824,19 @@ export class EntityManager {
|
|
|
1762
1824
|
set schema(schema) {
|
|
1763
1825
|
this.getContext(false)._schema = schema ?? undefined;
|
|
1764
1826
|
}
|
|
1827
|
+
/** @internal */
|
|
1828
|
+
async getDataLoader(type) {
|
|
1829
|
+
const em = this.getContext();
|
|
1830
|
+
if (em.loaders[type]) {
|
|
1831
|
+
return em.loaders[type];
|
|
1832
|
+
}
|
|
1833
|
+
const DataLoader = await DataloaderUtils.getDataLoader();
|
|
1834
|
+
switch (type) {
|
|
1835
|
+
case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
|
|
1836
|
+
case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
1837
|
+
case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1765
1840
|
/**
|
|
1766
1841
|
* Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
|
|
1767
1842
|
* if executed inside request context handler.
|