@mikro-orm/core 7.0.17 → 7.0.18-dev.1

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.
Files changed (69) hide show
  1. package/EntityManager.d.ts +106 -12
  2. package/EntityManager.js +228 -19
  3. package/MikroORM.d.ts +3 -3
  4. package/README.md +2 -1
  5. package/connections/Connection.d.ts +67 -1
  6. package/connections/Connection.js +64 -0
  7. package/drivers/DatabaseDriver.d.ts +1 -0
  8. package/drivers/DatabaseDriver.js +3 -0
  9. package/drivers/IDatabaseDriver.d.ts +50 -7
  10. package/entity/BaseEntity.d.ts +70 -3
  11. package/entity/BaseEntity.js +18 -0
  12. package/entity/Collection.d.ts +6 -3
  13. package/entity/Collection.js +15 -4
  14. package/entity/EntityAssigner.js +8 -0
  15. package/entity/EntityFactory.js +20 -1
  16. package/entity/EntityLoader.d.ts +8 -1
  17. package/entity/EntityLoader.js +41 -16
  18. package/entity/EntityRepository.d.ts +27 -9
  19. package/entity/EntityRepository.js +12 -0
  20. package/entity/Reference.d.ts +42 -1
  21. package/entity/Reference.js +9 -0
  22. package/entity/WrappedEntity.d.ts +2 -2
  23. package/entity/defineEntity.d.ts +92 -17
  24. package/entity/defineEntity.js +17 -6
  25. package/entity/utils.js +4 -5
  26. package/enums.d.ts +8 -1
  27. package/errors.d.ts +2 -0
  28. package/errors.js +4 -0
  29. package/index.d.ts +2 -2
  30. package/index.js +1 -1
  31. package/metadata/EntitySchema.js +3 -0
  32. package/metadata/MetadataDiscovery.d.ts +11 -0
  33. package/metadata/MetadataDiscovery.js +135 -10
  34. package/metadata/MetadataValidator.d.ts +26 -0
  35. package/metadata/MetadataValidator.js +236 -0
  36. package/metadata/Routine.d.ts +74 -0
  37. package/metadata/Routine.js +140 -0
  38. package/metadata/index.d.ts +1 -0
  39. package/metadata/index.js +1 -0
  40. package/metadata/types.d.ts +77 -4
  41. package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
  42. package/naming-strategy/NamingStrategy.d.ts +1 -1
  43. package/package.json +1 -1
  44. package/platforms/Platform.d.ts +7 -0
  45. package/platforms/Platform.js +15 -1
  46. package/serialization/EntitySerializer.d.ts +18 -6
  47. package/serialization/EntitySerializer.js +31 -4
  48. package/typings.d.ts +389 -18
  49. package/typings.js +21 -4
  50. package/unit-of-work/UnitOfWork.js +11 -3
  51. package/utils/AbstractMigrator.d.ts +26 -3
  52. package/utils/AbstractMigrator.js +264 -15
  53. package/utils/AbstractSchemaGenerator.d.ts +1 -1
  54. package/utils/AbstractSchemaGenerator.js +4 -1
  55. package/utils/Configuration.d.ts +40 -3
  56. package/utils/Configuration.js +46 -0
  57. package/utils/DataloaderUtils.d.ts +10 -1
  58. package/utils/DataloaderUtils.js +78 -0
  59. package/utils/QueryHelper.d.ts +16 -0
  60. package/utils/QueryHelper.js +15 -0
  61. package/utils/TransactionManager.js +2 -0
  62. package/utils/Utils.d.ts +1 -1
  63. package/utils/Utils.js +1 -1
  64. package/utils/fs-utils.d.ts +2 -0
  65. package/utils/fs-utils.js +7 -1
  66. package/utils/index.d.ts +1 -0
  67. package/utils/index.js +1 -0
  68. package/utils/partition-utils.d.ts +17 -0
  69. package/utils/partition-utils.js +79 -0
@@ -6,11 +6,12 @@ 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, RoutineArgs, RoutineReturn, UnboxArray, IndexFilterQuery, WithUsingOptions } from './typings.js';
11
+ import type { Routine } from './metadata/Routine.js';
11
12
  import { FlushMode, LockMode, PopulatePath, type TransactionOptions } from './enums.js';
12
13
  import type { MetadataStorage } from './metadata/MetadataStorage.js';
13
- import type { Transaction } from './connections/Connection.js';
14
+ import type { AbortQueryOptions, InflightQueryAbortStrategy, Transaction } from './connections/Connection.js';
14
15
  import { EventManager } from './events/EventManager.js';
15
16
  import type { EntityComparator } from './utils/EntityComparator.js';
16
17
  /**
@@ -33,6 +34,10 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
33
34
  /** The context name of this EntityManager, derived from the ORM configuration. */
34
35
  readonly name: string;
35
36
  protected loggerContext?: Dictionary;
37
+ /** @internal */
38
+ protected signal?: AbortSignal;
39
+ /** @internal */
40
+ protected inflightQueryAbortStrategy?: InflightQueryAbortStrategy;
36
41
  /**
37
42
  * @internal
38
43
  */
@@ -61,7 +66,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
61
66
  /**
62
67
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
63
68
  */
64
- find<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options?: FindOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
69
+ 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> & {
70
+ using?: Using | Using[];
71
+ }): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
65
72
  /**
66
73
  * Finds all entities and returns an async iterable (async generator) that yields results one by one.
67
74
  * The results are merged and mapped to entity instances, without adding them to the identity map.
@@ -79,11 +86,11 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
79
86
  * }
80
87
  * ```
81
88
  */
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>>;
89
+ 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
90
  /**
84
91
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
85
92
  */
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>[]>;
93
+ 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
94
  private getPopulateWhere;
88
95
  /**
89
96
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
@@ -130,7 +137,9 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
130
137
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
131
138
  * where the first element is the array of entities, and the second is the count.
132
139
  */
133
- findAndCount<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options?: FindOptions<Entity, Hint, Fields, Excludes>): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
140
+ 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> & {
141
+ using?: Using | Using[];
142
+ }): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
134
143
  /**
135
144
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
136
145
  * Supports `before`, `after`, `first` and `last` options while disallowing `limit` and `offset`. Explicit `orderBy` option
@@ -187,7 +196,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
187
196
  * }
188
197
  * ```
189
198
  */
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>>;
199
+ 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
200
  /**
192
201
  * Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been
193
202
  * persisted. Returns the same entity instance (same object reference), but re-hydrated. If the entity is no longer
@@ -203,14 +212,18 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
203
212
  /**
204
213
  * Finds first entity matching your `where` query.
205
214
  */
206
- findOne<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options?: FindOneOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
215
+ 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> & {
216
+ using?: Using | Using[];
217
+ }): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
207
218
  /**
208
219
  * Finds first entity matching your `where` query. If nothing found, it will throw an error.
209
220
  * If the `strict` option is specified and nothing is found or more than one matching entity is found, it will throw an error.
210
221
  * You can override the factory for creating this method via `options.failHandler` locally
211
222
  * or via `Configuration.findOneOrFailHandler` (`findExactlyOneOrFailHandler` when specifying `strict`) globally.
212
223
  */
213
- findOneOrFail<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
224
+ 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> & {
225
+ using?: Using | Using[];
226
+ }): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
214
227
  /**
215
228
  * Creates or updates the entity, based on whether it is already present in the database.
216
229
  * This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
@@ -307,6 +320,25 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
307
320
  * Fires native insert query. Calling this has no side effects on the context (identity map).
308
321
  */
309
322
  insert<Entity extends object>(entityNameOrEntity: EntityName<Entity> | Entity, data?: RequiredEntityData<Entity> | Entity, options?: NativeInsertUpdateOptions<Entity>): Promise<Primary<Entity>>;
323
+ /**
324
+ * Clones rows matching the condition at the database level via INSERT...SELECT.
325
+ * Automatically excludes auto-increment PKs, generated columns, and resets version properties.
326
+ * Returns the cloned entity, registered in the identity map.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * // Clone by entity class + where + overrides
331
+ * const cloned = await em.clone(Author, { id: 1 }, { email: 'new@email.com' });
332
+ *
333
+ * // Pure clone (all non-PK fields copied)
334
+ * const cloned = await em.clone(Author, { id: 1 });
335
+ *
336
+ * // Clone a loaded entity
337
+ * const author = await em.findOneOrFail(Author, 1);
338
+ * const cloned = await em.clone(author, { email: 'new@email.com' });
339
+ * ```
340
+ */
341
+ clone<Entity extends object>(entityNameOrEntity: EntityName<Entity> | Entity, whereOrOverrides?: FilterQuery<NoInfer<Entity>> | EntityData<Entity>, overridesOrOptions?: EntityData<Entity> | NativeInsertUpdateOptions<Entity>, options?: NativeInsertUpdateOptions<Entity>): Promise<Entity>;
310
342
  /**
311
343
  * Fires native multi-insert query. Calling this has no side effects on the context (identity map).
312
344
  */
@@ -315,6 +347,29 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
315
347
  * Fires native update query. Calling this has no side effects on the context (identity map).
316
348
  */
317
349
  nativeUpdate<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<NoInfer<Entity>>, data: EntityData<Entity>, options?: UpdateOptions<Entity>): Promise<number>;
350
+ /**
351
+ * Invokes a stored procedure or function declared via the {@link Routine} class. Arg and return
352
+ * types are inferred from the literal config; use {@link Routine.create} to refine when needed.
353
+ *
354
+ * Procedures emitting result sets (MySQL `SELECT`s, PG/Oracle refcursor OUT params) return
355
+ * `Dictionary[][]`; MSSQL multi-result procs aren't exposed here, use `em.getConnection().execute()`.
356
+ *
357
+ * Mongo throws. SQLite bridges `bodyJs` functions via better-sqlite3 UDFs (procedures and
358
+ * function-without-bodyJs throw); libSQL throws unconditionally. Oracle calls run on their own
359
+ * pool connection with `autoCommit: true`, so wrapping in `em.transactional(...)` throws.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * const hash = await em.callRoutine(HashUser, { name: 'jon', salt: 'pepper' });
364
+ *
365
+ * const hashRef = new ScalarReference<string>();
366
+ * await em.callRoutine(AddRecord, { p_name: 'jon', p_age: 30, p_hash: hashRef });
367
+ *
368
+ * const TwoCursors = Routine.create<Record<string, never>, unknown[][]>({ ... });
369
+ * const [users, books] = await em.transactional(em => em.callRoutine(TwoCursors, {}));
370
+ * ```
371
+ */
372
+ callRoutine<R extends Routine>(routine: R, args: RoutineArgs<R>): Promise<RoutineReturn<R>>;
318
373
  /**
319
374
  * Fires native delete query. Calling this has no side effects on the context (identity map).
320
375
  */
@@ -414,6 +469,27 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
414
469
  * Returns total number of entities matching your `where` query.
415
470
  */
416
471
  count<Entity extends object, Hint extends string = never>(entityName: EntityName<Entity>, where?: FilterQuery<NoInfer<Entity>>, options?: CountOptions<Entity, Hint>): Promise<number>;
472
+ /**
473
+ * Counts entities grouped by one or more properties. Returns a dictionary keyed by the grouped
474
+ * field value(s), with counts as values. For composite `groupBy`, keys are joined with `~~~`.
475
+ *
476
+ * SQL drivers issue a single `GROUP BY` query; MongoDB uses an aggregation pipeline.
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * // Count books per author
481
+ * const counts = await em.countBy(Book, 'author');
482
+ * // { '1': 2, '2': 1, '3': 3 }
483
+ *
484
+ * // Count with a filter
485
+ * const counts = await em.countBy(Book, 'author', { where: { active: true } });
486
+ *
487
+ * // Composite groupBy — keys joined with ~~~
488
+ * const counts = await em.countBy(Order, ['status', 'country']);
489
+ * // { 'pending~~~US': 5, 'shipped~~~DE': 3 }
490
+ * ```
491
+ */
492
+ countBy<Entity extends object>(entityName: EntityName<Entity>, groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
417
493
  /**
418
494
  * Tells the EntityManager to make an instance managed and persistent.
419
495
  * The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
@@ -484,6 +560,13 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
484
560
  * Gets the transaction context (driver dependent object used to make sure queries are executed on same connection).
485
561
  */
486
562
  getTransactionContext<T extends Transaction = Transaction>(): T | undefined;
563
+ /**
564
+ * Returns the cancellation defaults configured on this EntityManager (via `em.fork({ signal })`
565
+ * or inherited from a transactional fork). Returns `undefined` when no signal is set.
566
+ *
567
+ * @internal — exposed for subclass drivers and `UnitOfWork`; not part of the public API.
568
+ */
569
+ protected getAbortOptions(): AbortQueryOptions | undefined;
487
570
  /**
488
571
  * Sets the transaction context.
489
572
  */
@@ -505,16 +588,20 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
505
588
  */
506
589
  getComparator(): EntityComparator;
507
590
  private checkLockRequirements;
591
+ private validateIndexUsage;
592
+ private validateWhereKeysForIndex;
508
593
  private lockAndPopulate;
509
594
  private buildFields;
510
595
  /** @internal */
511
596
  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>[]>;
597
+ /** Force SELECT_IN strategy on populate entries with `limit`, since JOINED cannot do per-parent limiting. */
598
+ private forceSelectInForLimitedPopulate;
512
599
  /**
513
600
  * when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
514
601
  * some additional lazy properties, if so, we reload and merge the data from database
515
602
  */
516
603
  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;
517
- protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any>): void;
604
+ protected prepareOptions(options: (FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>) & AbortQueryOptions): void;
518
605
  /**
519
606
  * @internal
520
607
  */
@@ -556,7 +643,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
556
643
  */
557
644
  set schema(schema: string | null | undefined);
558
645
  /** @internal */
559
- getDataLoader(type: 'ref' | '1:m' | 'm:n'): Promise<any>;
646
+ getDataLoader(type: 'ref' | '1:m' | 'm:n' | 'count'): Promise<any>;
560
647
  /**
561
648
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
562
649
  * if executed inside request context handler.
@@ -609,4 +696,11 @@ export interface ForkOptions {
609
696
  schema?: string;
610
697
  /** default logger context, can be overridden via {@apilink FindOptions} */
611
698
  loggerContext?: Dictionary;
699
+ /**
700
+ * Default `AbortSignal` applied to every operation on this fork (queries and UoW flushes).
701
+ * Per-call options.signal still takes precedence.
702
+ */
703
+ signal?: AbortSignal;
704
+ /** Default cancellation strategy paired with {@link signal}. */
705
+ inflightQueryAbortStrategy?: InflightQueryAbortStrategy;
612
706
  }
package/EntityManager.js CHANGED
@@ -49,6 +49,10 @@ export class EntityManager {
49
49
  #disableTransactions;
50
50
  #flushMode;
51
51
  #schema;
52
+ /** @internal */
53
+ signal;
54
+ /** @internal */
55
+ inflightQueryAbortStrategy;
52
56
  #useContext;
53
57
  /**
54
58
  * @internal
@@ -103,9 +107,6 @@ export class EntityManager {
103
107
  repo(entityName) {
104
108
  return this.getRepository(entityName);
105
109
  }
106
- /**
107
- * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
108
- */
109
110
  async find(entityName, where, options = {}) {
110
111
  if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
111
112
  const em = this.getContext(false);
@@ -116,10 +117,11 @@ export class EntityManager {
116
117
  }
117
118
  const em = this.getContext();
118
119
  em.prepareOptions(options);
120
+ const meta = this.metadata.get(entityName);
121
+ em.validateIndexUsage(meta, where, options);
119
122
  await em.tryFlush(entityName, options);
120
123
  where = await em.processWhere(entityName, where, options, 'read');
121
124
  validateParams(where);
122
- const meta = this.metadata.get(entityName);
123
125
  if (meta.orderBy) {
124
126
  options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
125
127
  }
@@ -203,6 +205,7 @@ export class EntityManager {
203
205
  options.orderBy = options.orderBy || {};
204
206
  options.populate = (await em.preparePopulate(entityName, options));
205
207
  const meta = this.metadata.get(entityName);
208
+ em.validateIndexUsage(meta, options.where ?? {}, options);
206
209
  options = { ...options };
207
210
  // save the original hint value so we know it was infer/all
208
211
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
@@ -520,10 +523,6 @@ export class EntityManager {
520
523
  const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
521
524
  return conds.length > 1 ? { $and: conds } : conds[0];
522
525
  }
523
- /**
524
- * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
525
- * where the first element is the array of entities, and the second is the count.
526
- */
527
526
  async findAndCount(entityName, where, options = {}) {
528
527
  const em = this.getContext(false);
529
528
  await em.tryFlush(entityName, options);
@@ -657,9 +656,6 @@ export class EntityManager {
657
656
  }
658
657
  return entity;
659
658
  }
660
- /**
661
- * Finds first entity matching your `where` query.
662
- */
663
659
  async findOne(entityName, where, options = {}) {
664
660
  if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
665
661
  const em = this.getContext(false);
@@ -679,6 +675,7 @@ export class EntityManager {
679
675
  }
680
676
  await em.tryFlush(entityName, options);
681
677
  const meta = em.metadata.get(entityName);
678
+ em.validateIndexUsage(meta, where, options);
682
679
  where = await em.processWhere(entityName, where, options, 'read');
683
680
  validateEmptyWhere(where);
684
681
  em.checkLockRequirements(options.lockMode, meta);
@@ -727,12 +724,6 @@ export class EntityManager {
727
724
  await em.storeCache(options.cache, cached, () => helper(entity).toPOJO());
728
725
  return entity;
729
726
  }
730
- /**
731
- * Finds first entity matching your `where` query. If nothing found, it will throw an error.
732
- * If the `strict` option is specified and nothing is found or more than one matching entity is found, it will throw an error.
733
- * You can override the factory for creating this method via `options.failHandler` locally
734
- * or via `Configuration.findOneOrFailHandler` (`findExactlyOneOrFailHandler` when specifying `strict`) globally.
735
- */
736
727
  async findOneOrFail(entityName, where, options = {}) {
737
728
  let entity;
738
729
  let isStrictViolation = false;
@@ -1207,6 +1198,7 @@ export class EntityManager {
1207
1198
  */
1208
1199
  async lock(entity, lockMode, options = {}) {
1209
1200
  options = Utils.isPlainObject(options) ? options : { lockVersion: options };
1201
+ this.getContext(false).prepareOptions(options);
1210
1202
  await this.getUnitOfWork().lock(entity, { lockMode, ...options });
1211
1203
  }
1212
1204
  /**
@@ -1244,6 +1236,54 @@ export class EntityManager {
1244
1236
  });
1245
1237
  return res.insertId;
1246
1238
  }
1239
+ /**
1240
+ * Clones rows matching the condition at the database level via INSERT...SELECT.
1241
+ * Automatically excludes auto-increment PKs, generated columns, and resets version properties.
1242
+ * Returns the cloned entity, registered in the identity map.
1243
+ *
1244
+ * @example
1245
+ * ```ts
1246
+ * // Clone by entity class + where + overrides
1247
+ * const cloned = await em.clone(Author, { id: 1 }, { email: 'new@email.com' });
1248
+ *
1249
+ * // Pure clone (all non-PK fields copied)
1250
+ * const cloned = await em.clone(Author, { id: 1 });
1251
+ *
1252
+ * // Clone a loaded entity
1253
+ * const author = await em.findOneOrFail(Author, 1);
1254
+ * const cloned = await em.clone(author, { email: 'new@email.com' });
1255
+ * ```
1256
+ */
1257
+ async clone(entityNameOrEntity, whereOrOverrides, overridesOrOptions, options) {
1258
+ const em = this.getContext(false);
1259
+ let entityName;
1260
+ let where;
1261
+ let overrides;
1262
+ if (Utils.isEntity(entityNameOrEntity)) {
1263
+ // clone(entity, overrides?, options?)
1264
+ entityName = entityNameOrEntity.constructor;
1265
+ where = helper(entityNameOrEntity).getPrimaryKey();
1266
+ overrides = whereOrOverrides;
1267
+ options = overridesOrOptions;
1268
+ }
1269
+ else {
1270
+ // clone(EntityClass, where, overrides?, options?)
1271
+ entityName = entityNameOrEntity;
1272
+ where = whereOrOverrides;
1273
+ overrides = overridesOrOptions;
1274
+ }
1275
+ options ??= {};
1276
+ em.prepareOptions(options);
1277
+ const meta = em.metadata.get(entityName);
1278
+ const res = await em.driver.nativeClone(entityName, where, overrides, {
1279
+ ctx: em.#transactionContext,
1280
+ ...options,
1281
+ });
1282
+ const pk = res.insertId ?? res.row?.[meta.primaryKeys[0]];
1283
+ return em.findOneOrFail(entityName, pk, {
1284
+ schema: options.schema,
1285
+ });
1286
+ }
1247
1287
  /**
1248
1288
  * Fires native multi-insert query. Calling this has no side effects on the context (identity map).
1249
1289
  */
@@ -1305,6 +1345,37 @@ export class EntityManager {
1305
1345
  });
1306
1346
  return res.affectedRows;
1307
1347
  }
1348
+ /**
1349
+ * Invokes a stored procedure or function declared via the {@link Routine} class. Arg and return
1350
+ * types are inferred from the literal config; use {@link Routine.create} to refine when needed.
1351
+ *
1352
+ * Procedures emitting result sets (MySQL `SELECT`s, PG/Oracle refcursor OUT params) return
1353
+ * `Dictionary[][]`; MSSQL multi-result procs aren't exposed here, use `em.getConnection().execute()`.
1354
+ *
1355
+ * Mongo throws. SQLite bridges `bodyJs` functions via better-sqlite3 UDFs (procedures and
1356
+ * function-without-bodyJs throw); libSQL throws unconditionally. Oracle calls run on their own
1357
+ * pool connection with `autoCommit: true`, so wrapping in `em.transactional(...)` throws.
1358
+ *
1359
+ * @example
1360
+ * ```ts
1361
+ * const hash = await em.callRoutine(HashUser, { name: 'jon', salt: 'pepper' });
1362
+ *
1363
+ * const hashRef = new ScalarReference<string>();
1364
+ * await em.callRoutine(AddRecord, { p_name: 'jon', p_age: 30, p_hash: hashRef });
1365
+ *
1366
+ * const TwoCursors = Routine.create<Record<string, never>, unknown[][]>({ ... });
1367
+ * const [users, books] = await em.transactional(em => em.callRoutine(TwoCursors, {}));
1368
+ * ```
1369
+ */
1370
+ async callRoutine(routine, args) {
1371
+ const em = this.getContext(false);
1372
+ // Identity by reference so the user-held import survives bundling and tree-shakes unused routines.
1373
+ if (!em.config.hasRoutine(routine)) {
1374
+ throw new Error(`Routine '${routine.name}' is not registered in the 'routines' config option.`);
1375
+ }
1376
+ const conn = em.driver.getConnection('write');
1377
+ return conn.callRoutine(routine, args, em.#transactionContext);
1378
+ }
1308
1379
  /**
1309
1380
  * Fires native delete query. Calling this has no side effects on the context (identity map).
1310
1381
  */
@@ -1453,6 +1524,29 @@ export class EntityManager {
1453
1524
  await em.storeCache(options.cache, cached, () => +count);
1454
1525
  return +count;
1455
1526
  }
1527
+ /**
1528
+ * Counts entities grouped by one or more properties. Returns a dictionary keyed by the grouped
1529
+ * field value(s), with counts as values. For composite `groupBy`, keys are joined with `~~~`.
1530
+ *
1531
+ * SQL drivers issue a single `GROUP BY` query; MongoDB uses an aggregation pipeline.
1532
+ *
1533
+ * @example
1534
+ * ```ts
1535
+ * // Count books per author
1536
+ * const counts = await em.countBy(Book, 'author');
1537
+ * // { '1': 2, '2': 1, '3': 3 }
1538
+ *
1539
+ * // Count with a filter
1540
+ * const counts = await em.countBy(Book, 'author', { where: { active: true } });
1541
+ *
1542
+ * // Composite groupBy — keys joined with ~~~
1543
+ * const counts = await em.countBy(Order, ['status', 'country']);
1544
+ * // { 'pending~~~US': 5, 'shipped~~~DE': 3 }
1545
+ * ```
1546
+ */
1547
+ async countBy(entityName, groupBy, options) {
1548
+ throw new Error(`${this.constructor.name}.countBy() is not supported by the current driver`);
1549
+ }
1456
1550
  /**
1457
1551
  * Tells the EntityManager to make an instance managed and persistent.
1458
1552
  * The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
@@ -1558,7 +1652,7 @@ export class EntityManager {
1558
1652
  const em = this.getContext();
1559
1653
  em.prepareOptions(options);
1560
1654
  const entityName = arr[0].constructor;
1561
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1655
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters, populateHints: options.populateHints }, options.validate);
1562
1656
  await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
1563
1657
  return entities;
1564
1658
  }
@@ -1591,6 +1685,8 @@ export class EntityManager {
1591
1685
  fork.#filterParams = Utils.copy(em.#filterParams);
1592
1686
  fork.loggerContext = Utils.merge({}, em.loggerContext, options.loggerContext);
1593
1687
  fork.#schema = options.schema ?? em.#schema;
1688
+ fork.signal = options.signal ?? em.signal;
1689
+ fork.inflightQueryAbortStrategy = options.inflightQueryAbortStrategy ?? em.inflightQueryAbortStrategy;
1594
1690
  if (!options.clear) {
1595
1691
  for (const entity of em.#unitOfWork.getIdentityMap()) {
1596
1692
  fork.#unitOfWork.register(entity);
@@ -1666,6 +1762,22 @@ export class EntityManager {
1666
1762
  getTransactionContext() {
1667
1763
  return this.getContext(false).#transactionContext;
1668
1764
  }
1765
+ /**
1766
+ * Returns the cancellation defaults configured on this EntityManager (via `em.fork({ signal })`
1767
+ * or inherited from a transactional fork). Returns `undefined` when no signal is set.
1768
+ *
1769
+ * @internal — exposed for subclass drivers and `UnitOfWork`; not part of the public API.
1770
+ */
1771
+ getAbortOptions() {
1772
+ const em = this.getContext(false);
1773
+ if (em.signal == null && em.inflightQueryAbortStrategy == null) {
1774
+ return undefined;
1775
+ }
1776
+ return {
1777
+ signal: em.signal,
1778
+ inflightQueryAbortStrategy: em.inflightQueryAbortStrategy,
1779
+ };
1780
+ }
1669
1781
  /**
1670
1782
  * Sets the transaction context.
1671
1783
  */
@@ -1709,6 +1821,79 @@ export class EntityManager {
1709
1821
  throw ValidationError.transactionRequired();
1710
1822
  }
1711
1823
  }
1824
+ validateIndexUsage(meta, where, options) {
1825
+ if (!options.using) {
1826
+ return;
1827
+ }
1828
+ const indexNames = Utils.asArray(options.using);
1829
+ const allIndexes = [...meta.indexes, ...meta.uniques];
1830
+ const indexMap = new Map();
1831
+ for (const idx of allIndexes) {
1832
+ if (idx.name) {
1833
+ indexMap.set(idx.name, Utils.asArray(idx.properties ?? []));
1834
+ }
1835
+ }
1836
+ for (const prop of meta.props) {
1837
+ if (typeof prop.index === 'string') {
1838
+ indexMap.set(prop.index, [prop.name]);
1839
+ }
1840
+ if (typeof prop.unique === 'string') {
1841
+ indexMap.set(prop.unique, [prop.name]);
1842
+ }
1843
+ }
1844
+ const allowedProps = new Set();
1845
+ for (const name of indexNames) {
1846
+ const props = indexMap.get(name);
1847
+ if (!props) {
1848
+ const available = [...indexMap.keys()];
1849
+ throw new Error(`Index '${name}' not found on entity '${meta.className}'. ` +
1850
+ (available.length > 0 ? `Available indexes: ${available.join(', ')}` : 'No named indexes defined.'));
1851
+ }
1852
+ for (const prop of props) {
1853
+ allowedProps.add(prop);
1854
+ }
1855
+ }
1856
+ this.validateWhereKeysForIndex(where, allowedProps, indexNames);
1857
+ if (options.orderBy) {
1858
+ const orderMaps = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
1859
+ for (const orderMap of orderMaps) {
1860
+ for (const key of Object.keys(orderMap)) {
1861
+ if (!allowedProps.has(key)) {
1862
+ throw new Error(`Property '${key}' in orderBy is not covered by index '${indexNames.join("', '")}'. ` +
1863
+ `Allowed properties: ${[...allowedProps].join(', ')}`);
1864
+ }
1865
+ }
1866
+ }
1867
+ }
1868
+ }
1869
+ validateWhereKeysForIndex(where, allowedProps, indexNames) {
1870
+ if (typeof where !== 'object' || where === null) {
1871
+ return;
1872
+ }
1873
+ if (Array.isArray(where)) {
1874
+ for (const item of where) {
1875
+ this.validateWhereKeysForIndex(item, allowedProps, indexNames);
1876
+ }
1877
+ return;
1878
+ }
1879
+ for (const key of Object.keys(where)) {
1880
+ if (key === '$and' || key === '$or') {
1881
+ for (const item of Utils.asArray(where[key])) {
1882
+ this.validateWhereKeysForIndex(item, allowedProps, indexNames);
1883
+ }
1884
+ }
1885
+ else if (key === '$not') {
1886
+ this.validateWhereKeysForIndex(where[key], allowedProps, indexNames);
1887
+ }
1888
+ else if (key.startsWith('$')) {
1889
+ continue;
1890
+ }
1891
+ else if (!allowedProps.has(key)) {
1892
+ throw new Error(`Property '${key}' in where clause is not covered by index '${indexNames.join("', '")}'. ` +
1893
+ `Allowed properties: ${[...allowedProps].join(', ')}`);
1894
+ }
1895
+ }
1896
+ }
1712
1897
  async lockAndPopulate(meta, entity, where, options) {
1713
1898
  if (!meta.virtual && options.lockMode === LockMode.OPTIMISTIC) {
1714
1899
  await this.lock(entity, options.lockMode, {
@@ -1839,9 +2024,21 @@ export class EntityManager {
1839
2024
  }
1840
2025
  if (options.populateHints) {
1841
2026
  applyPopulateHints(populate, options.populateHints);
2027
+ this.forceSelectInForLimitedPopulate(populate);
1842
2028
  }
1843
2029
  return populate;
1844
2030
  }
2031
+ /** Force SELECT_IN strategy on populate entries with `limit`, since JOINED cannot do per-parent limiting. */
2032
+ forceSelectInForLimitedPopulate(populate) {
2033
+ for (const entry of populate) {
2034
+ if (entry.limit != null) {
2035
+ entry.strategy = LoadStrategy.SELECT_IN;
2036
+ }
2037
+ if (entry.children?.length) {
2038
+ this.forceSelectInForLimitedPopulate(entry.children);
2039
+ }
2040
+ }
2041
+ }
1845
2042
  /**
1846
2043
  * when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
1847
2044
  * some additional lazy properties, if so, we reload and merge the data from database
@@ -1873,6 +2070,8 @@ export class EntityManager {
1873
2070
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1874
2071
  }
1875
2072
  options.schema ??= this.#schema;
2073
+ options.signal ??= this.signal;
2074
+ options.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
1876
2075
  options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1877
2076
  }
1878
2077
  /**
@@ -1881,7 +2080,15 @@ export class EntityManager {
1881
2080
  cacheKey(entityName, options, method, where) {
1882
2081
  const { ...opts } = options;
1883
2082
  // ignore some irrelevant options, e.g. logger context can contain dynamic data for the same query
1884
- for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
2083
+ for (const k of [
2084
+ 'ctx',
2085
+ 'strategy',
2086
+ 'flushMode',
2087
+ 'logging',
2088
+ 'loggerContext',
2089
+ 'signal',
2090
+ 'inflightQueryAbortStrategy',
2091
+ ]) {
1885
2092
  delete opts[k];
1886
2093
  }
1887
2094
  return [Utils.className(entityName), method, opts, where];
@@ -1974,6 +2181,8 @@ export class EntityManager {
1974
2181
  return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1975
2182
  case 'm:n':
1976
2183
  return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
2184
+ case 'count':
2185
+ return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getCountBatchLoadFn(em)));
1977
2186
  }
1978
2187
  }
1979
2188
  /**
package/MikroORM.d.ts CHANGED
@@ -5,7 +5,7 @@ import { Configuration, type Options } from './utils/Configuration.js';
5
5
  import type { EntityManager } from './EntityManager.js';
6
6
  import type { AnyEntity, Constructor, EntityClass, EntityMetadata, EntityName, IEntityGenerator, IMigrator, ISeedManager } from './typings.js';
7
7
  /** @internal */
8
- export declare function loadOptionalDependencies(options: Partial<Options>): Promise<void>;
8
+ export declare function loadOptionalDependencies(options: Partial<Options<any, any, any>>): Promise<void>;
9
9
  /**
10
10
  * The main class used to configure and bootstrap the ORM.
11
11
  *
@@ -34,7 +34,7 @@ export declare function loadOptionalDependencies(options: Partial<Options>): Pro
34
34
  * await em.flush();
35
35
  * ```
36
36
  */
37
- export declare class MikroORM<Driver extends IDatabaseDriver = IDatabaseDriver, EM extends Driver[typeof EntityManagerType] & EntityManager<Driver> = Driver[typeof EntityManagerType] & EntityManager<Driver>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> {
37
+ export declare class MikroORM<Driver extends IDatabaseDriver = IDatabaseDriver, EM extends Driver[typeof EntityManagerType] & EntityManager<Driver> = Driver[typeof EntityManagerType] & EntityManager<Driver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> {
38
38
  #private;
39
39
  /** The global EntityManager instance. If you are using `RequestContext` helper, it will automatically pick the request specific context under the hood */
40
40
  em: EM & {
@@ -48,7 +48,7 @@ export declare class MikroORM<Driver extends IDatabaseDriver = IDatabaseDriver,
48
48
  * Initialize the ORM, load entity metadata, create EntityManager and connect to the database.
49
49
  * If you omit the `options` parameter, your CLI config will be used.
50
50
  */
51
- static init<D extends IDatabaseDriver = IDatabaseDriver, EM extends D[typeof EntityManagerType] & EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
51
+ static init<D extends IDatabaseDriver = IDatabaseDriver, EM extends D[typeof EntityManagerType] & EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
52
52
  /**
53
53
  * Synchronous variant of the `init` method with some limitations:
54
54
  * - folder-based discovery not supported