remult 0.27.7 → 0.27.8-next.1

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,10 +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;
119
+ }
120
+ async aggregate(options) {
121
+ return aggregateImpl(options, await this.init(), this.dp.createCommand(), false, (limit, offset) => ' limit ' + limit + ' offset ' + offset);
117
122
  }
118
123
  async count(where) {
119
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;
@@ -97,6 +98,98 @@ class MongoEntityDataProvider {
97
98
  let w = await x.resolveWhere();
98
99
  return await collection.countDocuments(w, { session: this.session });
99
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
+ }
100
193
  async find(options) {
101
194
  let { collection, e } = await this.collection();
102
195
  let x = new FilterConsumerBridgeToMongo(e);
@@ -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,56 @@ 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 'avg':
120
+ orderBy[element.field] = {
121
+ ...orderBy[element.field],
122
+ avg: direction,
123
+ };
124
+ break;
125
+ case 'count':
126
+ orderBy[AggregateCountMember] = direction;
127
+ break;
128
+ default:
129
+ orderBy[element.field] = direction;
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ let result = await this.repository.aggregate({
135
+ where: findOptions.where,
136
+ limit: findOptions.limit,
137
+ page: findOptions.page,
138
+ groupBy: body?.groupBy?.filter((x) => this.repository.fields.find(x).includedInApi()),
139
+ sum: body?.sum?.filter((x) => this.repository.fields.find(x).includedInApi()),
140
+ avg: body?.avg?.filter((x) => this.repository.fields.find(x).includedInApi()),
141
+ orderBy: orderBy,
142
+ });
143
+ if (!Array.isArray(result)) {
144
+ result = [result];
145
+ }
146
+ response.success(result);
147
+ }
101
148
  async getArrayImpl(response, request, body) {
149
+ let findOptions = await this.findOptionsFromRequest(request, body);
150
+ const r = await this.repository.find(findOptions).then(async (r) => {
151
+ return await Promise.all(r.map(async (y) => this.repository.getEntityRef(y).toApiJson()));
152
+ });
153
+ return { r, findOptions };
154
+ }
155
+ async findOptionsFromRequest(request, body) {
102
156
  let findOptions = {
103
157
  load: () => [],
104
158
  include: this.includeNone(),
@@ -147,14 +201,10 @@ export class DataApi {
147
201
  });
148
202
  }
149
203
  if (!hasId) {
150
- response.forbidden();
151
204
  throw new ForbiddenError();
152
205
  }
153
206
  }
154
- const r = await this.repository.find(findOptions).then(async (r) => {
155
- return await Promise.all(r.map(async (y) => this.repository.getEntityRef(y).toApiJson()));
156
- });
157
- return { r, findOptions };
207
+ return findOptions;
158
208
  }
159
209
  includeNone() {
160
210
  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?.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;
75
+ }
76
+ }
77
+ for (const element of options.avg) {
78
+ aggregates.push(new Avg(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 'avg':
126
+ return row[x.field.key].avg;
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
+ avg: options?.avg?.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,9 @@ class ActualSQLEntityDataProvider {
286
287
  return Number(r.rows[0].count);
287
288
  });
288
289
  }
290
+ async aggregate(options) {
291
+ return await aggregateImpl(options, await this.init(), this.sql.createCommand(), this.sql._getSourceSql().orderByNullsFirst, this.sql._getSourceSql().getLimitSqlSyntax);
292
+ }
289
293
  async find(options) {
290
294
  let e = await this.init();
291
295
  let { colKeys, select } = this.buildSelect(e);
@@ -537,3 +541,107 @@ export function getRowAfterUpdate(meta, dataProvider, data, id, operation) {
537
541
  return r[0];
538
542
  });
539
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
+ }
@@ -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, } from '../context.js';
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,62 @@ 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
+ 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
+ };
145
+ var dpOptions = {
146
+ where: findOptions.where,
147
+ limit: findOptions.limit,
148
+ page: findOptions.page,
149
+ groupBy: options?.groupBy?.map(getField),
150
+ sum: options?.sum?.map(getFieldNotInGroupBy),
151
+ avg: options?.avg?.map(getFieldNotInGroupBy),
152
+ };
153
+ if (options?.orderBy) {
154
+ dpOptions.orderBy = [];
155
+ for (const key in options.orderBy) {
156
+ if (Object.prototype.hasOwnProperty.call(options?.orderBy, key)) {
157
+ const element = options.orderBy[key];
158
+ if (element)
159
+ if (typeof element === 'string') {
160
+ dpOptions.orderBy.push({
161
+ field: key === '$count' ? undefined : getField(key),
162
+ isDescending: element === 'desc',
163
+ operation: key === '$count' ? 'count' : undefined,
164
+ });
165
+ }
166
+ else {
167
+ for (const operation in element) {
168
+ if (Object.prototype.hasOwnProperty.call(element, operation)) {
169
+ const direction = element[operation];
170
+ dpOptions.orderBy.push({
171
+ field: this.metadata.fields.find(key),
172
+ isDescending: direction === 'desc',
173
+ operation: operation,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ const result = await this._edp.aggregate(dpOptions);
182
+ if (!options?.groupBy)
183
+ return result[0];
184
+ return result;
185
+ }
130
186
  _idCache = new Map();
131
187
  _getCachedById(id, doNotLoadIfNotFound) {
132
188
  id = id + '';
@@ -1,10 +1,16 @@
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 - min, max, avg, sum, countDistinct - noam come up with an api
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 - order by $count
10
+ p1 - handle relations
11
+ p1 - mongodb
12
+ p1 - knex
13
+ */
8
14
  /*p1 - add id and use uuid by default, but allow changes with Fields.id.defaultIdProvider NO but defaultProvider yes???
9
15
  //p1 - replace uuid with crypto.randomUUID and allow custom fallback NO
10
16
  //p1 - Add example for luid