remult 0.27.8-next.0 → 0.27.8-next.2

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 { getRowAfterUpdate } from '../src/data-providers/sql-database.js';
10
+ import { aggregateImpl, getRowAfterUpdate, } from '../src/data-providers/sql-database.js';
11
11
  export class KnexDataProvider {
12
12
  knex;
13
13
  constructor(knex) {
@@ -70,7 +70,7 @@ export class KnexDataProvider {
70
70
  }
71
71
  }
72
72
  }
73
- return new KnexEntityDataProvider(entity, this.knex, this.wrapIdentifier);
73
+ return new KnexEntityDataProvider(entity, this.knex, this.wrapIdentifier, this);
74
74
  }
75
75
  async transaction(action) {
76
76
  let t = await this.knex.transaction();
@@ -110,13 +110,15 @@ class KnexEntityDataProvider {
110
110
  entity;
111
111
  knex;
112
112
  rawSqlWrapIdentifier;
113
- constructor(entity, knex, rawSqlWrapIdentifier) {
113
+ dp;
114
+ constructor(entity, knex, rawSqlWrapIdentifier, dp) {
114
115
  this.entity = entity;
115
116
  this.knex = knex;
116
117
  this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
118
+ this.dp = dp;
117
119
  }
118
- aggregate(options) {
119
- throw new Error('Method not implemented.');
120
+ async aggregate(options) {
121
+ return aggregateImpl(options, await this.init(), this.dp.createCommand(), false, (limit, offset) => ' limit ' + limit + ' offset ' + offset);
120
122
  }
121
123
  async count(where) {
122
124
  const e = await this.init();
@@ -5,6 +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
9
  export class MongoDataProvider {
9
10
  db;
10
11
  client;
@@ -70,9 +71,6 @@ class MongoEntityDataProvider {
70
71
  this.entity = entity;
71
72
  this.session = session;
72
73
  }
73
- aggregate(options) {
74
- throw new Error('Method not implemented.');
75
- }
76
74
  translateFromDb(row, nameProvider) {
77
75
  let result = {};
78
76
  for (const col of this.entity.fields) {
@@ -100,6 +98,98 @@ class MongoEntityDataProvider {
100
98
  let w = await x.resolveWhere();
101
99
  return await collection.countDocuments(w, { session: this.session });
102
100
  }
101
+ async aggregate(options) {
102
+ let { collection, e } = await this.collection();
103
+ let x = new FilterConsumerBridgeToMongo(e);
104
+ const pipeLine = [];
105
+ if (options?.where) {
106
+ options.where.__applyToConsumer(x);
107
+ let where = await x.resolveWhere();
108
+ pipeLine.push({ $match: where });
109
+ }
110
+ const processResultRow = [];
111
+ const $group = {
112
+ __count: { $sum: 1 },
113
+ };
114
+ processResultRow.push((mongoRow, resultRow) => {
115
+ resultRow[AggregateCountMember] = mongoRow.__count;
116
+ });
117
+ pipeLine.push({ $group });
118
+ if (options?.groupBy) {
119
+ $group._id = {};
120
+ for (const element of options.groupBy) {
121
+ const name = e.$dbNameOf(element);
122
+ $group._id[name] = `$${name}`;
123
+ processResultRow.push((mongoRow, resultRow) => {
124
+ resultRow[element.key] = element.valueConverter.fromDb(mongoRow._id[name]);
125
+ });
126
+ }
127
+ }
128
+ else {
129
+ $group._id = null;
130
+ }
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
+ });
153
+ }
154
+ }
155
+ if (options?.orderBy) {
156
+ let sort = {};
157
+ for (const x of options.orderBy) {
158
+ const direction = x.isDescending ? -1 : 1;
159
+ 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
+ case 'count':
167
+ sort['__count'] = direction;
168
+ break;
169
+ default:
170
+ sort['_id.' + e.$dbNameOf(x.field)] = direction;
171
+ }
172
+ }
173
+ pipeLine.push({ $sort: sort });
174
+ }
175
+ if (options?.limit) {
176
+ pipeLine.push({ $limit: options.limit });
177
+ if (options.page) {
178
+ pipeLine.push({ $skip: (options.page - 1) * options.limit });
179
+ }
180
+ }
181
+ const r = await collection
182
+ .aggregate(pipeLine, { session: this.session })
183
+ .toArray();
184
+ const r2 = r.map((x) => {
185
+ let resultRow = {};
186
+ for (const f of processResultRow) {
187
+ f(x, resultRow);
188
+ }
189
+ return resultRow;
190
+ });
191
+ return r2;
192
+ }
103
193
  async find(options) {
104
194
  let { collection, e } = await this.collection();
105
195
  let x = new FilterConsumerBridgeToMongo(e);
@@ -116,14 +116,15 @@ export class DataApi {
116
116
  sum: direction,
117
117
  };
118
118
  break;
119
- case 'average':
119
+ case 'avg':
120
120
  orderBy[element.field] = {
121
121
  ...orderBy[element.field],
122
- average: direction,
122
+ avg: direction,
123
123
  };
124
124
  break;
125
125
  case 'count':
126
126
  orderBy[AggregateCountMember] = direction;
127
+ break;
127
128
  default:
128
129
  orderBy[element.field] = direction;
129
130
  break;
@@ -136,7 +137,7 @@ export class DataApi {
136
137
  page: findOptions.page,
137
138
  groupBy: body?.groupBy?.filter((x) => this.repository.fields.find(x).includedInApi()),
138
139
  sum: body?.sum?.filter((x) => this.repository.fields.find(x).includedInApi()),
139
- average: body?.average?.filter((x) => this.repository.fields.find(x).includedInApi()),
140
+ avg: body?.avg?.filter((x) => this.repository.fields.find(x).includedInApi()),
140
141
  orderBy: orderBy,
141
142
  });
142
143
  if (!Array.isArray(result)) {
@@ -50,8 +50,8 @@ export class ArrayEntityDataProvider {
50
50
  aggregates.push(new Sum(element.key));
51
51
  }
52
52
  }
53
- if (options?.average) {
54
- class Average {
53
+ if (options?.avg) {
54
+ class Avg {
55
55
  key;
56
56
  constructor(key) {
57
57
  this.key = key;
@@ -68,14 +68,14 @@ export class ArrayEntityDataProvider {
68
68
  finishGroup(result) {
69
69
  result[this.key] = {
70
70
  ...result[this.key],
71
- average: this.sum / this.count,
71
+ avg: this.sum / this.count,
72
72
  };
73
73
  this.sum = 0;
74
74
  this.count = 0;
75
75
  }
76
76
  }
77
- for (const element of options.average) {
78
- aggregates.push(new Average(element.key));
77
+ for (const element of options.avg) {
78
+ aggregates.push(new Avg(element.key));
79
79
  }
80
80
  }
81
81
  function finishGroup() {
@@ -122,8 +122,8 @@ export class ArrayEntityDataProvider {
122
122
  switch (x.operation) {
123
123
  case 'sum':
124
124
  return row[x.field.key].sum;
125
- case 'average':
126
- return row[x.field.key].average;
125
+ case 'avg':
126
+ return row[x.field.key].avg;
127
127
  }
128
128
  }
129
129
  return row[x.field.key];
@@ -110,7 +110,7 @@ export class RestEntityDataProvider {
110
110
  const body = {
111
111
  groupBy: options?.groupBy?.map((x) => x.key),
112
112
  sum: options?.sum?.map((x) => x.key),
113
- average: options?.average?.map((x) => x.key),
113
+ avg: options?.avg?.map((x) => x.key),
114
114
  orderBy: options?.orderBy?.map((x) => ({ ...x, field: x.field?.key })),
115
115
  };
116
116
  const result = await run('aggregate', Object.keys(body).length > 0 ? body : undefined);
@@ -196,9 +196,13 @@ export class RestEntityDataProvider {
196
196
  if (action)
197
197
  u.add('__action', action);
198
198
  if (filterObject) {
199
- body = { set: body, where: filterObject };
199
+ if (method === 'put') {
200
+ return this.http().post(u.url, { set: body, where: filterObject });
201
+ }
202
+ else
203
+ body = { ...body, where: filterObject };
200
204
  }
201
- if (body)
205
+ if (body && method != 'put')
202
206
  return this.http().post(u.url, body);
203
207
  else
204
208
  return this.http()[method](u.url, body);
@@ -288,112 +288,7 @@ class ActualSQLEntityDataProvider {
288
288
  });
289
289
  }
290
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
- });
291
+ return await aggregateImpl(options, await this.init(), this.sql.createCommand(), this.sql._getSourceSql().orderByNullsFirst, this.sql._getSourceSql().getLimitSqlSyntax);
397
292
  }
398
293
  async find(options) {
399
294
  let e = await this.init();
@@ -646,3 +541,107 @@ export function getRowAfterUpdate(meta, dataProvider, data, id, operation) {
646
541
  return r[0];
647
542
  });
648
543
  }
544
+ export async function aggregateImpl(options, e, r, orderByNullFirst, limitSyntax) {
545
+ let select = 'select count(*) as count';
546
+ let groupBy = '';
547
+ const processResultRow = [];
548
+ processResultRow.push((sqlVal, theResult) => {
549
+ theResult[AggregateCountMember] = Number(sqlVal);
550
+ });
551
+ if (options?.groupBy)
552
+ for (const x of options?.groupBy) {
553
+ if (x.isServerExpression) {
554
+ }
555
+ else {
556
+ select += ', ' + e.$dbNameOf(x);
557
+ if (x.options.sqlExpression)
558
+ select += ' as ' + x.key;
559
+ if (groupBy == '')
560
+ groupBy = ' group by ';
561
+ else
562
+ groupBy += ', ';
563
+ groupBy += e.$dbNameOf(x);
564
+ }
565
+ processResultRow.push((sqlResult, theResult) => {
566
+ theResult[x.key] = x.valueConverter.fromDb(sqlResult);
567
+ });
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';
586
+ }
587
+ processResultRow.push((sqlResult, theResult) => {
588
+ theResult[x.key] = { ...theResult[x.key], avg: sqlResult };
589
+ });
590
+ }
591
+ select += '\n from ' + e.$entityName;
592
+ if (options?.where) {
593
+ let where = new FilterConsumerBridgeToSqlRequest(r, e);
594
+ options?.where.__applyToConsumer(where);
595
+ select += await where.resolveWhere();
596
+ }
597
+ if (groupBy)
598
+ select += groupBy;
599
+ let orderBy = '';
600
+ if (options?.orderBy) {
601
+ for (const x of options?.orderBy) {
602
+ if (orderBy == '')
603
+ orderBy = ' order by ';
604
+ else
605
+ 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;
620
+ }
621
+ if (x.isDescending)
622
+ orderBy += ' desc';
623
+ if (orderByNullFirst) {
624
+ if (x.isDescending)
625
+ select += ' nulls last';
626
+ else
627
+ select += ' nulls first';
628
+ }
629
+ }
630
+ if (orderBy)
631
+ select += orderBy;
632
+ }
633
+ if (options?.limit) {
634
+ let page = 1;
635
+ if (options.page)
636
+ page = options.page;
637
+ if (page < 1)
638
+ page = 1;
639
+ select += ' ' + limitSyntax(options.limit, (page - 1) * options.limit);
640
+ }
641
+ const result = await r.execute(select);
642
+ return result.rows.map((sql) => {
643
+ let theResult = {};
644
+ processResultRow.forEach((x, i) => x(sql[result.getColumnKeyInResultForIndexInSelect(i)], theResult));
645
+ return theResult;
646
+ });
647
+ }
@@ -131,13 +131,24 @@ export class RepositoryImplementation {
131
131
  let findOptions = await this._buildEntityDataProviderFindOptions({
132
132
  ...options,
133
133
  });
134
+ const getField = (key) => {
135
+ const r = this.metadata.fields.find(key);
136
+ if (r === undefined)
137
+ throw Error(`key "${key}" not found in entity`);
138
+ return r;
139
+ };
140
+ const getFieldNotInGroupBy = (key) => {
141
+ if (options?.groupBy?.includes(key))
142
+ throw Error(`field "${key}" cannot be used both in an aggregate and in group by`);
143
+ return getField(key);
144
+ };
134
145
  var dpOptions = {
135
146
  where: findOptions.where,
136
147
  limit: findOptions.limit,
137
148
  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)),
149
+ groupBy: options?.groupBy?.map(getField),
150
+ sum: options?.sum?.map(getFieldNotInGroupBy),
151
+ avg: options?.avg?.map(getFieldNotInGroupBy),
141
152
  };
142
153
  if (options?.orderBy) {
143
154
  dpOptions.orderBy = [];
@@ -147,9 +158,7 @@ export class RepositoryImplementation {
147
158
  if (element)
148
159
  if (typeof element === 'string') {
149
160
  dpOptions.orderBy.push({
150
- field: key === '$count'
151
- ? undefined
152
- : this.metadata.fields.find(key),
161
+ field: key === '$count' ? undefined : getField(key),
153
162
  isDescending: element === 'desc',
154
163
  operation: key === '$count' ? 'count' : undefined,
155
164
  });
@@ -7,8 +7,6 @@ export const flags = {
7
7
  //p1 - add not as an overload to not equal
8
8
  /*p1 - min, max, avg, sum, countDistinct - noam come up with an api
9
9
  p1 - handle relations
10
- p1 - mongodb
11
- p1 - knex
12
10
  */
13
11
  /*p1 - add id and use uuid by default, but allow changes with Fields.id.defaultIdProvider NO but defaultProvider yes???
14
12
  //p1 - replace uuid with crypto.randomUUID and allow custom fallback NO
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.27.8-next.0",
3
+ "version": "0.27.8-next.2",
4
4
  "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev",
6
6
  "repository": {
@@ -112,7 +112,7 @@ var KnexDataProvider = /** @class */ (function () {
112
112
  finally { if (e_2) throw e_2.error; }
113
113
  }
114
114
  }
115
- return new KnexEntityDataProvider(entity, this.knex, this.wrapIdentifier);
115
+ return new KnexEntityDataProvider(entity, this.knex, this.wrapIdentifier, this);
116
116
  };
117
117
  KnexDataProvider.prototype.transaction = function (action) {
118
118
  return tslib_1.__awaiter(this, void 0, void 0, function () {
@@ -196,13 +196,27 @@ var KnexDataProvider = /** @class */ (function () {
196
196
  }());
197
197
  exports.KnexDataProvider = KnexDataProvider;
198
198
  var KnexEntityDataProvider = /** @class */ (function () {
199
- function KnexEntityDataProvider(entity, knex, rawSqlWrapIdentifier) {
199
+ function KnexEntityDataProvider(entity, knex, rawSqlWrapIdentifier, dp) {
200
200
  this.entity = entity;
201
201
  this.knex = knex;
202
202
  this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
203
+ this.dp = dp;
203
204
  }
204
205
  KnexEntityDataProvider.prototype.aggregate = function (options) {
205
- throw new Error('Method not implemented.');
206
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
207
+ var _a, _b;
208
+ return tslib_1.__generator(this, function (_c) {
209
+ switch (_c.label) {
210
+ case 0:
211
+ _a = sql_database_js_1.aggregateImpl;
212
+ _b = [options];
213
+ return [4 /*yield*/, this.init()];
214
+ case 1: return [2 /*return*/, _a.apply(void 0, _b.concat([_c.sent(), this.dp.createCommand(),
215
+ false,
216
+ function (limit, offset) { return ' limit ' + limit + ' offset ' + offset; }]))];
217
+ }
218
+ });
219
+ });
206
220
  };
207
221
  KnexEntityDataProvider.prototype.count = function (where) {
208
222
  return tslib_1.__awaiter(this, void 0, void 0, function () {