@mikro-orm/core 7.1.0-dev.3 → 7.1.0-dev.30
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 +44 -11
- package/EntityManager.js +139 -31
- package/drivers/IDatabaseDriver.d.ts +39 -1
- 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/EntityFactory.js +20 -1
- package/entity/EntityLoader.d.ts +3 -1
- package/entity/EntityLoader.js +39 -16
- 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 +105 -23
- package/entity/defineEntity.js +17 -6
- package/entity/utils.js +4 -5
- 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 +185 -16
- package/metadata/MetadataValidator.d.ts +24 -0
- package/metadata/MetadataValidator.js +202 -1
- package/metadata/types.d.ts +77 -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 +8 -1
- package/platforms/Platform.js +40 -4
- package/serialization/EntitySerializer.js +2 -1
- package/typings.d.ts +176 -18
- package/typings.js +22 -5
- package/unit-of-work/UnitOfWork.js +3 -0
- 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/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/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 } 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';
|
|
@@ -61,7 +61,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
61
61
|
/**
|
|
62
62
|
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
63
63
|
*/
|
|
64
|
-
find<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity
|
|
64
|
+
find<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, where: [Using] extends [never] ? FilterQuery<NoInfer<Entity>> : IndexFilterQuery<NoInfer<Entity>, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
|
|
65
|
+
using?: Using | Using[];
|
|
66
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
65
67
|
/**
|
|
66
68
|
* Finds all entities and returns an async iterable (async generator) that yields results one by one.
|
|
67
69
|
* The results are merged and mapped to entity instances, without adding them to the identity map.
|
|
@@ -79,11 +81,11 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
79
81
|
* }
|
|
80
82
|
* ```
|
|
81
83
|
*/
|
|
82
|
-
stream<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, options?: StreamOptions<NoInfer<Entity>, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
84
|
+
stream<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, options?: WithUsingOptions<StreamOptions<NoInfer<Entity>, Hint, Fields, Excludes>, NoInfer<Entity>, Using>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
83
85
|
/**
|
|
84
86
|
* Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
|
|
85
87
|
*/
|
|
86
|
-
findAll<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, options?: FindAllOptions<NoInfer<Entity>, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
88
|
+
findAll<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, options?: WithUsingOptions<FindAllOptions<NoInfer<Entity>, Hint, Fields, Excludes>, NoInfer<Entity>, Using>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
87
89
|
private getPopulateWhere;
|
|
88
90
|
/**
|
|
89
91
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
@@ -130,7 +132,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
130
132
|
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
|
131
133
|
* where the first element is the array of entities, and the second is the count.
|
|
132
134
|
*/
|
|
133
|
-
findAndCount<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity
|
|
135
|
+
findAndCount<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, where: [Using] extends [never] ? FilterQuery<NoInfer<Entity>> : IndexFilterQuery<NoInfer<Entity>, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
|
|
136
|
+
using?: Using | Using[];
|
|
137
|
+
}): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
|
|
134
138
|
/**
|
|
135
139
|
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
|
|
136
140
|
* Supports `before`, `after`, `first` and `last` options while disallowing `limit` and `offset`. Explicit `orderBy` option
|
|
@@ -187,7 +191,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
187
191
|
* }
|
|
188
192
|
* ```
|
|
189
193
|
*/
|
|
190
|
-
findByCursor<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true>(entityName: EntityName<Entity>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
194
|
+
findByCursor<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true, Using extends string = never>(entityName: EntityName<Entity>, options: WithUsingOptions<FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>, Entity, Using>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
191
195
|
/**
|
|
192
196
|
* Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been
|
|
193
197
|
* persisted. Returns the same entity instance (same object reference), but re-hydrated. If the entity is no longer
|
|
@@ -203,14 +207,18 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
203
207
|
/**
|
|
204
208
|
* Finds first entity matching your `where` query.
|
|
205
209
|
*/
|
|
206
|
-
findOne<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity
|
|
210
|
+
findOne<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, where: [Using] extends [never] ? FilterQuery<NoInfer<Entity>> : IndexFilterQuery<NoInfer<Entity>, Using>, options?: FindOneOptions<Entity, Hint, Fields, Excludes> & {
|
|
211
|
+
using?: Using | Using[];
|
|
212
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
|
207
213
|
/**
|
|
208
214
|
* Finds first entity matching your `where` query. If nothing found, it will throw an error.
|
|
209
215
|
* If the `strict` option is specified and nothing is found or more than one matching entity is found, it will throw an error.
|
|
210
216
|
* You can override the factory for creating this method via `options.failHandler` locally
|
|
211
217
|
* or via `Configuration.findOneOrFailHandler` (`findExactlyOneOrFailHandler` when specifying `strict`) globally.
|
|
212
218
|
*/
|
|
213
|
-
findOneOrFail<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity
|
|
219
|
+
findOneOrFail<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, where: [Using] extends [never] ? FilterQuery<NoInfer<Entity>> : IndexFilterQuery<NoInfer<Entity>, Using>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes> & {
|
|
220
|
+
using?: Using | Using[];
|
|
221
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
214
222
|
/**
|
|
215
223
|
* Creates or updates the entity, based on whether it is already present in the database.
|
|
216
224
|
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
|
@@ -433,6 +441,27 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
433
441
|
* Returns total number of entities matching your `where` query.
|
|
434
442
|
*/
|
|
435
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>>;
|
|
436
465
|
/**
|
|
437
466
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
438
467
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -524,16 +553,20 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
524
553
|
*/
|
|
525
554
|
getComparator(): EntityComparator;
|
|
526
555
|
private checkLockRequirements;
|
|
556
|
+
private validateIndexUsage;
|
|
557
|
+
private validateWhereKeysForIndex;
|
|
527
558
|
private lockAndPopulate;
|
|
528
559
|
private buildFields;
|
|
529
560
|
/** @internal */
|
|
530
561
|
preparePopulate<Entity extends object>(entityName: EntityName<Entity>, options: Pick<FindOptions<Entity, any, any, any>, 'populate' | 'strategy' | 'fields' | 'flags' | 'filters' | 'exclude' | 'populateHints'>, validate?: boolean): Promise<PopulateOptions<Entity>[]>;
|
|
562
|
+
/** Force SELECT_IN strategy on populate entries with `limit`, since JOINED cannot do per-parent limiting. */
|
|
563
|
+
private forceSelectInForLimitedPopulate;
|
|
531
564
|
/**
|
|
532
565
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
533
566
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
534
567
|
*/
|
|
535
568
|
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;
|
|
536
|
-
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any>): void;
|
|
569
|
+
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>): void;
|
|
537
570
|
/**
|
|
538
571
|
* @internal
|
|
539
572
|
*/
|
|
@@ -575,7 +608,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
575
608
|
*/
|
|
576
609
|
set schema(schema: string | null | undefined);
|
|
577
610
|
/** @internal */
|
|
578
|
-
getDataLoader(type: 'ref' | '1:m' | 'm:n'): Promise<any>;
|
|
611
|
+
getDataLoader(type: 'ref' | '1:m' | 'm:n' | 'count'): Promise<any>;
|
|
579
612
|
/**
|
|
580
613
|
* Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
|
|
581
614
|
* if executed inside request context handler.
|
package/EntityManager.js
CHANGED
|
@@ -103,9 +103,6 @@ export class EntityManager {
|
|
|
103
103
|
repo(entityName) {
|
|
104
104
|
return this.getRepository(entityName);
|
|
105
105
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
108
|
-
*/
|
|
109
106
|
async find(entityName, where, options = {}) {
|
|
110
107
|
if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
|
|
111
108
|
const em = this.getContext(false);
|
|
@@ -116,10 +113,11 @@ export class EntityManager {
|
|
|
116
113
|
}
|
|
117
114
|
const em = this.getContext();
|
|
118
115
|
em.prepareOptions(options);
|
|
116
|
+
const meta = this.metadata.get(entityName);
|
|
117
|
+
em.validateIndexUsage(meta, where, options);
|
|
119
118
|
await em.tryFlush(entityName, options);
|
|
120
119
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
121
120
|
validateParams(where);
|
|
122
|
-
const meta = this.metadata.get(entityName);
|
|
123
121
|
if (meta.orderBy) {
|
|
124
122
|
options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
|
|
125
123
|
}
|
|
@@ -203,6 +201,7 @@ export class EntityManager {
|
|
|
203
201
|
options.orderBy = options.orderBy || {};
|
|
204
202
|
options.populate = (await em.preparePopulate(entityName, options));
|
|
205
203
|
const meta = this.metadata.get(entityName);
|
|
204
|
+
em.validateIndexUsage(meta, options.where ?? {}, options);
|
|
206
205
|
options = { ...options };
|
|
207
206
|
// save the original hint value so we know it was infer/all
|
|
208
207
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
@@ -355,7 +354,8 @@ export class EntityManager {
|
|
|
355
354
|
const field = hint.field.split(':')[0];
|
|
356
355
|
const prop = meta.properties[field];
|
|
357
356
|
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
358
|
-
const joined = strategy === LoadStrategy.JOINED
|
|
357
|
+
const joined = (strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) &&
|
|
358
|
+
prop.kind !== ReferenceKind.SCALAR;
|
|
359
359
|
if (!joined && !hint.filter) {
|
|
360
360
|
continue;
|
|
361
361
|
}
|
|
@@ -424,11 +424,13 @@ export class EntityManager {
|
|
|
424
424
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
425
425
|
let found = false;
|
|
426
426
|
for (const hint of populated) {
|
|
427
|
-
|
|
427
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
428
|
+
// inverse 1:1 always produces a JOIN (forced by `joinedProps()`), regardless of strategy
|
|
429
|
+
const willJoin = strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner);
|
|
430
|
+
if (!hint.all || willJoin) {
|
|
428
431
|
hint.filter = true;
|
|
429
432
|
}
|
|
430
|
-
|
|
431
|
-
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
433
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && willJoin)) {
|
|
432
434
|
found = true;
|
|
433
435
|
}
|
|
434
436
|
}
|
|
@@ -494,10 +496,6 @@ export class EntityManager {
|
|
|
494
496
|
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
|
|
495
497
|
return conds.length > 1 ? { $and: conds } : conds[0];
|
|
496
498
|
}
|
|
497
|
-
/**
|
|
498
|
-
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
|
499
|
-
* where the first element is the array of entities, and the second is the count.
|
|
500
|
-
*/
|
|
501
499
|
async findAndCount(entityName, where, options = {}) {
|
|
502
500
|
const em = this.getContext(false);
|
|
503
501
|
await em.tryFlush(entityName, options);
|
|
@@ -631,9 +629,6 @@ export class EntityManager {
|
|
|
631
629
|
}
|
|
632
630
|
return entity;
|
|
633
631
|
}
|
|
634
|
-
/**
|
|
635
|
-
* Finds first entity matching your `where` query.
|
|
636
|
-
*/
|
|
637
632
|
async findOne(entityName, where, options = {}) {
|
|
638
633
|
if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
|
|
639
634
|
const em = this.getContext(false);
|
|
@@ -653,6 +648,7 @@ export class EntityManager {
|
|
|
653
648
|
}
|
|
654
649
|
await em.tryFlush(entityName, options);
|
|
655
650
|
const meta = em.metadata.get(entityName);
|
|
651
|
+
em.validateIndexUsage(meta, where, options);
|
|
656
652
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
657
653
|
validateEmptyWhere(where);
|
|
658
654
|
em.checkLockRequirements(options.lockMode, meta);
|
|
@@ -701,12 +697,6 @@ export class EntityManager {
|
|
|
701
697
|
await em.storeCache(options.cache, cached, () => helper(entity).toPOJO());
|
|
702
698
|
return entity;
|
|
703
699
|
}
|
|
704
|
-
/**
|
|
705
|
-
* Finds first entity matching your `where` query. If nothing found, it will throw an error.
|
|
706
|
-
* If the `strict` option is specified and nothing is found or more than one matching entity is found, it will throw an error.
|
|
707
|
-
* You can override the factory for creating this method via `options.failHandler` locally
|
|
708
|
-
* or via `Configuration.findOneOrFailHandler` (`findExactlyOneOrFailHandler` when specifying `strict`) globally.
|
|
709
|
-
*/
|
|
710
700
|
async findOneOrFail(entityName, where, options = {}) {
|
|
711
701
|
let entity;
|
|
712
702
|
let isStrictViolation = false;
|
|
@@ -1192,9 +1182,6 @@ export class EntityManager {
|
|
|
1192
1182
|
helper(data).setSchema(options.schema);
|
|
1193
1183
|
}
|
|
1194
1184
|
if (!helper(data).__managed) {
|
|
1195
|
-
// the entity might have been created via `em.create()`, which adds it to the persist stack automatically
|
|
1196
|
-
em.#unitOfWork.getPersistStack().delete(data);
|
|
1197
|
-
// it can be also in the identity map if it had a PK value already
|
|
1198
1185
|
em.#unitOfWork.unsetIdentity(data);
|
|
1199
1186
|
}
|
|
1200
1187
|
const meta = helper(data).__meta;
|
|
@@ -1283,9 +1270,6 @@ export class EntityManager {
|
|
|
1283
1270
|
helper(row).setSchema(options.schema);
|
|
1284
1271
|
}
|
|
1285
1272
|
if (!helper(row).__managed) {
|
|
1286
|
-
// the entity might have been created via `em.create()`, which adds it to the persist stack automatically
|
|
1287
|
-
em.#unitOfWork.getPersistStack().delete(row);
|
|
1288
|
-
// it can be also in the identity map if it had a PK value already
|
|
1289
1273
|
em.#unitOfWork.unsetIdentity(row);
|
|
1290
1274
|
}
|
|
1291
1275
|
const payload = em.#comparator.prepareEntity(row);
|
|
@@ -1471,6 +1455,29 @@ export class EntityManager {
|
|
|
1471
1455
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1472
1456
|
return +count;
|
|
1473
1457
|
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Counts entities grouped by one or more properties. Returns a dictionary keyed by the grouped
|
|
1460
|
+
* field value(s), with counts as values. For composite `groupBy`, keys are joined with `~~~`.
|
|
1461
|
+
*
|
|
1462
|
+
* SQL drivers issue a single `GROUP BY` query; MongoDB uses an aggregation pipeline.
|
|
1463
|
+
*
|
|
1464
|
+
* @example
|
|
1465
|
+
* ```ts
|
|
1466
|
+
* // Count books per author
|
|
1467
|
+
* const counts = await em.countBy(Book, 'author');
|
|
1468
|
+
* // { '1': 2, '2': 1, '3': 3 }
|
|
1469
|
+
*
|
|
1470
|
+
* // Count with a filter
|
|
1471
|
+
* const counts = await em.countBy(Book, 'author', { where: { active: true } });
|
|
1472
|
+
*
|
|
1473
|
+
* // Composite groupBy — keys joined with ~~~
|
|
1474
|
+
* const counts = await em.countBy(Order, ['status', 'country']);
|
|
1475
|
+
* // { 'pending~~~US': 5, 'shipped~~~DE': 3 }
|
|
1476
|
+
* ```
|
|
1477
|
+
*/
|
|
1478
|
+
async countBy(entityName, groupBy, options) {
|
|
1479
|
+
throw new Error(`${this.constructor.name}.countBy() is not supported by the current driver`);
|
|
1480
|
+
}
|
|
1474
1481
|
/**
|
|
1475
1482
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
1476
1483
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -1576,7 +1583,7 @@ export class EntityManager {
|
|
|
1576
1583
|
const em = this.getContext();
|
|
1577
1584
|
em.prepareOptions(options);
|
|
1578
1585
|
const entityName = arr[0].constructor;
|
|
1579
|
-
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
|
|
1586
|
+
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters, populateHints: options.populateHints }, options.validate);
|
|
1580
1587
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1581
1588
|
return entities;
|
|
1582
1589
|
}
|
|
@@ -1727,6 +1734,79 @@ export class EntityManager {
|
|
|
1727
1734
|
throw ValidationError.transactionRequired();
|
|
1728
1735
|
}
|
|
1729
1736
|
}
|
|
1737
|
+
validateIndexUsage(meta, where, options) {
|
|
1738
|
+
if (!options.using) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
const indexNames = Utils.asArray(options.using);
|
|
1742
|
+
const allIndexes = [...meta.indexes, ...meta.uniques];
|
|
1743
|
+
const indexMap = new Map();
|
|
1744
|
+
for (const idx of allIndexes) {
|
|
1745
|
+
if (idx.name) {
|
|
1746
|
+
indexMap.set(idx.name, Utils.asArray(idx.properties ?? []));
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
for (const prop of meta.props) {
|
|
1750
|
+
if (typeof prop.index === 'string') {
|
|
1751
|
+
indexMap.set(prop.index, [prop.name]);
|
|
1752
|
+
}
|
|
1753
|
+
if (typeof prop.unique === 'string') {
|
|
1754
|
+
indexMap.set(prop.unique, [prop.name]);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const allowedProps = new Set();
|
|
1758
|
+
for (const name of indexNames) {
|
|
1759
|
+
const props = indexMap.get(name);
|
|
1760
|
+
if (!props) {
|
|
1761
|
+
const available = [...indexMap.keys()];
|
|
1762
|
+
throw new Error(`Index '${name}' not found on entity '${meta.className}'. ` +
|
|
1763
|
+
(available.length > 0 ? `Available indexes: ${available.join(', ')}` : 'No named indexes defined.'));
|
|
1764
|
+
}
|
|
1765
|
+
for (const prop of props) {
|
|
1766
|
+
allowedProps.add(prop);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
this.validateWhereKeysForIndex(where, allowedProps, indexNames);
|
|
1770
|
+
if (options.orderBy) {
|
|
1771
|
+
const orderMaps = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
1772
|
+
for (const orderMap of orderMaps) {
|
|
1773
|
+
for (const key of Object.keys(orderMap)) {
|
|
1774
|
+
if (!allowedProps.has(key)) {
|
|
1775
|
+
throw new Error(`Property '${key}' in orderBy is not covered by index '${indexNames.join("', '")}'. ` +
|
|
1776
|
+
`Allowed properties: ${[...allowedProps].join(', ')}`);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
validateWhereKeysForIndex(where, allowedProps, indexNames) {
|
|
1783
|
+
if (typeof where !== 'object' || where === null) {
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
if (Array.isArray(where)) {
|
|
1787
|
+
for (const item of where) {
|
|
1788
|
+
this.validateWhereKeysForIndex(item, allowedProps, indexNames);
|
|
1789
|
+
}
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
for (const key of Object.keys(where)) {
|
|
1793
|
+
if (key === '$and' || key === '$or') {
|
|
1794
|
+
for (const item of Utils.asArray(where[key])) {
|
|
1795
|
+
this.validateWhereKeysForIndex(item, allowedProps, indexNames);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
else if (key === '$not') {
|
|
1799
|
+
this.validateWhereKeysForIndex(where[key], allowedProps, indexNames);
|
|
1800
|
+
}
|
|
1801
|
+
else if (key.startsWith('$')) {
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
else if (!allowedProps.has(key)) {
|
|
1805
|
+
throw new Error(`Property '${key}' in where clause is not covered by index '${indexNames.join("', '")}'. ` +
|
|
1806
|
+
`Allowed properties: ${[...allowedProps].join(', ')}`);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1730
1810
|
async lockAndPopulate(meta, entity, where, options) {
|
|
1731
1811
|
if (!meta.virtual && options.lockMode === LockMode.OPTIMISTIC) {
|
|
1732
1812
|
await this.lock(entity, options.lockMode, {
|
|
@@ -1761,8 +1841,13 @@ export class EntityManager {
|
|
|
1761
1841
|
return [];
|
|
1762
1842
|
}
|
|
1763
1843
|
const meta = this.metadata.find(entityName);
|
|
1764
|
-
// infer populate
|
|
1765
|
-
|
|
1844
|
+
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set
|
|
1845
|
+
const fields = options.fields ? this.buildFields(options.fields) : undefined;
|
|
1846
|
+
const populateArray = Array.isArray(options.populate) ? options.populate : undefined;
|
|
1847
|
+
const hasNestedFields = fields?.some(f => f.includes('.')) ?? false;
|
|
1848
|
+
const shouldInfer = fields !== undefined && options.populate === undefined;
|
|
1849
|
+
const shouldMerge = fields !== undefined && populateArray !== undefined && populateArray.length > 0 && hasNestedFields;
|
|
1850
|
+
if (shouldInfer || shouldMerge) {
|
|
1766
1851
|
// we need to prune the `populate` hint from to-one relations, as partially loading them does not require their population, we want just the FK
|
|
1767
1852
|
const pruneToOneRelations = (meta, fields) => {
|
|
1768
1853
|
const ret = [];
|
|
@@ -1796,7 +1881,16 @@ export class EntityManager {
|
|
|
1796
1881
|
}
|
|
1797
1882
|
return Utils.unique(ret);
|
|
1798
1883
|
};
|
|
1799
|
-
|
|
1884
|
+
const fromFields = pruneToOneRelations(meta, fields);
|
|
1885
|
+
if (shouldInfer) {
|
|
1886
|
+
options.populate = fromFields;
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
// with an explicit populate, only nested paths (e.g. `item.id`) need to be merged in — they
|
|
1890
|
+
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver
|
|
1891
|
+
const extras = fromFields.filter(f => f.includes('.'));
|
|
1892
|
+
options.populate = [...populateArray, ...extras];
|
|
1893
|
+
}
|
|
1800
1894
|
}
|
|
1801
1895
|
if (!options.populate) {
|
|
1802
1896
|
const populate = this.#entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
|
|
@@ -1836,9 +1930,21 @@ export class EntityManager {
|
|
|
1836
1930
|
}
|
|
1837
1931
|
if (options.populateHints) {
|
|
1838
1932
|
applyPopulateHints(populate, options.populateHints);
|
|
1933
|
+
this.forceSelectInForLimitedPopulate(populate);
|
|
1839
1934
|
}
|
|
1840
1935
|
return populate;
|
|
1841
1936
|
}
|
|
1937
|
+
/** Force SELECT_IN strategy on populate entries with `limit`, since JOINED cannot do per-parent limiting. */
|
|
1938
|
+
forceSelectInForLimitedPopulate(populate) {
|
|
1939
|
+
for (const entry of populate) {
|
|
1940
|
+
if (entry.limit != null) {
|
|
1941
|
+
entry.strategy = LoadStrategy.SELECT_IN;
|
|
1942
|
+
}
|
|
1943
|
+
if (entry.children?.length) {
|
|
1944
|
+
this.forceSelectInForLimitedPopulate(entry.children);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1842
1948
|
/**
|
|
1843
1949
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
1844
1950
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
@@ -1971,6 +2077,8 @@ export class EntityManager {
|
|
|
1971
2077
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
1972
2078
|
case 'm:n':
|
|
1973
2079
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
2080
|
+
case 'count':
|
|
2081
|
+
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getCountBatchLoadFn(em)));
|
|
1974
2082
|
}
|
|
1975
2083
|
}
|
|
1976
2084
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConnectionType, Constructor, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes } from '../typings.js';
|
|
1
|
+
import type { ConnectionType, Constructor, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes, IndexName } from '../typings.js';
|
|
2
2
|
import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
|
|
3
3
|
import type { FlushMode, LockMode, QueryOrderMap, QueryFlag, LoadStrategy, PopulateHint, PopulatePath } from '../enums.js';
|
|
4
4
|
import type { Platform } from '../platforms/Platform.js';
|
|
@@ -110,6 +110,20 @@ export interface FindAllOptions<T, P extends string = never, F extends string =
|
|
|
110
110
|
}
|
|
111
111
|
/** Options for streaming query results via `em.stream()`. */
|
|
112
112
|
export interface StreamOptions<Entity, Populate extends string = never, Fields extends string = never, Exclude extends string = never> extends Omit<FindAllOptions<Entity, Populate, Fields, Exclude>, 'cache' | 'before' | 'after' | 'first' | 'last' | 'overfetch' | 'strategy'> {
|
|
113
|
+
/**
|
|
114
|
+
* How many rows to fetch in one round-trip.
|
|
115
|
+
* Lower values will result in more queries and network bandwidth, but less memory usage.
|
|
116
|
+
* Higher values will result in fewer queries and network bandwidth, but higher memory usage.
|
|
117
|
+
* Note that the results are iterated one row at a time regardless of this value.
|
|
118
|
+
*
|
|
119
|
+
* Honored on PostgreSQL (cursor-based fetch), MSSQL (tedious stream chunk size),
|
|
120
|
+
* Oracle (mapped to `fetchArraySize`) and MongoDB (mapped to `batchSize`). Ignored
|
|
121
|
+
* on MySQL, MariaDB, SQLite and libSQL, where the underlying driver already streams
|
|
122
|
+
* row-by-row with no batching knob.
|
|
123
|
+
*
|
|
124
|
+
* @default 100 (on dialects that honor it)
|
|
125
|
+
*/
|
|
126
|
+
chunkSize?: number;
|
|
113
127
|
/**
|
|
114
128
|
* When populating to-many relations, the ORM streams fully merged entities instead of yielding every row.
|
|
115
129
|
* You can opt out of this behavior by specifying `mergeResults: false`. This will yield every row from
|
|
@@ -216,6 +230,20 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
216
230
|
connectionType?: ConnectionType;
|
|
217
231
|
/** SQL: appended to FROM clause (e.g. `'force index(my_index)'`); MongoDB: index name or spec passed as `hint`. */
|
|
218
232
|
indexHint?: string | Dictionary;
|
|
233
|
+
/**
|
|
234
|
+
* Named index(es) for this query. When provided:
|
|
235
|
+
* - Validates that `where` and `orderBy` only reference columns covered by the specified index(es).
|
|
236
|
+
* - Emits SQL index hints where supported (MySQL/MariaDB: `USE INDEX`, MSSQL: `WITH (INDEX(...))`).
|
|
237
|
+
* - If `indexHint` is also set, `indexHint` takes precedence for SQL generation.
|
|
238
|
+
*
|
|
239
|
+
* Accepts a single index name or an array. For `defineEntity` entities with named indexes
|
|
240
|
+
* and decorator entities with `[IndexHints]`, index names are autocompleted.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* await em.find(Book, { title: 'foo' }, { using: 'idx_book_title' });
|
|
244
|
+
* await em.find(Book, { title: 'foo', author: 1 }, { using: ['idx_book_title', 'idx_book_author'] });
|
|
245
|
+
*/
|
|
246
|
+
using?: IndexName<Entity> | IndexName<Entity>[];
|
|
219
247
|
/** sql only */
|
|
220
248
|
comments?: string | string[];
|
|
221
249
|
/** sql only */
|
|
@@ -311,6 +339,16 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
311
339
|
/** @internal used to apply filters to the auto-joined relations */
|
|
312
340
|
em?: EntityManager;
|
|
313
341
|
}
|
|
342
|
+
/** Options for `em.countBy()` queries. */
|
|
343
|
+
export interface CountByOptions<T extends object> {
|
|
344
|
+
where?: FilterQuery<T>;
|
|
345
|
+
filters?: FilterOptions;
|
|
346
|
+
having?: FilterQuery<T>;
|
|
347
|
+
schema?: string;
|
|
348
|
+
flushMode?: FlushMode | `${FlushMode}`;
|
|
349
|
+
loggerContext?: LogContext;
|
|
350
|
+
logging?: LoggingOptions;
|
|
351
|
+
}
|
|
314
352
|
/** Options for `em.qb().update()` operations. */
|
|
315
353
|
export interface UpdateOptions<T> {
|
|
316
354
|
filters?: FilterOptions;
|
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
|
}
|