remult 0.27.7 → 0.27.8-next.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/esm/remult-knex/index.js +3 -0
- package/esm/remult-mongo.js +3 -0
- package/esm/src/data-api.js +54 -5
- package/esm/src/data-providers/array-entity-data-provider.js +122 -0
- package/esm/src/data-providers/json-data-provider.js +3 -0
- package/esm/src/data-providers/rest-data-provider.js +23 -1
- package/esm/src/data-providers/sql-database.js +109 -0
- package/esm/src/remult-proxy.js +5 -0
- package/esm/src/remult3/RepositoryImplementation.js +48 -1
- package/esm/src/remult3/remult3.js +7 -2
- package/esm/src/sort.js +18 -11
- package/package.json +1 -1
- package/remult-knex/index.js +3 -0
- package/remult-mongo.js +3 -0
- package/src/data-api.d.ts +3 -1
- package/src/data-api.js +122 -38
- package/src/data-interfaces.d.ts +12 -0
- package/src/data-providers/array-entity-data-provider.d.ts +3 -2
- package/src/data-providers/array-entity-data-provider.js +252 -25
- package/src/data-providers/json-data-provider.js +6 -0
- package/src/data-providers/rest-data-provider.d.ts +2 -1
- package/src/data-providers/rest-data-provider.js +57 -13
- package/src/data-providers/sql-database.d.ts +1 -1
- package/src/data-providers/sql-database.js +214 -20
- package/src/remult-proxy.js +11 -0
- package/src/remult3/RepositoryImplementation.d.ts +6 -1
- package/src/remult3/RepositoryImplementation.js +62 -0
- package/src/remult3/remult3.d.ts +128 -0
- package/src/remult3/remult3.js +8 -3
- package/src/sort.d.ts +2 -0
- package/src/sort.js +19 -10
package/esm/remult-knex/index.js
CHANGED
|
@@ -115,6 +115,9 @@ class KnexEntityDataProvider {
|
|
|
115
115
|
this.knex = knex;
|
|
116
116
|
this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
|
|
117
117
|
}
|
|
118
|
+
aggregate(options) {
|
|
119
|
+
throw new Error('Method not implemented.');
|
|
120
|
+
}
|
|
118
121
|
async count(where) {
|
|
119
122
|
const e = await this.init();
|
|
120
123
|
const br = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
|
package/esm/remult-mongo.js
CHANGED
|
@@ -70,6 +70,9 @@ class MongoEntityDataProvider {
|
|
|
70
70
|
this.entity = entity;
|
|
71
71
|
this.session = session;
|
|
72
72
|
}
|
|
73
|
+
aggregate(options) {
|
|
74
|
+
throw new Error('Method not implemented.');
|
|
75
|
+
}
|
|
73
76
|
translateFromDb(row, nameProvider) {
|
|
74
77
|
let result = {};
|
|
75
78
|
for (const col of this.entity.fields) {
|
package/esm/src/data-api.js
CHANGED
|
@@ -2,6 +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
6
|
import { ForbiddenError } from './server-action.js';
|
|
6
7
|
export class DataApi {
|
|
7
8
|
repository;
|
|
@@ -18,6 +19,8 @@ export class DataApi {
|
|
|
18
19
|
case 'get':
|
|
19
20
|
case 'count':
|
|
20
21
|
return this.count(res, req, undefined);
|
|
22
|
+
case 'aggregate':
|
|
23
|
+
return this.aggregate(res, req, undefined);
|
|
21
24
|
}
|
|
22
25
|
return this.getArray(res, req, undefined);
|
|
23
26
|
}
|
|
@@ -42,6 +45,8 @@ export class DataApi {
|
|
|
42
45
|
case 'count':
|
|
43
46
|
validateWhereInBody();
|
|
44
47
|
return this.count(res, req, body);
|
|
48
|
+
case 'aggregate':
|
|
49
|
+
return this.aggregate(res, req, body);
|
|
45
50
|
case 'deleteMany':
|
|
46
51
|
validateWhereInBody();
|
|
47
52
|
return this.deleteMany(res, req, body);
|
|
@@ -98,7 +103,55 @@ export class DataApi {
|
|
|
98
103
|
response.error(err, this.repository.metadata);
|
|
99
104
|
}
|
|
100
105
|
}
|
|
106
|
+
async aggregate(response, request, body) {
|
|
107
|
+
let findOptions = await this.findOptionsFromRequest(request, body);
|
|
108
|
+
let orderBy = {};
|
|
109
|
+
if (body?.orderBy) {
|
|
110
|
+
for (const element of body?.orderBy) {
|
|
111
|
+
const direction = element.isDescending ? 'desc' : 'asc';
|
|
112
|
+
switch (element.operation) {
|
|
113
|
+
case 'sum':
|
|
114
|
+
orderBy[element.field] = {
|
|
115
|
+
...orderBy[element.field],
|
|
116
|
+
sum: direction,
|
|
117
|
+
};
|
|
118
|
+
break;
|
|
119
|
+
case 'average':
|
|
120
|
+
orderBy[element.field] = {
|
|
121
|
+
...orderBy[element.field],
|
|
122
|
+
average: direction,
|
|
123
|
+
};
|
|
124
|
+
break;
|
|
125
|
+
case 'count':
|
|
126
|
+
orderBy[AggregateCountMember] = direction;
|
|
127
|
+
default:
|
|
128
|
+
orderBy[element.field] = direction;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
let result = await this.repository.aggregate({
|
|
134
|
+
where: findOptions.where,
|
|
135
|
+
limit: findOptions.limit,
|
|
136
|
+
page: findOptions.page,
|
|
137
|
+
groupBy: body?.groupBy?.filter((x) => this.repository.fields.find(x).includedInApi()),
|
|
138
|
+
sum: body?.sum?.filter((x) => this.repository.fields.find(x).includedInApi()),
|
|
139
|
+
average: body?.average?.filter((x) => this.repository.fields.find(x).includedInApi()),
|
|
140
|
+
orderBy: orderBy,
|
|
141
|
+
});
|
|
142
|
+
if (!Array.isArray(result)) {
|
|
143
|
+
result = [result];
|
|
144
|
+
}
|
|
145
|
+
response.success(result);
|
|
146
|
+
}
|
|
101
147
|
async getArrayImpl(response, request, body) {
|
|
148
|
+
let findOptions = await this.findOptionsFromRequest(request, body);
|
|
149
|
+
const r = await this.repository.find(findOptions).then(async (r) => {
|
|
150
|
+
return await Promise.all(r.map(async (y) => this.repository.getEntityRef(y).toApiJson()));
|
|
151
|
+
});
|
|
152
|
+
return { r, findOptions };
|
|
153
|
+
}
|
|
154
|
+
async findOptionsFromRequest(request, body) {
|
|
102
155
|
let findOptions = {
|
|
103
156
|
load: () => [],
|
|
104
157
|
include: this.includeNone(),
|
|
@@ -147,14 +200,10 @@ export class DataApi {
|
|
|
147
200
|
});
|
|
148
201
|
}
|
|
149
202
|
if (!hasId) {
|
|
150
|
-
response.forbidden();
|
|
151
203
|
throw new ForbiddenError();
|
|
152
204
|
}
|
|
153
205
|
}
|
|
154
|
-
|
|
155
|
-
return await Promise.all(r.map(async (y) => this.repository.getEntityRef(y).toApiJson()));
|
|
156
|
-
});
|
|
157
|
-
return { r, findOptions };
|
|
206
|
+
return findOptions;
|
|
158
207
|
}
|
|
159
208
|
includeNone() {
|
|
160
209
|
let include = {};
|
|
@@ -1,6 +1,8 @@
|
|
|
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';
|
|
5
|
+
import { compareForSort, Sort } from '../sort.js';
|
|
4
6
|
export class ArrayEntityDataProvider {
|
|
5
7
|
entity;
|
|
6
8
|
rows;
|
|
@@ -15,6 +17,126 @@ export class ArrayEntityDataProvider {
|
|
|
15
17
|
this.entity = entity;
|
|
16
18
|
this.rows = rows;
|
|
17
19
|
}
|
|
20
|
+
async aggregate(options) {
|
|
21
|
+
const sort = new Sort();
|
|
22
|
+
if (options?.groupBy)
|
|
23
|
+
for (const field of options?.groupBy) {
|
|
24
|
+
sort.Segments.push({ field: field });
|
|
25
|
+
}
|
|
26
|
+
const rows = await this.find({ orderBy: sort, where: options?.where });
|
|
27
|
+
let result = [];
|
|
28
|
+
let group = {};
|
|
29
|
+
let first = true;
|
|
30
|
+
let count = 0;
|
|
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?.average) {
|
|
54
|
+
class Average {
|
|
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
|
+
average: this.sum / this.count,
|
|
72
|
+
};
|
|
73
|
+
this.sum = 0;
|
|
74
|
+
this.count = 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const element of options.average) {
|
|
78
|
+
aggregates.push(new Average(element.key));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function finishGroup() {
|
|
82
|
+
const r = { ...group, $count: count };
|
|
83
|
+
for (const a of aggregates) {
|
|
84
|
+
a.finishGroup(r);
|
|
85
|
+
}
|
|
86
|
+
r[AggregateCountMember] = count;
|
|
87
|
+
result.push(r);
|
|
88
|
+
first = true;
|
|
89
|
+
count = 0;
|
|
90
|
+
}
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
if (options?.groupBy) {
|
|
93
|
+
if (!first) {
|
|
94
|
+
for (const field of options?.groupBy) {
|
|
95
|
+
if (group[field.key] != row[field.key]) {
|
|
96
|
+
finishGroup();
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (first) {
|
|
102
|
+
for (const field of options?.groupBy) {
|
|
103
|
+
group[field.key] = row[field.key];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const a of aggregates) {
|
|
108
|
+
a.process(row);
|
|
109
|
+
}
|
|
110
|
+
count++;
|
|
111
|
+
first = false;
|
|
112
|
+
}
|
|
113
|
+
finishGroup();
|
|
114
|
+
if (options?.orderBy) {
|
|
115
|
+
result.sort((a, b) => {
|
|
116
|
+
for (const x of options.orderBy) {
|
|
117
|
+
const getValue = (row) => {
|
|
118
|
+
if (!x.field && x.operation == 'count') {
|
|
119
|
+
return row[AggregateCountMember];
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
switch (x.operation) {
|
|
123
|
+
case 'sum':
|
|
124
|
+
return row[x.field.key].sum;
|
|
125
|
+
case 'average':
|
|
126
|
+
return row[x.field.key].average;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return row[x.field.key];
|
|
130
|
+
};
|
|
131
|
+
let compare = compareForSort(getValue(a), getValue(b), x.isDescending);
|
|
132
|
+
if (compare != 0)
|
|
133
|
+
return compare;
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return pageArray(result, { page: options?.page, limit: options?.limit });
|
|
139
|
+
}
|
|
18
140
|
//@internal
|
|
19
141
|
__names;
|
|
20
142
|
//@internal
|
|
@@ -22,6 +22,9 @@ 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))));
|
|
27
|
+
}
|
|
25
28
|
async loadEntityData(what) {
|
|
26
29
|
let data = [];
|
|
27
30
|
let dbName = await this.entity.dbName;
|
|
@@ -101,6 +101,27 @@ export class RestEntityDataProvider {
|
|
|
101
101
|
this.http = http;
|
|
102
102
|
this.entity = entity;
|
|
103
103
|
}
|
|
104
|
+
async aggregate(options) {
|
|
105
|
+
const { run } = this.buildFindRequest({
|
|
106
|
+
where: options?.where,
|
|
107
|
+
limit: options?.limit,
|
|
108
|
+
page: options?.page,
|
|
109
|
+
});
|
|
110
|
+
const body = {
|
|
111
|
+
groupBy: options?.groupBy?.map((x) => x.key),
|
|
112
|
+
sum: options?.sum?.map((x) => x.key),
|
|
113
|
+
average: options?.average?.map((x) => x.key),
|
|
114
|
+
orderBy: options?.orderBy?.map((x) => ({ ...x, field: x.field?.key })),
|
|
115
|
+
};
|
|
116
|
+
const result = await run('aggregate', Object.keys(body).length > 0 ? body : undefined);
|
|
117
|
+
if (options?.groupBy)
|
|
118
|
+
result.forEach((row) => {
|
|
119
|
+
for (const g of options.groupBy) {
|
|
120
|
+
row[g.key] = g.valueConverter.fromJson(row[g.key]);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
104
125
|
translateFromJson(row) {
|
|
105
126
|
let result = {};
|
|
106
127
|
for (const col of this.entity.fields) {
|
|
@@ -176,8 +197,9 @@ export class RestEntityDataProvider {
|
|
|
176
197
|
u.add('__action', action);
|
|
177
198
|
if (filterObject) {
|
|
178
199
|
body = { set: body, where: filterObject };
|
|
179
|
-
return this.http().post(u.url, body);
|
|
180
200
|
}
|
|
201
|
+
if (body)
|
|
202
|
+
return this.http().post(u.url, body);
|
|
181
203
|
else
|
|
182
204
|
return this.http()[method](u.url, body);
|
|
183
205
|
};
|
|
@@ -1,6 +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
5
|
import { getRepository, isAutoIncrement, } from '../remult3/RepositoryImplementation.js';
|
|
5
6
|
import { Sort } from '../sort.js';
|
|
6
7
|
import { ValueConverters } from '../valueConverters.js';
|
|
@@ -286,6 +287,114 @@ class ActualSQLEntityDataProvider {
|
|
|
286
287
|
return Number(r.rows[0].count);
|
|
287
288
|
});
|
|
288
289
|
}
|
|
290
|
+
async aggregate(options) {
|
|
291
|
+
let e = await this.init();
|
|
292
|
+
let select = 'select count(*) as count';
|
|
293
|
+
let groupBy = '';
|
|
294
|
+
const processResultRow = [];
|
|
295
|
+
processResultRow.push((sqlVal, theResult) => {
|
|
296
|
+
theResult[AggregateCountMember] = Number(sqlVal);
|
|
297
|
+
});
|
|
298
|
+
if (options?.groupBy)
|
|
299
|
+
for (const x of options?.groupBy) {
|
|
300
|
+
if (x.isServerExpression) {
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
select += ', ' + e.$dbNameOf(x);
|
|
304
|
+
if (x.options.sqlExpression)
|
|
305
|
+
select += ' as ' + x.key;
|
|
306
|
+
if (groupBy == '')
|
|
307
|
+
groupBy = ' group by ';
|
|
308
|
+
else
|
|
309
|
+
groupBy += ', ';
|
|
310
|
+
groupBy += e.$dbNameOf(x);
|
|
311
|
+
}
|
|
312
|
+
processResultRow.push((sqlResult, theResult) => {
|
|
313
|
+
theResult[x.key] = x.valueConverter.fromDb(sqlResult);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (options?.sum)
|
|
317
|
+
for (const x of options?.sum) {
|
|
318
|
+
if (x.isServerExpression) {
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
select += ', sum(' + e.$dbNameOf(x) + ') as ' + x.key + '_sum';
|
|
322
|
+
}
|
|
323
|
+
processResultRow.push((sqlResult, theResult) => {
|
|
324
|
+
theResult[x.key] = { ...theResult[x.key], sum: sqlResult };
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (options?.average)
|
|
328
|
+
for (const x of options?.average) {
|
|
329
|
+
if (x.isServerExpression) {
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
select += ', avg(' + e.$dbNameOf(x) + ') as ' + x.key + '_average';
|
|
333
|
+
}
|
|
334
|
+
processResultRow.push((sqlResult, theResult) => {
|
|
335
|
+
theResult[x.key] = { ...theResult[x.key], average: sqlResult };
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
select += '\n from ' + e.$entityName;
|
|
339
|
+
let r = this.sql.createCommand();
|
|
340
|
+
if (options?.where) {
|
|
341
|
+
let where = new FilterConsumerBridgeToSqlRequest(r, e);
|
|
342
|
+
options?.where.__applyToConsumer(where);
|
|
343
|
+
select += await where.resolveWhere();
|
|
344
|
+
}
|
|
345
|
+
if (groupBy)
|
|
346
|
+
select += groupBy;
|
|
347
|
+
let orderBy = '';
|
|
348
|
+
if (options?.orderBy) {
|
|
349
|
+
for (const x of options?.orderBy) {
|
|
350
|
+
if (orderBy == '')
|
|
351
|
+
orderBy = ' order by ';
|
|
352
|
+
else
|
|
353
|
+
orderBy += ', ';
|
|
354
|
+
if (!x.field && x.operation == 'count') {
|
|
355
|
+
orderBy += x.operation + '(*)';
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
let field = await e.$dbNameOf(x.field);
|
|
359
|
+
switch (x.operation) {
|
|
360
|
+
case 'sum':
|
|
361
|
+
field = 'sum(' + field + ')';
|
|
362
|
+
break;
|
|
363
|
+
case 'average':
|
|
364
|
+
field = 'avg(' + field + ')';
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
orderBy += field;
|
|
368
|
+
}
|
|
369
|
+
if (x.isDescending)
|
|
370
|
+
orderBy += ' desc';
|
|
371
|
+
if (this.sql._getSourceSql().orderByNullsFirst) {
|
|
372
|
+
if (x.isDescending)
|
|
373
|
+
select += ' nulls last';
|
|
374
|
+
else
|
|
375
|
+
select += ' nulls first';
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (orderBy)
|
|
379
|
+
select += orderBy;
|
|
380
|
+
}
|
|
381
|
+
if (options?.limit) {
|
|
382
|
+
let page = 1;
|
|
383
|
+
if (options.page)
|
|
384
|
+
page = options.page;
|
|
385
|
+
if (page < 1)
|
|
386
|
+
page = 1;
|
|
387
|
+
select +=
|
|
388
|
+
' ' +
|
|
389
|
+
this.strategy.getLimitSqlSyntax(options.limit, (page - 1) * options.limit);
|
|
390
|
+
}
|
|
391
|
+
const result = await r.execute(select);
|
|
392
|
+
return result.rows.map((sql) => {
|
|
393
|
+
let theResult = {};
|
|
394
|
+
processResultRow.forEach((x, i) => x(sql[result.getColumnKeyInResultForIndexInSelect(i)], theResult));
|
|
395
|
+
return theResult;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
289
398
|
async find(options) {
|
|
290
399
|
let e = await this.init();
|
|
291
400
|
let { colKeys, select } = this.buildSelect(e);
|
package/esm/src/remult-proxy.js
CHANGED
|
@@ -112,6 +112,11 @@ export class RemultProxy {
|
|
|
112
112
|
.remultFactory()
|
|
113
113
|
.repo(...args)
|
|
114
114
|
.find(...args2),
|
|
115
|
+
aggregate: (...args2) => self
|
|
116
|
+
.remultFactory()
|
|
117
|
+
.repo(...args)
|
|
118
|
+
//@ts-ignore
|
|
119
|
+
.aggregate(...args2),
|
|
115
120
|
findFirst: (...args2) => self
|
|
116
121
|
.remultFactory()
|
|
117
122
|
.repo(...args)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LookupColumn, makeTitle } from '../column.js';
|
|
2
2
|
import { CompoundIdField } from '../CompoundIdField.js';
|
|
3
|
-
import { Remult, isBackend, queryConfig
|
|
3
|
+
import { Remult, isBackend, queryConfig } from '../context.js';
|
|
4
4
|
import { Filter } from '../filter/filter-interfaces.js';
|
|
5
5
|
import { Sort } from '../sort.js';
|
|
6
6
|
import { assign } from '../../assign.js';
|
|
@@ -127,6 +127,53 @@ export class RepositoryImplementation {
|
|
|
127
127
|
this._info = _info;
|
|
128
128
|
this._defaultFindOptions = _defaultFindOptions;
|
|
129
129
|
}
|
|
130
|
+
async aggregate(options) {
|
|
131
|
+
let findOptions = await this._buildEntityDataProviderFindOptions({
|
|
132
|
+
...options,
|
|
133
|
+
});
|
|
134
|
+
var dpOptions = {
|
|
135
|
+
where: findOptions.where,
|
|
136
|
+
limit: findOptions.limit,
|
|
137
|
+
page: findOptions.page,
|
|
138
|
+
groupBy: options?.groupBy?.map((f) => this.metadata.fields.find(f)),
|
|
139
|
+
sum: options?.sum?.map((f) => this.metadata.fields.find(f)),
|
|
140
|
+
average: options?.average?.map((f) => this.metadata.fields.find(f)),
|
|
141
|
+
};
|
|
142
|
+
if (options?.orderBy) {
|
|
143
|
+
dpOptions.orderBy = [];
|
|
144
|
+
for (const key in options.orderBy) {
|
|
145
|
+
if (Object.prototype.hasOwnProperty.call(options?.orderBy, key)) {
|
|
146
|
+
const element = options.orderBy[key];
|
|
147
|
+
if (element)
|
|
148
|
+
if (typeof element === 'string') {
|
|
149
|
+
dpOptions.orderBy.push({
|
|
150
|
+
field: key === '$count'
|
|
151
|
+
? undefined
|
|
152
|
+
: this.metadata.fields.find(key),
|
|
153
|
+
isDescending: element === 'desc',
|
|
154
|
+
operation: key === '$count' ? 'count' : undefined,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
for (const operation in element) {
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(element, operation)) {
|
|
160
|
+
const direction = element[operation];
|
|
161
|
+
dpOptions.orderBy.push({
|
|
162
|
+
field: this.metadata.fields.find(key),
|
|
163
|
+
isDescending: direction === 'desc',
|
|
164
|
+
operation: operation,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const result = await this._edp.aggregate(dpOptions);
|
|
173
|
+
if (!options?.groupBy)
|
|
174
|
+
return result[0];
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
130
177
|
_idCache = new Map();
|
|
131
178
|
_getCachedById(id, doNotLoadIfNotFound) {
|
|
132
179
|
id = id + '';
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
export const AggregateCountMember = '$count';
|
|
1
2
|
export const flags = {
|
|
2
3
|
error500RetryCount: 4,
|
|
3
4
|
};
|
|
4
|
-
//p1 - add transactional option to backend method
|
|
5
5
|
//p1 - add parameter all to deleteMany, and updateMany
|
|
6
6
|
//p1 filter.apply ApiPreFilter
|
|
7
|
-
//p1 -
|
|
7
|
+
//p1 - add not as an overload to not equal
|
|
8
|
+
/*p1 - min, max, avg, sum, countDistinct - noam come up with an api
|
|
9
|
+
p1 - handle relations
|
|
10
|
+
p1 - mongodb
|
|
11
|
+
p1 - knex
|
|
12
|
+
*/
|
|
8
13
|
/*p1 - add id and use uuid by default, but allow changes with Fields.id.defaultIdProvider NO but defaultProvider yes???
|
|
9
14
|
//p1 - replace uuid with crypto.randomUUID and allow custom fallback NO
|
|
10
15
|
//p1 - Add example for luid
|
package/esm/src/sort.js
CHANGED
|
@@ -57,22 +57,17 @@ export class Sort {
|
|
|
57
57
|
compare(a, b, getFieldKey) {
|
|
58
58
|
if (!getFieldKey)
|
|
59
59
|
getFieldKey = (x) => x.key;
|
|
60
|
-
let r = 0;
|
|
61
60
|
for (let i = 0; i < this.Segments.length; i++) {
|
|
62
61
|
let seg = this.Segments[i];
|
|
63
|
-
let left =
|
|
64
|
-
let right =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
else if (left < right)
|
|
68
|
-
r = -1;
|
|
62
|
+
let left = a[getFieldKey(seg.field)];
|
|
63
|
+
let right = b[getFieldKey(seg.field)];
|
|
64
|
+
let descending = seg.isDescending;
|
|
65
|
+
let r = compareForSort(left, right, descending);
|
|
69
66
|
if (r != 0) {
|
|
70
|
-
if (seg.isDescending)
|
|
71
|
-
r *= -1;
|
|
72
67
|
return r;
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
|
-
return
|
|
70
|
+
return 0;
|
|
76
71
|
}
|
|
77
72
|
/**
|
|
78
73
|
* Translates an `EntityOrderBy` to a `Sort` instance.
|
|
@@ -169,7 +164,19 @@ export class Sort {
|
|
|
169
164
|
return orderBy;
|
|
170
165
|
}
|
|
171
166
|
}
|
|
172
|
-
function
|
|
167
|
+
export function compareForSort(left, right, descending) {
|
|
168
|
+
left = fixValueForSort(left);
|
|
169
|
+
right = fixValueForSort(right);
|
|
170
|
+
let r = 0;
|
|
171
|
+
if (left > right)
|
|
172
|
+
r = 1;
|
|
173
|
+
else if (left < right)
|
|
174
|
+
r = -1;
|
|
175
|
+
if (descending)
|
|
176
|
+
r *= -1;
|
|
177
|
+
return r;
|
|
178
|
+
}
|
|
179
|
+
export function fixValueForSort(a) {
|
|
173
180
|
if (a == undefined || a == null)
|
|
174
181
|
return a;
|
|
175
182
|
if (a.id !== undefined)
|
package/package.json
CHANGED
package/remult-knex/index.js
CHANGED
|
@@ -201,6 +201,9 @@ var KnexEntityDataProvider = /** @class */ (function () {
|
|
|
201
201
|
this.knex = knex;
|
|
202
202
|
this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
|
|
203
203
|
}
|
|
204
|
+
KnexEntityDataProvider.prototype.aggregate = function (options) {
|
|
205
|
+
throw new Error('Method not implemented.');
|
|
206
|
+
};
|
|
204
207
|
KnexEntityDataProvider.prototype.count = function (where) {
|
|
205
208
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
206
209
|
var e, br, r, result, row, key, element;
|
package/remult-mongo.js
CHANGED
|
@@ -107,6 +107,9 @@ var MongoEntityDataProvider = /** @class */ (function () {
|
|
|
107
107
|
this.entity = entity;
|
|
108
108
|
this.session = session;
|
|
109
109
|
}
|
|
110
|
+
MongoEntityDataProvider.prototype.aggregate = function (options) {
|
|
111
|
+
throw new Error('Method not implemented.');
|
|
112
|
+
};
|
|
110
113
|
MongoEntityDataProvider.prototype.translateFromDb = function (row, nameProvider) {
|
|
111
114
|
var e_1, _a;
|
|
112
115
|
var result = {};
|
package/src/data-api.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Remult } from './context.js';
|
|
2
2
|
import type { ErrorInfo } from './data-interfaces.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type EntityMetadata, type FindOptions, type Repository } from './remult3/remult3.js';
|
|
4
4
|
import type { rowHelperImplementation } from './remult3/RepositoryImplementation.js';
|
|
5
5
|
export declare class DataApi<T = unknown> {
|
|
6
6
|
private repository;
|
|
@@ -12,10 +12,12 @@ export declare class DataApi<T = unknown> {
|
|
|
12
12
|
get(response: DataApiResponse, id: any): Promise<void>;
|
|
13
13
|
count(response: DataApiResponse, request: DataApiRequest, body?: any): Promise<void>;
|
|
14
14
|
deleteMany(response: DataApiResponse, request: DataApiRequest, body?: any): Promise<void>;
|
|
15
|
+
aggregate(response: DataApiResponse, request: DataApiRequest, body: any): Promise<void>;
|
|
15
16
|
getArrayImpl(response: DataApiResponse, request: DataApiRequest, body: any): Promise<{
|
|
16
17
|
r: any[];
|
|
17
18
|
findOptions: FindOptions<T>;
|
|
18
19
|
}>;
|
|
20
|
+
private findOptionsFromRequest;
|
|
19
21
|
private includeNone;
|
|
20
22
|
getArray(response: DataApiResponse, request: DataApiRequest, body?: any): Promise<void>;
|
|
21
23
|
liveQuery(response: DataApiResponse, request: DataApiRequest, body: any, serializeContext: () => Promise<any>, queryChannel: string): Promise<void>;
|