remult 0.27.8-next.2 → 0.27.8-next.3

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.
@@ -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 { AggregateCountMember, AggregateOperators, } from './src/remult3/remult3.js';
9
9
  export class MongoDataProvider {
10
10
  db;
11
11
  client;
@@ -115,6 +115,8 @@ class MongoEntityDataProvider {
115
115
  resultRow[AggregateCountMember] = mongoRow.__count;
116
116
  });
117
117
  pipeLine.push({ $group });
118
+ const $addFields = {};
119
+ pipeLine.push({ $addFields });
118
120
  if (options?.groupBy) {
119
121
  $group._id = {};
120
122
  for (const element of options.groupBy) {
@@ -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 AggregateOperators) {
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 { AggregateCountMember, AggregateForApiKey, } from './remult3/remult3.js';
6
6
  import { ForbiddenError } from './server-action.js';
7
7
  export class DataApi {
8
8
  repository;
@@ -110,39 +110,46 @@ export class DataApi {
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
117
  orderBy[AggregateCountMember] = 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
  }
128
+ const groupBy = body?.groupBy?.filter((x) => this.repository.fields.find(x).includedInApi());
134
129
  let result = await this.repository.aggregate({
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
+ [AggregateForApiKey]: true,
135
+ groupBy,
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
143
  if (!Array.isArray(result)) {
144
144
  result = [result];
145
145
  }
146
+ else {
147
+ result.forEach((x) => {
148
+ for (const f of groupBy) {
149
+ x[f] = this.repository.fields.find(f).valueConverter.toJson(x[f]);
150
+ }
151
+ });
152
+ }
146
153
  response.success(result);
147
154
  }
148
155
  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 { AggregateCountMember, AggregateOperators, } from '../remult3/remult3.js';
5
5
  import { compareForSort, Sort } from '../sort.js';
6
6
  export class ArrayEntityDataProvider {
7
7
  entity;
@@ -29,54 +29,95 @@ 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 AggregateOperators) {
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 };
@@ -120,13 +161,14 @@ export class ArrayEntityDataProvider {
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[AggregateCountMember];
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)
@@ -111,6 +111,9 @@ export class RestEntityDataProvider {
111
111
  groupBy: options?.groupBy?.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
119
  const result = await run('aggregate', Object.keys(body).length > 0 ? body : undefined);
@@ -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 { AggregateCountMember, AggregateOperators, } 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';
@@ -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 AggregateOperators) {
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
  }
@@ -3,6 +3,7 @@ import { CompoundIdField } from '../CompoundIdField.js';
3
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
+ import { AggregateForApiKey, } from './remult3.js';
6
7
  import { assign } from '../../assign.js';
7
8
  import { ValueConverters } from '../valueConverters.js';
8
9
  import { findOptionsToJson } from '../data-providers/rest-data-provider.js';
@@ -149,6 +150,9 @@ export class RepositoryImplementation {
149
150
  groupBy: options?.groupBy?.map(getField),
150
151
  sum: options?.sum?.map(getFieldNotInGroupBy),
151
152
  avg: options?.avg?.map(getFieldNotInGroupBy),
153
+ min: options?.min?.map(getFieldNotInGroupBy),
154
+ max: options?.max?.map(getFieldNotInGroupBy),
155
+ distinctCount: options?.distinctCount?.map(getFieldNotInGroupBy),
152
156
  };
153
157
  if (options?.orderBy) {
154
158
  dpOptions.orderBy = [];
@@ -181,6 +185,18 @@ export class RepositoryImplementation {
181
185
  const result = await this._edp.aggregate(dpOptions);
182
186
  if (!options?.groupBy)
183
187
  return result[0];
188
+ //@ts-expect-error an internal option
189
+ if (!options?.[AggregateForApiKey]) {
190
+ const loaderOptions = {
191
+ include: {},
192
+ };
193
+ for (const key of options.groupBy) {
194
+ loaderOptions.include[key] = true;
195
+ }
196
+ const loader = new RelationLoader();
197
+ await this.populateRelationsForFields(dpOptions.groupBy, loaderOptions, result, loader);
198
+ await loader.resolveAll();
199
+ }
184
200
  return result;
185
201
  }
186
202
  _idCache = new Map();
@@ -535,7 +551,12 @@ export class RepositoryImplementation {
535
551
  }
536
552
  }
537
553
  let result = await promiseAll(rawRows, async (r) => await this._mapRawDataToResult(r, loadFields));
538
- for (const col of this.metadata.fields) {
554
+ const fields = this.metadata.fields.toArray();
555
+ this.populateRelationsForFields(fields, loadOptions, result, loader);
556
+ return result;
557
+ }
558
+ populateRelationsForFields(fields, loadOptions, result, loader) {
559
+ for (const col of fields) {
539
560
  let rel = getRelationFieldInfo(col);
540
561
  let incl = col.options
541
562
  .defaultIncluded;
@@ -573,7 +594,6 @@ export class RepositoryImplementation {
573
594
  }
574
595
  }
575
596
  }
576
- return result;
577
597
  }
578
598
  /*@internal */
579
599
  _findOptionsBasedOnRelation(rel, field, moreFindOptions, row, otherRepo) {
@@ -605,8 +625,9 @@ export class RepositoryImplementation {
605
625
  }
606
626
  const relFields = rel.getFields();
607
627
  const getFieldValue = (key) => {
608
- let val = rel.type === 'reference'
609
- ? getEntityRef(row).fields.find(field.key).getId()
628
+ const ref = getEntityRef(row, false);
629
+ let val = rel.type === 'reference' && ref
630
+ ? ref.fields.find(field.key).getId()
610
631
  : row[key];
611
632
  if (rel.type === 'toOne' || rel.type === 'reference') {
612
633
  if (val === null)
@@ -1,4 +1,12 @@
1
1
  export const AggregateCountMember = '$count';
2
+ export const AggregateForApiKey = Symbol.for('AggregateForApiKey');
3
+ export const AggregateOperators = [
4
+ 'sum',
5
+ 'avg',
6
+ 'min',
7
+ 'max',
8
+ 'distinctCount',
9
+ ];
2
10
  export const flags = {
3
11
  error500RetryCount: 4,
4
12
  };
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { ClassType } from './classType.js';
2
2
  import type { DataProvider } from './src/data-interfaces.js';
3
3
  export { ArrayEntityDataProvider } from './src/data-providers/array-entity-data-provider.js';
4
4
  export type { ClassType } from './classType.js';
5
- export { ValidateFieldEvent, MembersOnly, FieldsMetadata, FieldRef, IdFieldRef, FieldsRef, EntityMetadata, EntityOrderBy, EntityFilter, FindOptions, FindOptionsBase, QueryResult, QueryOptions, Repository, FindFirstOptions, ComparisonValueFilter, ValueFilter, IdFilter, ContainsStringValueFilter, EntityRef, SortSegments, Paginator, LiveQuery, LiveQueryChangeInfo, Subscribable, RefSubscriber, RefSubscriberBase, RelationOptions, ObjectMembersOnly, MembersToInclude, RepositoryRelations, EntityIdFields, ClassFieldDecorator, ClassFieldDecoratorContextStub, //n1 consider removing in ts5
5
+ export { ValidateFieldEvent, MembersOnly, FieldsMetadata, FieldRef, IdFieldRef, FieldsRef, EntityMetadata, EntityOrderBy, EntityFilter, FindOptions, FindOptionsBase, QueryResult, QueryOptions, Repository, AggregateOptions, FindFirstOptions, ComparisonValueFilter, ValueFilter, IdFilter, ContainsStringValueFilter, EntityRef, SortSegments, Paginator, LiveQuery, LiveQueryChangeInfo, Subscribable, RefSubscriber, RefSubscriberBase, RelationOptions, ObjectMembersOnly, MembersToInclude, RepositoryRelations, EntityIdFields, ClassFieldDecorator, ClassFieldDecoratorContextStub, //n1 consider removing in ts5
6
6
  LifecycleEvent, FieldsRefBase, FieldsRefForEntityBase, RepositoryRelationsForEntityBase, ControllerRefForControllerBase, ControllerRefBase, ControllerRef, EntityRefForEntityBase, IdMetadata, FindFirstOptionsBase, } from './src/remult3/remult3.js';
7
7
  export { EntityBase, ControllerBase, FieldType, getFields, ValueListFieldType, getValueList, ValueListFieldOptions, ValueListInfo, CaptionTransformer, } from './src/remult3/RepositoryImplementation.js';
8
8
  export { Entity } from './src/remult3/entity.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.27.8-next.2",
3
+ "version": "0.27.8-next.3",
4
4
  "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev",
6
6
  "repository": {