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/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/core';
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/core/dist/src/assemblers/assembler.serializer';
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
- 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,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.convertAsyncToDTOs(relationQueryBuilder.selectAndExecute(dto, assembler.convertQuery(query)));
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/core`.
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 qb = this.filterQueryBuilder.select(query);
1376
- await qb.applyFilters();
1377
- return qb.getResultList();
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 qb = this.filterQueryBuilder.aggregate({
1547
+ const { filterQuery } = this.filterQueryBuilder.buildFindOptions({
1381
1548
  filter
1382
- }, aggregate);
1383
- await qb.applyFilters();
1384
- const rawResults = await qb.execute();
1385
- return AggregateBuilder.convertToAggregateResponse(rawResults);
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 qb = this.filterQueryBuilder.select({
1676
+ const { filterQuery } = this.filterQueryBuilder.buildFindOptions({
1389
1677
  filter
1390
1678
  });
1391
- await qb.applyFilters();
1392
- return qb.getCount();
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 qb = this.filterQueryBuilder.selectById(id, opts ?? {});
1405
- await qb.applyFilters();
1406
- const result = await qb.getSingleResult();
1407
- return result ?? void 0;
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/core` Filter.
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/core` `Filter`.
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/core` `Filter`.
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, SQLComparisonBuilder, WhereBuilder, createMikroOrmQueryServiceProviders };
2039
+ export { AggregateBuilder, ComparisonBuilder, FilterQueryBuilder, MikroOrmQueryService, NestjsQueryMikroOrmModule, RelationQueryBuilder, RelationQueryService, WhereBuilder, createMikroOrmQueryServiceProviders };
1717
2040
  //# sourceMappingURL=index.mjs.map
1718
2041
  //# sourceMappingURL=index.mjs.map