@mikro-orm/core 7.1.0-dev.8 → 7.1.0-dev.9
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 +25 -4
- package/EntityManager.js +25 -0
- package/drivers/IDatabaseDriver.d.ts +10 -0
- package/entity/Collection.d.ts +3 -0
- package/entity/Collection.js +11 -3
- package/entity/EntityRepository.d.ts +12 -2
- package/entity/EntityRepository.js +12 -0
- package/package.json +1 -1
- package/utils/DataloaderUtils.d.ts +10 -1
- package/utils/DataloaderUtils.js +78 -0
- package/utils/Utils.js +1 -1
package/EntityManager.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { type EntityRepository } from './entity/EntityRepository.js';
|
|
|
6
6
|
import { EntityLoader, type EntityLoaderOptions } from './entity/EntityLoader.js';
|
|
7
7
|
import { Reference } from './entity/Reference.js';
|
|
8
8
|
import { UnitOfWork } from './unit-of-work/UnitOfWork.js';
|
|
9
|
-
import type { CountOptions, DeleteOptions, FilterOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers/IDatabaseDriver.js';
|
|
10
|
-
import type { AnyString, ArrayElement, AutoPath, ConnectionType, Dictionary, EntityClass, EntityData, EntityDictionary, EntityDTO, EntityMetadata, EntityName, FilterDef, FilterQuery, FromEntityType, GetRepository, IHydrator, IsSubset, Loaded, MergeLoaded, MergeSelected, ObjectQuery, PopulateOptions, Primary, Ref, RequiredEntityData, UnboxArray, IndexFilterQuery, WithUsingOptions } from './typings.js';
|
|
9
|
+
import type { CountByOptions, CountOptions, DeleteOptions, FilterOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers/IDatabaseDriver.js';
|
|
10
|
+
import type { AnyString, ArrayElement, AutoPath, ConnectionType, Dictionary, EntityClass, EntityData, EntityDictionary, EntityDTO, EntityKey, EntityMetadata, EntityName, FilterDef, FilterQuery, FromEntityType, GetRepository, IHydrator, IsSubset, Loaded, MergeLoaded, MergeSelected, ObjectQuery, PopulateOptions, Primary, Ref, RequiredEntityData, UnboxArray, IndexFilterQuery, WithUsingOptions } from './typings.js';
|
|
11
11
|
import { FlushMode, LockMode, PopulatePath, type TransactionOptions } from './enums.js';
|
|
12
12
|
import type { MetadataStorage } from './metadata/MetadataStorage.js';
|
|
13
13
|
import type { Transaction } from './connections/Connection.js';
|
|
@@ -441,6 +441,27 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
441
441
|
* Returns total number of entities matching your `where` query.
|
|
442
442
|
*/
|
|
443
443
|
count<Entity extends object, Hint extends string = never>(entityName: EntityName<Entity>, where?: FilterQuery<NoInfer<Entity>>, options?: CountOptions<Entity, Hint>): Promise<number>;
|
|
444
|
+
/**
|
|
445
|
+
* Counts entities grouped by one or more properties. Returns a dictionary keyed by the grouped
|
|
446
|
+
* field value(s), with counts as values. For composite `groupBy`, keys are joined with `~~~`.
|
|
447
|
+
*
|
|
448
|
+
* SQL drivers issue a single `GROUP BY` query; MongoDB uses an aggregation pipeline.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```ts
|
|
452
|
+
* // Count books per author
|
|
453
|
+
* const counts = await em.countBy(Book, 'author');
|
|
454
|
+
* // { '1': 2, '2': 1, '3': 3 }
|
|
455
|
+
*
|
|
456
|
+
* // Count with a filter
|
|
457
|
+
* const counts = await em.countBy(Book, 'author', { where: { active: true } });
|
|
458
|
+
*
|
|
459
|
+
* // Composite groupBy — keys joined with ~~~
|
|
460
|
+
* const counts = await em.countBy(Order, ['status', 'country']);
|
|
461
|
+
* // { 'pending~~~US': 5, 'shipped~~~DE': 3 }
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
countBy<Entity extends object>(entityName: EntityName<Entity>, groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
|
|
444
465
|
/**
|
|
445
466
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
446
467
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -543,7 +564,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
543
564
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
544
565
|
*/
|
|
545
566
|
protected shouldRefresh<T extends object, P extends string = never, F extends string = never, E extends string = never>(meta: EntityMetadata<T>, entity: T, options: FindOneOptions<T, P, F, E>): boolean;
|
|
546
|
-
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any>): void;
|
|
567
|
+
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>): void;
|
|
547
568
|
/**
|
|
548
569
|
* @internal
|
|
549
570
|
*/
|
|
@@ -585,7 +606,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
585
606
|
*/
|
|
586
607
|
set schema(schema: string | null | undefined);
|
|
587
608
|
/** @internal */
|
|
588
|
-
getDataLoader(type: 'ref' | '1:m' | 'm:n'): Promise<any>;
|
|
609
|
+
getDataLoader(type: 'ref' | '1:m' | 'm:n' | 'count'): Promise<any>;
|
|
589
610
|
/**
|
|
590
611
|
* Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
|
|
591
612
|
* if executed inside request context handler.
|
package/EntityManager.js
CHANGED
|
@@ -1458,6 +1458,29 @@ export class EntityManager {
|
|
|
1458
1458
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1459
1459
|
return +count;
|
|
1460
1460
|
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Counts entities grouped by one or more properties. Returns a dictionary keyed by the grouped
|
|
1463
|
+
* field value(s), with counts as values. For composite `groupBy`, keys are joined with `~~~`.
|
|
1464
|
+
*
|
|
1465
|
+
* SQL drivers issue a single `GROUP BY` query; MongoDB uses an aggregation pipeline.
|
|
1466
|
+
*
|
|
1467
|
+
* @example
|
|
1468
|
+
* ```ts
|
|
1469
|
+
* // Count books per author
|
|
1470
|
+
* const counts = await em.countBy(Book, 'author');
|
|
1471
|
+
* // { '1': 2, '2': 1, '3': 3 }
|
|
1472
|
+
*
|
|
1473
|
+
* // Count with a filter
|
|
1474
|
+
* const counts = await em.countBy(Book, 'author', { where: { active: true } });
|
|
1475
|
+
*
|
|
1476
|
+
* // Composite groupBy — keys joined with ~~~
|
|
1477
|
+
* const counts = await em.countBy(Order, ['status', 'country']);
|
|
1478
|
+
* // { 'pending~~~US': 5, 'shipped~~~DE': 3 }
|
|
1479
|
+
* ```
|
|
1480
|
+
*/
|
|
1481
|
+
async countBy(entityName, groupBy, options) {
|
|
1482
|
+
throw new Error(`${this.constructor.name}.countBy() is not supported by the current driver`);
|
|
1483
|
+
}
|
|
1461
1484
|
/**
|
|
1462
1485
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
1463
1486
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -2031,6 +2054,8 @@ export class EntityManager {
|
|
|
2031
2054
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
2032
2055
|
case 'm:n':
|
|
2033
2056
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
2057
|
+
case 'count':
|
|
2058
|
+
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getCountBatchLoadFn(em)));
|
|
2034
2059
|
}
|
|
2035
2060
|
}
|
|
2036
2061
|
/**
|
|
@@ -325,6 +325,16 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
325
325
|
/** @internal used to apply filters to the auto-joined relations */
|
|
326
326
|
em?: EntityManager;
|
|
327
327
|
}
|
|
328
|
+
/** Options for `em.countBy()` queries. */
|
|
329
|
+
export interface CountByOptions<T extends object> {
|
|
330
|
+
where?: FilterQuery<T>;
|
|
331
|
+
filters?: FilterOptions;
|
|
332
|
+
having?: FilterQuery<T>;
|
|
333
|
+
schema?: string;
|
|
334
|
+
flushMode?: FlushMode | `${FlushMode}`;
|
|
335
|
+
loggerContext?: LogContext;
|
|
336
|
+
logging?: LoggingOptions;
|
|
337
|
+
}
|
|
328
338
|
/** Options for `em.qb().update()` operations. */
|
|
329
339
|
export interface UpdateOptions<T> {
|
|
330
340
|
filters?: FilterOptions;
|
package/entity/Collection.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
37
37
|
/**
|
|
38
38
|
* Gets the count of collection items from database instead of counting loaded items.
|
|
39
39
|
* The value is cached (unless you use the `where` option), use `refresh: true` to force reload it.
|
|
40
|
+
* When the dataloader is enabled (globally or per-query), multiple calls are batched into a single grouped query.
|
|
40
41
|
*/
|
|
41
42
|
loadCount(options?: LoadCountOptions<T> | boolean): Promise<number>;
|
|
42
43
|
/** Queries a subset of the collection items from the database with custom filtering, ordering, and pagination. */
|
|
@@ -193,4 +194,6 @@ export interface LoadCountOptions<T extends object> extends CountOptions<T, '*'>
|
|
|
193
194
|
refresh?: boolean;
|
|
194
195
|
/** Additional filtering conditions for the count query. */
|
|
195
196
|
where?: FilterQuery<T>;
|
|
197
|
+
/** Whether to use the dataloader for batching count operations. */
|
|
198
|
+
dataloader?: boolean;
|
|
196
199
|
}
|
package/entity/Collection.js
CHANGED
|
@@ -79,10 +79,11 @@ export class Collection {
|
|
|
79
79
|
/**
|
|
80
80
|
* Gets the count of collection items from database instead of counting loaded items.
|
|
81
81
|
* The value is cached (unless you use the `where` option), use `refresh: true` to force reload it.
|
|
82
|
+
* When the dataloader is enabled (globally or per-query), multiple calls are batched into a single grouped query.
|
|
82
83
|
*/
|
|
83
84
|
async loadCount(options = {}) {
|
|
84
85
|
options = typeof options === 'boolean' ? { refresh: options } : options;
|
|
85
|
-
const { refresh, where, ...countOptions } = options;
|
|
86
|
+
const { refresh, where, dataloader, ...countOptions } = options;
|
|
86
87
|
if (!refresh && !where && this.#count != null) {
|
|
87
88
|
return this.#count;
|
|
88
89
|
}
|
|
@@ -92,8 +93,15 @@ export class Collection {
|
|
|
92
93
|
this.property.owner) {
|
|
93
94
|
return (this.#count = this.length);
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
let count;
|
|
97
|
+
if (dataloader ?? [DataloaderType.ALL, DataloaderType.COLLECTION].includes(em.config.getDataloaderType())) {
|
|
98
|
+
const loader = await em.getDataLoader('count');
|
|
99
|
+
count = await loader.load([this, { where, ...countOptions }]);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const cond = this.createLoadCountCondition(where ?? {});
|
|
103
|
+
count = await em.count(this.property.targetMeta.class, cond, countOptions);
|
|
104
|
+
}
|
|
97
105
|
if (!where) {
|
|
98
106
|
this.#count = count;
|
|
99
107
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PopulatePath } from '../enums.js';
|
|
2
2
|
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
|
|
3
3
|
import type { AssignOptions } from './EntityAssigner.js';
|
|
4
|
-
import type { EntityData,
|
|
5
|
-
import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
4
|
+
import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityName, FilterQuery, Loaded, Primary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement, IndexFilterQuery, WithUsingOptions } from '../typings.js';
|
|
5
|
+
import type { CountByOptions, CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
6
6
|
import type { EntityLoaderOptions } from './EntityLoader.js';
|
|
7
7
|
import type { Cursor } from '../utils/Cursor.js';
|
|
8
8
|
/** Repository class providing a type-safe API for querying and persisting a specific entity type. */
|
|
@@ -207,6 +207,16 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
207
207
|
* Returns total number of entities matching your `where` query.
|
|
208
208
|
*/
|
|
209
209
|
count<Hint extends string = never>(where?: FilterQuery<Entity>, options?: CountOptions<Entity, Hint>): Promise<number>;
|
|
210
|
+
/**
|
|
211
|
+
* Counts entities grouped by one or more properties.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* const counts = await repo.countBy('status');
|
|
216
|
+
* // { 'active': 5, 'inactive': 2 }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
countBy(groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
|
|
210
220
|
/** Returns the entity class name associated with this repository. */
|
|
211
221
|
getEntityName(): string;
|
|
212
222
|
/**
|
|
@@ -194,6 +194,18 @@ export class EntityRepository {
|
|
|
194
194
|
async count(where = {}, options = {}) {
|
|
195
195
|
return this.getEntityManager().count(this.entityName, where, options);
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Counts entities grouped by one or more properties.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* const counts = await repo.countBy('status');
|
|
203
|
+
* // { 'active': 5, 'inactive': 2 }
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async countBy(groupBy, options) {
|
|
207
|
+
return this.getEntityManager().countBy(this.entityName, groupBy, options);
|
|
208
|
+
}
|
|
197
209
|
/** Returns the entity class name associated with this repository. */
|
|
198
210
|
getEntityName() {
|
|
199
211
|
return Utils.className(this.entityName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.9",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Constructor, Primary, Ref } from '../typings.js';
|
|
2
|
-
import { Collection, type InitCollectionOptions } from '../entity/Collection.js';
|
|
2
|
+
import { Collection, type InitCollectionOptions, type LoadCountOptions } from '../entity/Collection.js';
|
|
3
3
|
import { type EntityManager } from '../EntityManager.js';
|
|
4
4
|
import { type LoadReferenceOptions } from '../entity/Reference.js';
|
|
5
5
|
type BatchLoadFn<K, V> = (keys: readonly K[]) => PromiseLike<ArrayLike<V | Error>>;
|
|
@@ -44,6 +44,15 @@ export declare class DataloaderUtils {
|
|
|
44
44
|
* makes one query per entity and maps each input collection to the corresponding result.
|
|
45
45
|
*/
|
|
46
46
|
static getManyToManyColBatchLoadFn(em: EntityManager): BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
|
|
47
|
+
/**
|
|
48
|
+
* Returns the count dataloader batchLoadFn, which aggregates `Collection.loadCount()` calls
|
|
49
|
+
* by entity and relation, issues a single grouped count query per entity+options combination
|
|
50
|
+
* via `em.countBy()`, and maps each input collection to the corresponding count.
|
|
51
|
+
*
|
|
52
|
+
* For 1:M relations, groups by the FK property on the target entity.
|
|
53
|
+
* For M:N relations, groups by the owner FK on the pivot entity.
|
|
54
|
+
*/
|
|
55
|
+
static getCountBatchLoadFn(em: EntityManager): BatchLoadFn<[Collection<any>, Omit<LoadCountOptions<any>, 'dataloader' | 'refresh'>?], number>;
|
|
47
56
|
static getDataLoader(): Promise<Constructor<{
|
|
48
57
|
load: (...args: unknown[]) => Promise<unknown>;
|
|
49
58
|
}>>;
|
package/utils/DataloaderUtils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Collection } from '../entity/Collection.js';
|
|
2
2
|
import { helper } from '../entity/wrap.js';
|
|
3
3
|
import { Reference } from '../entity/Reference.js';
|
|
4
|
+
import { ReferenceKind } from '../enums.js';
|
|
4
5
|
import { Utils } from './Utils.js';
|
|
5
6
|
export class DataloaderUtils {
|
|
6
7
|
static DataLoader;
|
|
@@ -216,6 +217,83 @@ export class DataloaderUtils {
|
|
|
216
217
|
return ret;
|
|
217
218
|
};
|
|
218
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Returns the count dataloader batchLoadFn, which aggregates `Collection.loadCount()` calls
|
|
222
|
+
* by entity and relation, issues a single grouped count query per entity+options combination
|
|
223
|
+
* via `em.countBy()`, and maps each input collection to the corresponding count.
|
|
224
|
+
*
|
|
225
|
+
* For 1:M relations, groups by the FK property on the target entity.
|
|
226
|
+
* For M:N relations, groups by the owner FK on the pivot entity.
|
|
227
|
+
*/
|
|
228
|
+
static getCountBatchLoadFn(em) {
|
|
229
|
+
return async (collsWithOpts) => {
|
|
230
|
+
const groups = new Map();
|
|
231
|
+
const keys = [];
|
|
232
|
+
for (const [col, opts] of collsWithOpts) {
|
|
233
|
+
const prop = col.property;
|
|
234
|
+
let fkProp;
|
|
235
|
+
let countByClass;
|
|
236
|
+
let targetFilterProp;
|
|
237
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
238
|
+
fkProp = prop.mappedBy;
|
|
239
|
+
countByClass = prop.targetMeta.class;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// M:N: group by the owner FK on the pivot entity
|
|
243
|
+
const pivotMeta = em.getMetadata().get(prop.pivotEntity);
|
|
244
|
+
const ownerPivotProp = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
245
|
+
const targetPivotProp = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
246
|
+
fkProp = ownerPivotProp.name;
|
|
247
|
+
countByClass = pivotMeta.class;
|
|
248
|
+
targetFilterProp = targetPivotProp.name;
|
|
249
|
+
}
|
|
250
|
+
// Include the owner-side uniqueName in the key so that two distinct owner entity types
|
|
251
|
+
// sharing a relation with the same property name (e.g. `Author.books` and `Publisher.books`)
|
|
252
|
+
// don't collide into a single batch that would use one owner's FK mapping for the other.
|
|
253
|
+
const ownerUniqueName = helper(col.owner).__meta.uniqueName;
|
|
254
|
+
const key = `${ownerUniqueName}.${prop.name}|${JSON.stringify(opts ?? {})}`;
|
|
255
|
+
keys.push(key);
|
|
256
|
+
let group = groups.get(key);
|
|
257
|
+
if (!group) {
|
|
258
|
+
group = { fkProp, countByClass, targetFilterProp, ownerPKs: new Map(), opts: opts ?? {} };
|
|
259
|
+
groups.set(key, group);
|
|
260
|
+
}
|
|
261
|
+
const pk = helper(col.owner).getPrimaryKey();
|
|
262
|
+
group.ownerPKs.set(JSON.stringify(pk), pk);
|
|
263
|
+
}
|
|
264
|
+
const promises = Array.from(groups.entries()).map(async ([key, group]) => {
|
|
265
|
+
const allPKs = Array.from(group.ownerPKs.values());
|
|
266
|
+
const { where, ...countOpts } = group.opts;
|
|
267
|
+
const conditions = [{ [group.fkProp]: { $in: allPKs } }];
|
|
268
|
+
if (where) {
|
|
269
|
+
conditions.push(where);
|
|
270
|
+
}
|
|
271
|
+
// For M:N, apply the target entity's filters through the pivot's target relation
|
|
272
|
+
if (group.targetFilterProp) {
|
|
273
|
+
const targetMeta = em.getMetadata().find(group.countByClass);
|
|
274
|
+
const targetRelMeta = targetMeta.properties[group.targetFilterProp]?.targetMeta;
|
|
275
|
+
if (targetRelMeta) {
|
|
276
|
+
const filterCond = await em.applyFilters(targetRelMeta.class, {}, countOpts.filters, 'read');
|
|
277
|
+
if (filterCond && Object.keys(filterCond).length > 0) {
|
|
278
|
+
conditions.push({ [group.targetFilterProp]: filterCond });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const fkWhere = conditions.length === 1 ? conditions[0] : { $and: conditions };
|
|
283
|
+
const counts = await em.countBy(group.countByClass, group.fkProp, {
|
|
284
|
+
where: fkWhere,
|
|
285
|
+
...countOpts,
|
|
286
|
+
});
|
|
287
|
+
return [key, counts];
|
|
288
|
+
});
|
|
289
|
+
const resultsMap = new Map(await Promise.all(promises));
|
|
290
|
+
return collsWithOpts.map(([col], i) => {
|
|
291
|
+
const pk = helper(col.owner).getPrimaryKey();
|
|
292
|
+
const counts = resultsMap.get(keys[i]);
|
|
293
|
+
return counts?.[String(pk)] ?? 0;
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
}
|
|
219
297
|
static async getDataLoader() {
|
|
220
298
|
if (this.DataLoader) {
|
|
221
299
|
return this.DataLoader;
|
package/utils/Utils.js
CHANGED
|
@@ -141,7 +141,7 @@ export function parseJsonSafe(value) {
|
|
|
141
141
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
142
142
|
export class Utils {
|
|
143
143
|
static PK_SEPARATOR = '~~~';
|
|
144
|
-
static #ORM_VERSION = '7.1.0-dev.
|
|
144
|
+
static #ORM_VERSION = '7.1.0-dev.9';
|
|
145
145
|
/**
|
|
146
146
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
147
147
|
*/
|