@mikro-orm/core 7.1.0-dev.4 → 7.1.0-dev.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EntityManager.d.ts +63 -12
- package/EntityManager.js +221 -40
- package/README.md +2 -1
- package/connections/Connection.d.ts +29 -0
- package/drivers/IDatabaseDriver.d.ts +45 -7
- package/entity/BaseEntity.d.ts +68 -1
- package/entity/BaseEntity.js +18 -0
- package/entity/Collection.d.ts +6 -3
- package/entity/Collection.js +15 -4
- package/entity/EntityAssigner.js +8 -0
- package/entity/EntityFactory.js +20 -1
- package/entity/EntityLoader.d.ts +8 -1
- package/entity/EntityLoader.js +89 -28
- package/entity/EntityRepository.d.ts +27 -9
- package/entity/EntityRepository.js +12 -0
- package/entity/Reference.d.ts +42 -1
- package/entity/Reference.js +9 -0
- package/entity/defineEntity.d.ts +99 -21
- package/entity/defineEntity.js +17 -6
- package/entity/utils.js +4 -5
- package/enums.d.ts +8 -1
- package/errors.d.ts +2 -0
- package/errors.js +4 -0
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/metadata/EntitySchema.js +3 -0
- package/metadata/MetadataDiscovery.d.ts +12 -0
- package/metadata/MetadataDiscovery.js +166 -20
- package/metadata/MetadataValidator.d.ts +24 -0
- package/metadata/MetadataValidator.js +202 -1
- package/metadata/types.d.ts +71 -4
- package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
- package/naming-strategy/NamingStrategy.d.ts +1 -1
- package/package.json +1 -1
- package/platforms/Platform.d.ts +18 -3
- package/platforms/Platform.js +58 -6
- package/serialization/EntitySerializer.js +2 -1
- package/typings.d.ts +202 -22
- package/typings.js +51 -14
- package/unit-of-work/UnitOfWork.js +15 -4
- package/utils/AbstractMigrator.d.ts +20 -5
- package/utils/AbstractMigrator.js +263 -28
- package/utils/AbstractSchemaGenerator.d.ts +1 -1
- package/utils/AbstractSchemaGenerator.js +4 -1
- package/utils/Configuration.d.ts +25 -0
- package/utils/Configuration.js +1 -0
- package/utils/DataloaderUtils.d.ts +10 -1
- package/utils/DataloaderUtils.js +78 -0
- package/utils/EntityComparator.js +1 -1
- package/utils/QueryHelper.d.ts +16 -0
- package/utils/QueryHelper.js +15 -0
- package/utils/TransactionManager.js +2 -0
- package/utils/Utils.js +1 -1
- package/utils/fs-utils.d.ts +2 -0
- package/utils/fs-utils.js +7 -1
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/partition-utils.d.ts +17 -0
- package/utils/partition-utils.js +79 -0
- package/utils/upsert-utils.d.ts +2 -0
- package/utils/upsert-utils.js +26 -1
package/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
|
|
@@ -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');
|
|
@@ -319,13 +322,13 @@ export class EntityManager {
|
|
|
319
322
|
}
|
|
320
323
|
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
321
324
|
const children = [];
|
|
322
|
-
const lookUpChildren = (ret,
|
|
323
|
-
const children = types.filter(meta2 => meta2.extends ===
|
|
324
|
-
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));
|
|
325
328
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
326
329
|
return children;
|
|
327
330
|
};
|
|
328
|
-
lookUpChildren(children, meta
|
|
331
|
+
lookUpChildren(children, meta);
|
|
329
332
|
/* v8 ignore next */
|
|
330
333
|
where[meta.root.discriminatorColumn] =
|
|
331
334
|
children.length > 0
|
|
@@ -355,10 +358,18 @@ export class EntityManager {
|
|
|
355
358
|
const field = hint.field.split(':')[0];
|
|
356
359
|
const prop = meta.properties[field];
|
|
357
360
|
const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
358
|
-
const joined = strategy === LoadStrategy.JOINED
|
|
361
|
+
const joined = (strategy === LoadStrategy.JOINED || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) &&
|
|
362
|
+
prop.kind !== ReferenceKind.SCALAR;
|
|
359
363
|
if (!joined && !hint.filter) {
|
|
360
364
|
continue;
|
|
361
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
|
+
}
|
|
362
373
|
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
363
374
|
const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
|
|
364
375
|
...options,
|
|
@@ -424,11 +435,13 @@ export class EntityManager {
|
|
|
424
435
|
const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
|
|
425
436
|
let found = false;
|
|
426
437
|
for (const hint of populated) {
|
|
427
|
-
|
|
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) {
|
|
428
442
|
hint.filter = true;
|
|
429
443
|
}
|
|
430
|
-
|
|
431
|
-
if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
|
|
444
|
+
if (hint.field === `${prop.name}:ref` || (hint.filter && willJoin)) {
|
|
432
445
|
found = true;
|
|
433
446
|
}
|
|
434
447
|
}
|
|
@@ -442,7 +455,23 @@ export class EntityManager {
|
|
|
442
455
|
const prop = meta?.properties[field];
|
|
443
456
|
if (prop && !ref) {
|
|
444
457
|
hint.children ??= [];
|
|
445
|
-
|
|
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
|
+
}
|
|
446
475
|
}
|
|
447
476
|
}
|
|
448
477
|
}
|
|
@@ -494,10 +523,6 @@ export class EntityManager {
|
|
|
494
523
|
const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
|
|
495
524
|
return conds.length > 1 ? { $and: conds } : conds[0];
|
|
496
525
|
}
|
|
497
|
-
/**
|
|
498
|
-
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
|
499
|
-
* where the first element is the array of entities, and the second is the count.
|
|
500
|
-
*/
|
|
501
526
|
async findAndCount(entityName, where, options = {}) {
|
|
502
527
|
const em = this.getContext(false);
|
|
503
528
|
await em.tryFlush(entityName, options);
|
|
@@ -631,9 +656,6 @@ export class EntityManager {
|
|
|
631
656
|
}
|
|
632
657
|
return entity;
|
|
633
658
|
}
|
|
634
|
-
/**
|
|
635
|
-
* Finds first entity matching your `where` query.
|
|
636
|
-
*/
|
|
637
659
|
async findOne(entityName, where, options = {}) {
|
|
638
660
|
if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
|
|
639
661
|
const em = this.getContext(false);
|
|
@@ -653,10 +675,11 @@ export class EntityManager {
|
|
|
653
675
|
}
|
|
654
676
|
await em.tryFlush(entityName, options);
|
|
655
677
|
const meta = em.metadata.get(entityName);
|
|
678
|
+
em.validateIndexUsage(meta, where, options);
|
|
656
679
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
657
680
|
validateEmptyWhere(where);
|
|
658
681
|
em.checkLockRequirements(options.lockMode, meta);
|
|
659
|
-
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
|
|
682
|
+
const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.NONE || options.lockMode === LockMode.OPTIMISTIC;
|
|
660
683
|
if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
|
|
661
684
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
662
685
|
}
|
|
@@ -701,12 +724,6 @@ export class EntityManager {
|
|
|
701
724
|
await em.storeCache(options.cache, cached, () => helper(entity).toPOJO());
|
|
702
725
|
return entity;
|
|
703
726
|
}
|
|
704
|
-
/**
|
|
705
|
-
* Finds first entity matching your `where` query. If nothing found, it will throw an error.
|
|
706
|
-
* If the `strict` option is specified and nothing is found or more than one matching entity is found, it will throw an error.
|
|
707
|
-
* You can override the factory for creating this method via `options.failHandler` locally
|
|
708
|
-
* or via `Configuration.findOneOrFailHandler` (`findExactlyOneOrFailHandler` when specifying `strict`) globally.
|
|
709
|
-
*/
|
|
710
727
|
async findOneOrFail(entityName, where, options = {}) {
|
|
711
728
|
let entity;
|
|
712
729
|
let isStrictViolation = false;
|
|
@@ -803,7 +820,10 @@ export class EntityManager {
|
|
|
803
820
|
data = QueryHelper.processObjectParams(data);
|
|
804
821
|
validateParams(data, 'insert data');
|
|
805
822
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
806
|
-
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
|
+
}
|
|
807
827
|
}
|
|
808
828
|
const ret = await em.driver.nativeUpdate(entityName, where, data, {
|
|
809
829
|
ctx: em.#transactionContext,
|
|
@@ -852,6 +872,7 @@ export class EntityManager {
|
|
|
852
872
|
em.getHydrator().hydrate(entity, meta, data2, em.#entityFactory, 'full', false, true);
|
|
853
873
|
}
|
|
854
874
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
875
|
+
resetUntouchedCollections(meta, entity);
|
|
855
876
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
856
877
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
857
878
|
if (em.eventManager.hasListeners(EventType.afterUpsert, meta)) {
|
|
@@ -918,6 +939,7 @@ export class EntityManager {
|
|
|
918
939
|
const allWhere = [];
|
|
919
940
|
const entities = new Map();
|
|
920
941
|
const entitiesByData = new Map();
|
|
942
|
+
const entitiesByAllDataIdx = new Map();
|
|
921
943
|
for (let i = 0; i < data.length; i++) {
|
|
922
944
|
let row = data[i];
|
|
923
945
|
let where;
|
|
@@ -931,6 +953,7 @@ export class EntityManager {
|
|
|
931
953
|
}
|
|
932
954
|
where = helper(entity).getPrimaryKey();
|
|
933
955
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
956
|
+
entitiesByAllDataIdx.set(allData.length, entity);
|
|
934
957
|
row = em.#comparator.prepareEntity(entity);
|
|
935
958
|
}
|
|
936
959
|
else {
|
|
@@ -978,6 +1001,9 @@ export class EntityManager {
|
|
|
978
1001
|
const entity = entitiesByData.get(dto) ?? dto;
|
|
979
1002
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity, em, meta }, meta);
|
|
980
1003
|
}
|
|
1004
|
+
for (const [idx, entity] of entitiesByAllDataIdx) {
|
|
1005
|
+
allData[idx] = em.#comparator.prepareEntity(entity);
|
|
1006
|
+
}
|
|
981
1007
|
}
|
|
982
1008
|
const res = await em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
983
1009
|
ctx: em.#transactionContext,
|
|
@@ -1078,6 +1104,7 @@ export class EntityManager {
|
|
|
1078
1104
|
}
|
|
1079
1105
|
for (const [entity] of entities) {
|
|
1080
1106
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
1107
|
+
resetUntouchedCollections(meta, entity);
|
|
1081
1108
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
1082
1109
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
1083
1110
|
}
|
|
@@ -1171,6 +1198,7 @@ export class EntityManager {
|
|
|
1171
1198
|
*/
|
|
1172
1199
|
async lock(entity, lockMode, options = {}) {
|
|
1173
1200
|
options = Utils.isPlainObject(options) ? options : { lockVersion: options };
|
|
1201
|
+
this.getContext(false).prepareOptions(options);
|
|
1174
1202
|
await this.getUnitOfWork().lock(entity, { lockMode, ...options });
|
|
1175
1203
|
}
|
|
1176
1204
|
/**
|
|
@@ -1192,9 +1220,6 @@ export class EntityManager {
|
|
|
1192
1220
|
helper(data).setSchema(options.schema);
|
|
1193
1221
|
}
|
|
1194
1222
|
if (!helper(data).__managed) {
|
|
1195
|
-
// the entity might have been created via `em.create()`, which adds it to the persist stack automatically
|
|
1196
|
-
em.#unitOfWork.getPersistStack().delete(data);
|
|
1197
|
-
// it can be also in the identity map if it had a PK value already
|
|
1198
1223
|
em.#unitOfWork.unsetIdentity(data);
|
|
1199
1224
|
}
|
|
1200
1225
|
const meta = helper(data).__meta;
|
|
@@ -1283,9 +1308,6 @@ export class EntityManager {
|
|
|
1283
1308
|
helper(row).setSchema(options.schema);
|
|
1284
1309
|
}
|
|
1285
1310
|
if (!helper(row).__managed) {
|
|
1286
|
-
// the entity might have been created via `em.create()`, which adds it to the persist stack automatically
|
|
1287
|
-
em.#unitOfWork.getPersistStack().delete(row);
|
|
1288
|
-
// it can be also in the identity map if it had a PK value already
|
|
1289
1311
|
em.#unitOfWork.unsetIdentity(row);
|
|
1290
1312
|
}
|
|
1291
1313
|
const payload = em.#comparator.prepareEntity(row);
|
|
@@ -1471,6 +1493,29 @@ export class EntityManager {
|
|
|
1471
1493
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1472
1494
|
return +count;
|
|
1473
1495
|
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Counts entities grouped by one or more properties. Returns a dictionary keyed by the grouped
|
|
1498
|
+
* field value(s), with counts as values. For composite `groupBy`, keys are joined with `~~~`.
|
|
1499
|
+
*
|
|
1500
|
+
* SQL drivers issue a single `GROUP BY` query; MongoDB uses an aggregation pipeline.
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* ```ts
|
|
1504
|
+
* // Count books per author
|
|
1505
|
+
* const counts = await em.countBy(Book, 'author');
|
|
1506
|
+
* // { '1': 2, '2': 1, '3': 3 }
|
|
1507
|
+
*
|
|
1508
|
+
* // Count with a filter
|
|
1509
|
+
* const counts = await em.countBy(Book, 'author', { where: { active: true } });
|
|
1510
|
+
*
|
|
1511
|
+
* // Composite groupBy — keys joined with ~~~
|
|
1512
|
+
* const counts = await em.countBy(Order, ['status', 'country']);
|
|
1513
|
+
* // { 'pending~~~US': 5, 'shipped~~~DE': 3 }
|
|
1514
|
+
* ```
|
|
1515
|
+
*/
|
|
1516
|
+
async countBy(entityName, groupBy, options) {
|
|
1517
|
+
throw new Error(`${this.constructor.name}.countBy() is not supported by the current driver`);
|
|
1518
|
+
}
|
|
1474
1519
|
/**
|
|
1475
1520
|
* Tells the EntityManager to make an instance managed and persistent.
|
|
1476
1521
|
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
|
|
@@ -1576,7 +1621,7 @@ export class EntityManager {
|
|
|
1576
1621
|
const em = this.getContext();
|
|
1577
1622
|
em.prepareOptions(options);
|
|
1578
1623
|
const entityName = arr[0].constructor;
|
|
1579
|
-
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
|
|
1624
|
+
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters, populateHints: options.populateHints }, options.validate);
|
|
1580
1625
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
1581
1626
|
return entities;
|
|
1582
1627
|
}
|
|
@@ -1609,6 +1654,8 @@ export class EntityManager {
|
|
|
1609
1654
|
fork.#filterParams = Utils.copy(em.#filterParams);
|
|
1610
1655
|
fork.loggerContext = Utils.merge({}, em.loggerContext, options.loggerContext);
|
|
1611
1656
|
fork.#schema = options.schema ?? em.#schema;
|
|
1657
|
+
fork.signal = options.signal ?? em.signal;
|
|
1658
|
+
fork.inflightQueryAbortStrategy = options.inflightQueryAbortStrategy ?? em.inflightQueryAbortStrategy;
|
|
1612
1659
|
if (!options.clear) {
|
|
1613
1660
|
for (const entity of em.#unitOfWork.getIdentityMap()) {
|
|
1614
1661
|
fork.#unitOfWork.register(entity);
|
|
@@ -1684,6 +1731,22 @@ export class EntityManager {
|
|
|
1684
1731
|
getTransactionContext() {
|
|
1685
1732
|
return this.getContext(false).#transactionContext;
|
|
1686
1733
|
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Returns the cancellation defaults configured on this EntityManager (via `em.fork({ signal })`
|
|
1736
|
+
* or inherited from a transactional fork). Returns `undefined` when no signal is set.
|
|
1737
|
+
*
|
|
1738
|
+
* @internal — exposed for subclass drivers and `UnitOfWork`; not part of the public API.
|
|
1739
|
+
*/
|
|
1740
|
+
getAbortOptions() {
|
|
1741
|
+
const em = this.getContext(false);
|
|
1742
|
+
if (em.signal == null && em.inflightQueryAbortStrategy == null) {
|
|
1743
|
+
return undefined;
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
signal: em.signal,
|
|
1747
|
+
inflightQueryAbortStrategy: em.inflightQueryAbortStrategy,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1687
1750
|
/**
|
|
1688
1751
|
* Sets the transaction context.
|
|
1689
1752
|
*/
|
|
@@ -1727,6 +1790,79 @@ export class EntityManager {
|
|
|
1727
1790
|
throw ValidationError.transactionRequired();
|
|
1728
1791
|
}
|
|
1729
1792
|
}
|
|
1793
|
+
validateIndexUsage(meta, where, options) {
|
|
1794
|
+
if (!options.using) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const indexNames = Utils.asArray(options.using);
|
|
1798
|
+
const allIndexes = [...meta.indexes, ...meta.uniques];
|
|
1799
|
+
const indexMap = new Map();
|
|
1800
|
+
for (const idx of allIndexes) {
|
|
1801
|
+
if (idx.name) {
|
|
1802
|
+
indexMap.set(idx.name, Utils.asArray(idx.properties ?? []));
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
for (const prop of meta.props) {
|
|
1806
|
+
if (typeof prop.index === 'string') {
|
|
1807
|
+
indexMap.set(prop.index, [prop.name]);
|
|
1808
|
+
}
|
|
1809
|
+
if (typeof prop.unique === 'string') {
|
|
1810
|
+
indexMap.set(prop.unique, [prop.name]);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
const allowedProps = new Set();
|
|
1814
|
+
for (const name of indexNames) {
|
|
1815
|
+
const props = indexMap.get(name);
|
|
1816
|
+
if (!props) {
|
|
1817
|
+
const available = [...indexMap.keys()];
|
|
1818
|
+
throw new Error(`Index '${name}' not found on entity '${meta.className}'. ` +
|
|
1819
|
+
(available.length > 0 ? `Available indexes: ${available.join(', ')}` : 'No named indexes defined.'));
|
|
1820
|
+
}
|
|
1821
|
+
for (const prop of props) {
|
|
1822
|
+
allowedProps.add(prop);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
this.validateWhereKeysForIndex(where, allowedProps, indexNames);
|
|
1826
|
+
if (options.orderBy) {
|
|
1827
|
+
const orderMaps = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
1828
|
+
for (const orderMap of orderMaps) {
|
|
1829
|
+
for (const key of Object.keys(orderMap)) {
|
|
1830
|
+
if (!allowedProps.has(key)) {
|
|
1831
|
+
throw new Error(`Property '${key}' in orderBy is not covered by index '${indexNames.join("', '")}'. ` +
|
|
1832
|
+
`Allowed properties: ${[...allowedProps].join(', ')}`);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
validateWhereKeysForIndex(where, allowedProps, indexNames) {
|
|
1839
|
+
if (typeof where !== 'object' || where === null) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (Array.isArray(where)) {
|
|
1843
|
+
for (const item of where) {
|
|
1844
|
+
this.validateWhereKeysForIndex(item, allowedProps, indexNames);
|
|
1845
|
+
}
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
for (const key of Object.keys(where)) {
|
|
1849
|
+
if (key === '$and' || key === '$or') {
|
|
1850
|
+
for (const item of Utils.asArray(where[key])) {
|
|
1851
|
+
this.validateWhereKeysForIndex(item, allowedProps, indexNames);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
else if (key === '$not') {
|
|
1855
|
+
this.validateWhereKeysForIndex(where[key], allowedProps, indexNames);
|
|
1856
|
+
}
|
|
1857
|
+
else if (key.startsWith('$')) {
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
else if (!allowedProps.has(key)) {
|
|
1861
|
+
throw new Error(`Property '${key}' in where clause is not covered by index '${indexNames.join("', '")}'. ` +
|
|
1862
|
+
`Allowed properties: ${[...allowedProps].join(', ')}`);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1730
1866
|
async lockAndPopulate(meta, entity, where, options) {
|
|
1731
1867
|
if (!meta.virtual && options.lockMode === LockMode.OPTIMISTIC) {
|
|
1732
1868
|
await this.lock(entity, options.lockMode, {
|
|
@@ -1761,8 +1897,17 @@ export class EntityManager {
|
|
|
1761
1897
|
return [];
|
|
1762
1898
|
}
|
|
1763
1899
|
const meta = this.metadata.find(entityName);
|
|
1764
|
-
// infer populate
|
|
1765
|
-
|
|
1900
|
+
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set.
|
|
1901
|
+
// `populateArray` is only kept when the entries are user-provided strings — when this method runs a
|
|
1902
|
+
// second time (e.g. from `lockAndPopulate`) the populate is already normalized and merging would be a no-op.
|
|
1903
|
+
const fields = options.fields ? this.buildFields(options.fields) : undefined;
|
|
1904
|
+
const populateArray = Array.isArray(options.populate) && options.populate.every(p => typeof p === 'string')
|
|
1905
|
+
? options.populate
|
|
1906
|
+
: undefined;
|
|
1907
|
+
const hasNestedFields = fields?.some(f => f.includes('.')) ?? false;
|
|
1908
|
+
const shouldInfer = fields !== undefined && options.populate === undefined;
|
|
1909
|
+
const shouldMerge = fields !== undefined && populateArray !== undefined && populateArray.length > 0 && hasNestedFields;
|
|
1910
|
+
if (shouldInfer || shouldMerge) {
|
|
1766
1911
|
// we need to prune the `populate` hint from to-one relations, as partially loading them does not require their population, we want just the FK
|
|
1767
1912
|
const pruneToOneRelations = (meta, fields) => {
|
|
1768
1913
|
const ret = [];
|
|
@@ -1796,7 +1941,19 @@ export class EntityManager {
|
|
|
1796
1941
|
}
|
|
1797
1942
|
return Utils.unique(ret);
|
|
1798
1943
|
};
|
|
1799
|
-
|
|
1944
|
+
const fromFields = pruneToOneRelations(meta, fields);
|
|
1945
|
+
if (shouldInfer) {
|
|
1946
|
+
options.populate = fromFields;
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
// with an explicit populate, only nested paths (e.g. `item.id`) need to be merged in — they
|
|
1950
|
+
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver.
|
|
1951
|
+
// skip paths already in user's `populate` (regardless of `:ref` modifier) to avoid producing
|
|
1952
|
+
// both a `:ref` and a non-`:ref` entry for the same relation
|
|
1953
|
+
const existing = new Set(populateArray.map(p => p.split(':')[0]));
|
|
1954
|
+
const extras = fromFields.filter(f => f.includes('.') && !existing.has(f));
|
|
1955
|
+
options.populate = [...populateArray, ...extras];
|
|
1956
|
+
}
|
|
1800
1957
|
}
|
|
1801
1958
|
if (!options.populate) {
|
|
1802
1959
|
const populate = this.#entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
|
|
@@ -1836,9 +1993,21 @@ export class EntityManager {
|
|
|
1836
1993
|
}
|
|
1837
1994
|
if (options.populateHints) {
|
|
1838
1995
|
applyPopulateHints(populate, options.populateHints);
|
|
1996
|
+
this.forceSelectInForLimitedPopulate(populate);
|
|
1839
1997
|
}
|
|
1840
1998
|
return populate;
|
|
1841
1999
|
}
|
|
2000
|
+
/** Force SELECT_IN strategy on populate entries with `limit`, since JOINED cannot do per-parent limiting. */
|
|
2001
|
+
forceSelectInForLimitedPopulate(populate) {
|
|
2002
|
+
for (const entry of populate) {
|
|
2003
|
+
if (entry.limit != null) {
|
|
2004
|
+
entry.strategy = LoadStrategy.SELECT_IN;
|
|
2005
|
+
}
|
|
2006
|
+
if (entry.children?.length) {
|
|
2007
|
+
this.forceSelectInForLimitedPopulate(entry.children);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1842
2011
|
/**
|
|
1843
2012
|
* when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
|
|
1844
2013
|
* some additional lazy properties, if so, we reload and merge the data from database
|
|
@@ -1870,6 +2039,8 @@ export class EntityManager {
|
|
|
1870
2039
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
1871
2040
|
}
|
|
1872
2041
|
options.schema ??= this.#schema;
|
|
2042
|
+
options.signal ??= this.signal;
|
|
2043
|
+
options.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
|
|
1873
2044
|
options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
|
|
1874
2045
|
}
|
|
1875
2046
|
/**
|
|
@@ -1878,7 +2049,15 @@ export class EntityManager {
|
|
|
1878
2049
|
cacheKey(entityName, options, method, where) {
|
|
1879
2050
|
const { ...opts } = options;
|
|
1880
2051
|
// ignore some irrelevant options, e.g. logger context can contain dynamic data for the same query
|
|
1881
|
-
for (const k of [
|
|
2052
|
+
for (const k of [
|
|
2053
|
+
'ctx',
|
|
2054
|
+
'strategy',
|
|
2055
|
+
'flushMode',
|
|
2056
|
+
'logging',
|
|
2057
|
+
'loggerContext',
|
|
2058
|
+
'signal',
|
|
2059
|
+
'inflightQueryAbortStrategy',
|
|
2060
|
+
]) {
|
|
1882
2061
|
delete opts[k];
|
|
1883
2062
|
}
|
|
1884
2063
|
return [Utils.className(entityName), method, opts, where];
|
|
@@ -1971,6 +2150,8 @@ export class EntityManager {
|
|
|
1971
2150
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
1972
2151
|
case 'm:n':
|
|
1973
2152
|
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
2153
|
+
case 'count':
|
|
2154
|
+
return (em.#loaders[type] ??= new DataLoader(DataloaderUtils.getCountBatchLoadFn(em)));
|
|
1974
2155
|
}
|
|
1975
2156
|
}
|
|
1976
2157
|
/**
|
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
|
|
@@ -104,3 +104,32 @@ export interface ConnectionConfig {
|
|
|
104
104
|
}
|
|
105
105
|
/** Opaque transaction context type, wrapping the driver-specific transaction object. */
|
|
106
106
|
export type Transaction<T = any> = T & {};
|
|
107
|
+
/**
|
|
108
|
+
* Strategy applied when an `AbortSignal` fires while a query is in flight.
|
|
109
|
+
*
|
|
110
|
+
* - `'ignore query'` — stop awaiting; the query keeps running on the server until it settles
|
|
111
|
+
* (the connection returns to the pool only when the database replies).
|
|
112
|
+
* - `'cancel query'` — ask the database to cancel the running query (e.g. `pg_cancel_backend`,
|
|
113
|
+
* `KILL QUERY`). Falls back to `'ignore query'` if the dialect cannot cancel.
|
|
114
|
+
* Most engines do not cancel writes; partial commits are possible.
|
|
115
|
+
* - `'kill session'` — terminate the database session/process the query runs in
|
|
116
|
+
* (`pg_terminate_backend` etc.). Falls back to `'cancel query'` if not supported.
|
|
117
|
+
*
|
|
118
|
+
* Default: `'ignore query'`.
|
|
119
|
+
*
|
|
120
|
+
* **Streaming queries (`em.stream()` / `qb.stream()`):** the strategy is silently treated as
|
|
121
|
+
* `'ignore query'` because the underlying driver only accepts a plain `AbortSignal` for
|
|
122
|
+
* streamed reads — there is no server-side cancel for an open cursor. The MongoDB driver also
|
|
123
|
+
* has no notion of strategies; only the signal is honored there.
|
|
124
|
+
*/
|
|
125
|
+
export type InflightQueryAbortStrategy = 'ignore query' | 'cancel query' | 'kill session';
|
|
126
|
+
/** Per-query cancellation controls forwarded to the underlying driver. */
|
|
127
|
+
export interface AbortQueryOptions {
|
|
128
|
+
/** AbortSignal that cancels the query when fired. */
|
|
129
|
+
signal?: AbortSignal;
|
|
130
|
+
/**
|
|
131
|
+
* Strategy used when the signal fires while the query is in flight. See
|
|
132
|
+
* {@apilink InflightQueryAbortStrategy} for caveats around streams and MongoDB.
|
|
133
|
+
*/
|
|
134
|
+
inflightQueryAbortStrategy?: InflightQueryAbortStrategy;
|
|
135
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ConnectionType, Constructor, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes } from '../typings.js';
|
|
2
|
-
import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
|
|
1
|
+
import type { ConnectionType, Constructor, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes, IndexName } from '../typings.js';
|
|
2
|
+
import type { AbortQueryOptions, Connection, QueryResult, Transaction } from '../connections/Connection.js';
|
|
3
3
|
import type { FlushMode, LockMode, QueryOrderMap, QueryFlag, LoadStrategy, PopulateHint, PopulatePath } from '../enums.js';
|
|
4
4
|
import type { Platform } from '../platforms/Platform.js';
|
|
5
5
|
import type { MetadataStorage } from '../metadata/MetadataStorage.js';
|
|
@@ -110,6 +110,20 @@ export interface FindAllOptions<T, P extends string = never, F extends string =
|
|
|
110
110
|
}
|
|
111
111
|
/** Options for streaming query results via `em.stream()`. */
|
|
112
112
|
export interface StreamOptions<Entity, Populate extends string = never, Fields extends string = never, Exclude extends string = never> extends Omit<FindAllOptions<Entity, Populate, Fields, Exclude>, 'cache' | 'before' | 'after' | 'first' | 'last' | 'overfetch' | 'strategy'> {
|
|
113
|
+
/**
|
|
114
|
+
* How many rows to fetch in one round-trip.
|
|
115
|
+
* Lower values will result in more queries and network bandwidth, but less memory usage.
|
|
116
|
+
* Higher values will result in fewer queries and network bandwidth, but higher memory usage.
|
|
117
|
+
* Note that the results are iterated one row at a time regardless of this value.
|
|
118
|
+
*
|
|
119
|
+
* Honored on PostgreSQL (cursor-based fetch), MSSQL (tedious stream chunk size),
|
|
120
|
+
* Oracle (mapped to `fetchArraySize`) and MongoDB (mapped to `batchSize`). Ignored
|
|
121
|
+
* on MySQL, MariaDB, SQLite and libSQL, where the underlying driver already streams
|
|
122
|
+
* row-by-row with no batching knob.
|
|
123
|
+
*
|
|
124
|
+
* @default 100 (on dialects that honor it)
|
|
125
|
+
*/
|
|
126
|
+
chunkSize?: number;
|
|
113
127
|
/**
|
|
114
128
|
* When populating to-many relations, the ORM streams fully merged entities instead of yielding every row.
|
|
115
129
|
* You can opt out of this behavior by specifying `mergeResults: false`. This will yield every row from
|
|
@@ -130,7 +144,7 @@ export interface LoadHint<Entity, Hint extends string = never, Fields extends st
|
|
|
130
144
|
exclude?: readonly AutoPath<Entity, Excludes>[];
|
|
131
145
|
}
|
|
132
146
|
/** Options for `em.find()` queries, including population, ordering, pagination, and locking. */
|
|
133
|
-
export interface FindOptions<Entity, Hint extends string = never, Fields extends string = never, Excludes extends string = never> extends LoadHint<Entity, Hint, Fields, Excludes
|
|
147
|
+
export interface FindOptions<Entity, Hint extends string = never, Fields extends string = never, Excludes extends string = never> extends LoadHint<Entity, Hint, Fields, Excludes>, AbortQueryOptions {
|
|
134
148
|
/**
|
|
135
149
|
* Where condition for populated relations. This will have no effect on the root entity.
|
|
136
150
|
* With `select-in` strategy, this is applied only to the populate queries.
|
|
@@ -216,6 +230,20 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
216
230
|
connectionType?: ConnectionType;
|
|
217
231
|
/** SQL: appended to FROM clause (e.g. `'force index(my_index)'`); MongoDB: index name or spec passed as `hint`. */
|
|
218
232
|
indexHint?: string | Dictionary;
|
|
233
|
+
/**
|
|
234
|
+
* Named index(es) for this query. When provided:
|
|
235
|
+
* - Validates that `where` and `orderBy` only reference columns covered by the specified index(es).
|
|
236
|
+
* - Emits SQL index hints where supported (MySQL/MariaDB: `USE INDEX`, MSSQL: `WITH (INDEX(...))`).
|
|
237
|
+
* - If `indexHint` is also set, `indexHint` takes precedence for SQL generation.
|
|
238
|
+
*
|
|
239
|
+
* Accepts a single index name or an array. For `defineEntity` entities with named indexes
|
|
240
|
+
* and decorator entities with `[IndexHints]`, index names are autocompleted.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* await em.find(Book, { title: 'foo' }, { using: 'idx_book_title' });
|
|
244
|
+
* await em.find(Book, { title: 'foo', author: 1 }, { using: ['idx_book_title', 'idx_book_author'] });
|
|
245
|
+
*/
|
|
246
|
+
using?: IndexName<Entity> | IndexName<Entity>[];
|
|
219
247
|
/** sql only */
|
|
220
248
|
comments?: string | string[];
|
|
221
249
|
/** sql only */
|
|
@@ -246,7 +274,7 @@ export interface FindOneOrFailOptions<T extends object, P extends string = never
|
|
|
246
274
|
strict?: boolean;
|
|
247
275
|
}
|
|
248
276
|
/** Options for native insert and update operations. */
|
|
249
|
-
export interface NativeInsertUpdateOptions<T> {
|
|
277
|
+
export interface NativeInsertUpdateOptions<T> extends AbortQueryOptions {
|
|
250
278
|
convertCustomTypes?: boolean;
|
|
251
279
|
ctx?: Transaction;
|
|
252
280
|
schema?: string;
|
|
@@ -280,7 +308,7 @@ export interface UpsertManyOptions<Entity, Fields extends string = never> extend
|
|
|
280
308
|
batchSize?: number;
|
|
281
309
|
}
|
|
282
310
|
/** Options for `em.count()` queries. */
|
|
283
|
-
export interface CountOptions<T extends object, P extends string = never> {
|
|
311
|
+
export interface CountOptions<T extends object, P extends string = never> extends AbortQueryOptions {
|
|
284
312
|
filters?: FilterOptions;
|
|
285
313
|
schema?: string;
|
|
286
314
|
groupBy?: string | readonly string[];
|
|
@@ -311,8 +339,18 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
311
339
|
/** @internal used to apply filters to the auto-joined relations */
|
|
312
340
|
em?: EntityManager;
|
|
313
341
|
}
|
|
342
|
+
/** Options for `em.countBy()` queries. */
|
|
343
|
+
export interface CountByOptions<T extends object> {
|
|
344
|
+
where?: FilterQuery<T>;
|
|
345
|
+
filters?: FilterOptions;
|
|
346
|
+
having?: FilterQuery<T>;
|
|
347
|
+
schema?: string;
|
|
348
|
+
flushMode?: FlushMode | `${FlushMode}`;
|
|
349
|
+
loggerContext?: LogContext;
|
|
350
|
+
logging?: LoggingOptions;
|
|
351
|
+
}
|
|
314
352
|
/** Options for `em.qb().update()` operations. */
|
|
315
|
-
export interface UpdateOptions<T> {
|
|
353
|
+
export interface UpdateOptions<T> extends AbortQueryOptions {
|
|
316
354
|
filters?: FilterOptions;
|
|
317
355
|
schema?: string;
|
|
318
356
|
ctx?: Transaction;
|
|
@@ -351,7 +389,7 @@ export interface LockOptions extends DriverMethodOptions {
|
|
|
351
389
|
logging?: LoggingOptions;
|
|
352
390
|
}
|
|
353
391
|
/** Base options shared by all driver methods (transaction context, schema, logging). */
|
|
354
|
-
export interface DriverMethodOptions {
|
|
392
|
+
export interface DriverMethodOptions extends AbortQueryOptions {
|
|
355
393
|
ctx?: Transaction;
|
|
356
394
|
schema?: string;
|
|
357
395
|
loggerContext?: LogContext;
|