nestjs-query-mikro-orm 0.0.7 → 0.0.9

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/dist/index.mjs CHANGED
@@ -5,150 +5,9 @@ import { getAssemblerSerializer } from '@nestjs-query/core/dist/src/assemblers/a
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
- aggregateBuilder;
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
- * Create a MikroORM QueryBuilder with `WHERE`, `ORDER BY` and `LIMIT/OFFSET` clauses.
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
- * Applies paging to a MikroORM query builder
442
- * @param qb - the MikroORM QueryBuilder
443
- * @param paging - the Paging options.
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
- applyPaging(qb, paging) {
446
- if (!paging) {
447
- return qb;
448
- }
449
- if (paging.limit !== void 0) {
450
- qb.limit(paging.limit);
451
- }
452
- if (paging.offset !== void 0) {
453
- qb.offset(paging.offset);
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 qb;
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/sql-comparison.builder.ts
617
- var SQLComparisonBuilder = class {
444
+ // src/lib/query/comparison.builder.ts
445
+ var ComparisonBuilder = class {
618
446
  static {
619
- __name(this, "SQLComparisonBuilder");
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
- * Builds and returns a QueryBuilder for selecting relations without executing it.
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
- select(entity, query) {
659
+ async selectAndExecute(entity, query) {
833
660
  const relationMeta = this.getRelationMeta();
834
661
  const em = this.repo.getEntityManager();
835
- const relationEntityName = relationMeta.type;
836
- const entityMeta = em.getMetadata().get(this.repo.getEntityName());
837
- const entityPrimaryKey = entityMeta.primaryKeys[0];
838
- const entityId = entity[entityPrimaryKey];
839
- let qb = em.createQueryBuilder(relationEntityName);
840
- if (relationMeta.kind === "1:1" && relationMeta.owner && relationMeta.inversedBy) {
841
- const fkFieldName = relationMeta.joinColumns?.[0] || relationMeta.fieldNames?.[0];
842
- const fkValue = fkFieldName ? entity[fkFieldName] : void 0;
843
- if (fkValue === void 0) {
844
- const parentAlias = "parent";
845
- const relationAlias = qb.alias;
846
- qb = qb.leftJoin(`${relationAlias}.${relationMeta.inversedBy}`, parentAlias).where({
847
- [`${parentAlias}.${entityPrimaryKey}`]: entityId
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 relationEntityName = relationMeta.type;
873
- let qb = em.createQueryBuilder(relationEntityName);
874
- const whereCondition = this.buildWhereCondition(entity, relationMeta);
875
- qb = qb.where(whereCondition);
876
- qb = this.filterQueryBuilder.applyFilter(qb, query.filter);
877
- return qb.getCount();
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 relationEntityName = relationMeta.type;
883
- let qb = em.createQueryBuilder(relationEntityName);
884
- const whereCondition = this.buildWhereCondition(entity, relationMeta);
885
- qb = qb.where(whereCondition);
886
- qb = this.filterQueryBuilder.applyAggregate(qb, aggregateQuery);
887
- qb = this.filterQueryBuilder.applyFilter(qb, query.filter);
888
- qb = this.filterQueryBuilder.applyAggregateSorting(qb, aggregateQuery.groupBy);
889
- qb = this.filterQueryBuilder.applyGroupBy(qb, aggregateQuery.groupBy);
890
- return qb.execute();
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,217 @@ 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
+ const aliases = (fields ?? []).map((f) => {
968
+ const col = alias ? `\`${alias}\`.\`${String(f)}\`` : `\`${String(f)}\``;
969
+ return [
970
+ `${func}(${col})`,
971
+ _AggregateBuilder.getAggregateAlias(func, f)
972
+ ];
973
+ });
974
+ funcSelects.push(...aliases);
975
+ });
976
+ const selects = [
977
+ ...groupBySelects,
978
+ ...funcSelects
979
+ ];
980
+ if (!selects.length) {
981
+ throw new BadRequestException("No aggregate fields found.");
982
+ }
983
+ return selects;
984
+ }
985
+ static async asyncConvertToAggregateResponse(responsePromise) {
986
+ const aggResponse = await responsePromise;
987
+ return this.convertToAggregateResponse(aggResponse);
988
+ }
989
+ static getAggregateSelects(query) {
990
+ return [
991
+ ...this.getAggregateGroupBySelects(query),
992
+ ...this.getAggregateFuncSelects(query)
993
+ ];
994
+ }
995
+ static getAggregateGroupBySelects(query) {
996
+ return (query.groupBy ?? []).map((f) => this.getGroupByAlias(f));
997
+ }
998
+ static getAggregateFuncSelects(query) {
999
+ const aggs = [
1000
+ [
1001
+ "COUNT",
1002
+ query.count
1003
+ ],
1004
+ [
1005
+ "SUM",
1006
+ query.sum
1007
+ ],
1008
+ [
1009
+ "AVG",
1010
+ query.avg
1011
+ ],
1012
+ [
1013
+ "MAX",
1014
+ query.max
1015
+ ],
1016
+ [
1017
+ "MIN",
1018
+ query.min
1019
+ ]
1020
+ ];
1021
+ return aggs.reduce((cols, [func, fields]) => {
1022
+ const aliases = (fields ?? []).map((f) => this.getAggregateAlias(func, f));
1023
+ return [
1024
+ ...cols,
1025
+ ...aliases
1026
+ ];
1027
+ }, []);
1028
+ }
1029
+ static getAggregateAlias(func, field) {
1030
+ return `${func}_${field}`;
1031
+ }
1032
+ static getGroupByAlias(field) {
1033
+ return `GROUP_BY_${field}`;
1034
+ }
1035
+ static convertToAggregateResponse(rawAggregates) {
1036
+ return rawAggregates.map((response) => {
1037
+ const agg = {};
1038
+ if (response._id && typeof response._id === "object") {
1039
+ const idObj = response._id;
1040
+ Object.keys(idObj).forEach((k) => {
1041
+ const m = /^(?:GROUP_BY|group_by|groupBy)_(.*)$/i.exec(k);
1042
+ if (m) {
1043
+ const field = m[1];
1044
+ agg.groupBy = {
1045
+ ...agg.groupBy,
1046
+ [field]: idObj[k]
1047
+ };
1048
+ }
1049
+ });
1050
+ }
1051
+ Object.keys(response).forEach((resultField) => {
1052
+ if (resultField === "_id") return;
1053
+ const matchResult = AGG_REGEXP.exec(resultField);
1054
+ if (!matchResult) {
1055
+ throw new Error("Unknown aggregate column encountered.");
1056
+ }
1057
+ const matchedFunc = matchResult[1];
1058
+ const matchedFieldName = matchResult[2];
1059
+ const funcKey = matchedFunc.toLowerCase();
1060
+ const aggFunc = funcKey === "group_by" || funcKey === "groupby" ? "groupBy" : funcKey;
1061
+ if (aggFunc === "groupBy") {
1062
+ agg.groupBy = {
1063
+ ...agg.groupBy,
1064
+ [matchedFieldName]: response[resultField]
1065
+ };
1066
+ return;
1067
+ }
1068
+ const fieldName = matchedFieldName;
1069
+ agg[aggFunc] = {
1070
+ ...agg[aggFunc],
1071
+ [fieldName]: response[resultField]
1072
+ };
1073
+ });
1074
+ return agg;
1075
+ });
1076
+ }
1077
+ /**
1078
+ * Gets the actual database column name for a property from entity metadata.
1079
+ * @param metadata - the entity metadata
1080
+ * @param propertyName - the property name
1081
+ * @returns the database column name
1082
+ */
1083
+ getColumnName(metadata, propertyName) {
1084
+ const prop = metadata.properties[propertyName];
1085
+ if (prop && prop.fieldNames && prop.fieldNames.length > 0) {
1086
+ return prop.fieldNames[0];
1087
+ }
1088
+ return propertyName;
1089
+ }
1090
+ /**
1091
+ * Builds aggregate SELECT clause for MikroORM QueryBuilder.
1092
+ * @param qb - the MikroORM QueryBuilder
1093
+ * @param aggregate - the aggregates to select.
1094
+ * @param alias - optional alias to use to qualify an identifier
1095
+ */
1096
+ build(qb, aggregate, alias) {
1097
+ const metadata = qb.mainAlias?.metadata;
1098
+ const selects = [
1099
+ ...this.createGroupBySelect(aggregate.groupBy, alias, metadata),
1100
+ ...this.createAggSelect("COUNT", aggregate.count, alias, metadata),
1101
+ ...this.createAggSelect("SUM", aggregate.sum, alias, metadata),
1102
+ ...this.createAggSelect("AVG", aggregate.avg, alias, metadata),
1103
+ ...this.createAggSelect("MAX", aggregate.max, alias, metadata),
1104
+ ...this.createAggSelect("MIN", aggregate.min, alias, metadata)
1105
+ ];
1106
+ if (!selects.length) {
1107
+ throw new BadRequestException("No aggregate fields found.");
1108
+ }
1109
+ selects.forEach(([selectExpr, selectAlias]) => {
1110
+ qb.addSelect(raw(`${selectExpr} as "${selectAlias}"`));
1111
+ });
1112
+ return qb;
1113
+ }
1114
+ createAggSelect(func, fields, alias, metadata) {
1115
+ if (!fields) {
1116
+ return [];
1117
+ }
1118
+ return fields.map((field) => {
1119
+ const columnName = metadata ? this.getColumnName(metadata, field) : field;
1120
+ const col = alias ? `\`${alias}\`.\`${columnName}\`` : `\`${columnName}\``;
1121
+ return [
1122
+ `${func}(${col})`,
1123
+ _AggregateBuilder.getAggregateAlias(func, field)
1124
+ ];
1125
+ });
1126
+ }
1127
+ createGroupBySelect(fields, alias, metadata) {
1128
+ if (!fields) {
1129
+ return [];
1130
+ }
1131
+ return fields.map((field) => {
1132
+ const columnName = metadata ? this.getColumnName(metadata, field) : field;
1133
+ const col = alias ? `\`${alias}\`.\`${columnName}\`` : `\`${columnName}\``;
1134
+ return [
1135
+ `${col}`,
1136
+ _AggregateBuilder.getGroupByAlias(field)
1137
+ ];
1138
+ });
1139
+ }
1140
+ };
1008
1141
  var RelationQueryService = class {
1009
1142
  static {
1010
1143
  __name(this, "RelationQueryService");
@@ -1372,24 +1505,169 @@ var MikroOrmQueryService = class extends RelationQueryService {
1372
1505
  * @param query - The Query used to filter, page, and sort rows.
1373
1506
  */
1374
1507
  async query(query) {
1375
- const qb = this.filterQueryBuilder.select(query);
1376
- await qb.applyFilters();
1377
- return qb.getResultList();
1508
+ const { filterQuery, options } = this.filterQueryBuilder.buildFindOptions(query);
1509
+ const em = this.repo.getEntityManager();
1510
+ let where = filterQuery;
1511
+ if (this.useSoftDelete) {
1512
+ const deletedFilter = {
1513
+ deletedAt: null
1514
+ };
1515
+ where = where ? {
1516
+ $and: [
1517
+ where,
1518
+ deletedFilter
1519
+ ]
1520
+ } : deletedFilter;
1521
+ }
1522
+ return em.find(this.EntityClass, where ?? {}, options);
1378
1523
  }
1379
1524
  async aggregate(filter, aggregate) {
1380
- const qb = this.filterQueryBuilder.aggregate({
1525
+ const { filterQuery } = this.filterQueryBuilder.buildFindOptions({
1381
1526
  filter
1382
- }, aggregate);
1383
- await qb.applyFilters();
1384
- const rawResults = await qb.execute();
1385
- return AggregateBuilder.convertToAggregateResponse(rawResults);
1527
+ });
1528
+ const em = this.repo.getEntityManager();
1529
+ let where = filterQuery;
1530
+ if (this.useSoftDelete) {
1531
+ const deletedFilter = {
1532
+ deletedAt: null
1533
+ };
1534
+ where = where ? {
1535
+ $and: [
1536
+ where,
1537
+ deletedFilter
1538
+ ]
1539
+ } : deletedFilter;
1540
+ }
1541
+ const rows = await em.find(this.EntityClass, where ?? {});
1542
+ const aggs = aggregate;
1543
+ const groupBy = aggs.groupBy ?? [];
1544
+ const records = [];
1545
+ const makeAggKey = /* @__PURE__ */ __name((func, field) => `${func}_${field}`, "makeAggKey");
1546
+ const makeGroupKey = /* @__PURE__ */ __name((field) => `GROUP_BY_${field}`, "makeGroupKey");
1547
+ const isNumeric = /* @__PURE__ */ __name((v) => typeof v === "number" || v instanceof Date, "isNumeric");
1548
+ if (groupBy.length === 0) {
1549
+ const out = {};
1550
+ const computeField = /* @__PURE__ */ __name((fn, field) => {
1551
+ const values = rows.map((r) => r[field]).filter((v) => v !== void 0 && v !== null);
1552
+ if (fn === "COUNT") {
1553
+ out[makeAggKey("COUNT", field)] = values.length;
1554
+ return;
1555
+ }
1556
+ if (values.length === 0) {
1557
+ out[makeAggKey(fn, field)] = null;
1558
+ return;
1559
+ }
1560
+ if (fn === "SUM" || fn === "AVG") {
1561
+ const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v)).filter((n) => !Number.isNaN(n));
1562
+ const sum = nums.reduce((s, v) => s + v, 0);
1563
+ out[makeAggKey(fn, field)] = fn === "SUM" ? sum : nums.length ? sum / nums.length : null;
1564
+ return;
1565
+ }
1566
+ if (fn === "MAX") {
1567
+ if (values.every(isNumeric)) {
1568
+ const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
1569
+ out[makeAggKey("MAX", field)] = Math.max(...nums);
1570
+ } else {
1571
+ out[makeAggKey("MAX", field)] = values.reduce((a, b) => String(a) > String(b) ? a : b);
1572
+ }
1573
+ return;
1574
+ }
1575
+ if (fn === "MIN") {
1576
+ if (values.every(isNumeric)) {
1577
+ const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
1578
+ out[makeAggKey("MIN", field)] = Math.min(...nums);
1579
+ } else {
1580
+ out[makeAggKey("MIN", field)] = values.reduce((a, b) => String(a) < String(b) ? a : b);
1581
+ }
1582
+ return;
1583
+ }
1584
+ }, "computeField");
1585
+ (aggs.count ?? []).forEach((f) => computeField("COUNT", String(f)));
1586
+ (aggs.sum ?? []).forEach((f) => computeField("SUM", String(f)));
1587
+ (aggs.avg ?? []).forEach((f) => computeField("AVG", String(f)));
1588
+ (aggs.max ?? []).forEach((f) => computeField("MAX", String(f)));
1589
+ (aggs.min ?? []).forEach((f) => computeField("MIN", String(f)));
1590
+ records.push(out);
1591
+ } else {
1592
+ const groups = /* @__PURE__ */ new Map();
1593
+ rows.forEach((r) => {
1594
+ const key = groupBy.map((g) => JSON.stringify(r[String(g)])).join("|");
1595
+ const arr = groups.get(key) ?? [];
1596
+ arr.push(r);
1597
+ groups.set(key, arr);
1598
+ });
1599
+ groups.forEach((groupRows, key) => {
1600
+ const parts = key.split("|").map((p) => JSON.parse(p));
1601
+ const out = {};
1602
+ groupBy.forEach((g, i) => {
1603
+ const val = parts[i];
1604
+ out[makeGroupKey(String(g))] = typeof val === "boolean" ? val ? 1 : 0 : val;
1605
+ });
1606
+ const computeField = /* @__PURE__ */ __name((fn, field) => {
1607
+ const values = groupRows.map((r) => r[field]).filter((v) => v !== void 0 && v !== null);
1608
+ if (fn === "COUNT") {
1609
+ out[makeAggKey("COUNT", field)] = values.length;
1610
+ return;
1611
+ }
1612
+ if (values.length === 0) {
1613
+ out[makeAggKey(fn, field)] = null;
1614
+ return;
1615
+ }
1616
+ if (fn === "SUM" || fn === "AVG") {
1617
+ const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v)).filter((n) => !Number.isNaN(n));
1618
+ const sum = nums.reduce((s, v) => s + v, 0);
1619
+ out[makeAggKey(fn, field)] = fn === "SUM" ? sum : nums.length ? sum / nums.length : null;
1620
+ return;
1621
+ }
1622
+ if (fn === "MAX") {
1623
+ if (values.every(isNumeric)) {
1624
+ const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
1625
+ out[makeAggKey("MAX", field)] = Math.max(...nums);
1626
+ } else {
1627
+ out[makeAggKey("MAX", field)] = values.reduce((a, b) => String(a) > String(b) ? a : b);
1628
+ }
1629
+ return;
1630
+ }
1631
+ if (fn === "MIN") {
1632
+ if (values.every(isNumeric)) {
1633
+ const nums = values.map((v) => v instanceof Date ? v.getTime() : Number(v));
1634
+ out[makeAggKey("MIN", field)] = Math.min(...nums);
1635
+ } else {
1636
+ out[makeAggKey("MIN", field)] = values.reduce((a, b) => String(a) < String(b) ? a : b);
1637
+ }
1638
+ return;
1639
+ }
1640
+ }, "computeField");
1641
+ (aggs.count ?? []).forEach((f) => computeField("COUNT", String(f)));
1642
+ (aggs.sum ?? []).forEach((f) => computeField("SUM", String(f)));
1643
+ (aggs.avg ?? []).forEach((f) => computeField("AVG", String(f)));
1644
+ (aggs.max ?? []).forEach((f) => computeField("MAX", String(f)));
1645
+ (aggs.min ?? []).forEach((f) => computeField("MIN", String(f)));
1646
+ records.push(out);
1647
+ });
1648
+ }
1649
+ return records.map((r) => AggregateBuilder.convertToAggregateResponse([
1650
+ r
1651
+ ])[0]);
1386
1652
  }
1387
1653
  async count(filter) {
1388
- const qb = this.filterQueryBuilder.select({
1654
+ const { filterQuery } = this.filterQueryBuilder.buildFindOptions({
1389
1655
  filter
1390
1656
  });
1391
- await qb.applyFilters();
1392
- return qb.getCount();
1657
+ const em = this.repo.getEntityManager();
1658
+ let where = filterQuery;
1659
+ if (this.useSoftDelete) {
1660
+ const deletedFilter = {
1661
+ deletedAt: null
1662
+ };
1663
+ where = where ? {
1664
+ $and: [
1665
+ where,
1666
+ deletedFilter
1667
+ ]
1668
+ } : deletedFilter;
1669
+ }
1670
+ return em.count(this.EntityClass, where ?? {});
1393
1671
  }
1394
1672
  /**
1395
1673
  * Find an entity by it's `id`.
@@ -1401,10 +1679,33 @@ var MikroOrmQueryService = class extends RelationQueryService {
1401
1679
  * @param id - The id of the record to find.
1402
1680
  */
1403
1681
  async findById(id, opts) {
1404
- const qb = this.filterQueryBuilder.selectById(id, opts ?? {});
1405
- await qb.applyFilters();
1406
- const result = await qb.getSingleResult();
1407
- return result ?? void 0;
1682
+ const metadata = this.em.getMetadata().get(this.repo.getEntityName());
1683
+ const primaryKey = metadata.primaryKeys[0];
1684
+ let where = {
1685
+ [primaryKey]: id
1686
+ };
1687
+ if (opts?.filter) {
1688
+ const whereBuilder = new WhereBuilder();
1689
+ const additional = whereBuilder.build(opts.filter);
1690
+ where = {
1691
+ $and: [
1692
+ where,
1693
+ additional
1694
+ ]
1695
+ };
1696
+ }
1697
+ if (this.useSoftDelete) {
1698
+ where = {
1699
+ $and: [
1700
+ where,
1701
+ {
1702
+ deletedAt: null
1703
+ }
1704
+ ]
1705
+ };
1706
+ }
1707
+ const entity = await this.em.findOne(this.EntityClass, where);
1708
+ return entity ?? void 0;
1408
1709
  }
1409
1710
  /**
1410
1711
  * Gets an entity by it's `id`. If the entity is not found a rejected promise is returned.
@@ -1469,9 +1770,10 @@ var MikroOrmQueryService = class extends RelationQueryService {
1469
1770
  * @param opts - Additional options.
1470
1771
  */
1471
1772
  async updateOne(id, update, opts) {
1472
- this.ensureIdIsNotPresent(update);
1773
+ const dateWithClearUndefined = Object.fromEntries(Object.entries(update).filter(([, value]) => value !== void 0));
1774
+ this.ensureIdIsNotPresent(dateWithClearUndefined);
1473
1775
  const entity = await this.getById(id, opts);
1474
- wrap(entity).assign(update);
1776
+ wrap(entity).assign(dateWithClearUndefined);
1475
1777
  await this.repo.getEntityManager().flush();
1476
1778
  return entity;
1477
1779
  }
@@ -1712,6 +2014,6 @@ var NestjsQueryMikroOrmModule = class _NestjsQueryMikroOrmModule {
1712
2014
  }
1713
2015
  };
1714
2016
 
1715
- export { AggregateBuilder, FilterQueryBuilder, MikroOrmQueryService, NestjsQueryMikroOrmModule, RelationQueryBuilder, RelationQueryService, SQLComparisonBuilder, WhereBuilder, createMikroOrmQueryServiceProviders };
2017
+ export { AggregateBuilder, ComparisonBuilder, FilterQueryBuilder, MikroOrmQueryService, NestjsQueryMikroOrmModule, RelationQueryBuilder, RelationQueryService, WhereBuilder, createMikroOrmQueryServiceProviders };
1716
2018
  //# sourceMappingURL=index.mjs.map
1717
2019
  //# sourceMappingURL=index.mjs.map