remult 0.27.8-next.2 → 0.27.8-next.4

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.
@@ -7,7 +7,7 @@ import { Sort } from '../src/sort.js';
7
7
  import { ValueConverters } from '../src/valueConverters.js';
8
8
  import { getRepositoryInternals } from '../src/remult3/repository-internals.js';
9
9
  import { remultStatic } from '../src/remult-static.js';
10
- import { aggregateImpl, getRowAfterUpdate, } from '../src/data-providers/sql-database.js';
10
+ import { groupByImpl, getRowAfterUpdate, } from '../src/data-providers/sql-database.js';
11
11
  export class KnexDataProvider {
12
12
  knex;
13
13
  constructor(knex) {
@@ -117,8 +117,8 @@ class KnexEntityDataProvider {
117
117
  this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
118
118
  this.dp = dp;
119
119
  }
120
- async aggregate(options) {
121
- return aggregateImpl(options, await this.init(), this.dp.createCommand(), false, (limit, offset) => ' limit ' + limit + ' offset ' + offset);
120
+ async groupBy(options) {
121
+ return groupByImpl(options, await this.init(), this.dp.createCommand(), false, (limit, offset) => ' limit ' + limit + ' offset ' + offset);
122
122
  }
123
123
  async count(where) {
124
124
  const e = await this.init();
@@ -5,7 +5,7 @@ import { remult as remultContext } from './src/remult-proxy.js';
5
5
  import { getRepository } from './src/remult3/RepositoryImplementation.js';
6
6
  import { getRepositoryInternals } from './src/remult3/repository-internals.js';
7
7
  import { getRowAfterUpdate } from './src/data-providers/sql-database.js';
8
- import { AggregateCountMember } from './src/remult3/remult3.js';
8
+ import { GroupByCountMember, GroupByOperators } from './src/remult3/remult3.js';
9
9
  export class MongoDataProvider {
10
10
  db;
11
11
  client;
@@ -98,7 +98,7 @@ class MongoEntityDataProvider {
98
98
  let w = await x.resolveWhere();
99
99
  return await collection.countDocuments(w, { session: this.session });
100
100
  }
101
- async aggregate(options) {
101
+ async groupBy(options) {
102
102
  let { collection, e } = await this.collection();
103
103
  let x = new FilterConsumerBridgeToMongo(e);
104
104
  const pipeLine = [];
@@ -112,12 +112,14 @@ class MongoEntityDataProvider {
112
112
  __count: { $sum: 1 },
113
113
  };
114
114
  processResultRow.push((mongoRow, resultRow) => {
115
- resultRow[AggregateCountMember] = mongoRow.__count;
115
+ resultRow[GroupByCountMember] = mongoRow.__count;
116
116
  });
117
117
  pipeLine.push({ $group });
118
- if (options?.groupBy) {
118
+ const $addFields = {};
119
+ pipeLine.push({ $addFields });
120
+ if (options?.group) {
119
121
  $group._id = {};
120
- for (const element of options.groupBy) {
122
+ for (const element of options.group) {
121
123
  const name = e.$dbNameOf(element);
122
124
  $group._id[name] = `$${name}`;
123
125
  processResultRow.push((mongoRow, resultRow) => {
@@ -128,28 +130,23 @@ class MongoEntityDataProvider {
128
130
  else {
129
131
  $group._id = null;
130
132
  }
131
- if (options?.sum) {
132
- for (const element of options.sum) {
133
- const name = e.$dbNameOf(element) + '_sum';
134
- $group[name] = { $sum: `$${e.$dbNameOf(element)}` };
135
- processResultRow.push((mongoRow, resultRow) => {
136
- resultRow[element.key] = {
137
- ...resultRow[element.key],
138
- sum: element.valueConverter.fromDb(mongoRow[name]),
139
- };
140
- });
141
- }
142
- }
143
- if (options?.avg) {
144
- for (const element of options.avg) {
145
- const name = e.$dbNameOf(element) + '_avg';
146
- $group[name] = { $avg: `$${e.$dbNameOf(element)}` };
147
- processResultRow.push((mongoRow, resultRow) => {
148
- resultRow[element.key] = {
149
- ...resultRow[element.key],
150
- avg: element.valueConverter.fromDb(mongoRow[name]),
151
- };
152
- });
133
+ for (const operator of GroupByOperators) {
134
+ if (options?.[operator]) {
135
+ for (const element of options[operator]) {
136
+ const name = e.$dbNameOf(element) + '_' + operator;
137
+ if (operator === 'distinctCount') {
138
+ $group[name + '_temp'] = { $addToSet: `$${e.$dbNameOf(element)}` };
139
+ $addFields[name] = { $size: `$${name + '_temp'}` };
140
+ }
141
+ else
142
+ $group[name] = { ['$' + operator]: `$${e.$dbNameOf(element)}` };
143
+ processResultRow.push((mongoRow, resultRow) => {
144
+ resultRow[element.key] = {
145
+ ...resultRow[element.key],
146
+ [operator]: mongoRow[name],
147
+ };
148
+ });
149
+ }
153
150
  }
154
151
  }
155
152
  if (options?.orderBy) {
@@ -157,17 +154,15 @@ class MongoEntityDataProvider {
157
154
  for (const x of options.orderBy) {
158
155
  const direction = x.isDescending ? -1 : 1;
159
156
  switch (x.operation) {
160
- case 'sum':
161
- sort[e.$dbNameOf(x.field) + '_sum'] = direction;
162
- break;
163
- case 'avg':
164
- sort[e.$dbNameOf(x.field) + '_avg'] = direction;
165
- break;
166
157
  case 'count':
167
158
  sort['__count'] = direction;
168
159
  break;
169
- default:
160
+ case undefined:
170
161
  sort['_id.' + e.$dbNameOf(x.field)] = direction;
162
+ break;
163
+ default:
164
+ sort[e.$dbNameOf(x.field) + '_' + x.operation] = direction;
165
+ break;
171
166
  }
172
167
  }
173
168
  pipeLine.push({ $sort: sort });
@@ -2,7 +2,7 @@ import { doTransaction } from './context.js';
2
2
  import { findOptionsToJson, liveQueryAction, } from './data-providers/rest-data-provider.js';
3
3
  import { Filter, buildFilterFromRequestParameters, customUrlToken, } from './filter/filter-interfaces.js';
4
4
  import { getRelationFieldInfo } from './remult3/relationInfoMember.js';
5
- import { AggregateCountMember, } from './remult3/remult3.js';
5
+ import { GroupByCountMember, GroupByForApiKey, } from './remult3/remult3.js';
6
6
  import { ForbiddenError } from './server-action.js';
7
7
  export class DataApi {
8
8
  repository;
@@ -19,8 +19,8 @@ export class DataApi {
19
19
  case 'get':
20
20
  case 'count':
21
21
  return this.count(res, req, undefined);
22
- case 'aggregate':
23
- return this.aggregate(res, req, undefined);
22
+ case 'groupBy':
23
+ return this.groupBy(res, req, undefined);
24
24
  }
25
25
  return this.getArray(res, req, undefined);
26
26
  }
@@ -45,8 +45,8 @@ export class DataApi {
45
45
  case 'count':
46
46
  validateWhereInBody();
47
47
  return this.count(res, req, body);
48
- case 'aggregate':
49
- return this.aggregate(res, req, body);
48
+ case 'groupBy':
49
+ return this.groupBy(res, req, body);
50
50
  case 'deleteMany':
51
51
  validateWhereInBody();
52
52
  return this.deleteMany(res, req, body);
@@ -103,46 +103,49 @@ export class DataApi {
103
103
  response.error(err, this.repository.metadata);
104
104
  }
105
105
  }
106
- async aggregate(response, request, body) {
106
+ async groupBy(response, request, body) {
107
107
  let findOptions = await this.findOptionsFromRequest(request, body);
108
108
  let orderBy = {};
109
109
  if (body?.orderBy) {
110
110
  for (const element of body?.orderBy) {
111
111
  const direction = element.isDescending ? 'desc' : 'asc';
112
112
  switch (element.operation) {
113
- case 'sum':
114
- orderBy[element.field] = {
115
- ...orderBy[element.field],
116
- sum: direction,
117
- };
118
- break;
119
- case 'avg':
120
- orderBy[element.field] = {
121
- ...orderBy[element.field],
122
- avg: direction,
123
- };
113
+ case undefined:
114
+ orderBy[element.field] = direction;
124
115
  break;
125
116
  case 'count':
126
- orderBy[AggregateCountMember] = direction;
117
+ orderBy[GroupByCountMember] = direction;
127
118
  break;
128
119
  default:
129
- orderBy[element.field] = direction;
120
+ orderBy[element.field] = {
121
+ ...orderBy[element.field],
122
+ [element.operation]: direction,
123
+ };
130
124
  break;
131
125
  }
132
126
  }
133
127
  }
134
- let result = await this.repository.aggregate({
128
+ const group = body?.groupBy?.filter((x) => this.repository.fields.find(x).includedInApi());
129
+ let result = await this.repository.groupBy({
135
130
  where: findOptions.where,
136
131
  limit: findOptions.limit,
137
132
  page: findOptions.page,
138
- groupBy: body?.groupBy?.filter((x) => this.repository.fields.find(x).includedInApi()),
133
+ //@ts-expect-error internal key
134
+ [GroupByForApiKey]: true,
135
+ group,
139
136
  sum: body?.sum?.filter((x) => this.repository.fields.find(x).includedInApi()),
140
137
  avg: body?.avg?.filter((x) => this.repository.fields.find(x).includedInApi()),
138
+ min: body?.min?.filter((x) => this.repository.fields.find(x).includedInApi()),
139
+ max: body?.max?.filter((x) => this.repository.fields.find(x).includedInApi()),
140
+ distinctCount: body?.distinctCount?.filter((x) => this.repository.fields.find(x).includedInApi()),
141
141
  orderBy: orderBy,
142
142
  });
143
- if (!Array.isArray(result)) {
144
- result = [result];
145
- }
143
+ if (group)
144
+ result.forEach((x) => {
145
+ for (const f of group) {
146
+ x[f] = this.repository.fields.find(f).valueConverter.toJson(x[f]);
147
+ }
148
+ });
146
149
  response.success(result);
147
150
  }
148
151
  async getArrayImpl(response, request, body) {
@@ -1,7 +1,7 @@
1
1
  import { CompoundIdField } from '../CompoundIdField.js';
2
2
  import { dbNamesOf, isDbReadonly, } from '../filter/filter-consumer-bridge-to-sql-request.js';
3
3
  import { Filter, customDatabaseFilterToken, } from '../filter/filter-interfaces.js';
4
- import { AggregateCountMember, } from '../remult3/remult3.js';
4
+ import { GroupByCountMember, GroupByOperators, } from '../remult3/remult3.js';
5
5
  import { compareForSort, Sort } from '../sort.js';
6
6
  export class ArrayEntityDataProvider {
7
7
  entity;
@@ -17,10 +17,10 @@ export class ArrayEntityDataProvider {
17
17
  this.entity = entity;
18
18
  this.rows = rows;
19
19
  }
20
- async aggregate(options) {
20
+ async groupBy(options) {
21
21
  const sort = new Sort();
22
- if (options?.groupBy)
23
- for (const field of options?.groupBy) {
22
+ if (options?.group)
23
+ for (const field of options?.group) {
24
24
  sort.Segments.push({ field: field });
25
25
  }
26
26
  const rows = await this.find({ orderBy: sort, where: options?.where });
@@ -29,69 +29,110 @@ export class ArrayEntityDataProvider {
29
29
  let first = true;
30
30
  let count = 0;
31
31
  let aggregates = [];
32
- if (options?.sum) {
33
- class Sum {
34
- key;
35
- constructor(key) {
36
- this.key = key;
37
- }
38
- sum = 0;
39
- process(row) {
40
- const val = row[this.key];
41
- if (val !== undefined && val !== null)
42
- this.sum += row[this.key];
43
- }
44
- finishGroup(result) {
45
- result[this.key] = { ...result[this.key], sum: this.sum };
46
- this.sum = 0;
47
- }
48
- }
49
- for (const element of options.sum) {
50
- aggregates.push(new Sum(element.key));
51
- }
52
- }
53
- if (options?.avg) {
54
- class Avg {
55
- key;
56
- constructor(key) {
57
- this.key = key;
58
- }
59
- sum = 0;
60
- count = 0;
61
- process(row) {
62
- const val = row[this.key];
63
- if (val !== undefined && val !== null) {
64
- this.sum += row[this.key];
65
- this.count++;
66
- }
67
- }
68
- finishGroup(result) {
69
- result[this.key] = {
70
- ...result[this.key],
71
- avg: this.sum / this.count,
72
- };
73
- this.sum = 0;
74
- this.count = 0;
32
+ const operatorImpl = {
33
+ sum: (key) => {
34
+ let sum = 0;
35
+ return {
36
+ process(row) {
37
+ const val = row[key];
38
+ if (val !== undefined && val !== null)
39
+ sum += row[key];
40
+ },
41
+ finishGroup(result) {
42
+ result[key] = { ...result[key], sum };
43
+ sum = 0;
44
+ },
45
+ };
46
+ },
47
+ avg: (key) => {
48
+ let sum = 0;
49
+ let count = 0;
50
+ return {
51
+ process(row) {
52
+ const val = row[key];
53
+ if (val !== undefined && val !== null) {
54
+ sum += row[key];
55
+ count++;
56
+ }
57
+ },
58
+ finishGroup(result) {
59
+ result[key] = {
60
+ ...result[key],
61
+ avg: sum / count,
62
+ };
63
+ sum = 0;
64
+ count = 0;
65
+ },
66
+ };
67
+ },
68
+ min: (key) => {
69
+ let min = undefined;
70
+ return {
71
+ process(row) {
72
+ const val = row[key];
73
+ if (val !== undefined && val !== null) {
74
+ if (min === undefined || val < min)
75
+ min = val;
76
+ }
77
+ },
78
+ finishGroup(result) {
79
+ result[key] = { ...result[key], min };
80
+ min = undefined;
81
+ },
82
+ };
83
+ },
84
+ max: (key) => {
85
+ let max = undefined;
86
+ return {
87
+ process(row) {
88
+ const val = row[key];
89
+ if (val !== undefined && val !== null) {
90
+ if (max === undefined || val > max)
91
+ max = val;
92
+ }
93
+ },
94
+ finishGroup(result) {
95
+ result[key] = { ...result[key], max };
96
+ max = undefined;
97
+ },
98
+ };
99
+ },
100
+ distinctCount: (key) => {
101
+ let distinct = new Set();
102
+ return {
103
+ process(row) {
104
+ const val = row[key];
105
+ if (val !== undefined && val !== null)
106
+ distinct.add(val);
107
+ },
108
+ finishGroup(result) {
109
+ result[key] = { ...result[key], distinctCount: distinct.size };
110
+ distinct.clear();
111
+ },
112
+ };
113
+ },
114
+ };
115
+ for (let operator of GroupByOperators) {
116
+ if (options?.[operator]) {
117
+ for (const element of options[operator]) {
118
+ aggregates.push(operatorImpl[operator](element.key));
75
119
  }
76
120
  }
77
- for (const element of options.avg) {
78
- aggregates.push(new Avg(element.key));
79
- }
80
121
  }
81
122
  function finishGroup() {
82
123
  const r = { ...group, $count: count };
83
124
  for (const a of aggregates) {
84
125
  a.finishGroup(r);
85
126
  }
86
- r[AggregateCountMember] = count;
127
+ r[GroupByCountMember] = count;
87
128
  result.push(r);
88
129
  first = true;
89
130
  count = 0;
90
131
  }
91
132
  for (const row of rows) {
92
- if (options?.groupBy) {
133
+ if (options?.group) {
93
134
  if (!first) {
94
- for (const field of options?.groupBy) {
135
+ for (const field of options?.group) {
95
136
  if (group[field.key] != row[field.key]) {
96
137
  finishGroup();
97
138
  break;
@@ -99,7 +140,7 @@ export class ArrayEntityDataProvider {
99
140
  }
100
141
  }
101
142
  if (first) {
102
- for (const field of options?.groupBy) {
143
+ for (const field of options?.group) {
103
144
  group[field.key] = row[field.key];
104
145
  }
105
146
  }
@@ -116,17 +157,18 @@ export class ArrayEntityDataProvider {
116
157
  for (const x of options.orderBy) {
117
158
  const getValue = (row) => {
118
159
  if (!x.field && x.operation == 'count') {
119
- return row[AggregateCountMember];
160
+ return row[GroupByCountMember];
120
161
  }
121
162
  else {
122
163
  switch (x.operation) {
123
- case 'sum':
124
- return row[x.field.key].sum;
125
- case 'avg':
126
- return row[x.field.key].avg;
164
+ case 'count':
165
+ return row[GroupByCountMember];
166
+ case undefined:
167
+ return row[x.field.key];
168
+ default:
169
+ return row[x.field.key][x.operation];
127
170
  }
128
171
  }
129
- return row[x.field.key];
130
172
  };
131
173
  let compare = compareForSort(getValue(a), getValue(b), x.isDescending);
132
174
  if (compare != 0)
@@ -22,8 +22,8 @@ class JsonEntityDataProvider {
22
22
  this.helper = helper;
23
23
  this.formatted = formatted;
24
24
  }
25
- aggregate(options) {
26
- return (this.p = this.p.then(() => this.loadEntityData((dp, save) => dp.aggregate(options))));
25
+ groupBy(options) {
26
+ return (this.p = this.p.then(() => this.loadEntityData((dp, save) => dp.groupBy(options))));
27
27
  }
28
28
  async loadEntityData(what) {
29
29
  let data = [];
@@ -101,22 +101,25 @@ export class RestEntityDataProvider {
101
101
  this.http = http;
102
102
  this.entity = entity;
103
103
  }
104
- async aggregate(options) {
104
+ async groupBy(options) {
105
105
  const { run } = this.buildFindRequest({
106
106
  where: options?.where,
107
107
  limit: options?.limit,
108
108
  page: options?.page,
109
109
  });
110
110
  const body = {
111
- groupBy: options?.groupBy?.map((x) => x.key),
111
+ groupBy: options?.group?.map((x) => x.key),
112
112
  sum: options?.sum?.map((x) => x.key),
113
113
  avg: options?.avg?.map((x) => x.key),
114
+ min: options?.min?.map((x) => x.key),
115
+ max: options?.max?.map((x) => x.key),
116
+ distinctCount: options?.distinctCount?.map((x) => x.key),
114
117
  orderBy: options?.orderBy?.map((x) => ({ ...x, field: x.field?.key })),
115
118
  };
116
- const result = await run('aggregate', Object.keys(body).length > 0 ? body : undefined);
117
- if (options?.groupBy)
119
+ const result = await run('groupBy', Object.keys(body).length > 0 ? body : undefined);
120
+ if (options?.group)
118
121
  result.forEach((row) => {
119
- for (const g of options.groupBy) {
122
+ for (const g of options.group) {
120
123
  row[g.key] = g.valueConverter.fromJson(row[g.key]);
121
124
  }
122
125
  });
@@ -1,7 +1,7 @@
1
1
  import { FilterConsumerBridgeToSqlRequest, dbNamesOfWithForceSqlExpression, isDbReadonly, } from '../filter/filter-consumer-bridge-to-sql-request.js';
2
2
  import { Filter, customDatabaseFilterToken, } from '../filter/filter-interfaces.js';
3
3
  import { remult as defaultRemult } from '../remult-proxy.js';
4
- import { AggregateCountMember, } from '../remult3/remult3.js';
4
+ import { GroupByCountMember, GroupByOperators, } from '../remult3/remult3.js';
5
5
  import { getRepository, isAutoIncrement, } from '../remult3/RepositoryImplementation.js';
6
6
  import { Sort } from '../sort.js';
7
7
  import { ValueConverters } from '../valueConverters.js';
@@ -287,8 +287,8 @@ class ActualSQLEntityDataProvider {
287
287
  return Number(r.rows[0].count);
288
288
  });
289
289
  }
290
- async aggregate(options) {
291
- return await aggregateImpl(options, await this.init(), this.sql.createCommand(), this.sql._getSourceSql().orderByNullsFirst, this.sql._getSourceSql().getLimitSqlSyntax);
290
+ async groupBy(options) {
291
+ return await groupByImpl(options, await this.init(), this.sql.createCommand(), this.sql._getSourceSql().orderByNullsFirst, this.sql._getSourceSql().getLimitSqlSyntax);
292
292
  }
293
293
  async find(options) {
294
294
  let e = await this.init();
@@ -541,15 +541,15 @@ export function getRowAfterUpdate(meta, dataProvider, data, id, operation) {
541
541
  return r[0];
542
542
  });
543
543
  }
544
- export async function aggregateImpl(options, e, r, orderByNullFirst, limitSyntax) {
544
+ export async function groupByImpl(options, e, r, orderByNullFirst, limitSyntax) {
545
545
  let select = 'select count(*) as count';
546
546
  let groupBy = '';
547
547
  const processResultRow = [];
548
548
  processResultRow.push((sqlVal, theResult) => {
549
- theResult[AggregateCountMember] = Number(sqlVal);
549
+ theResult[GroupByCountMember] = Number(sqlVal);
550
550
  });
551
- if (options?.groupBy)
552
- for (const x of options?.groupBy) {
551
+ if (options?.group)
552
+ for (const x of options?.group) {
553
553
  if (x.isServerExpression) {
554
554
  }
555
555
  else {
@@ -566,28 +566,21 @@ export async function aggregateImpl(options, e, r, orderByNullFirst, limitSyntax
566
566
  theResult[x.key] = x.valueConverter.fromDb(sqlResult);
567
567
  });
568
568
  }
569
- if (options?.sum)
570
- for (const x of options?.sum) {
571
- if (x.isServerExpression) {
572
- }
573
- else {
574
- select += ', sum(' + e.$dbNameOf(x) + ') as ' + x.key + '_sum';
575
- }
576
- processResultRow.push((sqlResult, theResult) => {
577
- theResult[x.key] = { ...theResult[x.key], sum: sqlResult };
578
- });
579
- }
580
- if (options?.avg)
581
- for (const x of options?.avg) {
582
- if (x.isServerExpression) {
583
- }
584
- else {
585
- select += ', avg(' + e.$dbNameOf(x) + ') as ' + x.key + '_avg';
569
+ for (const operator of GroupByOperators) {
570
+ const fields = options?.[operator];
571
+ if (fields)
572
+ for (const x of fields) {
573
+ if (x.isServerExpression) {
574
+ }
575
+ else {
576
+ const dbName = await e.$dbNameOf(x);
577
+ select += `, ${aggregateSqlSyntax(operator, dbName)} as ${x.key}_${operator}`;
578
+ }
579
+ processResultRow.push((sqlResult, theResult) => {
580
+ theResult[x.key] = { ...theResult[x.key], [operator]: sqlResult };
581
+ });
586
582
  }
587
- processResultRow.push((sqlResult, theResult) => {
588
- theResult[x.key] = { ...theResult[x.key], avg: sqlResult };
589
- });
590
- }
583
+ }
591
584
  select += '\n from ' + e.$entityName;
592
585
  if (options?.where) {
593
586
  let where = new FilterConsumerBridgeToSqlRequest(r, e);
@@ -603,21 +596,17 @@ export async function aggregateImpl(options, e, r, orderByNullFirst, limitSyntax
603
596
  orderBy = ' order by ';
604
597
  else
605
598
  orderBy += ', ';
606
- if (!x.field && x.operation == 'count') {
607
- orderBy += x.operation + '(*)';
608
- }
609
- else {
610
- let field = await e.$dbNameOf(x.field);
611
- switch (x.operation) {
612
- case 'sum':
613
- field = 'sum(' + field + ')';
614
- break;
615
- case 'avg':
616
- field = 'avg(' + field + ')';
617
- break;
618
- }
619
- orderBy += field;
599
+ let field = x.field && (await e.$dbNameOf(x.field));
600
+ switch (x.operation) {
601
+ case 'count':
602
+ field = x.operation + '(*)';
603
+ break;
604
+ case undefined:
605
+ break;
606
+ default:
607
+ field = aggregateSqlSyntax(x.operation, field);
620
608
  }
609
+ orderBy += field;
621
610
  if (x.isDescending)
622
611
  orderBy += ' desc';
623
612
  if (orderByNullFirst) {
@@ -644,4 +633,9 @@ export async function aggregateImpl(options, e, r, orderByNullFirst, limitSyntax
644
633
  processResultRow.forEach((x, i) => x(sql[result.getColumnKeyInResultForIndexInSelect(i)], theResult));
645
634
  return theResult;
646
635
  });
636
+ function aggregateSqlSyntax(operator, dbName) {
637
+ return operator === 'distinctCount'
638
+ ? `count (distinct ${dbName})`
639
+ : `${operator}( ${dbName} )`;
640
+ }
647
641
  }
@@ -112,6 +112,11 @@ export class RemultProxy {
112
112
  .remultFactory()
113
113
  .repo(...args)
114
114
  .find(...args2),
115
+ groupBy: (...args2) => self
116
+ .remultFactory()
117
+ .repo(...args)
118
+ //@ts-ignore
119
+ .groupBy(...args2),
115
120
  aggregate: (...args2) => self
116
121
  .remultFactory()
117
122
  .repo(...args)