@mikro-orm/core 7.1.0-dev.5 → 7.1.0-dev.50
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 +87 -12
- package/EntityManager.js +252 -40
- package/README.md +2 -1
- package/connections/Connection.d.ts +67 -1
- package/connections/Connection.js +64 -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 +5 -3
- package/entity/EntityLoader.d.ts +8 -1
- package/entity/EntityLoader.js +103 -30
- 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 +101 -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 +18 -0
- package/metadata/MetadataDiscovery.d.ts +12 -0
- package/metadata/MetadataDiscovery.js +173 -22
- package/metadata/MetadataValidator.d.ts +26 -0
- package/metadata/MetadataValidator.js +237 -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 +78 -9
- 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 +4 -3
- package/typings.d.ts +399 -22
- package/typings.js +43 -12
- package/unit-of-work/UnitOfWork.js +43 -5
- 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 +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/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/entity/EntityLoader.js
CHANGED
|
@@ -177,8 +177,9 @@ export class EntityLoader {
|
|
|
177
177
|
Utils.isObject(orderBy[prop.name]))
|
|
178
178
|
.flatMap(orderBy => orderBy[prop.name]);
|
|
179
179
|
const where = await this.extractChildCondition(options, prop);
|
|
180
|
+
const mergedOrderBy = populate.orderBy ? Utils.asArray(populate.orderBy) : innerOrderBy;
|
|
180
181
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
|
|
181
|
-
const pivotOrderBy = QueryHelper.mergeOrderBy(
|
|
182
|
+
const pivotOrderBy = QueryHelper.mergeOrderBy(mergedOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
|
|
182
183
|
const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
|
|
183
184
|
return Utils.flatten(res);
|
|
184
185
|
}
|
|
@@ -188,10 +189,12 @@ export class EntityLoader {
|
|
|
188
189
|
const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
|
|
189
190
|
...options,
|
|
190
191
|
where,
|
|
191
|
-
orderBy:
|
|
192
|
+
orderBy: mergedOrderBy,
|
|
192
193
|
}, !!(ref || prop.mapToPk));
|
|
193
|
-
const
|
|
194
|
-
|
|
194
|
+
const hasLimit = populate.limit != null;
|
|
195
|
+
const isPartial = partial || hasLimit;
|
|
196
|
+
const customOrder = mergedOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
|
|
197
|
+
this.initializeCollections(filtered, prop, field, items, customOrder, isPartial, hasLimit);
|
|
195
198
|
return items;
|
|
196
199
|
}
|
|
197
200
|
async populateScalar(meta, filtered, options) {
|
|
@@ -269,15 +272,15 @@ export class EntityLoader {
|
|
|
269
272
|
}));
|
|
270
273
|
return allItems;
|
|
271
274
|
}
|
|
272
|
-
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
|
275
|
+
initializeCollections(filtered, prop, field, children, customOrder, partial, readonly) {
|
|
273
276
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
274
|
-
this.initializeOneToMany(filtered, children, prop, field, partial);
|
|
277
|
+
this.initializeOneToMany(filtered, children, prop, field, partial, readonly);
|
|
275
278
|
}
|
|
276
279
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
|
|
277
|
-
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
|
280
|
+
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial, readonly);
|
|
278
281
|
}
|
|
279
282
|
}
|
|
280
|
-
initializeOneToMany(filtered, children, prop, field, partial) {
|
|
283
|
+
initializeOneToMany(filtered, children, prop, field, partial, readonly) {
|
|
281
284
|
const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
|
|
282
285
|
const map = {};
|
|
283
286
|
for (const entity of filtered) {
|
|
@@ -293,14 +296,14 @@ export class EntityLoader {
|
|
|
293
296
|
}
|
|
294
297
|
for (const entity of filtered) {
|
|
295
298
|
const key = helper(entity).getSerializedPrimaryKey();
|
|
296
|
-
entity[field].hydrate(map[key], undefined, partial);
|
|
299
|
+
entity[field].hydrate(map[key], undefined, partial, readonly);
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
|
-
initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
|
|
302
|
+
initializeManyToMany(filtered, children, prop, field, customOrder, partial, readonly) {
|
|
300
303
|
if (prop.mappedBy) {
|
|
301
304
|
for (const entity of filtered) {
|
|
302
305
|
const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
|
|
303
|
-
entity[field].hydrate(items, true, partial);
|
|
306
|
+
entity[field].hydrate(items, true, partial, readonly);
|
|
304
307
|
}
|
|
305
308
|
}
|
|
306
309
|
else {
|
|
@@ -311,12 +314,12 @@ export class EntityLoader {
|
|
|
311
314
|
if (!customOrder) {
|
|
312
315
|
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
313
316
|
}
|
|
314
|
-
entity[field].hydrate(items, true, partial);
|
|
317
|
+
entity[field].hydrate(items, true, partial, readonly);
|
|
315
318
|
}
|
|
316
319
|
}
|
|
317
320
|
}
|
|
318
321
|
async findChildren(entities, prop, populate, options, ref) {
|
|
319
|
-
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
|
322
|
+
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref, populate));
|
|
320
323
|
const meta = prop.targetMeta;
|
|
321
324
|
// When targetKey is set, use it for FK lookup instead of the PK
|
|
322
325
|
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
@@ -334,10 +337,10 @@ export class EntityLoader {
|
|
|
334
337
|
fk = ownerProp.name;
|
|
335
338
|
}
|
|
336
339
|
}
|
|
337
|
-
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
|
|
340
|
+
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && (!ref || (!prop.mapToPk && !populate.filter))) {
|
|
338
341
|
children.length = 0;
|
|
339
342
|
fk = meta.properties[prop.mappedBy].name;
|
|
340
|
-
children.push(...this.filterByReferences(entities, prop
|
|
343
|
+
children.push(...this.filterByReferences(entities, prop, options, ref));
|
|
341
344
|
}
|
|
342
345
|
if (children.length === 0) {
|
|
343
346
|
return { items: [], partial };
|
|
@@ -366,8 +369,11 @@ export class EntityLoader {
|
|
|
366
369
|
}
|
|
367
370
|
const fields = this.buildFields(options.fields, prop, ref);
|
|
368
371
|
/* eslint-disable prefer-const */
|
|
369
|
-
let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere
|
|
372
|
+
let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging } = options;
|
|
370
373
|
/* eslint-enable prefer-const */
|
|
374
|
+
// `populateWhere` is typed as required via `Required<EntityLoaderOptions>`, but `em.populate(...)`
|
|
375
|
+
// forwards user-supplied options without filling it in — keep the runtime default.
|
|
376
|
+
populateWhere ??= 'infer';
|
|
371
377
|
if (typeof populateWhere === 'object') {
|
|
372
378
|
populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
|
|
373
379
|
}
|
|
@@ -375,7 +381,7 @@ export class EntityLoader {
|
|
|
375
381
|
where = { $and: [where, prop.where] };
|
|
376
382
|
}
|
|
377
383
|
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
|
378
|
-
const
|
|
384
|
+
const findOptions = {
|
|
379
385
|
filters,
|
|
380
386
|
convertCustomTypes,
|
|
381
387
|
lockMode,
|
|
@@ -390,11 +396,21 @@ export class EntityLoader {
|
|
|
390
396
|
fields,
|
|
391
397
|
schema,
|
|
392
398
|
connectionType,
|
|
399
|
+
signal: options.signal,
|
|
400
|
+
inflightQueryAbortStrategy: options.inflightQueryAbortStrategy,
|
|
393
401
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
394
402
|
refresh: refresh && !children.every(item => options.visited.has(item)),
|
|
395
403
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
396
404
|
visited: options.visited,
|
|
397
|
-
}
|
|
405
|
+
};
|
|
406
|
+
if (populate.limit != null) {
|
|
407
|
+
findOptions._partitionLimit = {
|
|
408
|
+
partitionBy: fk,
|
|
409
|
+
limit: populate.limit,
|
|
410
|
+
offset: populate.offset,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const items = await this.#em.find(meta.class, where, findOptions);
|
|
398
414
|
// For targetKey relations, wire up loaded entities to parent references
|
|
399
415
|
// This is needed because the references were created under alternate key,
|
|
400
416
|
// but loaded entities are stored under PK, so they don't automatically merge
|
|
@@ -582,11 +598,18 @@ export class EntityLoader {
|
|
|
582
598
|
const options2 = { ...options, fields, exclude, populateFilter };
|
|
583
599
|
['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
|
|
584
600
|
options2.populate = populate?.children ?? [];
|
|
601
|
+
if (populate?.limit != null) {
|
|
602
|
+
options2._partitionLimit = {
|
|
603
|
+
limit: populate.limit,
|
|
604
|
+
offset: populate.offset,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
585
607
|
if (!Utils.isEmpty(prop.where)) {
|
|
586
608
|
where = { $and: [where, prop.where] };
|
|
587
609
|
}
|
|
588
610
|
const map = await this.#driver.loadFromPivotTable(prop, ids, where, orderBy, this.#em.getTransactionContext(), options2, pivotJoin);
|
|
589
611
|
const children = [];
|
|
612
|
+
const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
|
|
590
613
|
for (let i = 0; i < filtered.length; i++) {
|
|
591
614
|
const entity = filtered[i];
|
|
592
615
|
const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
|
|
@@ -596,7 +619,11 @@ export class EntityLoader {
|
|
|
596
619
|
schema: options.schema ?? this.#em.config.get('schema'),
|
|
597
620
|
});
|
|
598
621
|
}
|
|
599
|
-
|
|
622
|
+
// Union-target items carry their concrete class via `constructor` — dispatch to the right factory call.
|
|
623
|
+
const targetClass = isUnionTargetMN && item.constructor !== Object
|
|
624
|
+
? item.constructor
|
|
625
|
+
: prop.targetMeta.class;
|
|
626
|
+
const entity = this.#em.getEntityFactory().create(targetClass, item, {
|
|
600
627
|
refresh,
|
|
601
628
|
merge: true,
|
|
602
629
|
convertCustomTypes: true,
|
|
@@ -604,7 +631,8 @@ export class EntityLoader {
|
|
|
604
631
|
});
|
|
605
632
|
return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
|
|
606
633
|
});
|
|
607
|
-
|
|
634
|
+
const hasLimit = populate?.limit != null;
|
|
635
|
+
entity[prop.name].hydrate(items, true, hasLimit, hasLimit);
|
|
608
636
|
children.push(items);
|
|
609
637
|
}
|
|
610
638
|
return children;
|
|
@@ -665,8 +693,11 @@ export class EntityLoader {
|
|
|
665
693
|
}
|
|
666
694
|
return ret;
|
|
667
695
|
}, []);
|
|
668
|
-
// we need to automatically select the FKs too, e.g. for 1:m
|
|
669
|
-
|
|
696
|
+
// we need to automatically select the FKs too, e.g. for 1:m and inverse 1:1 relations
|
|
697
|
+
// to be able to wire them with the items
|
|
698
|
+
if (prop.kind === ReferenceKind.ONE_TO_MANY ||
|
|
699
|
+
prop.kind === ReferenceKind.MANY_TO_MANY ||
|
|
700
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
670
701
|
const owner = prop.targetMeta.properties[prop.mappedBy];
|
|
671
702
|
// when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
|
|
672
703
|
// otherwise the driver will exclude it and we won't be able to map children to their parent collections
|
|
@@ -679,7 +710,7 @@ export class EntityLoader {
|
|
|
679
710
|
}
|
|
680
711
|
return ret;
|
|
681
712
|
}
|
|
682
|
-
getChildReferences(entities, prop, options, ref) {
|
|
713
|
+
getChildReferences(entities, prop, options, ref, populate) {
|
|
683
714
|
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
684
715
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
685
716
|
return filtered.map(e => e[prop.name].owner);
|
|
@@ -695,7 +726,7 @@ export class EntityLoader {
|
|
|
695
726
|
return filtered;
|
|
696
727
|
}
|
|
697
728
|
// MANY_TO_ONE or ONE_TO_ONE
|
|
698
|
-
return this.filterReferences(entities, prop.name, options, ref);
|
|
729
|
+
return this.filterReferences(entities, prop.name, options, ref, populate);
|
|
699
730
|
}
|
|
700
731
|
filterCollections(entities, field, options, ref) {
|
|
701
732
|
if (options.refresh) {
|
|
@@ -709,7 +740,16 @@ export class EntityLoader {
|
|
|
709
740
|
}
|
|
710
741
|
const wrapped = helper(entity);
|
|
711
742
|
if (!field.includes('.')) {
|
|
712
|
-
|
|
743
|
+
if (wrapped.__loadedProperties.has(field)) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
// Inlined embeddables track each leaf child separately, not the parent property,
|
|
747
|
+
// so we need to recurse for nested inline embeddables.
|
|
748
|
+
const prop = wrapped.__meta.properties[field];
|
|
749
|
+
if (prop?.kind === ReferenceKind.EMBEDDED && !prop.object) {
|
|
750
|
+
return Object.values(prop.embeddedProps).every(child => this.isPropertyLoaded(entity, child.name));
|
|
751
|
+
}
|
|
752
|
+
return false;
|
|
713
753
|
}
|
|
714
754
|
const [f, ...r] = field.split('.');
|
|
715
755
|
/* v8 ignore next */
|
|
@@ -721,7 +761,7 @@ export class EntityLoader {
|
|
|
721
761
|
}
|
|
722
762
|
return this.isPropertyLoaded(entity[f], r.join('.'));
|
|
723
763
|
}
|
|
724
|
-
filterReferences(entities, field, options, ref) {
|
|
764
|
+
filterReferences(entities, field, options, ref, populate) {
|
|
725
765
|
if (ref) {
|
|
726
766
|
return [];
|
|
727
767
|
}
|
|
@@ -730,13 +770,19 @@ export class EntityLoader {
|
|
|
730
770
|
return children.map(e => Reference.unwrapReference(e[field]));
|
|
731
771
|
}
|
|
732
772
|
if (options.fields) {
|
|
773
|
+
// `:ref` populate children are satisfied by FK/PK and will be loaded lazily — they don't force a parent reload
|
|
774
|
+
const refChildren = new Set((populate?.children ?? [])
|
|
775
|
+
.map(c => c.field.split(':'))
|
|
776
|
+
.filter(parts => parts[1] === 'ref')
|
|
777
|
+
.map(parts => parts[0]));
|
|
733
778
|
return children
|
|
734
779
|
.map(e => Reference.unwrapReference(e[field]))
|
|
735
780
|
.filter(target => {
|
|
736
781
|
const wrapped = helper(target);
|
|
737
782
|
const childFields = options.fields
|
|
738
783
|
.filter(f => f.startsWith(`${field}.`))
|
|
739
|
-
.map(f => f.substring(field.length + 1))
|
|
784
|
+
.map(f => f.substring(field.length + 1))
|
|
785
|
+
.filter(cf => !refChildren.has(cf.split('.')[0]));
|
|
740
786
|
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
|
741
787
|
});
|
|
742
788
|
}
|
|
@@ -744,12 +790,39 @@ export class EntityLoader {
|
|
|
744
790
|
.filter(e => !e[field].__helper.__initialized)
|
|
745
791
|
.map(e => Reference.unwrapReference(e[field]));
|
|
746
792
|
}
|
|
747
|
-
filterByReferences(entities,
|
|
793
|
+
filterByReferences(entities, prop, options, ref) {
|
|
748
794
|
/* v8 ignore next */
|
|
749
|
-
if (refresh) {
|
|
795
|
+
if (options.refresh) {
|
|
750
796
|
return entities;
|
|
751
797
|
}
|
|
752
|
-
|
|
798
|
+
const field = prop.name;
|
|
799
|
+
return entities.filter(e => {
|
|
800
|
+
const value = e[field];
|
|
801
|
+
if (value === null) {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
if (value === undefined || !Utils.isEntity(value, true)) {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
const target = Reference.unwrapReference(value);
|
|
808
|
+
const wrapped = helper(target);
|
|
809
|
+
if (!wrapped.__initialized) {
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
if (ref) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
if (options.fields) {
|
|
816
|
+
const childFields = options.fields
|
|
817
|
+
.filter(f => f.startsWith(`${prop.name}.`))
|
|
818
|
+
.map(f => f.substring(prop.name.length + 1));
|
|
819
|
+
return childFields.length > 0 && !childFields.every(f => this.isPropertyLoaded(target, f));
|
|
820
|
+
}
|
|
821
|
+
return prop.targetMeta.comparableProps.some(targetProp => {
|
|
822
|
+
const inlineEmbedded = targetProp.kind === ReferenceKind.EMBEDDED && !targetProp.object;
|
|
823
|
+
return !inlineEmbedded && !targetProp.lazy && !wrapped.__loadedProperties.has(targetProp.name);
|
|
824
|
+
});
|
|
825
|
+
});
|
|
753
826
|
}
|
|
754
827
|
lookupAllRelationships(entityName) {
|
|
755
828
|
const ret = [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PopulatePath } from '../enums.js';
|
|
2
2
|
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
|
|
3
3
|
import type { AssignOptions } from './EntityAssigner.js';
|
|
4
|
-
import type { EntityData,
|
|
5
|
-
import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
4
|
+
import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityName, FilterQuery, Loaded, Primary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement, IndexFilterQuery, WithUsingOptions } from '../typings.js';
|
|
5
|
+
import type { CountByOptions, CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
6
6
|
import type { EntityLoaderOptions } from './EntityLoader.js';
|
|
7
7
|
import type { Cursor } from '../utils/Cursor.js';
|
|
8
8
|
/** Repository class providing a type-safe API for querying and persisting a specific entity type. */
|
|
@@ -13,13 +13,17 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
13
13
|
/**
|
|
14
14
|
* Finds first entity matching your `where` query.
|
|
15
15
|
*/
|
|
16
|
-
findOne<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity
|
|
16
|
+
findOne<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOneOptions<Entity, Hint, Fields, Excludes> & {
|
|
17
|
+
using?: Using | Using[];
|
|
18
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
|
17
19
|
/**
|
|
18
20
|
* Finds first entity matching your `where` query. If nothing is found, it will throw an error.
|
|
19
21
|
* You can override the factory for creating this method via `options.failHandler` locally
|
|
20
22
|
* or via `Configuration.findOneOrFailHandler` globally.
|
|
21
23
|
*/
|
|
22
|
-
findOneOrFail<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes>
|
|
24
|
+
findOneOrFail<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes> & {
|
|
25
|
+
using?: Using | Using[];
|
|
26
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
23
27
|
/**
|
|
24
28
|
* Creates or updates the entity, based on whether it is already present in the database.
|
|
25
29
|
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
|
@@ -72,24 +76,28 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
72
76
|
/**
|
|
73
77
|
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
|
74
78
|
*/
|
|
75
|
-
find<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity>, options?: FindOptions<Entity, Hint, Fields, Excludes>
|
|
79
|
+
find<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
|
|
80
|
+
using?: Using | Using[];
|
|
81
|
+
}): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
76
82
|
/**
|
|
77
83
|
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
|
78
84
|
* where first element is the array of entities, and the second is the count.
|
|
79
85
|
*/
|
|
80
|
-
findAndCount<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(where: FilterQuery<Entity
|
|
86
|
+
findAndCount<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(where: [Using] extends [never] ? FilterQuery<Entity> : IndexFilterQuery<Entity, Using>, options?: FindOptions<Entity, Hint, Fields, Excludes> & {
|
|
87
|
+
using?: Using | Using[];
|
|
88
|
+
}): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
|
|
81
89
|
/**
|
|
82
90
|
* @inheritDoc EntityManager.findByCursor
|
|
83
91
|
*/
|
|
84
|
-
findByCursor<Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true>(options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
92
|
+
findByCursor<Hint extends string = never, Fields extends string = never, Excludes extends string = never, IncludeCount extends boolean = true, Using extends string = never>(options: WithUsingOptions<FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>, Entity, Using>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
85
93
|
/**
|
|
86
94
|
* Finds all entities of given type. You can pass additional options via the `options` parameter.
|
|
87
95
|
*/
|
|
88
|
-
findAll<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: FindAllOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
96
|
+
findAll<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(options?: WithUsingOptions<FindAllOptions<Entity, Hint, Fields, Excludes>, Entity, Using>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
89
97
|
/**
|
|
90
98
|
* @inheritDoc EntityManager.stream
|
|
91
99
|
*/
|
|
92
|
-
stream<Hint extends string = never, Fields extends string = never, Excludes extends string = never>(options?: StreamOptions<Entity, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
100
|
+
stream<Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(options?: WithUsingOptions<StreamOptions<Entity, Hint, Fields, Excludes>, Entity, Using>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
93
101
|
/**
|
|
94
102
|
* @inheritDoc EntityManager.insert
|
|
95
103
|
*/
|
|
@@ -199,6 +207,16 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
199
207
|
* Returns total number of entities matching your `where` query.
|
|
200
208
|
*/
|
|
201
209
|
count<Hint extends string = never>(where?: FilterQuery<Entity>, options?: CountOptions<Entity, Hint>): Promise<number>;
|
|
210
|
+
/**
|
|
211
|
+
* Counts entities grouped by one or more properties.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* const counts = await repo.countBy('status');
|
|
216
|
+
* // { 'active': 5, 'inactive': 2 }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
countBy(groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
|
|
202
220
|
/** Returns the entity class name associated with this repository. */
|
|
203
221
|
getEntityName(): string;
|
|
204
222
|
/**
|
|
@@ -194,6 +194,18 @@ export class EntityRepository {
|
|
|
194
194
|
async count(where = {}, options = {}) {
|
|
195
195
|
return this.getEntityManager().count(this.entityName, where, options);
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Counts entities grouped by one or more properties.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* const counts = await repo.countBy('status');
|
|
203
|
+
* // { 'active': 5, 'inactive': 2 }
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async countBy(groupBy, options) {
|
|
207
|
+
return this.getEntityManager().countBy(this.entityName, groupBy, options);
|
|
208
|
+
}
|
|
197
209
|
/** Returns the entity class name associated with this repository. */
|
|
198
210
|
getEntityName() {
|
|
199
211
|
return Utils.className(this.entityName);
|
package/entity/Reference.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
|
|
1
|
+
import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, LazyRef, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
|
|
2
2
|
import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
|
|
3
3
|
/** Wrapper around an entity that provides lazy loading capabilities and identity-preserving reference semantics. */
|
|
4
4
|
export declare class Reference<T extends object> {
|
|
@@ -98,6 +98,47 @@ export declare function ref<I extends unknown | Ref<unknown> | undefined | null,
|
|
|
98
98
|
* shortcut for `Reference.createFromPK(entityType, pk)`
|
|
99
99
|
*/
|
|
100
100
|
export declare function ref<I extends unknown | undefined | null, T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk: I): Ref<T> | AddOptional<typeof pk>;
|
|
101
|
+
/**
|
|
102
|
+
* Unwraps a reference to its underlying value, returning the entity or scalar. Works on any of:
|
|
103
|
+
*
|
|
104
|
+
* - `Ref<T>` / `Reference<T>` (entity) — calls `.unwrap()` to extract the wrapped entity, returns `T`.
|
|
105
|
+
* - `LazyRef<T>` — type-only marker; runtime value is already `T`, so returned as-is.
|
|
106
|
+
* - plain entity `T` — returned as-is.
|
|
107
|
+
* - `ScalarReference<V>` / `ScalarRef<V>` — calls `.unwrap()` to extract the scalar value, returns `V | undefined`
|
|
108
|
+
* (a scalar reference may be bound without an initial value).
|
|
109
|
+
*
|
|
110
|
+
* Inverse of the {@link ref} helper. Use as a typed escape hatch when you know a relation is populated
|
|
111
|
+
* but don't want to (or can't) thread `Loaded<T, 'relation'>` through a function signature.
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* function logAuthor(book: Book) {
|
|
115
|
+
* // `book.author` is typed as `LazyRef<Author>`, so `.name` is not directly accessible
|
|
116
|
+
* console.log(unref(book.author).name);
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* // Scalar reference:
|
|
120
|
+
* const scalar = ref('hello');
|
|
121
|
+
* unref(scalar); // 'hello' (type: string | undefined)
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* Note: `unref` is a compile-time narrowing only for entity refs. On a `LazyRef<T>` or plain entity
|
|
125
|
+
* relation that is a stub (PK only, never populated), reads of non-PK properties will still return
|
|
126
|
+
* `undefined` at runtime — same footgun as a plain non-`Ref` relation.
|
|
127
|
+
*/
|
|
128
|
+
export declare function unref<V>(value: ScalarReference<V>): V | undefined;
|
|
129
|
+
export declare function unref<T extends object>(value: Ref<T> | LazyRef<T> | T): T;
|
|
130
|
+
/**
|
|
131
|
+
* Unwraps a reference to its underlying value. Nullable variant — returns `null` unchanged.
|
|
132
|
+
*/
|
|
133
|
+
export declare function unref<T extends object>(value: Ref<T> | LazyRef<T> | T | null): T | null;
|
|
134
|
+
/**
|
|
135
|
+
* Unwraps a reference to its underlying value. Optional variant — returns `undefined` unchanged.
|
|
136
|
+
*/
|
|
137
|
+
export declare function unref<T extends object>(value: Ref<T> | LazyRef<T> | T | undefined): T | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Unwraps a reference to its underlying value. Nullable variant — returns `null`/`undefined` unchanged.
|
|
140
|
+
*/
|
|
141
|
+
export declare function unref<T extends object>(value: Ref<T> | LazyRef<T> | T | null | undefined): T | null | undefined;
|
|
101
142
|
/**
|
|
102
143
|
* shortcut for `Reference.createNakedFromPK(entityType, pk)`
|
|
103
144
|
*/
|
package/entity/Reference.js
CHANGED
|
@@ -303,6 +303,15 @@ export function ref(entityOrType, pk) {
|
|
|
303
303
|
}
|
|
304
304
|
return Reference.createFromPK(entityOrType, pk);
|
|
305
305
|
}
|
|
306
|
+
export function unref(value) {
|
|
307
|
+
if (value == null) {
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
if (ScalarReference.isScalarReference(value)) {
|
|
311
|
+
return value.unwrap();
|
|
312
|
+
}
|
|
313
|
+
return Reference.unwrapReference(value);
|
|
314
|
+
}
|
|
306
315
|
/**
|
|
307
316
|
* shortcut for `Reference.createNakedFromPK(entityType, pk)`
|
|
308
317
|
*/
|