@mikro-orm/core 7.1.0-dev.4 → 7.1.0-dev.41
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 +63 -12
- package/EntityManager.js +221 -40
- package/README.md +2 -1
- package/connections/Connection.d.ts +29 -0
- package/drivers/IDatabaseDriver.d.ts +45 -7
- package/entity/BaseEntity.d.ts +68 -1
- package/entity/BaseEntity.js +18 -0
- package/entity/Collection.d.ts +6 -3
- package/entity/Collection.js +15 -4
- package/entity/EntityAssigner.js +8 -0
- package/entity/EntityFactory.js +20 -1
- package/entity/EntityLoader.d.ts +8 -1
- package/entity/EntityLoader.js +89 -28
- package/entity/EntityRepository.d.ts +27 -9
- package/entity/EntityRepository.js +12 -0
- package/entity/Reference.d.ts +42 -1
- package/entity/Reference.js +9 -0
- package/entity/defineEntity.d.ts +99 -21
- package/entity/defineEntity.js +17 -6
- package/entity/utils.js +4 -5
- package/enums.d.ts +8 -1
- package/errors.d.ts +2 -0
- package/errors.js +4 -0
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/metadata/EntitySchema.js +3 -0
- package/metadata/MetadataDiscovery.d.ts +12 -0
- package/metadata/MetadataDiscovery.js +166 -20
- package/metadata/MetadataValidator.d.ts +24 -0
- package/metadata/MetadataValidator.js +202 -1
- package/metadata/types.d.ts +71 -4
- package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
- package/naming-strategy/NamingStrategy.d.ts +1 -1
- package/package.json +1 -1
- package/platforms/Platform.d.ts +18 -3
- package/platforms/Platform.js +58 -6
- package/serialization/EntitySerializer.js +2 -1
- package/typings.d.ts +202 -22
- package/typings.js +51 -14
- package/unit-of-work/UnitOfWork.js +15 -4
- package/utils/AbstractMigrator.d.ts +20 -5
- package/utils/AbstractMigrator.js +263 -28
- package/utils/AbstractSchemaGenerator.d.ts +1 -1
- package/utils/AbstractSchemaGenerator.js +4 -1
- package/utils/Configuration.d.ts +25 -0
- package/utils/Configuration.js +1 -0
- package/utils/DataloaderUtils.d.ts +10 -1
- package/utils/DataloaderUtils.js +78 -0
- package/utils/EntityComparator.js +1 -1
- package/utils/QueryHelper.d.ts +16 -0
- package/utils/QueryHelper.js +15 -0
- package/utils/TransactionManager.js +2 -0
- package/utils/Utils.js +1 -1
- package/utils/fs-utils.d.ts +2 -0
- package/utils/fs-utils.js +7 -1
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/partition-utils.d.ts +17 -0
- package/utils/partition-utils.js +79 -0
- package/utils/upsert-utils.d.ts +2 -0
- package/utils/upsert-utils.js +26 -1
package/entity/BaseEntity.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ref } from './Reference.js';
|
|
1
|
+
import { type LoadReferenceOptions, type LoadReferenceOrFailOptions, type Ref } from './Reference.js';
|
|
2
2
|
import type { AutoPath, EntityData, EntityDTO, ExtractFieldsHint, Loaded, LoadedReference, AddEager, EntityKey, FromEntityType, IsSubset, MergeSelected, ResolveSerializeFields, SerializeDTO, SerializeFieldsKeepPK } from '../typings.js';
|
|
3
3
|
import { type AssignOptions } from './EntityAssigner.js';
|
|
4
4
|
import type { EntityLoaderOptions } from './EntityLoader.js';
|
|
@@ -90,3 +90,70 @@ export declare abstract class BaseEntity {
|
|
|
90
90
|
/** Sets the database schema for this entity. */
|
|
91
91
|
setSchema(schema?: string): void;
|
|
92
92
|
}
|
|
93
|
+
type EntityConstructor<T extends object = object> = abstract new (...args: any[]) => T;
|
|
94
|
+
/**
|
|
95
|
+
* The `load()` / `loadOrFail()` methods added by the {@link Loadable} mixin. Declared as an interface so the
|
|
96
|
+
* mixin function can have an explicit return type (required by JSR fast-check).
|
|
97
|
+
*/
|
|
98
|
+
export interface LoadableEntity {
|
|
99
|
+
/**
|
|
100
|
+
* Ensures this entity is loaded (without reloading it if it already is). Returns the entity, or `null` if it
|
|
101
|
+
* was not found in the database (e.g. it was deleted in the meantime, or active filters disallow loading it).
|
|
102
|
+
* Use `loadOrFail()` if you want an error to be thrown in such a case.
|
|
103
|
+
*/
|
|
104
|
+
load<Entity extends this = this, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: LoadReferenceOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
|
105
|
+
/**
|
|
106
|
+
* Ensures this entity is loaded (without reloading it if it already is). Returns the entity, or throws an error
|
|
107
|
+
* just like `em.findOneOrFail()` (and respects the same config options) if it was not found.
|
|
108
|
+
*/
|
|
109
|
+
loadOrFail<Entity extends this = this, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: LoadReferenceOrFailOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
110
|
+
}
|
|
111
|
+
/** Return-type shape of {@link Loadable} — a constructor that produces instances of `TBase` enriched with {@link LoadableEntity}. */
|
|
112
|
+
export type LoadableConstructor<TBase extends EntityConstructor> = abstract new (...args: any[]) => InstanceType<TBase> & LoadableEntity;
|
|
113
|
+
/** Internal: rejects a base class that already defines `load` or `loadOrFail` to prevent silent override. */
|
|
114
|
+
type EnsureNoLoadConflict<TBase extends EntityConstructor> = InstanceType<TBase> extends {
|
|
115
|
+
load: any;
|
|
116
|
+
} ? 'Loadable: base class already defines `load` — remove it or do not apply the mixin' : InstanceType<TBase> extends {
|
|
117
|
+
loadOrFail: any;
|
|
118
|
+
} ? 'Loadable: base class already defines `loadOrFail` — remove it or do not apply the mixin' : TBase;
|
|
119
|
+
/** Empty base for {@link Loadable} when called without arguments — standalone mixin, no inherited base. */
|
|
120
|
+
declare abstract class EmptyBase {
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Mixin that adds `load()` / `loadOrFail()` methods to an entity class. These methods ensure the entity is loaded
|
|
124
|
+
* from the database without reloading it if it already is — unlike `init()`, which always refreshes.
|
|
125
|
+
*
|
|
126
|
+
* Useful when migrating from a non-`Ref`-based codebase where lazy loading support is desired without the
|
|
127
|
+
* `.$` / `.get()` indirection that the `Reference` wrapper requires. Opt-in so it does not conflict with entities
|
|
128
|
+
* that already define a `load` or `loadOrFail` property — applying the mixin to a base class that already has
|
|
129
|
+
* either method is a compile error to prevent silent override.
|
|
130
|
+
*
|
|
131
|
+
* Call without arguments (`Loadable()`) for a standalone base with no other inheritance, or pass a base class
|
|
132
|
+
* (`Loadable(BaseEntity)`) to compose. The convenience alias {@link LoadableBaseEntity} is shorthand for the
|
|
133
|
+
* latter.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // compose with BaseEntity
|
|
138
|
+
* class User extends Loadable(BaseEntity) {
|
|
139
|
+
* @PrimaryKey()
|
|
140
|
+
* id!: number;
|
|
141
|
+
* }
|
|
142
|
+
*
|
|
143
|
+
* // standalone — no inherited base
|
|
144
|
+
* class Product extends Loadable() {
|
|
145
|
+
* @PrimaryKey()
|
|
146
|
+
* id!: number;
|
|
147
|
+
* }
|
|
148
|
+
*
|
|
149
|
+
* const user = orm.em.getReference(User, 1);
|
|
150
|
+
* await user.load();
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export declare function Loadable(): LoadableConstructor<typeof EmptyBase> & typeof EmptyBase;
|
|
154
|
+
export declare function Loadable<TBase extends EntityConstructor>(Base: EnsureNoLoadConflict<TBase> extends TBase ? TBase : never): LoadableConstructor<TBase> & TBase;
|
|
155
|
+
declare const LoadableBaseEntityBase: LoadableConstructor<typeof BaseEntity> & typeof BaseEntity;
|
|
156
|
+
/** Convenience: `BaseEntity` pre-composed with the `Loadable` mixin. */
|
|
157
|
+
export declare abstract class LoadableBaseEntity extends LoadableBaseEntityBase {
|
|
158
|
+
}
|
|
159
|
+
export {};
|
package/entity/BaseEntity.js
CHANGED
|
@@ -49,3 +49,21 @@ export class BaseEntity {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
Object.defineProperty(BaseEntity.prototype, '__baseEntity', { value: true, writable: false, enumerable: false });
|
|
52
|
+
/** Empty base for {@link Loadable} when called without arguments — standalone mixin, no inherited base. */
|
|
53
|
+
class EmptyBase {
|
|
54
|
+
}
|
|
55
|
+
export function Loadable(Base = EmptyBase) {
|
|
56
|
+
class LoadableMixin extends Base {
|
|
57
|
+
async load(options) {
|
|
58
|
+
return Reference.create(this).load(options);
|
|
59
|
+
}
|
|
60
|
+
async loadOrFail(options) {
|
|
61
|
+
return Reference.create(this).loadOrFail(options);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return LoadableMixin;
|
|
65
|
+
}
|
|
66
|
+
const LoadableBaseEntityBase = Loadable(BaseEntity);
|
|
67
|
+
/** Convenience: `BaseEntity` pre-composed with the `Loadable` mixin. */
|
|
68
|
+
export class LoadableBaseEntity extends LoadableBaseEntityBase {
|
|
69
|
+
}
|
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. */
|
|
@@ -48,14 +49,14 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
48
49
|
/** Serializes the collection items to plain JSON objects. Returns an empty array if not initialized. */
|
|
49
50
|
toJSON<TT extends T>(): EntityDTO<TT>[];
|
|
50
51
|
/** Adds one or more items to the collection, propagating the change to the inverse side. Returns the number of items added. */
|
|
51
|
-
add<TT extends T>(entity: TT | Reference<TT> | Iterable<TT | Reference<TT>>, ...entities: (
|
|
52
|
+
add<TT extends T>(entity: TT | Reference<TT> | Iterable<TT | Reference<TT>>, ...entities: (T | Reference<T>)[]): number;
|
|
52
53
|
/**
|
|
53
54
|
* Remove specified item(s) from the collection. Note that removing item from collection does not necessarily imply deleting the target entity,
|
|
54
55
|
* it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
|
|
55
56
|
* is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
|
|
56
57
|
* which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
|
|
57
58
|
*/
|
|
58
|
-
remove<TT extends T>(entity: TT | Reference<TT> | Iterable<TT | Reference<TT>> | ((item:
|
|
59
|
+
remove<TT extends T>(entity: TT | Reference<TT> | Iterable<TT | Reference<TT>> | ((item: T) => boolean), ...entities: (T | Reference<T>)[]): number;
|
|
59
60
|
/** Checks whether the collection contains the given item. */
|
|
60
61
|
contains<TT extends T>(item: TT | Reference<TT>, check?: boolean): boolean;
|
|
61
62
|
/** Returns the number of items in the collection. Throws if the collection is not initialized. */
|
|
@@ -93,7 +94,7 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
93
94
|
/**
|
|
94
95
|
* @internal
|
|
95
96
|
*/
|
|
96
|
-
hydrate(items: T[], forcePropagate?: boolean, partial?: boolean): void;
|
|
97
|
+
hydrate(items: T[], forcePropagate?: boolean, partial?: boolean, readonly?: boolean): void;
|
|
97
98
|
/**
|
|
98
99
|
* Remove all items from the collection. Note that removing items from collection does not necessarily imply deleting the target entity,
|
|
99
100
|
* it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
|
|
@@ -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
|
}
|
|
@@ -462,7 +470,7 @@ export class Collection {
|
|
|
462
470
|
/**
|
|
463
471
|
* @internal
|
|
464
472
|
*/
|
|
465
|
-
hydrate(items, forcePropagate, partial) {
|
|
473
|
+
hydrate(items, forcePropagate, partial, readonly) {
|
|
466
474
|
for (let i = 0; i < this.#items.size; i++) {
|
|
467
475
|
delete this[i];
|
|
468
476
|
}
|
|
@@ -472,6 +480,9 @@ export class Collection {
|
|
|
472
480
|
this.#count = 0;
|
|
473
481
|
this.add(items);
|
|
474
482
|
this.takeSnapshot(forcePropagate);
|
|
483
|
+
if (readonly) {
|
|
484
|
+
this.#readonly = true;
|
|
485
|
+
}
|
|
475
486
|
}
|
|
476
487
|
/**
|
|
477
488
|
* Remove all items from the collection. Note that removing items from collection does not necessarily imply deleting the target entity,
|
package/entity/EntityAssigner.js
CHANGED
|
@@ -99,6 +99,14 @@ export class EntityAssigner {
|
|
|
99
99
|
return EntityAssigner.assignReference(entity, value, prop, options.em, options);
|
|
100
100
|
}
|
|
101
101
|
if (prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && (prop.setter || !prop.getter)) {
|
|
102
|
+
// mirror the hydrator (used by `em.create`) and coerce string/number inputs to `Date` instances,
|
|
103
|
+
// since `EntityData` already permits `string | Date` for `Date`-typed properties at the type level
|
|
104
|
+
if (prop.runtimeType === 'Date' &&
|
|
105
|
+
value != null &&
|
|
106
|
+
!(value instanceof Date) &&
|
|
107
|
+
(typeof value === 'string' || typeof value === 'number')) {
|
|
108
|
+
value = new Date(value);
|
|
109
|
+
}
|
|
102
110
|
validateProperty(prop, value, entity);
|
|
103
111
|
return (entity[prop.name] = value);
|
|
104
112
|
}
|
package/entity/EntityFactory.js
CHANGED
|
@@ -316,11 +316,30 @@ export class EntityFactory {
|
|
|
316
316
|
else if (!onCreateOnly && prop.default != null && !isRaw(prop.default) && entity[prop.name] === undefined) {
|
|
317
317
|
entity[prop.name] = prop.default;
|
|
318
318
|
}
|
|
319
|
+
else if (!onCreateOnly &&
|
|
320
|
+
this.#config.get('initNullableProperties') &&
|
|
321
|
+
prop.nullable &&
|
|
322
|
+
prop.default == null &&
|
|
323
|
+
!prop.defaultRaw &&
|
|
324
|
+
entity[prop.name] === undefined) {
|
|
325
|
+
entity[prop.name] = (this.#config.get('forceUndefined') ? undefined : null);
|
|
326
|
+
}
|
|
319
327
|
if (prop.kind === ReferenceKind.EMBEDDED && entity[prop.name]) {
|
|
320
328
|
const items = prop.array ? entity[prop.name] : [entity[prop.name]];
|
|
321
329
|
for (const item of items) {
|
|
322
330
|
// Embedded sub-properties need all defaults since the DB can't apply them within JSON columns.
|
|
323
|
-
|
|
331
|
+
// For polymorphic embeddables, resolve the actual subtype to avoid setting
|
|
332
|
+
// properties from other subtypes (e.g. Cat's canMeow on a Dog instance).
|
|
333
|
+
let targetMeta = prop.targetMeta;
|
|
334
|
+
if (targetMeta.polymorphs && targetMeta.discriminatorColumn) {
|
|
335
|
+
const discValue = item[targetMeta.discriminatorColumn];
|
|
336
|
+
// eslint-disable-next-line eqeqeq
|
|
337
|
+
const resolved = targetMeta.polymorphs.find(m => m.discriminatorValue == discValue);
|
|
338
|
+
if (resolved) {
|
|
339
|
+
targetMeta = resolved;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
this.assignDefaultValues(item, targetMeta);
|
|
324
343
|
}
|
|
325
344
|
}
|
|
326
345
|
}
|
package/entity/EntityLoader.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
|
|
1
|
+
import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateHintOptions, PopulateOptions } from '../typings.js';
|
|
2
2
|
import type { EntityManager } from '../EntityManager.js';
|
|
3
3
|
import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
|
|
4
|
+
import type { InflightQueryAbortStrategy } from '../connections/Connection.js';
|
|
4
5
|
import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
|
|
5
6
|
import type { LoggingOptions } from '../logging/Logger.js';
|
|
6
7
|
/** Options for controlling how relations are loaded by the EntityLoader. */
|
|
@@ -37,6 +38,12 @@ export interface EntityLoaderOptions<Entity, Fields extends string = never, Excl
|
|
|
37
38
|
connectionType?: ConnectionType;
|
|
38
39
|
/** Logging options for the query. */
|
|
39
40
|
logging?: LoggingOptions;
|
|
41
|
+
/** Per-relation populate overrides (limit, offset, orderBy). */
|
|
42
|
+
populateHints?: Record<string, PopulateHintOptions>;
|
|
43
|
+
/** AbortSignal forwarded to populated relation queries. */
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
/** Cancellation strategy paired with {@link signal}. */
|
|
46
|
+
inflightQueryAbortStrategy?: InflightQueryAbortStrategy;
|
|
40
47
|
}
|
|
41
48
|
/** Responsible for batch-loading entity relations using either select-in or joined loading strategies. */
|
|
42
49
|
export declare class EntityLoader {
|
package/entity/EntityLoader.js
CHANGED
|
@@ -177,8 +177,9 @@ export class EntityLoader {
|
|
|
177
177
|
Utils.isObject(orderBy[prop.name]))
|
|
178
178
|
.flatMap(orderBy => orderBy[prop.name]);
|
|
179
179
|
const where = await this.extractChildCondition(options, prop);
|
|
180
|
+
const mergedOrderBy = populate.orderBy ? Utils.asArray(populate.orderBy) : innerOrderBy;
|
|
180
181
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
|
|
181
|
-
const pivotOrderBy = QueryHelper.mergeOrderBy(
|
|
182
|
+
const pivotOrderBy = QueryHelper.mergeOrderBy(mergedOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
|
|
182
183
|
const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
|
|
183
184
|
return Utils.flatten(res);
|
|
184
185
|
}
|
|
@@ -188,10 +189,12 @@ export class EntityLoader {
|
|
|
188
189
|
const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
|
|
189
190
|
...options,
|
|
190
191
|
where,
|
|
191
|
-
orderBy:
|
|
192
|
+
orderBy: mergedOrderBy,
|
|
192
193
|
}, !!(ref || prop.mapToPk));
|
|
193
|
-
const
|
|
194
|
-
|
|
194
|
+
const hasLimit = populate.limit != null;
|
|
195
|
+
const isPartial = partial || hasLimit;
|
|
196
|
+
const customOrder = mergedOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
|
|
197
|
+
this.initializeCollections(filtered, prop, field, items, customOrder, isPartial, hasLimit);
|
|
195
198
|
return items;
|
|
196
199
|
}
|
|
197
200
|
async populateScalar(meta, filtered, options) {
|
|
@@ -269,15 +272,15 @@ export class EntityLoader {
|
|
|
269
272
|
}));
|
|
270
273
|
return allItems;
|
|
271
274
|
}
|
|
272
|
-
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
|
275
|
+
initializeCollections(filtered, prop, field, children, customOrder, partial, readonly) {
|
|
273
276
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
274
|
-
this.initializeOneToMany(filtered, children, prop, field, partial);
|
|
277
|
+
this.initializeOneToMany(filtered, children, prop, field, partial, readonly);
|
|
275
278
|
}
|
|
276
279
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
|
|
277
|
-
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
|
280
|
+
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial, readonly);
|
|
278
281
|
}
|
|
279
282
|
}
|
|
280
|
-
initializeOneToMany(filtered, children, prop, field, partial) {
|
|
283
|
+
initializeOneToMany(filtered, children, prop, field, partial, readonly) {
|
|
281
284
|
const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
|
|
282
285
|
const map = {};
|
|
283
286
|
for (const entity of filtered) {
|
|
@@ -293,14 +296,14 @@ export class EntityLoader {
|
|
|
293
296
|
}
|
|
294
297
|
for (const entity of filtered) {
|
|
295
298
|
const key = helper(entity).getSerializedPrimaryKey();
|
|
296
|
-
entity[field].hydrate(map[key], undefined, partial);
|
|
299
|
+
entity[field].hydrate(map[key], undefined, partial, readonly);
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
|
-
initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
|
|
302
|
+
initializeManyToMany(filtered, children, prop, field, customOrder, partial, readonly) {
|
|
300
303
|
if (prop.mappedBy) {
|
|
301
304
|
for (const entity of filtered) {
|
|
302
305
|
const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
|
|
303
|
-
entity[field].hydrate(items, true, partial);
|
|
306
|
+
entity[field].hydrate(items, true, partial, readonly);
|
|
304
307
|
}
|
|
305
308
|
}
|
|
306
309
|
else {
|
|
@@ -311,12 +314,12 @@ export class EntityLoader {
|
|
|
311
314
|
if (!customOrder) {
|
|
312
315
|
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
313
316
|
}
|
|
314
|
-
entity[field].hydrate(items, true, partial);
|
|
317
|
+
entity[field].hydrate(items, true, partial, readonly);
|
|
315
318
|
}
|
|
316
319
|
}
|
|
317
320
|
}
|
|
318
321
|
async findChildren(entities, prop, populate, options, ref) {
|
|
319
|
-
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
|
322
|
+
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref, populate));
|
|
320
323
|
const meta = prop.targetMeta;
|
|
321
324
|
// When targetKey is set, use it for FK lookup instead of the PK
|
|
322
325
|
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
@@ -334,10 +337,10 @@ export class EntityLoader {
|
|
|
334
337
|
fk = ownerProp.name;
|
|
335
338
|
}
|
|
336
339
|
}
|
|
337
|
-
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
|
|
340
|
+
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && (!ref || (!prop.mapToPk && !populate.filter))) {
|
|
338
341
|
children.length = 0;
|
|
339
342
|
fk = meta.properties[prop.mappedBy].name;
|
|
340
|
-
children.push(...this.filterByReferences(entities, prop
|
|
343
|
+
children.push(...this.filterByReferences(entities, prop, options, ref));
|
|
341
344
|
}
|
|
342
345
|
if (children.length === 0) {
|
|
343
346
|
return { items: [], partial };
|
|
@@ -375,7 +378,7 @@ export class EntityLoader {
|
|
|
375
378
|
where = { $and: [where, prop.where] };
|
|
376
379
|
}
|
|
377
380
|
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
|
378
|
-
const
|
|
381
|
+
const findOptions = {
|
|
379
382
|
filters,
|
|
380
383
|
convertCustomTypes,
|
|
381
384
|
lockMode,
|
|
@@ -390,11 +393,21 @@ export class EntityLoader {
|
|
|
390
393
|
fields,
|
|
391
394
|
schema,
|
|
392
395
|
connectionType,
|
|
396
|
+
signal: options.signal,
|
|
397
|
+
inflightQueryAbortStrategy: options.inflightQueryAbortStrategy,
|
|
393
398
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
394
399
|
refresh: refresh && !children.every(item => options.visited.has(item)),
|
|
395
400
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
396
401
|
visited: options.visited,
|
|
397
|
-
}
|
|
402
|
+
};
|
|
403
|
+
if (populate.limit != null) {
|
|
404
|
+
findOptions._partitionLimit = {
|
|
405
|
+
partitionBy: fk,
|
|
406
|
+
limit: populate.limit,
|
|
407
|
+
offset: populate.offset,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const items = await this.#em.find(meta.class, where, findOptions);
|
|
398
411
|
// For targetKey relations, wire up loaded entities to parent references
|
|
399
412
|
// This is needed because the references were created under alternate key,
|
|
400
413
|
// but loaded entities are stored under PK, so they don't automatically merge
|
|
@@ -582,11 +595,18 @@ export class EntityLoader {
|
|
|
582
595
|
const options2 = { ...options, fields, exclude, populateFilter };
|
|
583
596
|
['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
|
|
584
597
|
options2.populate = populate?.children ?? [];
|
|
598
|
+
if (populate?.limit != null) {
|
|
599
|
+
options2._partitionLimit = {
|
|
600
|
+
limit: populate.limit,
|
|
601
|
+
offset: populate.offset,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
585
604
|
if (!Utils.isEmpty(prop.where)) {
|
|
586
605
|
where = { $and: [where, prop.where] };
|
|
587
606
|
}
|
|
588
607
|
const map = await this.#driver.loadFromPivotTable(prop, ids, where, orderBy, this.#em.getTransactionContext(), options2, pivotJoin);
|
|
589
608
|
const children = [];
|
|
609
|
+
const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
|
|
590
610
|
for (let i = 0; i < filtered.length; i++) {
|
|
591
611
|
const entity = filtered[i];
|
|
592
612
|
const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
|
|
@@ -596,7 +616,11 @@ export class EntityLoader {
|
|
|
596
616
|
schema: options.schema ?? this.#em.config.get('schema'),
|
|
597
617
|
});
|
|
598
618
|
}
|
|
599
|
-
|
|
619
|
+
// Union-target items carry their concrete class via `constructor` — dispatch to the right factory call.
|
|
620
|
+
const targetClass = isUnionTargetMN && item.constructor !== Object
|
|
621
|
+
? item.constructor
|
|
622
|
+
: prop.targetMeta.class;
|
|
623
|
+
const entity = this.#em.getEntityFactory().create(targetClass, item, {
|
|
600
624
|
refresh,
|
|
601
625
|
merge: true,
|
|
602
626
|
convertCustomTypes: true,
|
|
@@ -604,7 +628,8 @@ export class EntityLoader {
|
|
|
604
628
|
});
|
|
605
629
|
return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
|
|
606
630
|
});
|
|
607
|
-
|
|
631
|
+
const hasLimit = populate?.limit != null;
|
|
632
|
+
entity[prop.name].hydrate(items, true, hasLimit, hasLimit);
|
|
608
633
|
children.push(items);
|
|
609
634
|
}
|
|
610
635
|
return children;
|
|
@@ -665,8 +690,11 @@ export class EntityLoader {
|
|
|
665
690
|
}
|
|
666
691
|
return ret;
|
|
667
692
|
}, []);
|
|
668
|
-
// we need to automatically select the FKs too, e.g. for 1:m
|
|
669
|
-
|
|
693
|
+
// we need to automatically select the FKs too, e.g. for 1:m and inverse 1:1 relations
|
|
694
|
+
// to be able to wire them with the items
|
|
695
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY ||
|
|
696
|
+
prop.kind === ReferenceKind.MANY_TO_MANY ||
|
|
697
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
670
698
|
const owner = prop.targetMeta.properties[prop.mappedBy];
|
|
671
699
|
// when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
|
|
672
700
|
// otherwise the driver will exclude it and we won't be able to map children to their parent collections
|
|
@@ -679,7 +707,7 @@ export class EntityLoader {
|
|
|
679
707
|
}
|
|
680
708
|
return ret;
|
|
681
709
|
}
|
|
682
|
-
getChildReferences(entities, prop, options, ref) {
|
|
710
|
+
getChildReferences(entities, prop, options, ref, populate) {
|
|
683
711
|
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
684
712
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
685
713
|
return filtered.map(e => e[prop.name].owner);
|
|
@@ -695,7 +723,7 @@ export class EntityLoader {
|
|
|
695
723
|
return filtered;
|
|
696
724
|
}
|
|
697
725
|
// MANY_TO_ONE or ONE_TO_ONE
|
|
698
|
-
return this.filterReferences(entities, prop.name, options, ref);
|
|
726
|
+
return this.filterReferences(entities, prop.name, options, ref, populate);
|
|
699
727
|
}
|
|
700
728
|
filterCollections(entities, field, options, ref) {
|
|
701
729
|
if (options.refresh) {
|
|
@@ -721,7 +749,7 @@ export class EntityLoader {
|
|
|
721
749
|
}
|
|
722
750
|
return this.isPropertyLoaded(entity[f], r.join('.'));
|
|
723
751
|
}
|
|
724
|
-
filterReferences(entities, field, options, ref) {
|
|
752
|
+
filterReferences(entities, field, options, ref, populate) {
|
|
725
753
|
if (ref) {
|
|
726
754
|
return [];
|
|
727
755
|
}
|
|
@@ -730,13 +758,19 @@ export class EntityLoader {
|
|
|
730
758
|
return children.map(e => Reference.unwrapReference(e[field]));
|
|
731
759
|
}
|
|
732
760
|
if (options.fields) {
|
|
761
|
+
// `:ref` populate children are satisfied by FK/PK and will be loaded lazily — they don't force a parent reload
|
|
762
|
+
const refChildren = new Set((populate?.children ?? [])
|
|
763
|
+
.map(c => c.field.split(':'))
|
|
764
|
+
.filter(parts => parts[1] === 'ref')
|
|
765
|
+
.map(parts => parts[0]));
|
|
733
766
|
return children
|
|
734
767
|
.map(e => Reference.unwrapReference(e[field]))
|
|
735
768
|
.filter(target => {
|
|
736
769
|
const wrapped = helper(target);
|
|
737
770
|
const childFields = options.fields
|
|
738
771
|
.filter(f => f.startsWith(`${field}.`))
|
|
739
|
-
.map(f => f.substring(field.length + 1))
|
|
772
|
+
.map(f => f.substring(field.length + 1))
|
|
773
|
+
.filter(cf => !refChildren.has(cf.split('.')[0]));
|
|
740
774
|
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
|
741
775
|
});
|
|
742
776
|
}
|
|
@@ -744,12 +778,39 @@ export class EntityLoader {
|
|
|
744
778
|
.filter(e => !e[field].__helper.__initialized)
|
|
745
779
|
.map(e => Reference.unwrapReference(e[field]));
|
|
746
780
|
}
|
|
747
|
-
filterByReferences(entities,
|
|
781
|
+
filterByReferences(entities, prop, options, ref) {
|
|
748
782
|
/* v8 ignore next */
|
|
749
|
-
if (refresh) {
|
|
783
|
+
if (options.refresh) {
|
|
750
784
|
return entities;
|
|
751
785
|
}
|
|
752
|
-
|
|
786
|
+
const field = prop.name;
|
|
787
|
+
return entities.filter(e => {
|
|
788
|
+
const value = e[field];
|
|
789
|
+
if (value === null) {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
if (value === undefined || !Utils.isEntity(value, true)) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
const target = Reference.unwrapReference(value);
|
|
796
|
+
const wrapped = helper(target);
|
|
797
|
+
if (!wrapped.__initialized) {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
if (ref) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
if (options.fields) {
|
|
804
|
+
const childFields = options.fields
|
|
805
|
+
.filter(f => f.startsWith(`${prop.name}.`))
|
|
806
|
+
.map(f => f.substring(prop.name.length + 1));
|
|
807
|
+
return childFields.length > 0 && !childFields.every(f => this.isPropertyLoaded(target, f));
|
|
808
|
+
}
|
|
809
|
+
return prop.targetMeta.comparableProps.some(targetProp => {
|
|
810
|
+
const inlineEmbedded = targetProp.kind === ReferenceKind.EMBEDDED && !targetProp.object;
|
|
811
|
+
return !inlineEmbedded && !targetProp.lazy && !wrapped.__loadedProperties.has(targetProp.name);
|
|
812
|
+
});
|
|
813
|
+
});
|
|
753
814
|
}
|
|
754
815
|
lookupAllRelationships(entityName) {
|
|
755
816
|
const ret = [];
|
|
@@ -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. */
|
|
@@ -13,13 +13,17 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
13
13
|
/**
|
|
14
14
|
* Finds first entity matching your `where` query.
|
|
15
15
|
*/
|
|
16
|
-
findOne<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity
|
|
16
|
+
findOne<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOneOptions<Entity, Hint, Fields, Excludes> & {
|
|
17
|
+
using?: Using | Using[];
|
|
18
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
|
17
19
|
/**
|
|
18
20
|
* Finds first entity matching your `where` query. If nothing is found, it will throw an error.
|
|
19
21
|
* You can override the factory for creating this method via `options.failHandler` locally
|
|
20
22
|
* or via `Configuration.findOneOrFailHandler` globally.
|
|
21
23
|
*/
|
|
22
|
-
findOneOrFail<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes>
|
|
24
|
+
findOneOrFail<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes> & {
|
|
25
|
+
using?: Using | Using[];
|
|
26
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
23
27
|
/**
|
|
24
28
|
* Creates or updates the entity, based on whether it is already present in the database.
|
|
25
29
|
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
|
@@ -72,24 +76,28 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
72
76
|
/**
|
|
73
77
|
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
74
78
|
*/
|
|
75
|
-
find<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOptions<Entity, Hint, Fields, Excludes>
|
|
79
|
+
find<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
|
|
80
|
+
using?: Using | Using[];
|
|
81
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
76
82
|
/**
|
|
77
83
|
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
|
78
84
|
* where first element is the array of entities, and the second is the count.
|
|
79
85
|
*/
|
|
80
|
-
findAndCount<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity
|
|
86
|
+
findAndCount<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
|
|
87
|
+
using?: Using | Using[];
|
|
88
|
+
}): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
|
|
81
89
|
/**
|
|
82
90
|
* @inheritDoc EntityManager.findByCursor
|
|
83
91
|
*/
|
|
84
|
-
findByCursor<Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true>(options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
92
|
+
findByCursor<Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true, Using extends string = never>(options: WithUsingOptions<FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>, Entity, Using>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
85
93
|
/**
|
|
86
94
|
* Finds all entities of given type. You can pass additional options via the `options` parameter.
|
|
87
95
|
*/
|
|
88
|
-
findAll<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: FindAllOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
96
|
+
findAll<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(options?: WithUsingOptions<FindAllOptions<Entity, Hint, Fields, Excludes>, Entity, Using>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
89
97
|
/**
|
|
90
98
|
* @inheritDoc EntityManager.stream
|
|
91
99
|
*/
|
|
92
|
-
stream<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: StreamOptions<Entity, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
100
|
+
stream<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(options?: WithUsingOptions<StreamOptions<Entity, Hint, Fields, Excludes>, Entity, Using>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
93
101
|
/**
|
|
94
102
|
* @inheritDoc EntityManager.insert
|
|
95
103
|
*/
|
|
@@ -199,6 +207,16 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
199
207
|
* Returns total number of entities matching your `where` query.
|
|
200
208
|
*/
|
|
201
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>>;
|
|
202
220
|
/** Returns the entity class name associated with this repository. */
|
|
203
221
|
getEntityName(): string;
|
|
204
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);
|