nestjs-query-mikro-orm 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +621 -298
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -72
- package/dist/index.d.ts +71 -72
- package/dist/index.mjs +610 -287
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -9
package/dist/index.mjs
CHANGED
|
@@ -1,154 +1,13 @@
|
|
|
1
1
|
import { MikroOrmModule, getRepositoryToken } from '@mikro-orm/nestjs';
|
|
2
|
-
import { getFilterFields, AssemblerFactory, AssemblerSerializer, AssemblerDeserializer, getQueryServiceToken } from '@nestjs-query
|
|
2
|
+
import { getFilterFields, AssemblerFactory, AssemblerSerializer, AssemblerDeserializer, getQueryServiceToken } from '@ptc-org/nestjs-query-core';
|
|
3
3
|
import { raw, wrap } from '@mikro-orm/core';
|
|
4
|
-
import { getAssemblerSerializer } from '@nestjs-query
|
|
4
|
+
import { getAssemblerSerializer } from '@ptc-org/nestjs-query-core/src/assemblers/assembler.serializer';
|
|
5
5
|
import { BadRequestException, NotFoundException, MethodNotAllowedException } from '@nestjs/common';
|
|
6
6
|
import { instanceToPlain } from 'class-transformer';
|
|
7
7
|
import merge from 'lodash.merge';
|
|
8
|
-
import { camelCase } from 'camel-case';
|
|
9
8
|
|
|
10
9
|
var __defProp = Object.defineProperty;
|
|
11
10
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
12
|
-
var AGG_REGEXP = /(AVG|SUM|COUNT|MAX|MIN|GROUP_BY)_(.*)/;
|
|
13
|
-
var AggregateBuilder = class _AggregateBuilder {
|
|
14
|
-
static {
|
|
15
|
-
__name(this, "AggregateBuilder");
|
|
16
|
-
}
|
|
17
|
-
static async asyncConvertToAggregateResponse(responsePromise) {
|
|
18
|
-
const aggResponse = await responsePromise;
|
|
19
|
-
return this.convertToAggregateResponse(aggResponse);
|
|
20
|
-
}
|
|
21
|
-
static getAggregateSelects(query) {
|
|
22
|
-
return [
|
|
23
|
-
...this.getAggregateGroupBySelects(query),
|
|
24
|
-
...this.getAggregateFuncSelects(query)
|
|
25
|
-
];
|
|
26
|
-
}
|
|
27
|
-
static getAggregateGroupBySelects(query) {
|
|
28
|
-
return (query.groupBy ?? []).map((f) => this.getGroupByAlias(f));
|
|
29
|
-
}
|
|
30
|
-
static getAggregateFuncSelects(query) {
|
|
31
|
-
const aggs = [
|
|
32
|
-
[
|
|
33
|
-
"COUNT",
|
|
34
|
-
query.count
|
|
35
|
-
],
|
|
36
|
-
[
|
|
37
|
-
"SUM",
|
|
38
|
-
query.sum
|
|
39
|
-
],
|
|
40
|
-
[
|
|
41
|
-
"AVG",
|
|
42
|
-
query.avg
|
|
43
|
-
],
|
|
44
|
-
[
|
|
45
|
-
"MAX",
|
|
46
|
-
query.max
|
|
47
|
-
],
|
|
48
|
-
[
|
|
49
|
-
"MIN",
|
|
50
|
-
query.min
|
|
51
|
-
]
|
|
52
|
-
];
|
|
53
|
-
return aggs.reduce((cols, [func, fields]) => {
|
|
54
|
-
const aliases = (fields ?? []).map((f) => this.getAggregateAlias(func, f));
|
|
55
|
-
return [
|
|
56
|
-
...cols,
|
|
57
|
-
...aliases
|
|
58
|
-
];
|
|
59
|
-
}, []);
|
|
60
|
-
}
|
|
61
|
-
static getAggregateAlias(func, field) {
|
|
62
|
-
return `${func}_${field}`;
|
|
63
|
-
}
|
|
64
|
-
static getGroupByAlias(field) {
|
|
65
|
-
return `GROUP_BY_${field}`;
|
|
66
|
-
}
|
|
67
|
-
static convertToAggregateResponse(rawAggregates) {
|
|
68
|
-
return rawAggregates.map((response) => {
|
|
69
|
-
return Object.keys(response).reduce((agg, resultField) => {
|
|
70
|
-
const matchResult = AGG_REGEXP.exec(resultField);
|
|
71
|
-
if (!matchResult) {
|
|
72
|
-
throw new Error("Unknown aggregate column encountered.");
|
|
73
|
-
}
|
|
74
|
-
const [matchedFunc, matchedFieldName] = matchResult.slice(1);
|
|
75
|
-
const aggFunc = camelCase(matchedFunc.toLowerCase());
|
|
76
|
-
const fieldName = matchedFieldName;
|
|
77
|
-
const aggResult = agg[aggFunc] || {};
|
|
78
|
-
return {
|
|
79
|
-
...agg,
|
|
80
|
-
[aggFunc]: {
|
|
81
|
-
...aggResult,
|
|
82
|
-
[fieldName]: response[resultField]
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
}, {});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Gets the actual database column name for a property from entity metadata.
|
|
90
|
-
* @param metadata - the entity metadata
|
|
91
|
-
* @param propertyName - the property name
|
|
92
|
-
* @returns the database column name
|
|
93
|
-
*/
|
|
94
|
-
getColumnName(metadata, propertyName) {
|
|
95
|
-
const prop = metadata.properties[propertyName];
|
|
96
|
-
if (prop && prop.fieldNames && prop.fieldNames.length > 0) {
|
|
97
|
-
return prop.fieldNames[0];
|
|
98
|
-
}
|
|
99
|
-
return propertyName;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Builds aggregate SELECT clause for MikroORM QueryBuilder.
|
|
103
|
-
* @param qb - the MikroORM QueryBuilder
|
|
104
|
-
* @param aggregate - the aggregates to select.
|
|
105
|
-
* @param alias - optional alias to use to qualify an identifier
|
|
106
|
-
*/
|
|
107
|
-
build(qb, aggregate, alias) {
|
|
108
|
-
const metadata = qb.mainAlias?.metadata;
|
|
109
|
-
const selects = [
|
|
110
|
-
...this.createGroupBySelect(aggregate.groupBy, alias, metadata),
|
|
111
|
-
...this.createAggSelect("COUNT", aggregate.count, alias, metadata),
|
|
112
|
-
...this.createAggSelect("SUM", aggregate.sum, alias, metadata),
|
|
113
|
-
...this.createAggSelect("AVG", aggregate.avg, alias, metadata),
|
|
114
|
-
...this.createAggSelect("MAX", aggregate.max, alias, metadata),
|
|
115
|
-
...this.createAggSelect("MIN", aggregate.min, alias, metadata)
|
|
116
|
-
];
|
|
117
|
-
if (!selects.length) {
|
|
118
|
-
throw new BadRequestException("No aggregate fields found.");
|
|
119
|
-
}
|
|
120
|
-
selects.forEach(([selectExpr, selectAlias]) => {
|
|
121
|
-
qb.addSelect(raw(`${selectExpr} as "${selectAlias}"`));
|
|
122
|
-
});
|
|
123
|
-
return qb;
|
|
124
|
-
}
|
|
125
|
-
createAggSelect(func, fields, alias, metadata) {
|
|
126
|
-
if (!fields) {
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
129
|
-
return fields.map((field) => {
|
|
130
|
-
const columnName = metadata ? this.getColumnName(metadata, field) : field;
|
|
131
|
-
const col = alias ? `\`${alias}\`.\`${columnName}\`` : `\`${columnName}\``;
|
|
132
|
-
return [
|
|
133
|
-
`${func}(${col})`,
|
|
134
|
-
_AggregateBuilder.getAggregateAlias(func, field)
|
|
135
|
-
];
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
createGroupBySelect(fields, alias, metadata) {
|
|
139
|
-
if (!fields) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
return fields.map((field) => {
|
|
143
|
-
const columnName = metadata ? this.getColumnName(metadata, field) : field;
|
|
144
|
-
const col = alias ? `\`${alias}\`.\`${columnName}\`` : `\`${columnName}\``;
|
|
145
|
-
return [
|
|
146
|
-
`${col}`,
|
|
147
|
-
_AggregateBuilder.getGroupByAlias(field)
|
|
148
|
-
];
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
11
|
|
|
153
12
|
// src/lib/query/where.builder.ts
|
|
154
13
|
var WhereBuilder = class {
|
|
@@ -388,81 +247,50 @@ var FilterQueryBuilder = class {
|
|
|
388
247
|
}
|
|
389
248
|
repo;
|
|
390
249
|
whereBuilder;
|
|
391
|
-
|
|
392
|
-
constructor(repo, whereBuilder = new WhereBuilder(), aggregateBuilder = new AggregateBuilder()) {
|
|
250
|
+
constructor(repo, whereBuilder = new WhereBuilder()) {
|
|
393
251
|
this.repo = repo;
|
|
394
252
|
this.whereBuilder = whereBuilder;
|
|
395
|
-
this.aggregateBuilder = aggregateBuilder;
|
|
396
253
|
}
|
|
397
254
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* @param query - the query to apply.
|
|
255
|
+
* NOTE: QueryBuilder-specific helpers removed; use `buildFindOptions` to
|
|
256
|
+
* produce a filter and options for `em.find`/`repo.find`.
|
|
401
257
|
*/
|
|
402
|
-
select(query) {
|
|
403
|
-
const alias = this.getEntityAlias();
|
|
404
|
-
const qb = this.createQueryBuilder(alias);
|
|
405
|
-
this.applyFilter(qb, query.filter, alias);
|
|
406
|
-
this.applySorting(qb, query.sorting, alias);
|
|
407
|
-
this.applyPaging(qb, query.paging);
|
|
408
|
-
return qb;
|
|
409
|
-
}
|
|
410
|
-
selectById(id, query) {
|
|
411
|
-
const alias = this.getEntityAlias();
|
|
412
|
-
const qb = this.createQueryBuilder(alias);
|
|
413
|
-
const metadata = this.repo.getEntityManager().getMetadata().get(this.repo.getEntityName());
|
|
414
|
-
const primaryKey = metadata.primaryKeys[0];
|
|
415
|
-
if (Array.isArray(id)) {
|
|
416
|
-
qb.where({
|
|
417
|
-
[primaryKey]: {
|
|
418
|
-
$in: id
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
} else {
|
|
422
|
-
qb.where({
|
|
423
|
-
[primaryKey]: id
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
this.applyFilter(qb, query.filter, alias);
|
|
427
|
-
this.applySorting(qb, query.sorting, alias);
|
|
428
|
-
this.applyPaging(qb, query.paging);
|
|
429
|
-
return qb;
|
|
430
|
-
}
|
|
431
|
-
aggregate(query, aggregate) {
|
|
432
|
-
const alias = this.getEntityAlias();
|
|
433
|
-
const qb = this.createQueryBuilder(alias);
|
|
434
|
-
this.applyAggregate(qb, aggregate, alias);
|
|
435
|
-
this.applyFilter(qb, query.filter, alias);
|
|
436
|
-
this.applyAggregateSorting(qb, aggregate.groupBy, alias);
|
|
437
|
-
this.applyGroupBy(qb, aggregate.groupBy, alias);
|
|
438
|
-
return qb;
|
|
439
|
-
}
|
|
440
258
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
259
|
+
* Build a filter query and find options suitable for `em.find`/`repo.find` calls.
|
|
260
|
+
* This keeps usage DB-agnostic by returning plain filter objects and options
|
|
261
|
+
* instead of driver-specific QueryBuilder instances.
|
|
444
262
|
*/
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
263
|
+
buildFindOptions(query) {
|
|
264
|
+
const result = {};
|
|
265
|
+
if (query.filter) {
|
|
266
|
+
const mikroOrmFilter = this.whereBuilder.build(query.filter);
|
|
267
|
+
result.filterQuery = mikroOrmFilter;
|
|
268
|
+
}
|
|
269
|
+
const paging = query.paging;
|
|
270
|
+
const sorting = query.sorting;
|
|
271
|
+
if (paging && (paging.limit !== void 0 || paging.offset !== void 0) || sorting && sorting.length) {
|
|
272
|
+
const options = {};
|
|
273
|
+
if (paging) {
|
|
274
|
+
if (paging.limit !== void 0) options.limit = paging.limit;
|
|
275
|
+
if (paging.offset !== void 0) options.offset = paging.offset;
|
|
276
|
+
}
|
|
277
|
+
if (sorting && sorting.length > 0) {
|
|
278
|
+
const orderBy = sorting.reduce((acc, { field, direction, nulls }) => {
|
|
279
|
+
const order = direction === "ASC" ? "asc" : "desc";
|
|
280
|
+
let orderValue = order;
|
|
281
|
+
if (nulls) {
|
|
282
|
+
orderValue = `${order} ${nulls.toLowerCase().replace("_", " ")}`;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
...acc,
|
|
286
|
+
[field]: orderValue
|
|
287
|
+
};
|
|
288
|
+
}, {});
|
|
289
|
+
options.orderBy = orderBy;
|
|
290
|
+
}
|
|
291
|
+
result.options = options;
|
|
454
292
|
}
|
|
455
|
-
return
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* Applies the aggregate selects from a Query to a MikroORM QueryBuilder.
|
|
459
|
-
*
|
|
460
|
-
* @param qb - the MikroORM QueryBuilder.
|
|
461
|
-
* @param aggregate - the aggregates to select.
|
|
462
|
-
* @param alias - optional alias to use to qualify an identifier
|
|
463
|
-
*/
|
|
464
|
-
applyAggregate(qb, aggregate, alias) {
|
|
465
|
-
return this.aggregateBuilder.build(qb, aggregate, alias);
|
|
293
|
+
return result;
|
|
466
294
|
}
|
|
467
295
|
/**
|
|
468
296
|
* Applies the filter from a Query to a MikroORM QueryBuilder.
|
|
@@ -524,7 +352,7 @@ var FilterQueryBuilder = class {
|
|
|
524
352
|
* Create a MikroORM QueryBuilder.
|
|
525
353
|
*/
|
|
526
354
|
createQueryBuilder(alias) {
|
|
527
|
-
return this.repo.createQueryBuilder(alias);
|
|
355
|
+
return this.repo.createQueryBuilder?.(alias);
|
|
528
356
|
}
|
|
529
357
|
/**
|
|
530
358
|
* Gets the entity alias based on the entity name.
|
|
@@ -613,10 +441,10 @@ var FilterQueryBuilder = class {
|
|
|
613
441
|
}
|
|
614
442
|
};
|
|
615
443
|
|
|
616
|
-
// src/lib/query/
|
|
617
|
-
var
|
|
444
|
+
// src/lib/query/comparison.builder.ts
|
|
445
|
+
var ComparisonBuilder = class {
|
|
618
446
|
static {
|
|
619
|
-
__name(this, "
|
|
447
|
+
__name(this, "ComparisonBuilder");
|
|
620
448
|
}
|
|
621
449
|
/**
|
|
622
450
|
* Maps a comparison operator to MikroORM filter format.
|
|
@@ -826,68 +654,162 @@ var RelationQueryBuilder = class {
|
|
|
826
654
|
this.filterQueryBuilder = new FilterQueryBuilder(relationRepo);
|
|
827
655
|
}
|
|
828
656
|
/**
|
|
829
|
-
*
|
|
830
|
-
* This is useful for testing or when you need to inspect/modify the query before execution.
|
|
657
|
+
* Executes a relation select using `em.find` so the implementation is database-agnostic.
|
|
831
658
|
*/
|
|
832
|
-
|
|
659
|
+
async selectAndExecute(entity, query) {
|
|
833
660
|
const relationMeta = this.getRelationMeta();
|
|
834
661
|
const em = this.repo.getEntityManager();
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
qb = this.filterQueryBuilder.applyFilter(qb, query.filter);
|
|
850
|
-
qb = this.filterQueryBuilder.applyPaging(qb, query.paging);
|
|
851
|
-
qb = this.filterQueryBuilder.applySorting(qb, query.sorting);
|
|
852
|
-
return qb;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
const whereCondition = this.buildWhereCondition(entity, relationMeta);
|
|
856
|
-
qb = qb.where(whereCondition);
|
|
857
|
-
qb = this.filterQueryBuilder.applyFilter(qb, query.filter);
|
|
858
|
-
qb = this.filterQueryBuilder.applyPaging(qb, query.paging);
|
|
859
|
-
qb = this.filterQueryBuilder.applySorting(qb, query.sorting);
|
|
860
|
-
return qb;
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Executes the select query and returns the results.
|
|
864
|
-
*/
|
|
865
|
-
async selectAndExecute(entity, query) {
|
|
866
|
-
const qb = this.select(entity, query);
|
|
867
|
-
return qb.getResultList();
|
|
662
|
+
const RelationEntity = relationMeta.type;
|
|
663
|
+
const baseWhere = this.buildWhereCondition(entity, relationMeta);
|
|
664
|
+
const { filterQuery, options } = this.filterQueryBuilder.buildFindOptions(query);
|
|
665
|
+
const finalWhere = filterQuery ? {
|
|
666
|
+
$and: [
|
|
667
|
+
baseWhere,
|
|
668
|
+
filterQuery
|
|
669
|
+
]
|
|
670
|
+
} : baseWhere;
|
|
671
|
+
const findOptions = {};
|
|
672
|
+
if (options?.orderBy) findOptions.orderBy = options.orderBy;
|
|
673
|
+
if (options?.limit !== void 0) findOptions.limit = options.limit;
|
|
674
|
+
if (options?.offset !== void 0) findOptions.offset = options.offset;
|
|
675
|
+
return await em.find(RelationEntity, finalWhere, findOptions);
|
|
868
676
|
}
|
|
869
677
|
async count(entity, query) {
|
|
870
678
|
const relationMeta = this.getRelationMeta();
|
|
871
679
|
const em = this.repo.getEntityManager();
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
680
|
+
const RelationEntity = relationMeta.type;
|
|
681
|
+
const baseWhere = this.buildWhereCondition(entity, relationMeta);
|
|
682
|
+
const { filterQuery } = this.filterQueryBuilder.buildFindOptions(query);
|
|
683
|
+
const finalWhere = filterQuery ? {
|
|
684
|
+
$and: [
|
|
685
|
+
baseWhere,
|
|
686
|
+
filterQuery
|
|
687
|
+
]
|
|
688
|
+
} : baseWhere;
|
|
689
|
+
return em.count(RelationEntity, finalWhere);
|
|
878
690
|
}
|
|
879
691
|
async aggregate(entity, query, aggregateQuery) {
|
|
880
692
|
const relationMeta = this.getRelationMeta();
|
|
881
693
|
const em = this.repo.getEntityManager();
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
694
|
+
const RelationEntity = relationMeta.type;
|
|
695
|
+
const baseWhere = this.buildWhereCondition(entity, relationMeta);
|
|
696
|
+
const { filterQuery } = this.filterQueryBuilder.buildFindOptions(query);
|
|
697
|
+
const finalWhere = filterQuery ? {
|
|
698
|
+
$and: [
|
|
699
|
+
baseWhere,
|
|
700
|
+
filterQuery
|
|
701
|
+
]
|
|
702
|
+
} : baseWhere;
|
|
703
|
+
const rows = await em.find(RelationEntity, finalWhere);
|
|
704
|
+
const aggs = aggregateQuery;
|
|
705
|
+
const groupBy = aggs.groupBy ?? [];
|
|
706
|
+
const makeAggKey = /* @__PURE__ */ __name((func, field) => `${func}_${field}`, "makeAggKey");
|
|
707
|
+
const makeGroupKey = /* @__PURE__ */ __name((field) => `GROUP_BY_${field}`, "makeGroupKey");
|
|
708
|
+
const records = [];
|
|
709
|
+
const isNumeric = /* @__PURE__ */ __name((v) => typeof v === "number" || v instanceof Date, "isNumeric");
|
|
710
|
+
if (groupBy.length === 0) {
|
|
711
|
+
const out = {};
|
|
712
|
+
const computeField = /* @__PURE__ */ __name((fn, field) => {
|
|
713
|
+
const values = rows.map((r) => r[field]).filter((v) => v !== void 0 && v !== null);
|
|
714
|
+
if (fn === "COUNT") {
|
|
715
|
+
out[makeAggKey("COUNT", field)] = values.length;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (values.length === 0) {
|
|
719
|
+
out[makeAggKey(fn, field)] = null;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (fn === "SUM" || fn === "AVG") {
|
|
723
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v)).filter((n) => !Number.isNaN(n));
|
|
724
|
+
const sum = nums.reduce((s, v) => s + v, 0);
|
|
725
|
+
out[makeAggKey(fn, field)] = fn === "SUM" ? sum : nums.length ? sum / nums.length : null;
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (fn === "MAX") {
|
|
729
|
+
if (values.every(isNumeric)) {
|
|
730
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
731
|
+
out[makeAggKey("MAX", field)] = Math.max(...nums);
|
|
732
|
+
} else {
|
|
733
|
+
out[makeAggKey("MAX", field)] = values.reduce((a, b) => String(a) > String(b) ? a : b);
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (fn === "MIN") {
|
|
738
|
+
if (values.every(isNumeric)) {
|
|
739
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
740
|
+
out[makeAggKey("MIN", field)] = Math.min(...nums);
|
|
741
|
+
} else {
|
|
742
|
+
out[makeAggKey("MIN", field)] = values.reduce((a, b) => String(a) < String(b) ? a : b);
|
|
743
|
+
}
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}, "computeField");
|
|
747
|
+
(aggs.count ?? []).forEach((f) => computeField("COUNT", String(f)));
|
|
748
|
+
(aggs.sum ?? []).forEach((f) => computeField("SUM", String(f)));
|
|
749
|
+
(aggs.avg ?? []).forEach((f) => computeField("AVG", String(f)));
|
|
750
|
+
(aggs.max ?? []).forEach((f) => computeField("MAX", String(f)));
|
|
751
|
+
(aggs.min ?? []).forEach((f) => computeField("MIN", String(f)));
|
|
752
|
+
records.push(out);
|
|
753
|
+
} else {
|
|
754
|
+
const groups = /* @__PURE__ */ new Map();
|
|
755
|
+
rows.forEach((r) => {
|
|
756
|
+
const keyParts = groupBy.map((g) => JSON.stringify(r[String(g)]));
|
|
757
|
+
const key = keyParts.join("|");
|
|
758
|
+
const arr = groups.get(key) ?? [];
|
|
759
|
+
arr.push(r);
|
|
760
|
+
groups.set(key, arr);
|
|
761
|
+
});
|
|
762
|
+
groups.forEach((groupRows, key) => {
|
|
763
|
+
const parts = key.split("|").map((p) => JSON.parse(p));
|
|
764
|
+
const out = {};
|
|
765
|
+
groupBy.forEach((g, i) => {
|
|
766
|
+
const val = parts[i];
|
|
767
|
+
out[makeGroupKey(String(g))] = typeof val === "boolean" ? val ? 1 : 0 : val;
|
|
768
|
+
});
|
|
769
|
+
const computeField = /* @__PURE__ */ __name((fn, field) => {
|
|
770
|
+
const values = groupRows.map((r) => r[field]).filter((v) => v !== void 0 && v !== null);
|
|
771
|
+
if (fn === "COUNT") {
|
|
772
|
+
out[makeAggKey("COUNT", field)] = values.length;
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (values.length === 0) {
|
|
776
|
+
out[makeAggKey(fn, field)] = null;
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (fn === "SUM" || fn === "AVG") {
|
|
780
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v)).filter((n) => !Number.isNaN(n));
|
|
781
|
+
const sum = nums.reduce((s, v) => s + v, 0);
|
|
782
|
+
out[makeAggKey(fn, field)] = fn === "SUM" ? sum : nums.length ? sum / nums.length : null;
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (fn === "MAX") {
|
|
786
|
+
if (values.every(isNumeric)) {
|
|
787
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
788
|
+
out[makeAggKey("MAX", field)] = Math.max(...nums);
|
|
789
|
+
} else {
|
|
790
|
+
out[makeAggKey("MAX", field)] = values.reduce((a, b) => String(a) > String(b) ? a : b);
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (fn === "MIN") {
|
|
795
|
+
if (values.every(isNumeric)) {
|
|
796
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
797
|
+
out[makeAggKey("MIN", field)] = Math.min(...nums);
|
|
798
|
+
} else {
|
|
799
|
+
out[makeAggKey("MIN", field)] = values.reduce((a, b) => String(a) < String(b) ? a : b);
|
|
800
|
+
}
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
}, "computeField");
|
|
804
|
+
(aggs.count ?? []).forEach((f) => computeField("COUNT", String(f)));
|
|
805
|
+
(aggs.sum ?? []).forEach((f) => computeField("SUM", String(f)));
|
|
806
|
+
(aggs.avg ?? []).forEach((f) => computeField("AVG", String(f)));
|
|
807
|
+
(aggs.max ?? []).forEach((f) => computeField("MAX", String(f)));
|
|
808
|
+
(aggs.min ?? []).forEach((f) => computeField("MIN", String(f)));
|
|
809
|
+
records.push(out);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return records;
|
|
891
813
|
}
|
|
892
814
|
buildWhereCondition(entity, relationMeta) {
|
|
893
815
|
const em = this.repo.getEntityManager();
|
|
@@ -1005,6 +927,239 @@ var RelationQueryBuilder = class {
|
|
|
1005
927
|
});
|
|
1006
928
|
}
|
|
1007
929
|
};
|
|
930
|
+
var AGG_REGEXP = /^(AVG|SUM|COUNT|MAX|MIN|GROUP_BY|group_by|groupBy|avg|sum|count|max|min)_(.*)$/i;
|
|
931
|
+
var AggregateBuilder = class _AggregateBuilder {
|
|
932
|
+
static {
|
|
933
|
+
__name(this, "AggregateBuilder");
|
|
934
|
+
}
|
|
935
|
+
static buildSelectExpressions(aggregate, alias) {
|
|
936
|
+
const aggs = [
|
|
937
|
+
[
|
|
938
|
+
"COUNT",
|
|
939
|
+
aggregate.count
|
|
940
|
+
],
|
|
941
|
+
[
|
|
942
|
+
"SUM",
|
|
943
|
+
aggregate.sum
|
|
944
|
+
],
|
|
945
|
+
[
|
|
946
|
+
"AVG",
|
|
947
|
+
aggregate.avg
|
|
948
|
+
],
|
|
949
|
+
[
|
|
950
|
+
"MAX",
|
|
951
|
+
aggregate.max
|
|
952
|
+
],
|
|
953
|
+
[
|
|
954
|
+
"MIN",
|
|
955
|
+
aggregate.min
|
|
956
|
+
]
|
|
957
|
+
];
|
|
958
|
+
const groupBySelects = (aggregate.groupBy ?? []).map((f) => {
|
|
959
|
+
const col = alias ? `\`${alias}\`.\`${String(f)}\`` : `\`${String(f)}\``;
|
|
960
|
+
return [
|
|
961
|
+
col,
|
|
962
|
+
_AggregateBuilder.getGroupByAlias(f)
|
|
963
|
+
];
|
|
964
|
+
});
|
|
965
|
+
const funcSelects = [];
|
|
966
|
+
aggs.forEach(([func, fields]) => {
|
|
967
|
+
if (!fields || fields.length === 0) return;
|
|
968
|
+
const aliases = fields.map((f) => {
|
|
969
|
+
const col = alias ? `\`${alias}\`.\`${String(f)}\`` : `\`${String(f)}\``;
|
|
970
|
+
return [
|
|
971
|
+
`${func}(${col})`,
|
|
972
|
+
_AggregateBuilder.getAggregateAlias(func, f)
|
|
973
|
+
];
|
|
974
|
+
});
|
|
975
|
+
funcSelects.push(...aliases);
|
|
976
|
+
});
|
|
977
|
+
const selects = [
|
|
978
|
+
...groupBySelects,
|
|
979
|
+
...funcSelects
|
|
980
|
+
];
|
|
981
|
+
if (!selects.length) {
|
|
982
|
+
throw new BadRequestException("No aggregate fields found.");
|
|
983
|
+
}
|
|
984
|
+
return selects;
|
|
985
|
+
}
|
|
986
|
+
static async asyncConvertToAggregateResponse(responsePromise) {
|
|
987
|
+
const aggResponse = await responsePromise;
|
|
988
|
+
return this.convertToAggregateResponse(aggResponse);
|
|
989
|
+
}
|
|
990
|
+
static getAggregateSelects(query) {
|
|
991
|
+
return [
|
|
992
|
+
...this.getAggregateGroupBySelects(query),
|
|
993
|
+
...this.getAggregateFuncSelects(query)
|
|
994
|
+
];
|
|
995
|
+
}
|
|
996
|
+
static getAggregateGroupBySelects(query) {
|
|
997
|
+
return (query.groupBy ?? []).map((f) => this.getGroupByAlias(f));
|
|
998
|
+
}
|
|
999
|
+
static getAggregateFuncSelects(query) {
|
|
1000
|
+
const aggs = [
|
|
1001
|
+
[
|
|
1002
|
+
"COUNT",
|
|
1003
|
+
query.count
|
|
1004
|
+
],
|
|
1005
|
+
[
|
|
1006
|
+
"SUM",
|
|
1007
|
+
query.sum
|
|
1008
|
+
],
|
|
1009
|
+
[
|
|
1010
|
+
"AVG",
|
|
1011
|
+
query.avg
|
|
1012
|
+
],
|
|
1013
|
+
[
|
|
1014
|
+
"MAX",
|
|
1015
|
+
query.max
|
|
1016
|
+
],
|
|
1017
|
+
[
|
|
1018
|
+
"MIN",
|
|
1019
|
+
query.min
|
|
1020
|
+
]
|
|
1021
|
+
];
|
|
1022
|
+
return aggs.reduce((cols, [func, fields]) => {
|
|
1023
|
+
if (!fields || fields.length === 0) return cols;
|
|
1024
|
+
const aliases = fields.map((f) => this.getAggregateAlias(func, f));
|
|
1025
|
+
return [
|
|
1026
|
+
...cols,
|
|
1027
|
+
...aliases
|
|
1028
|
+
];
|
|
1029
|
+
}, []);
|
|
1030
|
+
}
|
|
1031
|
+
static getAggregateAlias(func, field) {
|
|
1032
|
+
return `${func}_${field}`;
|
|
1033
|
+
}
|
|
1034
|
+
static getGroupByAlias(field) {
|
|
1035
|
+
return `GROUP_BY_${field}`;
|
|
1036
|
+
}
|
|
1037
|
+
static convertToAggregateResponse(rawAggregates) {
|
|
1038
|
+
return rawAggregates.map((response) => {
|
|
1039
|
+
const agg = {};
|
|
1040
|
+
if (response._id && typeof response._id === "object") {
|
|
1041
|
+
const idObj = response._id;
|
|
1042
|
+
Object.keys(idObj).forEach((k) => {
|
|
1043
|
+
const m = /^(?:GROUP_BY|group_by|groupBy)_(.*)$/i.exec(k);
|
|
1044
|
+
if (m) {
|
|
1045
|
+
const field = m[1];
|
|
1046
|
+
agg.groupBy = {
|
|
1047
|
+
...agg.groupBy,
|
|
1048
|
+
[field]: idObj[k]
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
Object.keys(response).forEach((resultField) => {
|
|
1054
|
+
if (resultField === "_id") return;
|
|
1055
|
+
const matchResult = AGG_REGEXP.exec(resultField);
|
|
1056
|
+
if (!matchResult) {
|
|
1057
|
+
throw new Error("Unknown aggregate column encountered.");
|
|
1058
|
+
}
|
|
1059
|
+
const matchedFunc = matchResult[1];
|
|
1060
|
+
const matchedFieldName = matchResult[2];
|
|
1061
|
+
const funcKey = matchedFunc.toLowerCase();
|
|
1062
|
+
const aggFunc = funcKey === "group_by" || funcKey === "groupby" ? "groupBy" : funcKey;
|
|
1063
|
+
if (aggFunc === "groupBy") {
|
|
1064
|
+
agg.groupBy = {
|
|
1065
|
+
...agg.groupBy,
|
|
1066
|
+
[matchedFieldName]: response[resultField]
|
|
1067
|
+
};
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const fieldName = matchedFieldName;
|
|
1071
|
+
agg[aggFunc] = {
|
|
1072
|
+
...agg[aggFunc],
|
|
1073
|
+
[fieldName]: response[resultField]
|
|
1074
|
+
};
|
|
1075
|
+
});
|
|
1076
|
+
return agg;
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Gets the actual database column name for a property from entity metadata.
|
|
1081
|
+
* @param metadata - the entity metadata
|
|
1082
|
+
* @param propertyName - the property name
|
|
1083
|
+
* @returns the database column name
|
|
1084
|
+
*/
|
|
1085
|
+
getColumnName(metadata, propertyName) {
|
|
1086
|
+
const prop = metadata.properties[propertyName];
|
|
1087
|
+
if (prop && prop.fieldNames && prop.fieldNames.length > 0) {
|
|
1088
|
+
return prop.fieldNames[0];
|
|
1089
|
+
}
|
|
1090
|
+
return propertyName;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Builds aggregate SELECT clause for MikroORM QueryBuilder.
|
|
1094
|
+
* @param qb - the MikroORM QueryBuilder
|
|
1095
|
+
* @param aggregate - the aggregates to select.
|
|
1096
|
+
* @param alias - optional alias to use to qualify an identifier
|
|
1097
|
+
*/
|
|
1098
|
+
build(qb, aggregate, alias) {
|
|
1099
|
+
const metadata = qb.mainAlias?.metadata;
|
|
1100
|
+
const selects = [];
|
|
1101
|
+
selects.push(...this.createGroupBySelect(aggregate.groupBy, alias, metadata));
|
|
1102
|
+
const aggs = [
|
|
1103
|
+
[
|
|
1104
|
+
"COUNT",
|
|
1105
|
+
aggregate.count
|
|
1106
|
+
],
|
|
1107
|
+
[
|
|
1108
|
+
"SUM",
|
|
1109
|
+
aggregate.sum
|
|
1110
|
+
],
|
|
1111
|
+
[
|
|
1112
|
+
"AVG",
|
|
1113
|
+
aggregate.avg
|
|
1114
|
+
],
|
|
1115
|
+
[
|
|
1116
|
+
"MAX",
|
|
1117
|
+
aggregate.max
|
|
1118
|
+
],
|
|
1119
|
+
[
|
|
1120
|
+
"MIN",
|
|
1121
|
+
aggregate.min
|
|
1122
|
+
]
|
|
1123
|
+
];
|
|
1124
|
+
aggs.forEach(([func, fields]) => {
|
|
1125
|
+
if (!fields || fields.length === 0) return;
|
|
1126
|
+
selects.push(...this.createAggSelect(func, fields, alias, metadata));
|
|
1127
|
+
});
|
|
1128
|
+
if (!selects.length) {
|
|
1129
|
+
throw new BadRequestException("No aggregate fields found.");
|
|
1130
|
+
}
|
|
1131
|
+
selects.forEach(([selectExpr, selectAlias]) => {
|
|
1132
|
+
qb.addSelect(raw(`${selectExpr} as "${selectAlias}"`));
|
|
1133
|
+
});
|
|
1134
|
+
return qb;
|
|
1135
|
+
}
|
|
1136
|
+
createAggSelect(func, fields, alias, metadata) {
|
|
1137
|
+
if (!fields) {
|
|
1138
|
+
return [];
|
|
1139
|
+
}
|
|
1140
|
+
return fields.map((field) => {
|
|
1141
|
+
const columnName = metadata ? this.getColumnName(metadata, field) : field;
|
|
1142
|
+
const col = alias ? `\`${alias}\`.\`${columnName}\`` : `\`${columnName}\``;
|
|
1143
|
+
return [
|
|
1144
|
+
`${func}(${col})`,
|
|
1145
|
+
_AggregateBuilder.getAggregateAlias(func, field)
|
|
1146
|
+
];
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
createGroupBySelect(fields, alias, metadata) {
|
|
1150
|
+
if (!fields) {
|
|
1151
|
+
return [];
|
|
1152
|
+
}
|
|
1153
|
+
return fields.map((field) => {
|
|
1154
|
+
const columnName = metadata ? this.getColumnName(metadata, field) : field;
|
|
1155
|
+
const col = alias ? `\`${alias}\`.\`${columnName}\`` : `\`${columnName}\``;
|
|
1156
|
+
return [
|
|
1157
|
+
`${col}`,
|
|
1158
|
+
_AggregateBuilder.getGroupByAlias(field)
|
|
1159
|
+
];
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1008
1163
|
var RelationQueryService = class {
|
|
1009
1164
|
static {
|
|
1010
1165
|
__name(this, "RelationQueryService");
|
|
@@ -1015,7 +1170,7 @@ var RelationQueryService = class {
|
|
|
1015
1170
|
}
|
|
1016
1171
|
const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName));
|
|
1017
1172
|
const relationQueryBuilder = this.getRelationQueryBuilder(relationName);
|
|
1018
|
-
return assembler.
|
|
1173
|
+
return assembler.convertToDTOs(await relationQueryBuilder.selectAndExecute(dto, assembler.convertQuery(query)));
|
|
1019
1174
|
}
|
|
1020
1175
|
async aggregateRelations(RelationClass, relationName, dto, filter, aggregate) {
|
|
1021
1176
|
if (Array.isArray(dto)) {
|
|
@@ -1201,7 +1356,7 @@ var RelationQueryService = class {
|
|
|
1201
1356
|
const results = /* @__PURE__ */ new Map();
|
|
1202
1357
|
await Promise.all(entities.map(async (entity) => {
|
|
1203
1358
|
const relations = await relationQueryBuilder.selectAndExecute(entity, convertedQuery);
|
|
1204
|
-
const relationDtos = assembler.convertToDTOs(relations);
|
|
1359
|
+
const relationDtos = await assembler.convertToDTOs(relations);
|
|
1205
1360
|
if (relationDtos.length > 0) {
|
|
1206
1361
|
results.set(entity, relationDtos);
|
|
1207
1362
|
}
|
|
@@ -1359,7 +1514,7 @@ var MikroOrmQueryService = class extends RelationQueryService {
|
|
|
1359
1514
|
return metadata.class;
|
|
1360
1515
|
}
|
|
1361
1516
|
/**
|
|
1362
|
-
* Query for multiple entities, using a Query from `@nestjs-query
|
|
1517
|
+
* Query for multiple entities, using a Query from `@ptc-org/nestjs-query-core`.
|
|
1363
1518
|
*
|
|
1364
1519
|
* @example
|
|
1365
1520
|
* ```ts
|
|
@@ -1372,24 +1527,169 @@ var MikroOrmQueryService = class extends RelationQueryService {
|
|
|
1372
1527
|
* @param query - The Query used to filter, page, and sort rows.
|
|
1373
1528
|
*/
|
|
1374
1529
|
async query(query) {
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
|
|
1530
|
+
const { filterQuery, options } = this.filterQueryBuilder.buildFindOptions(query);
|
|
1531
|
+
const em = this.repo.getEntityManager();
|
|
1532
|
+
let where = filterQuery;
|
|
1533
|
+
if (this.useSoftDelete) {
|
|
1534
|
+
const deletedFilter = {
|
|
1535
|
+
deletedAt: null
|
|
1536
|
+
};
|
|
1537
|
+
where = where ? {
|
|
1538
|
+
$and: [
|
|
1539
|
+
where,
|
|
1540
|
+
deletedFilter
|
|
1541
|
+
]
|
|
1542
|
+
} : deletedFilter;
|
|
1543
|
+
}
|
|
1544
|
+
return em.find(this.EntityClass, where ?? {}, options);
|
|
1378
1545
|
}
|
|
1379
1546
|
async aggregate(filter, aggregate) {
|
|
1380
|
-
const
|
|
1547
|
+
const { filterQuery } = this.filterQueryBuilder.buildFindOptions({
|
|
1381
1548
|
filter
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1549
|
+
});
|
|
1550
|
+
const em = this.repo.getEntityManager();
|
|
1551
|
+
let where = filterQuery;
|
|
1552
|
+
if (this.useSoftDelete) {
|
|
1553
|
+
const deletedFilter = {
|
|
1554
|
+
deletedAt: null
|
|
1555
|
+
};
|
|
1556
|
+
where = where ? {
|
|
1557
|
+
$and: [
|
|
1558
|
+
where,
|
|
1559
|
+
deletedFilter
|
|
1560
|
+
]
|
|
1561
|
+
} : deletedFilter;
|
|
1562
|
+
}
|
|
1563
|
+
const rows = await em.find(this.EntityClass, where ?? {});
|
|
1564
|
+
const aggs = aggregate;
|
|
1565
|
+
const groupBy = aggs.groupBy ?? [];
|
|
1566
|
+
const records = [];
|
|
1567
|
+
const makeAggKey = /* @__PURE__ */ __name((func, field) => `${func}_${field}`, "makeAggKey");
|
|
1568
|
+
const makeGroupKey = /* @__PURE__ */ __name((field) => `GROUP_BY_${field}`, "makeGroupKey");
|
|
1569
|
+
const isNumeric = /* @__PURE__ */ __name((v) => typeof v === "number" || v instanceof Date, "isNumeric");
|
|
1570
|
+
if (groupBy.length === 0) {
|
|
1571
|
+
const out = {};
|
|
1572
|
+
const computeField = /* @__PURE__ */ __name((fn, field) => {
|
|
1573
|
+
const values = rows.map((r) => r[field]).filter((v) => v !== void 0 && v !== null);
|
|
1574
|
+
if (fn === "COUNT") {
|
|
1575
|
+
out[makeAggKey("COUNT", field)] = values.length;
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (values.length === 0) {
|
|
1579
|
+
out[makeAggKey(fn, field)] = null;
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (fn === "SUM" || fn === "AVG") {
|
|
1583
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v)).filter((n) => !Number.isNaN(n));
|
|
1584
|
+
const sum = nums.reduce((s, v) => s + v, 0);
|
|
1585
|
+
out[makeAggKey(fn, field)] = fn === "SUM" ? sum : nums.length ? sum / nums.length : null;
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (fn === "MAX") {
|
|
1589
|
+
if (values.every(isNumeric)) {
|
|
1590
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
1591
|
+
out[makeAggKey("MAX", field)] = Math.max(...nums);
|
|
1592
|
+
} else {
|
|
1593
|
+
out[makeAggKey("MAX", field)] = values.reduce((a, b) => String(a) > String(b) ? a : b);
|
|
1594
|
+
}
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (fn === "MIN") {
|
|
1598
|
+
if (values.every(isNumeric)) {
|
|
1599
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
1600
|
+
out[makeAggKey("MIN", field)] = Math.min(...nums);
|
|
1601
|
+
} else {
|
|
1602
|
+
out[makeAggKey("MIN", field)] = values.reduce((a, b) => String(a) < String(b) ? a : b);
|
|
1603
|
+
}
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
}, "computeField");
|
|
1607
|
+
(aggs.count ?? []).forEach((f) => computeField("COUNT", String(f)));
|
|
1608
|
+
(aggs.sum ?? []).forEach((f) => computeField("SUM", String(f)));
|
|
1609
|
+
(aggs.avg ?? []).forEach((f) => computeField("AVG", String(f)));
|
|
1610
|
+
(aggs.max ?? []).forEach((f) => computeField("MAX", String(f)));
|
|
1611
|
+
(aggs.min ?? []).forEach((f) => computeField("MIN", String(f)));
|
|
1612
|
+
records.push(out);
|
|
1613
|
+
} else {
|
|
1614
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1615
|
+
rows.forEach((r) => {
|
|
1616
|
+
const key = groupBy.map((g) => JSON.stringify(r[String(g)])).join("|");
|
|
1617
|
+
const arr = groups.get(key) ?? [];
|
|
1618
|
+
arr.push(r);
|
|
1619
|
+
groups.set(key, arr);
|
|
1620
|
+
});
|
|
1621
|
+
groups.forEach((groupRows, key) => {
|
|
1622
|
+
const parts = key.split("|").map((p) => JSON.parse(p));
|
|
1623
|
+
const out = {};
|
|
1624
|
+
groupBy.forEach((g, i) => {
|
|
1625
|
+
const val = parts[i];
|
|
1626
|
+
out[makeGroupKey(String(g))] = typeof val === "boolean" ? val ? 1 : 0 : val;
|
|
1627
|
+
});
|
|
1628
|
+
const computeField = /* @__PURE__ */ __name((fn, field) => {
|
|
1629
|
+
const values = groupRows.map((r) => r[field]).filter((v) => v !== void 0 && v !== null);
|
|
1630
|
+
if (fn === "COUNT") {
|
|
1631
|
+
out[makeAggKey("COUNT", field)] = values.length;
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (values.length === 0) {
|
|
1635
|
+
out[makeAggKey(fn, field)] = null;
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
if (fn === "SUM" || fn === "AVG") {
|
|
1639
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v)).filter((n) => !Number.isNaN(n));
|
|
1640
|
+
const sum = nums.reduce((s, v) => s + v, 0);
|
|
1641
|
+
out[makeAggKey(fn, field)] = fn === "SUM" ? sum : nums.length ? sum / nums.length : null;
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
if (fn === "MAX") {
|
|
1645
|
+
if (values.every(isNumeric)) {
|
|
1646
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
1647
|
+
out[makeAggKey("MAX", field)] = Math.max(...nums);
|
|
1648
|
+
} else {
|
|
1649
|
+
out[makeAggKey("MAX", field)] = values.reduce((a, b) => String(a) > String(b) ? a : b);
|
|
1650
|
+
}
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (fn === "MIN") {
|
|
1654
|
+
if (values.every(isNumeric)) {
|
|
1655
|
+
const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
|
|
1656
|
+
out[makeAggKey("MIN", field)] = Math.min(...nums);
|
|
1657
|
+
} else {
|
|
1658
|
+
out[makeAggKey("MIN", field)] = values.reduce((a, b) => String(a) < String(b) ? a : b);
|
|
1659
|
+
}
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
}, "computeField");
|
|
1663
|
+
(aggs.count ?? []).forEach((f) => computeField("COUNT", String(f)));
|
|
1664
|
+
(aggs.sum ?? []).forEach((f) => computeField("SUM", String(f)));
|
|
1665
|
+
(aggs.avg ?? []).forEach((f) => computeField("AVG", String(f)));
|
|
1666
|
+
(aggs.max ?? []).forEach((f) => computeField("MAX", String(f)));
|
|
1667
|
+
(aggs.min ?? []).forEach((f) => computeField("MIN", String(f)));
|
|
1668
|
+
records.push(out);
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
return records.map((r) => AggregateBuilder.convertToAggregateResponse([
|
|
1672
|
+
r
|
|
1673
|
+
])[0]);
|
|
1386
1674
|
}
|
|
1387
1675
|
async count(filter) {
|
|
1388
|
-
const
|
|
1676
|
+
const { filterQuery } = this.filterQueryBuilder.buildFindOptions({
|
|
1389
1677
|
filter
|
|
1390
1678
|
});
|
|
1391
|
-
|
|
1392
|
-
|
|
1679
|
+
const em = this.repo.getEntityManager();
|
|
1680
|
+
let where = filterQuery;
|
|
1681
|
+
if (this.useSoftDelete) {
|
|
1682
|
+
const deletedFilter = {
|
|
1683
|
+
deletedAt: null
|
|
1684
|
+
};
|
|
1685
|
+
where = where ? {
|
|
1686
|
+
$and: [
|
|
1687
|
+
where,
|
|
1688
|
+
deletedFilter
|
|
1689
|
+
]
|
|
1690
|
+
} : deletedFilter;
|
|
1691
|
+
}
|
|
1692
|
+
return em.count(this.EntityClass, where ?? {});
|
|
1393
1693
|
}
|
|
1394
1694
|
/**
|
|
1395
1695
|
* Find an entity by it's `id`.
|
|
@@ -1401,10 +1701,33 @@ var MikroOrmQueryService = class extends RelationQueryService {
|
|
|
1401
1701
|
* @param id - The id of the record to find.
|
|
1402
1702
|
*/
|
|
1403
1703
|
async findById(id, opts) {
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1704
|
+
const metadata = this.em.getMetadata().get(this.repo.getEntityName());
|
|
1705
|
+
const primaryKey = metadata.primaryKeys[0];
|
|
1706
|
+
let where = {
|
|
1707
|
+
[primaryKey]: id
|
|
1708
|
+
};
|
|
1709
|
+
if (opts?.filter) {
|
|
1710
|
+
const whereBuilder = new WhereBuilder();
|
|
1711
|
+
const additional = whereBuilder.build(opts.filter);
|
|
1712
|
+
where = {
|
|
1713
|
+
$and: [
|
|
1714
|
+
where,
|
|
1715
|
+
additional
|
|
1716
|
+
]
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
if (this.useSoftDelete) {
|
|
1720
|
+
where = {
|
|
1721
|
+
$and: [
|
|
1722
|
+
where,
|
|
1723
|
+
{
|
|
1724
|
+
deletedAt: null
|
|
1725
|
+
}
|
|
1726
|
+
]
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
const entity = await this.em.findOne(this.EntityClass, where);
|
|
1730
|
+
return entity ?? void 0;
|
|
1408
1731
|
}
|
|
1409
1732
|
/**
|
|
1410
1733
|
* Gets an entity by it's `id`. If the entity is not found a rejected promise is returned.
|
|
@@ -1477,7 +1800,7 @@ var MikroOrmQueryService = class extends RelationQueryService {
|
|
|
1477
1800
|
return entity;
|
|
1478
1801
|
}
|
|
1479
1802
|
/**
|
|
1480
|
-
* Update multiple entities with a `@nestjs-query
|
|
1803
|
+
* Update multiple entities with a `@ptc-org/nestjs-query-core` Filter.
|
|
1481
1804
|
*
|
|
1482
1805
|
* @example
|
|
1483
1806
|
* ```ts
|
|
@@ -1528,7 +1851,7 @@ var MikroOrmQueryService = class extends RelationQueryService {
|
|
|
1528
1851
|
return entity;
|
|
1529
1852
|
}
|
|
1530
1853
|
/**
|
|
1531
|
-
* Delete multiple records with a `@nestjs-query
|
|
1854
|
+
* Delete multiple records with a `@ptc-org/nestjs-query-core` `Filter`.
|
|
1532
1855
|
*
|
|
1533
1856
|
* @example
|
|
1534
1857
|
*
|
|
@@ -1604,7 +1927,7 @@ var MikroOrmQueryService = class extends RelationQueryService {
|
|
|
1604
1927
|
return entity;
|
|
1605
1928
|
}
|
|
1606
1929
|
/**
|
|
1607
|
-
* Restores multiple records with a `@nestjs-query
|
|
1930
|
+
* Restores multiple records with a `@ptc-org/nestjs-query-core` `Filter`.
|
|
1608
1931
|
*
|
|
1609
1932
|
* @example
|
|
1610
1933
|
*
|
|
@@ -1713,6 +2036,6 @@ var NestjsQueryMikroOrmModule = class _NestjsQueryMikroOrmModule {
|
|
|
1713
2036
|
}
|
|
1714
2037
|
};
|
|
1715
2038
|
|
|
1716
|
-
export { AggregateBuilder, FilterQueryBuilder, MikroOrmQueryService, NestjsQueryMikroOrmModule, RelationQueryBuilder, RelationQueryService,
|
|
2039
|
+
export { AggregateBuilder, ComparisonBuilder, FilterQueryBuilder, MikroOrmQueryService, NestjsQueryMikroOrmModule, RelationQueryBuilder, RelationQueryService, WhereBuilder, createMikroOrmQueryServiceProviders };
|
|
1717
2040
|
//# sourceMappingURL=index.mjs.map
|
|
1718
2041
|
//# sourceMappingURL=index.mjs.map
|