@mikro-orm/core 7.1.0-dev.8 → 7.1.0
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 +70 -5
- package/EntityManager.js +175 -23
- package/README.md +2 -1
- package/connections/Connection.d.ts +67 -1
- package/connections/Connection.js +64 -0
- package/drivers/IDatabaseDriver.d.ts +30 -6
- package/entity/BaseEntity.d.ts +68 -1
- package/entity/BaseEntity.js +18 -0
- package/entity/Collection.d.ts +4 -1
- package/entity/Collection.js +15 -4
- package/entity/EntityAssigner.js +8 -0
- package/entity/EntityFactory.js +5 -3
- package/entity/EntityLoader.d.ts +8 -1
- package/entity/EntityLoader.js +97 -29
- package/entity/EntityRepository.d.ts +12 -2
- package/entity/EntityRepository.js +12 -0
- package/entity/Reference.d.ts +42 -1
- package/entity/Reference.js +9 -0
- package/entity/defineEntity.d.ts +69 -16
- package/entity/defineEntity.js +17 -0
- package/entity/utils.js +4 -5
- package/enums.d.ts +8 -1
- package/index.d.ts +1 -1
- package/metadata/EntitySchema.js +18 -0
- package/metadata/MetadataDiscovery.d.ts +1 -0
- package/metadata/MetadataDiscovery.js +79 -15
- package/metadata/MetadataValidator.d.ts +26 -0
- package/metadata/MetadataValidator.js +228 -1
- package/metadata/Routine.d.ts +74 -0
- package/metadata/Routine.js +140 -0
- package/metadata/index.d.ts +1 -0
- package/metadata/index.js +1 -0
- package/metadata/types.d.ts +75 -8
- package/package.json +1 -1
- package/platforms/Platform.d.ts +13 -3
- package/platforms/Platform.js +51 -6
- package/serialization/EntitySerializer.js +4 -3
- package/typings.d.ts +323 -21
- package/typings.js +32 -12
- package/unit-of-work/UnitOfWork.js +43 -5
- package/utils/AbstractMigrator.d.ts +9 -4
- package/utils/AbstractMigrator.js +60 -28
- package/utils/AbstractSchemaGenerator.d.ts +1 -1
- package/utils/AbstractSchemaGenerator.js +4 -1
- package/utils/Configuration.d.ts +30 -0
- package/utils/Configuration.js +45 -0
- package/utils/DataloaderUtils.d.ts +10 -1
- package/utils/DataloaderUtils.js +78 -0
- package/utils/EntityComparator.js +1 -1
- package/utils/TransactionManager.js +2 -0
- package/utils/Utils.js +1 -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/EntityManager.d.ts
CHANGED
|
@@ -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, IndexFilterQuery, WithUsingOptions } from './typings.js';
|
|
9
|
+
import type { CountByOptions, CountOptions, DeleteOptions, FilterOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from './drivers/IDatabaseDriver.js';
|
|
10
|
+
import type { AnyString, ArrayElement, AutoPath, ConnectionType, Dictionary, EntityClass, EntityData, EntityDictionary, EntityDTO, EntityKey, EntityMetadata, EntityName, FilterDef, FilterQuery, FromEntityType, GetRepository, IHydrator, IsSubset, Loaded, MergeLoaded, MergeSelected, ObjectQuery, PopulateOptions, Primary, Ref, RequiredEntityData, 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
|
*/
|
|
@@ -342,6 +347,29 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
342
347
|
* Fires native update query. Calling this has no side effects on the context (identity map).
|
|
343
348
|
*/
|
|
344
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>>;
|
|
345
373
|
/**
|
|
346
374
|
* Fires native delete query. Calling this has no side effects on the context (identity map).
|
|
347
375
|
*/
|
|
@@ -441,6 +469,27 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
441
469
|
* Returns total number of entities matching your `where` query.
|
|
442
470
|
*/
|
|
443
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>>;
|
|
444
493
|
/**
|
|
445
494
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
446
495
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -511,6 +560,13 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
511
560
|
* Gets the transaction context (driver dependent object used to make sure queries are executed on same connection).
|
|
512
561
|
*/
|
|
513
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;
|
|
514
570
|
/**
|
|
515
571
|
* Sets the transaction context.
|
|
516
572
|
*/
|
|
@@ -538,12 +594,14 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
538
594
|
private buildFields;
|
|
539
595
|
/** @internal */
|
|
540
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;
|
|
541
599
|
/**
|
|
542
600
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
543
601
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
544
602
|
*/
|
|
545
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;
|
|
546
|
-
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;
|
|
547
605
|
/**
|
|
548
606
|
* @internal
|
|
549
607
|
*/
|
|
@@ -585,7 +643,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
585
643
|
*/
|
|
586
644
|
set schema(schema: string | null | undefined);
|
|
587
645
|
/** @internal */
|
|
588
|
-
getDataLoader(type: 'ref' | '1:m' | 'm:n'): Promise<any>;
|
|
646
|
+
getDataLoader(type: 'ref' | '1:m' | 'm:n' | 'count'): Promise<any>;
|
|
589
647
|
/**
|
|
590
648
|
* Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
|
|
591
649
|
* if executed inside request context handler.
|
|
@@ -638,4 +696,11 @@ export interface ForkOptions {
|
|
|
638
696
|
schema?: string;
|
|
639
697
|
/** default logger context, can be overridden via {@apilink FindOptions} */
|
|
640
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;
|
|
641
706
|
}
|
package/EntityManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
|
|
1
|
+
import { getOnConflictReturningFields, getWhereCondition, resetUntouchedCollections } from './utils/upsert-utils.js';
|
|
2
2
|
import { Utils } from './utils/Utils.js';
|
|
3
3
|
import { Cursor } from './utils/Cursor.js';
|
|
4
4
|
import { QueryHelper } from './utils/QueryHelper.js';
|
|
@@ -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
|
|
@@ -318,13 +322,13 @@ export class EntityManager {
|
|
|
318
322
|
}
|
|
319
323
|
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
320
324
|
const children = [];
|
|
321
|
-
const lookUpChildren = (ret,
|
|
322
|
-
const children = types.filter(meta2 => meta2.extends ===
|
|
323
|
-
children.forEach(m => lookUpChildren(ret, m
|
|
325
|
+
const lookUpChildren = (ret, parent) => {
|
|
326
|
+
const children = types.filter(meta2 => meta2.extends && this.metadata.find(meta2.extends) === parent);
|
|
327
|
+
children.forEach(m => lookUpChildren(ret, m));
|
|
324
328
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
325
329
|
return children;
|
|
326
330
|
};
|
|
327
|
-
lookUpChildren(children, meta
|
|
331
|
+
lookUpChildren(children, meta);
|
|
328
332
|
/* v8 ignore next */
|
|
329
333
|
where[meta.root.discriminatorColumn] =
|
|
330
334
|
children.length > 0
|
|
@@ -354,10 +358,18 @@ export class EntityManager {
|
|
|
354
358
|
const field = hint.field.split(':')[0];
|
|
355
359
|
const prop = meta.properties[field];
|
|
356
360
|
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
357
|
-
const joined = strategy === LoadStrategy.JOINED
|
|
361
|
+
const joined = (strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) &&
|
|
362
|
+
prop.kind !== ReferenceKind.SCALAR;
|
|
358
363
|
if (!joined && !hint.filter) {
|
|
359
364
|
continue;
|
|
360
365
|
}
|
|
366
|
+
// Polymorphic to-one relations are already filtered via per-target LEFT JOINs created
|
|
367
|
+
// for the `:ref filter` hint; emitting a populate-filter entry here would auto-join only
|
|
368
|
+
// the first target meta and either drop rows pointing to other targets or reference
|
|
369
|
+
// parent-table-only columns on the child sub-table alias for TPT children.
|
|
370
|
+
if (prop.polymorphic && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
361
373
|
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
362
374
|
const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
|
|
363
375
|
...options,
|
|
@@ -423,11 +435,13 @@ export class EntityManager {
|
|
|
423
435
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
424
436
|
let found = false;
|
|
425
437
|
for (const hint of populated) {
|
|
426
|
-
|
|
438
|
+
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
439
|
+
// inverse 1:1 always produces a JOIN (forced by `joinedProps()`), regardless of strategy
|
|
440
|
+
const willJoin = strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner);
|
|
441
|
+
if (!hint.all || willJoin) {
|
|
427
442
|
hint.filter = true;
|
|
428
443
|
}
|
|
429
|
-
|
|
430
|
-
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
444
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && willJoin)) {
|
|
431
445
|
found = true;
|
|
432
446
|
}
|
|
433
447
|
}
|
|
@@ -441,7 +455,23 @@ export class EntityManager {
|
|
|
441
455
|
const prop = meta?.properties[field];
|
|
442
456
|
if (prop && !ref) {
|
|
443
457
|
hint.children ??= [];
|
|
444
|
-
|
|
458
|
+
const targets = prop.polymorphic && prop.polymorphTargets?.length ? prop.polymorphTargets : [prop.targetMeta];
|
|
459
|
+
for (const targetMeta of targets) {
|
|
460
|
+
const before = hint.children.length;
|
|
461
|
+
await this.autoJoinRefsForFilters(targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
|
|
462
|
+
// For polymorphic relations, `hint.children` is applied to every polymorph target during
|
|
463
|
+
// population, so drop any auto-added hint whose field does not exist on every target —
|
|
464
|
+
// otherwise the shared `:ref` would crash when applied to a target lacking it (GH #7722).
|
|
465
|
+
if (targets.length > 1 && hint.children.length > before) {
|
|
466
|
+
const added = hint.children.splice(before);
|
|
467
|
+
for (const h of added) {
|
|
468
|
+
const f = h.field.split(':')[0];
|
|
469
|
+
if (targets.every(t => f in t.properties)) {
|
|
470
|
+
hint.children.push(h);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
445
475
|
}
|
|
446
476
|
}
|
|
447
477
|
}
|
|
@@ -649,7 +679,7 @@ export class EntityManager {
|
|
|
649
679
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
650
680
|
validateEmptyWhere(where);
|
|
651
681
|
em.checkLockRequirements(options.lockMode, meta);
|
|
652
|
-
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
|
|
682
|
+
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.NONE || options.lockMode === LockMode.OPTIMISTIC;
|
|
653
683
|
if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
|
|
654
684
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
655
685
|
}
|
|
@@ -790,7 +820,10 @@ export class EntityManager {
|
|
|
790
820
|
data = QueryHelper.processObjectParams(data);
|
|
791
821
|
validateParams(data, 'insert data');
|
|
792
822
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
793
|
-
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
|
|
823
|
+
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: entity ?? data, em, meta }, meta);
|
|
824
|
+
if (entity) {
|
|
825
|
+
data = em.#comparator.prepareEntity(entity);
|
|
826
|
+
}
|
|
794
827
|
}
|
|
795
828
|
const ret = await em.driver.nativeUpdate(entityName, where, data, {
|
|
796
829
|
ctx: em.#transactionContext,
|
|
@@ -839,6 +872,7 @@ export class EntityManager {
|
|
|
839
872
|
em.getHydrator().hydrate(entity, meta, data2, em.#entityFactory, 'full', false, true);
|
|
840
873
|
}
|
|
841
874
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
875
|
+
resetUntouchedCollections(meta, entity);
|
|
842
876
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
843
877
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
844
878
|
if (em.eventManager.hasListeners(EventType.afterUpsert, meta)) {
|
|
@@ -905,6 +939,7 @@ export class EntityManager {
|
|
|
905
939
|
const allWhere = [];
|
|
906
940
|
const entities = new Map();
|
|
907
941
|
const entitiesByData = new Map();
|
|
942
|
+
const entitiesByAllDataIdx = new Map();
|
|
908
943
|
for (let i = 0; i < data.length; i++) {
|
|
909
944
|
let row = data[i];
|
|
910
945
|
let where;
|
|
@@ -918,6 +953,7 @@ export class EntityManager {
|
|
|
918
953
|
}
|
|
919
954
|
where = helper(entity).getPrimaryKey();
|
|
920
955
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
956
|
+
entitiesByAllDataIdx.set(allData.length, entity);
|
|
921
957
|
row = em.#comparator.prepareEntity(entity);
|
|
922
958
|
}
|
|
923
959
|
else {
|
|
@@ -965,6 +1001,9 @@ export class EntityManager {
|
|
|
965
1001
|
const entity = entitiesByData.get(dto) ?? dto;
|
|
966
1002
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity, em, meta }, meta);
|
|
967
1003
|
}
|
|
1004
|
+
for (const [idx, entity] of entitiesByAllDataIdx) {
|
|
1005
|
+
allData[idx] = em.#comparator.prepareEntity(entity);
|
|
1006
|
+
}
|
|
968
1007
|
}
|
|
969
1008
|
const res = await em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
970
1009
|
ctx: em.#transactionContext,
|
|
@@ -1065,6 +1104,7 @@ export class EntityManager {
|
|
|
1065
1104
|
}
|
|
1066
1105
|
for (const [entity] of entities) {
|
|
1067
1106
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
1107
|
+
resetUntouchedCollections(meta, entity);
|
|
1068
1108
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
1069
1109
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
1070
1110
|
}
|
|
@@ -1158,6 +1198,7 @@ export class EntityManager {
|
|
|
1158
1198
|
*/
|
|
1159
1199
|
async lock(entity, lockMode, options = {}) {
|
|
1160
1200
|
options = Utils.isPlainObject(options) ? options : { lockVersion: options };
|
|
1201
|
+
this.getContext(false).prepareOptions(options);
|
|
1161
1202
|
await this.getUnitOfWork().lock(entity, { lockMode, ...options });
|
|
1162
1203
|
}
|
|
1163
1204
|
/**
|
|
@@ -1179,9 +1220,6 @@ export class EntityManager {
|
|
|
1179
1220
|
helper(data).setSchema(options.schema);
|
|
1180
1221
|
}
|
|
1181
1222
|
if (!helper(data).__managed) {
|
|
1182
|
-
// the entity might have been created via `em.create()`, which adds it to the persist stack automatically
|
|
1183
|
-
em.#unitOfWork.getPersistStack().delete(data);
|
|
1184
|
-
// it can be also in the identity map if it had a PK value already
|
|
1185
1223
|
em.#unitOfWork.unsetIdentity(data);
|
|
1186
1224
|
}
|
|
1187
1225
|
const meta = helper(data).__meta;
|
|
@@ -1270,9 +1308,6 @@ export class EntityManager {
|
|
|
1270
1308
|
helper(row).setSchema(options.schema);
|
|
1271
1309
|
}
|
|
1272
1310
|
if (!helper(row).__managed) {
|
|
1273
|
-
// the entity might have been created via `em.create()`, which adds it to the persist stack automatically
|
|
1274
|
-
em.#unitOfWork.getPersistStack().delete(row);
|
|
1275
|
-
// it can be also in the identity map if it had a PK value already
|
|
1276
1311
|
em.#unitOfWork.unsetIdentity(row);
|
|
1277
1312
|
}
|
|
1278
1313
|
const payload = em.#comparator.prepareEntity(row);
|
|
@@ -1310,6 +1345,37 @@ export class EntityManager {
|
|
|
1310
1345
|
});
|
|
1311
1346
|
return res.affectedRows;
|
|
1312
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
|
+
}
|
|
1313
1379
|
/**
|
|
1314
1380
|
* Fires native delete query. Calling this has no side effects on the context (identity map).
|
|
1315
1381
|
*/
|
|
@@ -1458,6 +1524,29 @@ export class EntityManager {
|
|
|
1458
1524
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1459
1525
|
return +count;
|
|
1460
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
|
+
}
|
|
1461
1550
|
/**
|
|
1462
1551
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
1463
1552
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -1563,7 +1652,7 @@ export class EntityManager {
|
|
|
1563
1652
|
const em = this.getContext();
|
|
1564
1653
|
em.prepareOptions(options);
|
|
1565
1654
|
const entityName = arr[0].constructor;
|
|
1566
|
-
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);
|
|
1567
1656
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1568
1657
|
return entities;
|
|
1569
1658
|
}
|
|
@@ -1596,6 +1685,8 @@ export class EntityManager {
|
|
|
1596
1685
|
fork.#filterParams = Utils.copy(em.#filterParams);
|
|
1597
1686
|
fork.loggerContext = Utils.merge({}, em.loggerContext, options.loggerContext);
|
|
1598
1687
|
fork.#schema = options.schema ?? em.#schema;
|
|
1688
|
+
fork.signal = options.signal ?? em.signal;
|
|
1689
|
+
fork.inflightQueryAbortStrategy = options.inflightQueryAbortStrategy ?? em.inflightQueryAbortStrategy;
|
|
1599
1690
|
if (!options.clear) {
|
|
1600
1691
|
for (const entity of em.#unitOfWork.getIdentityMap()) {
|
|
1601
1692
|
fork.#unitOfWork.register(entity);
|
|
@@ -1671,6 +1762,22 @@ export class EntityManager {
|
|
|
1671
1762
|
getTransactionContext() {
|
|
1672
1763
|
return this.getContext(false).#transactionContext;
|
|
1673
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
|
+
}
|
|
1674
1781
|
/**
|
|
1675
1782
|
* Sets the transaction context.
|
|
1676
1783
|
*/
|
|
@@ -1821,8 +1928,17 @@ export class EntityManager {
|
|
|
1821
1928
|
return [];
|
|
1822
1929
|
}
|
|
1823
1930
|
const meta = this.metadata.find(entityName);
|
|
1824
|
-
// infer populate
|
|
1825
|
-
|
|
1931
|
+
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set.
|
|
1932
|
+
// `populateArray` is only kept when the entries are user-provided strings — when this method runs a
|
|
1933
|
+
// second time (e.g. from `lockAndPopulate`) the populate is already normalized and merging would be a no-op.
|
|
1934
|
+
const fields = options.fields ? this.buildFields(options.fields) : undefined;
|
|
1935
|
+
const populateArray = Array.isArray(options.populate) && options.populate.every(p => typeof p === 'string')
|
|
1936
|
+
? options.populate
|
|
1937
|
+
: undefined;
|
|
1938
|
+
const hasNestedFields = fields?.some(f => f.includes('.')) ?? false;
|
|
1939
|
+
const shouldInfer = fields !== undefined && options.populate === undefined;
|
|
1940
|
+
const shouldMerge = fields !== undefined && populateArray !== undefined && populateArray.length > 0 && hasNestedFields;
|
|
1941
|
+
if (shouldInfer || shouldMerge) {
|
|
1826
1942
|
// 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
|
|
1827
1943
|
const pruneToOneRelations = (meta, fields) => {
|
|
1828
1944
|
const ret = [];
|
|
@@ -1856,7 +1972,19 @@ export class EntityManager {
|
|
|
1856
1972
|
}
|
|
1857
1973
|
return Utils.unique(ret);
|
|
1858
1974
|
};
|
|
1859
|
-
|
|
1975
|
+
const fromFields = pruneToOneRelations(meta, fields);
|
|
1976
|
+
if (shouldInfer) {
|
|
1977
|
+
options.populate = fromFields;
|
|
1978
|
+
}
|
|
1979
|
+
else {
|
|
1980
|
+
// with an explicit populate, only nested paths (e.g. `item.id`) need to be merged in — they
|
|
1981
|
+
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver.
|
|
1982
|
+
// skip paths already in user's `populate` (regardless of `:ref` modifier) to avoid producing
|
|
1983
|
+
// both a `:ref` and a non-`:ref` entry for the same relation
|
|
1984
|
+
const existing = new Set(populateArray.map(p => p.split(':')[0]));
|
|
1985
|
+
const extras = fromFields.filter(f => f.includes('.') && !existing.has(f));
|
|
1986
|
+
options.populate = [...populateArray, ...extras];
|
|
1987
|
+
}
|
|
1860
1988
|
}
|
|
1861
1989
|
if (!options.populate) {
|
|
1862
1990
|
const populate = this.#entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
|
|
@@ -1896,9 +2024,21 @@ export class EntityManager {
|
|
|
1896
2024
|
}
|
|
1897
2025
|
if (options.populateHints) {
|
|
1898
2026
|
applyPopulateHints(populate, options.populateHints);
|
|
2027
|
+
this.forceSelectInForLimitedPopulate(populate);
|
|
1899
2028
|
}
|
|
1900
2029
|
return populate;
|
|
1901
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
|
+
}
|
|
1902
2042
|
/**
|
|
1903
2043
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
1904
2044
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
@@ -1930,6 +2070,8 @@ export class EntityManager {
|
|
|
1930
2070
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1931
2071
|
}
|
|
1932
2072
|
options.schema ??= this.#schema;
|
|
2073
|
+
options.signal ??= this.signal;
|
|
2074
|
+
options.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
|
|
1933
2075
|
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1934
2076
|
}
|
|
1935
2077
|
/**
|
|
@@ -1938,7 +2080,15 @@ export class EntityManager {
|
|
|
1938
2080
|
cacheKey(entityName, options, method, where) {
|
|
1939
2081
|
const { ...opts } = options;
|
|
1940
2082
|
// ignore some irrelevant options, e.g. logger context can contain dynamic data for the same query
|
|
1941
|
-
for (const k of [
|
|
2083
|
+
for (const k of [
|
|
2084
|
+
'ctx',
|
|
2085
|
+
'strategy',
|
|
2086
|
+
'flushMode',
|
|
2087
|
+
'logging',
|
|
2088
|
+
'loggerContext',
|
|
2089
|
+
'signal',
|
|
2090
|
+
'inflightQueryAbortStrategy',
|
|
2091
|
+
]) {
|
|
1942
2092
|
delete opts[k];
|
|
1943
2093
|
}
|
|
1944
2094
|
return [Utils.className(entityName), method, opts, where];
|
|
@@ -2031,6 +2181,8 @@ export class EntityManager {
|
|
|
2031
2181
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
2032
2182
|
case 'm:n':
|
|
2033
2183
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
2184
|
+
case 'count':
|
|
2185
|
+
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getCountBatchLoadFn(em)));
|
|
2034
2186
|
}
|
|
2035
2187
|
}
|
|
2036
2188
|
/**
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
5
|
+
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL (including CockroachDB and PGlite), SQLite (including libSQL), MSSQL and Oracle databases.
|
|
6
6
|
|
|
7
7
|
> Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
|
|
8
8
|
|
|
@@ -19,6 +19,7 @@ Install a driver package for your database:
|
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
npm install @mikro-orm/postgresql # PostgreSQL
|
|
22
|
+
npm install @mikro-orm/pglite # PGlite (embedded PostgreSQL in WASM)
|
|
22
23
|
npm install @mikro-orm/mysql # MySQL
|
|
23
24
|
npm install @mikro-orm/mariadb # MariaDB
|
|
24
25
|
npm install @mikro-orm/sqlite # SQLite
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type Configuration, type ConnectionOptions } from '../utils/Configuration.js';
|
|
2
2
|
import type { LogContext, Logger } from '../logging/Logger.js';
|
|
3
3
|
import type { MetadataStorage } from '../metadata/MetadataStorage.js';
|
|
4
|
-
import type { ConnectionType, Dictionary, MaybePromise, Primary } from '../typings.js';
|
|
4
|
+
import type { ConnectionType, Dictionary, MaybePromise, Primary, RoutineProperty } from '../typings.js';
|
|
5
|
+
import type { Routine } from '../metadata/Routine.js';
|
|
5
6
|
import type { Platform } from '../platforms/Platform.js';
|
|
7
|
+
import type { Type } from '../types/Type.js';
|
|
6
8
|
import type { TransactionEventBroadcaster } from '../events/TransactionEventBroadcaster.js';
|
|
7
9
|
import type { IsolationLevel } from '../enums.js';
|
|
8
10
|
/** Abstract base class for database connections, providing transaction and query execution support. */
|
|
@@ -72,6 +74,41 @@ export declare abstract class Connection {
|
|
|
72
74
|
rollback(ctx: Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
73
75
|
/** Executes a raw query and returns the result. */
|
|
74
76
|
abstract execute<T>(query: string, params?: any[], method?: 'all' | 'get' | 'run', ctx?: Transaction): Promise<QueryResult<T> | any | any[]>;
|
|
77
|
+
/** @internal — public callers go through {@link EntityManager.callRoutine}. */
|
|
78
|
+
callRoutine<T>(routine: Routine, args: Record<string, unknown>, ctx?: Transaction): Promise<T>;
|
|
79
|
+
/**
|
|
80
|
+
* Unwraps a routine argument (resolving any `ScalarReference` wrapper) and, when the param
|
|
81
|
+
* declares a `customType`, marshals it through `convertToDatabaseValue`. `undefined` is
|
|
82
|
+
* normalised to `null` so every driver sees the same shape.
|
|
83
|
+
*
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
protected convertRoutineInbound(value: unknown, param: RoutineProperty | undefined): unknown;
|
|
87
|
+
/**
|
|
88
|
+
* Converts a raw database value to its JS representation via the supplied `customType`, when
|
|
89
|
+
* one is declared. Used to marshal scalar function returns and OUT/INOUT values back to the
|
|
90
|
+
* caller before they land in a `ScalarReference` or `em.callRoutine`'s return value.
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
protected convertRoutineOutbound<T>(value: unknown, customType: Type<unknown> | undefined): T;
|
|
95
|
+
/**
|
|
96
|
+
* Executes a scalar function routine as `select <qualified>(?, ?, ...) as value`, marshalling
|
|
97
|
+
* IN params on the way in and the return value on the way out. The qualified name is built
|
|
98
|
+
* from `routine.schema`, falling back to the platform's default schema (e.g. `dbo` on MSSQL),
|
|
99
|
+
* which gives MySQL/SQLite a bare name and MSSQL/Oracle the mandatory `schema.name` form.
|
|
100
|
+
*
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
protected callRoutineFunction<T>(routine: Routine, args: Record<string, unknown>, ctx?: Transaction): Promise<T>;
|
|
104
|
+
/**
|
|
105
|
+
* Walks a result row produced by an OUT/INOUT-param SELECT and writes each value into the
|
|
106
|
+
* caller's `ScalarReference` slot. Non-reference args are ignored (the user opted out of
|
|
107
|
+
* receiving the OUT value).
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
protected applyRoutineOutParams(row: Dictionary, outParams: RoutineProperty[], args: Record<string, unknown>): void;
|
|
75
112
|
/** Parses and returns the resolved connection configuration (host, port, user, etc.). */
|
|
76
113
|
getConnectionOptions(): ConnectionConfig;
|
|
77
114
|
/** Sets the metadata storage on this connection. */
|
|
@@ -104,3 +141,32 @@ export interface ConnectionConfig {
|
|
|
104
141
|
}
|
|
105
142
|
/** Opaque transaction context type, wrapping the driver-specific transaction object. */
|
|
106
143
|
export type Transaction<T = any> = T & {};
|
|
144
|
+
/**
|
|
145
|
+
* Strategy applied when an `AbortSignal` fires while a query is in flight.
|
|
146
|
+
*
|
|
147
|
+
* - `'ignore query'` — stop awaiting; the query keeps running on the server until it settles
|
|
148
|
+
* (the connection returns to the pool only when the database replies).
|
|
149
|
+
* - `'cancel query'` — ask the database to cancel the running query (e.g. `pg_cancel_backend`,
|
|
150
|
+
* `KILL QUERY`). Falls back to `'ignore query'` if the dialect cannot cancel.
|
|
151
|
+
* Most engines do not cancel writes; partial commits are possible.
|
|
152
|
+
* - `'kill session'` — terminate the database session/process the query runs in
|
|
153
|
+
* (`pg_terminate_backend` etc.). Falls back to `'cancel query'` if not supported.
|
|
154
|
+
*
|
|
155
|
+
* Default: `'ignore query'`.
|
|
156
|
+
*
|
|
157
|
+
* **Streaming queries (`em.stream()` / `qb.stream()`):** the strategy is silently treated as
|
|
158
|
+
* `'ignore query'` because the underlying driver only accepts a plain `AbortSignal` for
|
|
159
|
+
* streamed reads — there is no server-side cancel for an open cursor. The MongoDB driver also
|
|
160
|
+
* has no notion of strategies; only the signal is honored there.
|
|
161
|
+
*/
|
|
162
|
+
export type InflightQueryAbortStrategy = 'ignore query' | 'cancel query' | 'kill session';
|
|
163
|
+
/** Per-query cancellation controls forwarded to the underlying driver. */
|
|
164
|
+
export interface AbortQueryOptions {
|
|
165
|
+
/** AbortSignal that cancels the query when fired. */
|
|
166
|
+
signal?: AbortSignal;
|
|
167
|
+
/**
|
|
168
|
+
* Strategy used when the signal fires while the query is in flight. See
|
|
169
|
+
* {@apilink InflightQueryAbortStrategy} for caveats around streams and MongoDB.
|
|
170
|
+
*/
|
|
171
|
+
inflightQueryAbortStrategy?: InflightQueryAbortStrategy;
|
|
172
|
+
}
|