@mikro-orm/core 7.0.0-dev.4 → 7.0.0-dev.40
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 +84 -18
- package/EntityManager.js +265 -172
- package/MikroORM.d.ts +7 -5
- package/MikroORM.js +0 -1
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +2 -1
- package/cache/FileCacheAdapter.js +6 -4
- package/connections/Connection.d.ts +4 -2
- package/connections/Connection.js +2 -2
- package/decorators/Check.d.ts +2 -2
- package/decorators/Embeddable.d.ts +5 -5
- package/decorators/Embeddable.js +1 -1
- package/decorators/Embedded.d.ts +6 -12
- package/decorators/Entity.d.ts +18 -3
- package/decorators/Enum.d.ts +1 -1
- package/decorators/Formula.d.ts +1 -2
- package/decorators/Indexed.d.ts +2 -2
- package/decorators/ManyToMany.d.ts +4 -2
- package/decorators/ManyToOne.d.ts +6 -2
- package/decorators/OneToMany.d.ts +4 -4
- package/decorators/OneToOne.d.ts +5 -1
- package/decorators/PrimaryKey.d.ts +2 -3
- package/decorators/Property.d.ts +54 -4
- package/decorators/Transactional.d.ts +1 -0
- package/decorators/Transactional.js +3 -3
- package/decorators/index.d.ts +1 -1
- package/drivers/DatabaseDriver.d.ts +4 -3
- package/drivers/IDatabaseDriver.d.ts +22 -2
- package/entity/ArrayCollection.d.ts +4 -2
- package/entity/ArrayCollection.js +18 -6
- package/entity/Collection.d.ts +1 -2
- package/entity/Collection.js +19 -10
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +9 -1
- package/entity/EntityFactory.d.ts +7 -0
- package/entity/EntityFactory.js +29 -9
- package/entity/EntityHelper.js +25 -3
- package/entity/EntityLoader.d.ts +5 -4
- package/entity/EntityLoader.js +74 -37
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/EntityValidator.js +1 -1
- package/entity/Reference.d.ts +9 -7
- package/entity/Reference.js +30 -3
- package/entity/WrappedEntity.js +1 -1
- package/entity/defineEntity.d.ts +561 -0
- package/entity/defineEntity.js +537 -0
- package/entity/index.d.ts +2 -0
- package/entity/index.js +2 -0
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +15 -3
- package/enums.d.ts +16 -3
- package/enums.js +13 -0
- package/errors.d.ts +6 -0
- package/errors.js +14 -0
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +35 -24
- package/index.d.ts +2 -1
- package/index.js +1 -1
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/SimpleLogger.d.ts +1 -1
- package/metadata/EntitySchema.d.ts +8 -4
- package/metadata/EntitySchema.js +39 -19
- package/metadata/MetadataDiscovery.d.ts +1 -1
- package/metadata/MetadataDiscovery.js +88 -32
- package/metadata/MetadataStorage.js +1 -1
- package/metadata/MetadataValidator.js +4 -3
- package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
- package/naming-strategy/AbstractNamingStrategy.js +7 -1
- package/naming-strategy/NamingStrategy.d.ts +11 -1
- package/package.json +5 -5
- package/platforms/Platform.d.ts +5 -3
- package/platforms/Platform.js +4 -8
- package/serialization/EntitySerializer.d.ts +2 -0
- package/serialization/EntitySerializer.js +2 -2
- package/serialization/EntityTransformer.js +1 -1
- package/serialization/SerializationContext.js +14 -11
- package/types/BigIntType.d.ts +9 -6
- package/types/BigIntType.js +3 -0
- package/types/BooleanType.d.ts +1 -1
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +1 -1
- package/types/DoubleType.js +1 -1
- package/types/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/Type.d.ts +2 -1
- package/types/Type.js +1 -1
- package/types/index.d.ts +1 -1
- package/typings.d.ts +88 -39
- package/typings.js +24 -4
- package/unit-of-work/ChangeSetComputer.js +3 -1
- package/unit-of-work/ChangeSetPersister.d.ts +4 -2
- package/unit-of-work/ChangeSetPersister.js +37 -16
- package/unit-of-work/UnitOfWork.d.ts +8 -1
- package/unit-of-work/UnitOfWork.js +109 -41
- package/utils/Configuration.d.ts +23 -5
- package/utils/Configuration.js +17 -3
- package/utils/ConfigurationLoader.d.ts +0 -2
- package/utils/ConfigurationLoader.js +2 -24
- package/utils/Cursor.d.ts +3 -3
- package/utils/Cursor.js +3 -0
- package/utils/DataloaderUtils.d.ts +7 -2
- package/utils/DataloaderUtils.js +38 -7
- package/utils/EntityComparator.d.ts +6 -2
- package/utils/EntityComparator.js +104 -58
- package/utils/QueryHelper.d.ts +9 -1
- package/utils/QueryHelper.js +66 -5
- package/utils/RawQueryFragment.d.ts +36 -2
- package/utils/RawQueryFragment.js +35 -1
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +218 -0
- package/utils/Utils.d.ts +11 -5
- package/utils/Utils.js +76 -33
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/upsert-utils.d.ts +7 -2
- package/utils/upsert-utils.js +52 -1
package/EntityManager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
import DataLoader from 'dataloader';
|
|
3
|
-
import { getOnConflictReturningFields } from './utils/upsert-utils.js';
|
|
3
|
+
import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
|
|
4
4
|
import { Utils } from './utils/Utils.js';
|
|
5
5
|
import { Cursor } from './utils/Cursor.js';
|
|
6
6
|
import { DataloaderUtils } from './utils/DataloaderUtils.js';
|
|
@@ -19,6 +19,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
|
|
|
19
19
|
import { EventManager } from './events/EventManager.js';
|
|
20
20
|
import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
|
|
21
21
|
import { OptimisticLockError, ValidationError } from './errors.js';
|
|
22
|
+
import { getLoadingStrategy } from './entity/utils.js';
|
|
23
|
+
import { TransactionManager } from './utils/TransactionManager.js';
|
|
22
24
|
/**
|
|
23
25
|
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
|
|
24
26
|
* such as UnitOfWork, Query Language, and Repository API.
|
|
@@ -36,6 +38,7 @@ export class EntityManager {
|
|
|
36
38
|
name;
|
|
37
39
|
refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
|
|
38
40
|
colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
|
|
41
|
+
colLoaderMtoN = new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(this));
|
|
39
42
|
validator;
|
|
40
43
|
repositoryMap = {};
|
|
41
44
|
entityLoader;
|
|
@@ -137,7 +140,6 @@ export class EntityManager {
|
|
|
137
140
|
await em.entityLoader.populate(entityName, cached.data, populate, {
|
|
138
141
|
...options,
|
|
139
142
|
...em.getPopulateWhere(where, options),
|
|
140
|
-
convertCustomTypes: false,
|
|
141
143
|
ignoreLazyScalarProperties: true,
|
|
142
144
|
lookup: false,
|
|
143
145
|
});
|
|
@@ -148,8 +150,8 @@ export class EntityManager {
|
|
|
148
150
|
// save the original hint value so we know it was infer/all
|
|
149
151
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
150
152
|
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 });
|
|
153
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
154
|
+
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
153
155
|
if (results.length === 0) {
|
|
154
156
|
await em.storeCache(options.cache, cached, []);
|
|
155
157
|
return [];
|
|
@@ -168,7 +170,6 @@ export class EntityManager {
|
|
|
168
170
|
await em.entityLoader.populate(entityName, unique, populate, {
|
|
169
171
|
...options,
|
|
170
172
|
...em.getPopulateWhere(where, options),
|
|
171
|
-
convertCustomTypes: false,
|
|
172
173
|
ignoreLazyScalarProperties: true,
|
|
173
174
|
lookup: false,
|
|
174
175
|
});
|
|
@@ -181,6 +182,61 @@ export class EntityManager {
|
|
|
181
182
|
}
|
|
182
183
|
return unique;
|
|
183
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Finds all entities and returns an async iterable (async generator) that yields results one by one.
|
|
187
|
+
* The results are merged and mapped to entity instances, without adding them to the identity map.
|
|
188
|
+
* You can disable merging by passing the options `{ mergeResults: false }`.
|
|
189
|
+
* With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
|
|
190
|
+
* root entities when there are multiple items in the populated collection.
|
|
191
|
+
* This is useful for processing large datasets without loading everything into memory at once.
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* const stream = em.stream(Book, { populate: ['author'] });
|
|
195
|
+
*
|
|
196
|
+
* for await (const book of stream) {
|
|
197
|
+
* // book is an instance of Book entity
|
|
198
|
+
* console.log(book.title, book.author.name);
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
async *stream(entityName, options = {}) {
|
|
203
|
+
const em = this.getContext();
|
|
204
|
+
em.prepareOptions(options);
|
|
205
|
+
options.strategy = 'joined';
|
|
206
|
+
await em.tryFlush(entityName, options);
|
|
207
|
+
entityName = Utils.className(entityName);
|
|
208
|
+
const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
|
|
209
|
+
em.validator.validateParams(where);
|
|
210
|
+
options.orderBy = options.orderBy || {};
|
|
211
|
+
options.populate = await em.preparePopulate(entityName, options);
|
|
212
|
+
const meta = this.metadata.get(entityName);
|
|
213
|
+
options = { ...options };
|
|
214
|
+
// save the original hint value so we know it was infer/all
|
|
215
|
+
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
216
|
+
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
217
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
218
|
+
const stream = em.driver.stream(entityName, where, {
|
|
219
|
+
ctx: em.transactionContext,
|
|
220
|
+
mapResults: false,
|
|
221
|
+
...options,
|
|
222
|
+
});
|
|
223
|
+
for await (const data of stream) {
|
|
224
|
+
const fork = em.fork();
|
|
225
|
+
const entity = fork.entityFactory.create(entityName, data, {
|
|
226
|
+
refresh: options.refresh,
|
|
227
|
+
schema: options.schema,
|
|
228
|
+
convertCustomTypes: true,
|
|
229
|
+
});
|
|
230
|
+
helper(entity).setSerializationContext({
|
|
231
|
+
populate: options.populate,
|
|
232
|
+
fields: options.fields,
|
|
233
|
+
exclude: options.exclude,
|
|
234
|
+
});
|
|
235
|
+
await fork.unitOfWork.dispatchOnLoadEvent();
|
|
236
|
+
fork.clear();
|
|
237
|
+
yield entity;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
184
240
|
/**
|
|
185
241
|
* Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
|
|
186
242
|
*/
|
|
@@ -203,8 +259,8 @@ export class EntityManager {
|
|
|
203
259
|
/**
|
|
204
260
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
205
261
|
*/
|
|
206
|
-
addFilter(name, cond, entityName,
|
|
207
|
-
|
|
262
|
+
addFilter(name, cond, entityName, options = true) {
|
|
263
|
+
options = typeof options === 'object' ? { name, cond, default: true, ...options } : { name, cond, default: options };
|
|
208
264
|
if (entityName) {
|
|
209
265
|
options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
|
|
210
266
|
}
|
|
@@ -232,8 +288,8 @@ export class EntityManager {
|
|
|
232
288
|
/**
|
|
233
289
|
* Gets logger context for this entity manager.
|
|
234
290
|
*/
|
|
235
|
-
getLoggerContext() {
|
|
236
|
-
const em = this.getContext();
|
|
291
|
+
getLoggerContext(options) {
|
|
292
|
+
const em = options?.disableContextResolution ? this : this.getContext();
|
|
237
293
|
em.loggerContext ??= {};
|
|
238
294
|
return em.loggerContext;
|
|
239
295
|
}
|
|
@@ -283,28 +339,39 @@ export class EntityManager {
|
|
|
283
339
|
}
|
|
284
340
|
return ret;
|
|
285
341
|
}
|
|
286
|
-
async getJoinedFilters(meta,
|
|
342
|
+
async getJoinedFilters(meta, options) {
|
|
343
|
+
if (!this.config.get('filtersOnRelations') || !options.populate) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
287
346
|
const ret = {};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
347
|
+
for (const hint of options.populate) {
|
|
348
|
+
const field = hint.field.split(':')[0];
|
|
349
|
+
const prop = meta.properties[field];
|
|
350
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
351
|
+
const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
|
|
352
|
+
if (!joined && !hint.filter) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
356
|
+
const where = await this.applyFilters(prop.type, {}, filters, 'read', {
|
|
357
|
+
...options,
|
|
358
|
+
populate: hint.children,
|
|
359
|
+
});
|
|
360
|
+
const where2 = await this.getJoinedFilters(prop.targetMeta, {
|
|
361
|
+
...options,
|
|
362
|
+
filters,
|
|
363
|
+
populate: hint.children,
|
|
364
|
+
populateWhere: PopulateHint.ALL,
|
|
365
|
+
});
|
|
366
|
+
if (Utils.hasObjectKeys(where)) {
|
|
367
|
+
ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
|
|
368
|
+
}
|
|
369
|
+
if (where2 && Utils.hasObjectKeys(where2)) {
|
|
370
|
+
if (ret[field]) {
|
|
371
|
+
Utils.merge(ret[field], where2);
|
|
300
372
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Utils.merge(ret[field], where2);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
ret[field] = where2;
|
|
307
|
-
}
|
|
373
|
+
else {
|
|
374
|
+
ret[field] = where2;
|
|
308
375
|
}
|
|
309
376
|
}
|
|
310
377
|
}
|
|
@@ -313,27 +380,45 @@ export class EntityManager {
|
|
|
313
380
|
/**
|
|
314
381
|
* 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
382
|
*/
|
|
316
|
-
async autoJoinRefsForFilters(meta, options) {
|
|
317
|
-
if (!meta || !this.config.get('autoJoinRefsForFilters')) {
|
|
383
|
+
async autoJoinRefsForFilters(meta, options, parent) {
|
|
384
|
+
if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
|
|
318
385
|
return;
|
|
319
386
|
}
|
|
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
387
|
const ret = options.populate;
|
|
325
|
-
for (const prop of
|
|
326
|
-
|
|
388
|
+
for (const prop of meta.relations) {
|
|
389
|
+
if (prop.object
|
|
390
|
+
|| ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
|
|
391
|
+
|| !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
392
|
+
|| (parent?.className === prop.targetMeta.root.className && parent.propName === prop.inversedBy)) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
396
|
+
const cond = await this.applyFilters(prop.type, {}, options.filters, 'read', options);
|
|
327
397
|
if (!Utils.isEmpty(cond)) {
|
|
328
398
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
329
|
-
|
|
330
|
-
|
|
399
|
+
let found = false;
|
|
400
|
+
for (const hint of populated) {
|
|
401
|
+
if (!hint.all) {
|
|
402
|
+
hint.filter = true;
|
|
403
|
+
}
|
|
404
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
405
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
406
|
+
found = true;
|
|
407
|
+
}
|
|
331
408
|
}
|
|
332
|
-
|
|
409
|
+
if (!found) {
|
|
333
410
|
ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
|
|
334
411
|
}
|
|
335
412
|
}
|
|
336
413
|
}
|
|
414
|
+
for (const hint of ret) {
|
|
415
|
+
const [field, ref] = hint.field.split(':');
|
|
416
|
+
const prop = meta?.properties[field];
|
|
417
|
+
if (prop && !ref) {
|
|
418
|
+
hint.children ??= [];
|
|
419
|
+
await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { className: meta.root.className, propName: prop.name });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
337
422
|
}
|
|
338
423
|
/**
|
|
339
424
|
* @internal
|
|
@@ -363,7 +448,7 @@ export class EntityManager {
|
|
|
363
448
|
let cond;
|
|
364
449
|
if (filter.cond instanceof Function) {
|
|
365
450
|
// @ts-ignore
|
|
366
|
-
const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
451
|
+
const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
367
452
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
368
453
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
369
454
|
}
|
|
@@ -372,13 +457,17 @@ export class EntityManager {
|
|
|
372
457
|
else {
|
|
373
458
|
cond = filter.cond;
|
|
374
459
|
}
|
|
375
|
-
|
|
460
|
+
cond = QueryHelper.processWhere({
|
|
376
461
|
where: cond,
|
|
377
462
|
entityName,
|
|
378
463
|
metadata: this.metadata,
|
|
379
464
|
platform: this.driver.getPlatform(),
|
|
380
465
|
aliased: type === 'read',
|
|
381
|
-
})
|
|
466
|
+
});
|
|
467
|
+
if (filter.strict) {
|
|
468
|
+
Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
|
|
469
|
+
}
|
|
470
|
+
ret.push(cond);
|
|
382
471
|
}
|
|
383
472
|
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
|
|
384
473
|
return conds.length > 1 ? { $and: conds } : conds[0];
|
|
@@ -433,6 +522,10 @@ export class EntityManager {
|
|
|
433
522
|
* });
|
|
434
523
|
* ```
|
|
435
524
|
*
|
|
525
|
+
* The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
|
|
526
|
+
* returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
|
|
527
|
+
* of pages.
|
|
528
|
+
*
|
|
436
529
|
* The `Cursor` object provides the following interface:
|
|
437
530
|
*
|
|
438
531
|
* ```ts
|
|
@@ -442,7 +535,7 @@ export class EntityManager {
|
|
|
442
535
|
* User { ... },
|
|
443
536
|
* User { ... },
|
|
444
537
|
* ],
|
|
445
|
-
* totalCount: 50,
|
|
538
|
+
* totalCount: 50, // not included if `includeCount: false`
|
|
446
539
|
* startCursor: 'WzRd',
|
|
447
540
|
* endCursor: 'WzZd',
|
|
448
541
|
* hasPrevPage: true,
|
|
@@ -457,7 +550,9 @@ export class EntityManager {
|
|
|
457
550
|
if (Utils.isEmpty(options.orderBy)) {
|
|
458
551
|
throw new Error('Explicit `orderBy` option required');
|
|
459
552
|
}
|
|
460
|
-
const [entities, count] =
|
|
553
|
+
const [entities, count] = options.includeCount !== false
|
|
554
|
+
? await em.findAndCount(entityName, where, options)
|
|
555
|
+
: [await em.find(entityName, where, options)];
|
|
461
556
|
return new Cursor(entities, count, options, this.metadata.get(entityName));
|
|
462
557
|
}
|
|
463
558
|
/**
|
|
@@ -483,18 +578,31 @@ export class EntityManager {
|
|
|
483
578
|
async refresh(entity, options = {}) {
|
|
484
579
|
const fork = this.fork({ keepTransactionContext: true });
|
|
485
580
|
const entityName = entity.constructor.name;
|
|
581
|
+
const wrapped = helper(entity);
|
|
486
582
|
const reloaded = await fork.findOne(entityName, entity, {
|
|
487
|
-
schema:
|
|
583
|
+
schema: wrapped.__schema,
|
|
488
584
|
...options,
|
|
489
585
|
flushMode: FlushMode.COMMIT,
|
|
490
586
|
});
|
|
491
|
-
|
|
492
|
-
|
|
587
|
+
const em = this.getContext();
|
|
588
|
+
if (!reloaded) {
|
|
589
|
+
em.unitOfWork.unsetIdentity(entity);
|
|
590
|
+
return null;
|
|
493
591
|
}
|
|
494
|
-
|
|
495
|
-
|
|
592
|
+
let found = false;
|
|
593
|
+
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
594
|
+
const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
|
|
595
|
+
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true });
|
|
596
|
+
em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, true);
|
|
597
|
+
Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
|
|
598
|
+
found ||= ref === entity;
|
|
496
599
|
}
|
|
497
|
-
|
|
600
|
+
if (!found) {
|
|
601
|
+
const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true });
|
|
602
|
+
em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
|
|
603
|
+
Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
|
|
604
|
+
}
|
|
605
|
+
return entity;
|
|
498
606
|
}
|
|
499
607
|
/**
|
|
500
608
|
* Finds first entity matching your `where` query.
|
|
@@ -530,23 +638,25 @@ export class EntityManager {
|
|
|
530
638
|
options.populate = await em.preparePopulate(entityName, options);
|
|
531
639
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
532
640
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
533
|
-
if (cached?.data) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
641
|
+
if (cached?.data !== undefined) {
|
|
642
|
+
if (cached.data) {
|
|
643
|
+
await em.entityLoader.populate(entityName, [cached.data], options.populate, {
|
|
644
|
+
...options,
|
|
645
|
+
...em.getPopulateWhere(where, options),
|
|
646
|
+
ignoreLazyScalarProperties: true,
|
|
647
|
+
lookup: false,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
541
650
|
return cached.data;
|
|
542
651
|
}
|
|
543
652
|
options = { ...options };
|
|
544
653
|
// save the original hint value so we know it was infer/all
|
|
545
654
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
546
655
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
547
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
656
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
548
657
|
const data = await em.driver.findOne(entityName, where, {
|
|
549
658
|
ctx: em.transactionContext,
|
|
659
|
+
em,
|
|
550
660
|
...options,
|
|
551
661
|
});
|
|
552
662
|
if (!data) {
|
|
@@ -654,24 +764,7 @@ export class EntityManager {
|
|
|
654
764
|
}
|
|
655
765
|
}
|
|
656
766
|
}
|
|
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
|
-
}
|
|
767
|
+
where = getWhereCondition(meta, options.onConflictFields, data, where).where;
|
|
675
768
|
data = QueryHelper.processObjectParams(data);
|
|
676
769
|
em.validator.validateParams(data, 'insert data');
|
|
677
770
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
@@ -716,6 +809,7 @@ export class EntityManager {
|
|
|
716
809
|
ctx: em.transactionContext,
|
|
717
810
|
convertCustomTypes: true,
|
|
718
811
|
connectionType: 'write',
|
|
812
|
+
schema: options.schema,
|
|
719
813
|
});
|
|
720
814
|
em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
|
|
721
815
|
}
|
|
@@ -813,31 +907,17 @@ export class EntityManager {
|
|
|
813
907
|
}
|
|
814
908
|
}
|
|
815
909
|
}
|
|
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);
|
|
910
|
+
const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
|
|
911
|
+
propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
912
|
+
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
913
|
+
propIndex = tmp.propIndex;
|
|
835
914
|
where = QueryHelper.processWhere({
|
|
836
|
-
where,
|
|
915
|
+
where: tmp.where,
|
|
837
916
|
entityName,
|
|
838
917
|
metadata: this.metadata,
|
|
839
918
|
platform: this.getPlatform(),
|
|
840
919
|
});
|
|
920
|
+
row = QueryHelper.processObjectParams(row);
|
|
841
921
|
em.validator.validateParams(row, 'insert data');
|
|
842
922
|
allData.push(row);
|
|
843
923
|
allWhere.push(where);
|
|
@@ -879,7 +959,7 @@ export class EntityManager {
|
|
|
879
959
|
const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
|
|
880
960
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
881
961
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
882
|
-
const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
|
|
962
|
+
const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
|
|
883
963
|
for (const cond of loadPK.values()) {
|
|
884
964
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
885
965
|
}
|
|
@@ -896,6 +976,7 @@ export class EntityManager {
|
|
|
896
976
|
ctx: em.transactionContext,
|
|
897
977
|
convertCustomTypes: true,
|
|
898
978
|
connectionType: 'write',
|
|
979
|
+
schema: options.schema,
|
|
899
980
|
});
|
|
900
981
|
for (const [entity, cond] of loadPK.entries()) {
|
|
901
982
|
const row = data2.find(row => {
|
|
@@ -952,45 +1033,37 @@ export class EntityManager {
|
|
|
952
1033
|
}
|
|
953
1034
|
/**
|
|
954
1035
|
* Runs your callback wrapped inside a database transaction.
|
|
1036
|
+
*
|
|
1037
|
+
* If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
|
|
1038
|
+
* can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
|
|
1039
|
+
* should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
|
|
1040
|
+
* method automatically creates an async context for the transaction.
|
|
1041
|
+
*
|
|
1042
|
+
* **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
|
|
1043
|
+
* `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
|
|
1044
|
+
* between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
|
|
1045
|
+
* the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
|
|
1046
|
+
*
|
|
1047
|
+
* **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
|
|
1048
|
+
* parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
|
|
1049
|
+
* and then call this method on the fork.
|
|
1050
|
+
*
|
|
1051
|
+
* **Example:**
|
|
1052
|
+
* ```ts
|
|
1053
|
+
* await em.transactional(async (em) => {
|
|
1054
|
+
* const author = new Author('Jon');
|
|
1055
|
+
* em.persist(author);
|
|
1056
|
+
* // flush is called automatically at the end of the callback
|
|
1057
|
+
* });
|
|
1058
|
+
* ```
|
|
955
1059
|
*/
|
|
956
1060
|
async transactional(cb, options = {}) {
|
|
957
1061
|
const em = this.getContext(false);
|
|
958
1062
|
if (this.disableTransactions || em.disableTransactions) {
|
|
959
1063
|
return cb(em);
|
|
960
1064
|
}
|
|
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
|
-
});
|
|
1065
|
+
const manager = new TransactionManager(this);
|
|
1066
|
+
return manager.handle(cb, options);
|
|
994
1067
|
}
|
|
995
1068
|
/**
|
|
996
1069
|
* Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
|
|
@@ -1170,22 +1243,35 @@ export class EntityManager {
|
|
|
1170
1243
|
* via second parameter. By default, it will return already loaded entities without modifying them.
|
|
1171
1244
|
*/
|
|
1172
1245
|
merge(entityName, data, options = {}) {
|
|
1173
|
-
const em = this.getContext();
|
|
1174
1246
|
if (Utils.isEntity(entityName)) {
|
|
1175
|
-
return
|
|
1247
|
+
return this.merge(entityName.constructor.name, entityName, data);
|
|
1176
1248
|
}
|
|
1249
|
+
const em = options.disableContextResolution ? this : this.getContext();
|
|
1177
1250
|
options.schema ??= em._schema;
|
|
1251
|
+
options.validate ??= true;
|
|
1252
|
+
options.cascade ??= true;
|
|
1178
1253
|
entityName = Utils.className(entityName);
|
|
1179
|
-
|
|
1254
|
+
if (options.validate) {
|
|
1255
|
+
em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
|
|
1256
|
+
}
|
|
1180
1257
|
let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
|
|
1181
1258
|
if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
|
|
1182
1259
|
return entity;
|
|
1183
1260
|
}
|
|
1184
1261
|
const meta = em.metadata.find(entityName);
|
|
1185
1262
|
const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1263
|
+
const dataIsEntity = Utils.isEntity(data);
|
|
1264
|
+
if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
|
|
1265
|
+
helper(entity).__data = helper(data).__data;
|
|
1266
|
+
helper(entity).__originalEntityData = helper(data).__originalEntityData;
|
|
1267
|
+
return entity;
|
|
1268
|
+
}
|
|
1269
|
+
entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1270
|
+
if (options.validate) {
|
|
1271
|
+
em.validator.validate(entity, data, childMeta ?? meta);
|
|
1272
|
+
}
|
|
1273
|
+
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1274
|
+
em.unitOfWork.merge(entity, visited);
|
|
1189
1275
|
return entity;
|
|
1190
1276
|
}
|
|
1191
1277
|
/**
|
|
@@ -1210,6 +1296,7 @@ export class EntityManager {
|
|
|
1210
1296
|
...options,
|
|
1211
1297
|
newEntity: !options.managed,
|
|
1212
1298
|
merge: options.managed,
|
|
1299
|
+
normalizeAccessors: true,
|
|
1213
1300
|
});
|
|
1214
1301
|
options.persist ??= em.config.get('persistOnCreate');
|
|
1215
1302
|
if (options.persist && !this.getMetadata(entityName).embeddable) {
|
|
@@ -1248,10 +1335,8 @@ export class EntityManager {
|
|
|
1248
1335
|
async count(entityName, where = {}, options = {}) {
|
|
1249
1336
|
const em = this.getContext(false);
|
|
1250
1337
|
// Shallow copy options since the object will be modified when deleting orderBy
|
|
1251
|
-
options = {
|
|
1252
|
-
|
|
1253
|
-
...options,
|
|
1254
|
-
};
|
|
1338
|
+
options = { ...options };
|
|
1339
|
+
em.prepareOptions(options);
|
|
1255
1340
|
entityName = Utils.className(entityName);
|
|
1256
1341
|
await em.tryFlush(entityName, options);
|
|
1257
1342
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
@@ -1261,15 +1346,15 @@ export class EntityManager {
|
|
|
1261
1346
|
const meta = em.metadata.find(entityName);
|
|
1262
1347
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
1263
1348
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
1264
|
-
options.populateFilter = await this.getJoinedFilters(meta,
|
|
1349
|
+
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
1265
1350
|
em.validator.validateParams(where);
|
|
1266
1351
|
delete options.orderBy;
|
|
1267
1352
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1268
1353
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1269
|
-
if (cached?.data) {
|
|
1354
|
+
if (cached?.data !== undefined) {
|
|
1270
1355
|
return cached.data;
|
|
1271
1356
|
}
|
|
1272
|
-
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
|
|
1357
|
+
const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
1273
1358
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1274
1359
|
return +count;
|
|
1275
1360
|
}
|
|
@@ -1395,7 +1480,7 @@ export class EntityManager {
|
|
|
1395
1480
|
const em = this.getContext();
|
|
1396
1481
|
em.prepareOptions(options);
|
|
1397
1482
|
const entityName = arr[0].constructor.name;
|
|
1398
|
-
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
|
|
1483
|
+
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
|
|
1399
1484
|
await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1400
1485
|
return entities;
|
|
1401
1486
|
}
|
|
@@ -1431,6 +1516,9 @@ export class EntityManager {
|
|
|
1431
1516
|
for (const entity of em.unitOfWork.getIdentityMap()) {
|
|
1432
1517
|
fork.unitOfWork.register(entity);
|
|
1433
1518
|
}
|
|
1519
|
+
for (const entity of em.unitOfWork.getPersistStack()) {
|
|
1520
|
+
fork.unitOfWork.persist(entity);
|
|
1521
|
+
}
|
|
1434
1522
|
for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
|
|
1435
1523
|
fork.unitOfWork.getOrphanRemoveStack().add(entity);
|
|
1436
1524
|
}
|
|
@@ -1452,6 +1540,12 @@ export class EntityManager {
|
|
|
1452
1540
|
getEntityFactory() {
|
|
1453
1541
|
return this.getContext().entityFactory;
|
|
1454
1542
|
}
|
|
1543
|
+
/**
|
|
1544
|
+
* @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
|
|
1545
|
+
*/
|
|
1546
|
+
getEntityLoader() {
|
|
1547
|
+
return this.getContext().entityLoader;
|
|
1548
|
+
}
|
|
1455
1549
|
/**
|
|
1456
1550
|
* Gets the Hydrator used by the EntityManager.
|
|
1457
1551
|
*/
|
|
@@ -1548,7 +1642,6 @@ export class EntityManager {
|
|
|
1548
1642
|
...options,
|
|
1549
1643
|
...this.getPopulateWhere(where, options),
|
|
1550
1644
|
orderBy: options.populateOrderBy ?? options.orderBy,
|
|
1551
|
-
convertCustomTypes: false,
|
|
1552
1645
|
ignoreLazyScalarProperties: true,
|
|
1553
1646
|
lookup: false,
|
|
1554
1647
|
});
|
|
@@ -1671,7 +1764,7 @@ export class EntityManager {
|
|
|
1671
1764
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1672
1765
|
}
|
|
1673
1766
|
options.schema ??= this._schema;
|
|
1674
|
-
options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1767
|
+
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1675
1768
|
}
|
|
1676
1769
|
/**
|
|
1677
1770
|
* @internal
|
|
@@ -1695,31 +1788,31 @@ export class EntityManager {
|
|
|
1695
1788
|
const em = this.getContext();
|
|
1696
1789
|
const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
|
|
1697
1790
|
const cached = await em.resultCache.get(cacheKey);
|
|
1698
|
-
if (cached) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
data = em.entityFactory.create(entityName, cached, {
|
|
1710
|
-
merge: true,
|
|
1711
|
-
convertCustomTypes: true,
|
|
1712
|
-
refresh,
|
|
1713
|
-
recomputeSnapshot: true,
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
else {
|
|
1717
|
-
data = cached;
|
|
1718
|
-
}
|
|
1719
|
-
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1720
|
-
return { key: cacheKey, data };
|
|
1791
|
+
if (!cached) {
|
|
1792
|
+
return { key: cacheKey, data: cached };
|
|
1793
|
+
}
|
|
1794
|
+
let data;
|
|
1795
|
+
if (Array.isArray(cached) && merge) {
|
|
1796
|
+
data = cached.map(item => em.entityFactory.create(entityName, item, {
|
|
1797
|
+
merge: true,
|
|
1798
|
+
convertCustomTypes: true,
|
|
1799
|
+
refresh,
|
|
1800
|
+
recomputeSnapshot: true,
|
|
1801
|
+
}));
|
|
1721
1802
|
}
|
|
1722
|
-
|
|
1803
|
+
else if (Utils.isObject(cached) && merge) {
|
|
1804
|
+
data = em.entityFactory.create(entityName, cached, {
|
|
1805
|
+
merge: true,
|
|
1806
|
+
convertCustomTypes: true,
|
|
1807
|
+
refresh,
|
|
1808
|
+
recomputeSnapshot: true,
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
else {
|
|
1812
|
+
data = cached;
|
|
1813
|
+
}
|
|
1814
|
+
await em.unitOfWork.dispatchOnLoadEvent();
|
|
1815
|
+
return { key: cacheKey, data };
|
|
1723
1816
|
}
|
|
1724
1817
|
/**
|
|
1725
1818
|
* @internal
|