@mikro-orm/core 7.1.0-dev.9 → 7.1.1-dev.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 +47 -3
- package/EntityManager.js +150 -23
- package/README.md +2 -1
- package/connections/Connection.d.ts +67 -1
- package/connections/Connection.js +64 -0
- package/drivers/IDatabaseDriver.d.ts +20 -6
- package/entity/BaseEntity.d.ts +68 -1
- package/entity/BaseEntity.js +18 -0
- package/entity/Collection.d.ts +1 -1
- package/entity/Collection.js +4 -1
- 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/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/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
|
@@ -7,10 +7,11 @@ 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
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';
|
|
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
|
*/
|
|
@@ -532,6 +560,13 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
532
560
|
* Gets the transaction context (driver dependent object used to make sure queries are executed on same connection).
|
|
533
561
|
*/
|
|
534
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;
|
|
535
570
|
/**
|
|
536
571
|
* Sets the transaction context.
|
|
537
572
|
*/
|
|
@@ -559,12 +594,14 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
559
594
|
private buildFields;
|
|
560
595
|
/** @internal */
|
|
561
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;
|
|
562
599
|
/**
|
|
563
600
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
564
601
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
565
602
|
*/
|
|
566
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;
|
|
567
|
-
protected prepareOptions(options: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>): void;
|
|
604
|
+
protected prepareOptions(options: (FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any> | CountOptions<any, any> | CountByOptions<any>) & AbortQueryOptions): void;
|
|
568
605
|
/**
|
|
569
606
|
* @internal
|
|
570
607
|
*/
|
|
@@ -659,4 +696,11 @@ export interface ForkOptions {
|
|
|
659
696
|
schema?: string;
|
|
660
697
|
/** default logger context, can be overridden via {@apilink FindOptions} */
|
|
661
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;
|
|
662
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
|
*/
|
|
@@ -1586,7 +1652,7 @@ export class EntityManager {
|
|
|
1586
1652
|
const em = this.getContext();
|
|
1587
1653
|
em.prepareOptions(options);
|
|
1588
1654
|
const entityName = arr[0].constructor;
|
|
1589
|
-
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);
|
|
1590
1656
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1591
1657
|
return entities;
|
|
1592
1658
|
}
|
|
@@ -1619,6 +1685,8 @@ export class EntityManager {
|
|
|
1619
1685
|
fork.#filterParams = Utils.copy(em.#filterParams);
|
|
1620
1686
|
fork.loggerContext = Utils.merge({}, em.loggerContext, options.loggerContext);
|
|
1621
1687
|
fork.#schema = options.schema ?? em.#schema;
|
|
1688
|
+
fork.signal = options.signal ?? em.signal;
|
|
1689
|
+
fork.inflightQueryAbortStrategy = options.inflightQueryAbortStrategy ?? em.inflightQueryAbortStrategy;
|
|
1622
1690
|
if (!options.clear) {
|
|
1623
1691
|
for (const entity of em.#unitOfWork.getIdentityMap()) {
|
|
1624
1692
|
fork.#unitOfWork.register(entity);
|
|
@@ -1694,6 +1762,22 @@ export class EntityManager {
|
|
|
1694
1762
|
getTransactionContext() {
|
|
1695
1763
|
return this.getContext(false).#transactionContext;
|
|
1696
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
|
+
}
|
|
1697
1781
|
/**
|
|
1698
1782
|
* Sets the transaction context.
|
|
1699
1783
|
*/
|
|
@@ -1844,8 +1928,17 @@ export class EntityManager {
|
|
|
1844
1928
|
return [];
|
|
1845
1929
|
}
|
|
1846
1930
|
const meta = this.metadata.find(entityName);
|
|
1847
|
-
// infer populate
|
|
1848
|
-
|
|
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) {
|
|
1849
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
|
|
1850
1943
|
const pruneToOneRelations = (meta, fields) => {
|
|
1851
1944
|
const ret = [];
|
|
@@ -1879,7 +1972,19 @@ export class EntityManager {
|
|
|
1879
1972
|
}
|
|
1880
1973
|
return Utils.unique(ret);
|
|
1881
1974
|
};
|
|
1882
|
-
|
|
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
|
+
}
|
|
1883
1988
|
}
|
|
1884
1989
|
if (!options.populate) {
|
|
1885
1990
|
const populate = this.#entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
|
|
@@ -1919,9 +2024,21 @@ export class EntityManager {
|
|
|
1919
2024
|
}
|
|
1920
2025
|
if (options.populateHints) {
|
|
1921
2026
|
applyPopulateHints(populate, options.populateHints);
|
|
2027
|
+
this.forceSelectInForLimitedPopulate(populate);
|
|
1922
2028
|
}
|
|
1923
2029
|
return populate;
|
|
1924
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
|
+
}
|
|
1925
2042
|
/**
|
|
1926
2043
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
1927
2044
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
@@ -1953,6 +2070,8 @@ export class EntityManager {
|
|
|
1953
2070
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1954
2071
|
}
|
|
1955
2072
|
options.schema ??= this.#schema;
|
|
2073
|
+
options.signal ??= this.signal;
|
|
2074
|
+
options.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
|
|
1956
2075
|
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1957
2076
|
}
|
|
1958
2077
|
/**
|
|
@@ -1961,7 +2080,15 @@ export class EntityManager {
|
|
|
1961
2080
|
cacheKey(entityName, options, method, where) {
|
|
1962
2081
|
const { ...opts } = options;
|
|
1963
2082
|
// ignore some irrelevant options, e.g. logger context can contain dynamic data for the same query
|
|
1964
|
-
for (const k of [
|
|
2083
|
+
for (const k of [
|
|
2084
|
+
'ctx',
|
|
2085
|
+
'strategy',
|
|
2086
|
+
'flushMode',
|
|
2087
|
+
'logging',
|
|
2088
|
+
'loggerContext',
|
|
2089
|
+
'signal',
|
|
2090
|
+
'inflightQueryAbortStrategy',
|
|
2091
|
+
]) {
|
|
1965
2092
|
delete opts[k];
|
|
1966
2093
|
}
|
|
1967
2094
|
return [Utils.className(entityName), method, opts, where];
|
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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Utils } from '../utils/Utils.js';
|
|
2
|
+
import { ScalarReference } from '../entity/Reference.js';
|
|
2
3
|
/** Abstract base class for database connections, providing transaction and query execution support. */
|
|
3
4
|
export class Connection {
|
|
4
5
|
config;
|
|
@@ -92,6 +93,69 @@ export class Connection {
|
|
|
92
93
|
async rollback(ctx, eventBroadcaster, loggerContext) {
|
|
93
94
|
throw new Error(`Transactions are not supported by current driver`);
|
|
94
95
|
}
|
|
96
|
+
/** @internal — public callers go through {@link EntityManager.callRoutine}. */
|
|
97
|
+
async callRoutine(routine, args, ctx) {
|
|
98
|
+
throw new Error(`Stored routines are not supported by the current driver`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Unwraps a routine argument (resolving any `ScalarReference` wrapper) and, when the param
|
|
102
|
+
* declares a `customType`, marshals it through `convertToDatabaseValue`. `undefined` is
|
|
103
|
+
* normalised to `null` so every driver sees the same shape.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
convertRoutineInbound(value, param) {
|
|
108
|
+
const resolved = value instanceof ScalarReference ? value.unwrap() : value;
|
|
109
|
+
const coerced = resolved === undefined ? null : resolved;
|
|
110
|
+
if (coerced === null || !param?.customType) {
|
|
111
|
+
return coerced;
|
|
112
|
+
}
|
|
113
|
+
return param.customType.convertToDatabaseValue(coerced, this.platform);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Converts a raw database value to its JS representation via the supplied `customType`, when
|
|
117
|
+
* one is declared. Used to marshal scalar function returns and OUT/INOUT values back to the
|
|
118
|
+
* caller before they land in a `ScalarReference` or `em.callRoutine`'s return value.
|
|
119
|
+
*
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
convertRoutineOutbound(value, customType) {
|
|
123
|
+
if (value === null || value === undefined || !customType) {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
return customType.convertToJSValue(value, this.platform);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Executes a scalar function routine as `select <qualified>(?, ?, ...) as value`, marshalling
|
|
130
|
+
* IN params on the way in and the return value on the way out. The qualified name is built
|
|
131
|
+
* from `routine.schema`, falling back to the platform's default schema (e.g. `dbo` on MSSQL),
|
|
132
|
+
* which gives MySQL/SQLite a bare name and MSSQL/Oracle the mandatory `schema.name` form.
|
|
133
|
+
*
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
async callRoutineFunction(routine, args, ctx) {
|
|
137
|
+
const schema = routine.schema ?? this.platform.getDefaultSchemaName();
|
|
138
|
+
const qualified = (schema ? `${this.platform.quoteIdentifier(schema)}.` : '') + this.platform.quoteIdentifier(routine.name);
|
|
139
|
+
const placeholders = routine.params.map(() => '?').join(', ');
|
|
140
|
+
const positional = routine.params.map(p => this.convertRoutineInbound(args[p.name], p));
|
|
141
|
+
const rows = (await this.execute(`select ${qualified}(${placeholders}) as value`, positional, 'all', ctx));
|
|
142
|
+
return this.convertRoutineOutbound(rows[0]?.value, routine.returnCustomType);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Walks a result row produced by an OUT/INOUT-param SELECT and writes each value into the
|
|
146
|
+
* caller's `ScalarReference` slot. Non-reference args are ignored (the user opted out of
|
|
147
|
+
* receiving the OUT value).
|
|
148
|
+
*
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
applyRoutineOutParams(row, outParams, args) {
|
|
152
|
+
for (const param of outParams) {
|
|
153
|
+
const ref = args[param.name];
|
|
154
|
+
if (ref instanceof ScalarReference) {
|
|
155
|
+
ref.set(this.convertRoutineOutbound(row[param.name], param.customType));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
95
159
|
/** Parses and returns the resolved connection configuration (host, port, user, etc.). */
|
|
96
160
|
getConnectionOptions() {
|
|
97
161
|
const ret = {};
|