remult 0.27.20 → 0.27.21-next.6

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/graphql.js CHANGED
@@ -315,7 +315,7 @@ export function remultGraphql(options) {
315
315
  value: getMetaType(meta),
316
316
  }, argErrorDetail, argClientMutationId);
317
317
  root[createResolverKey] = handleMutationWithErrors(async (dApi, response, setResult, arg1, req) => {
318
- await dApi.post({
318
+ await dApi.httpPost({
319
319
  ...response,
320
320
  created: (y) => {
321
321
  const orig = { ...y };
@@ -324,7 +324,7 @@ export function remultGraphql(options) {
324
324
  [toCamelCase(getMetaType(meta))]: y,
325
325
  });
326
326
  },
327
- }, arg1.input);
327
+ }, { get: () => undefined }, arg1.input, undefined);
328
328
  });
329
329
  resolversMutation[createResolverKey] = (origItem, args, req, gqlInfo) => root[createResolverKey](args, req, gqlInfo);
330
330
  }
@@ -266,7 +266,9 @@ class MongoEntityDataProvider {
266
266
  const { e, collection } = await this.collection();
267
267
  let f = new FilterConsumerBridgeToMongo(e);
268
268
  Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
269
- collection.deleteOne(await f.resolveWhere(), { session: this.session });
269
+ await collection.deleteOne(await f.resolveWhere(), {
270
+ session: this.session,
271
+ });
270
272
  }
271
273
  async insert(data) {
272
274
  let { collection, e } = await this.collection();
@@ -258,7 +258,7 @@ export class RemultServerImplementation {
258
258
  if (this.options.logApiEndPoints)
259
259
  console.info('[remult] ' + myRoute);
260
260
  r.route(myRoute)
261
- .get(this.process((c, req, res, orig) => dataApiFactory(c).httpGet(res, req, () => this.serializeContext(c))))
261
+ .get(this.process(async (c, req, res, orig) => dataApiFactory(c).httpGet(res, req, () => this.serializeContext(c))))
262
262
  .put(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).updateManyThroughPutRequest(res, req, await this.coreOptions.getRequestBody(orig))))
263
263
  .delete(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).deleteMany(res, req, undefined)))
264
264
  .post(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).httpPost(res, req, await this.coreOptions.getRequestBody(orig), () => this.serializeContext(c))));
package/esm/src/column.js CHANGED
@@ -3,11 +3,10 @@ import { getRepositoryInternals } from './remult3/repository-internals.js';
3
3
  export function makeTitle(name) {
4
4
  // insert a space before all caps
5
5
  return (name
6
- .replace(/([A-Z])/g, ' $1')
7
- // uppercase the first character
6
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
7
+ // Uppercase the first character
8
8
  .replace(/^./, (str) => str.toUpperCase())
9
- .replace('Email', 'eMail')
10
- .replace(' I D', ' ID'));
9
+ .replace('Email', 'eMail'));
11
10
  }
12
11
  export class LookupColumn {
13
12
  repository;
@@ -20,7 +20,7 @@ export class DataApi {
20
20
  case 'count':
21
21
  return this.count(res, req, undefined);
22
22
  case 'groupBy':
23
- return this.groupBy(res, req, undefined);
23
+ return res.success(this.groupBy(req, undefined));
24
24
  }
25
25
  return this.getArray(res, req, undefined);
26
26
  }
@@ -34,31 +34,90 @@ export class DataApi {
34
34
  };
35
35
  }
36
36
  }
37
- if (action?.startsWith(liveQueryAction)) {
38
- validateWhereInBody();
39
- return this.liveQuery(res, req, body, serializeContext, action.substring(liveQueryAction.length));
40
- }
41
- switch (action) {
42
- case 'get':
43
- validateWhereInBody();
44
- return this.getArray(res, req, body);
45
- case 'count':
46
- validateWhereInBody();
47
- return this.count(res, req, body);
48
- case 'groupBy':
49
- return this.groupBy(res, req, body);
50
- case 'deleteMany':
51
- validateWhereInBody();
52
- return this.deleteMany(res, req, body);
53
- case 'updateMany':
37
+ try {
38
+ if (action?.startsWith(liveQueryAction)) {
54
39
  validateWhereInBody();
55
- return this.updateManyImplementation(res, req, body);
56
- case 'endLiveQuery':
57
- await this.remult.liveQueryStorage.remove(body.id);
58
- res.success('ok');
59
- return;
60
- default:
61
- return this.post(res, body);
40
+ return this.liveQuery(res, req, body, serializeContext, action.substring(liveQueryAction.length));
41
+ }
42
+ switch (action) {
43
+ case 'get':
44
+ validateWhereInBody();
45
+ return this.getArray(res, req, body);
46
+ case 'count':
47
+ validateWhereInBody();
48
+ return this.count(res, req, body);
49
+ case 'groupBy':
50
+ return res.success(await this.groupBy(req, body));
51
+ case 'deleteMany':
52
+ validateWhereInBody();
53
+ return this.deleteMany(res, req, body);
54
+ case 'updateMany':
55
+ validateWhereInBody();
56
+ return this.updateManyImplementation(res, req, body);
57
+ case 'upsertMany':
58
+ return this.upsertMany(res, req, body);
59
+ case 'endLiveQuery':
60
+ await this.remult.liveQueryStorage.remove(body.id);
61
+ res.success('ok');
62
+ return;
63
+ case 'query':
64
+ return res.success(await this.query(res, req, body));
65
+ default:
66
+ return res.created(await this.post(body));
67
+ }
68
+ }
69
+ catch (err) {
70
+ if (err.isForbiddenError)
71
+ res.forbidden(err.message);
72
+ else
73
+ res.error(err, this.repository.metadata);
74
+ }
75
+ }
76
+ async upsertMany(response, request, body) {
77
+ return await doTransaction(this.remult, async () => {
78
+ let result = [];
79
+ for (const item of body) {
80
+ let where = await this.buildWhere(request, { where: item.where });
81
+ Filter.throwErrorIfFilterIsEmpty(where, 'upsert');
82
+ let r = await this.repository.find({
83
+ where,
84
+ include: this.includeNone(),
85
+ });
86
+ if (r.length == 0) {
87
+ result.push(await this.post({ ...item.where, ...item.set }));
88
+ }
89
+ else {
90
+ if (item.set !== undefined) {
91
+ result.push((await this.actualUpdate(r[0], item.set)).toApiJson());
92
+ }
93
+ else
94
+ result.push(this.repository.getEntityRef(r[0]).toApiJson());
95
+ }
96
+ }
97
+ response.success(result);
98
+ });
99
+ }
100
+ async query(response, request, body) {
101
+ if (!this.repository.metadata.apiReadAllowed) {
102
+ response.forbidden();
103
+ return;
104
+ }
105
+ try {
106
+ let { aggregate, ...rest } = body;
107
+ let [{ r }, [aggregates]] = await Promise.all([
108
+ this.getArrayImpl(request, rest),
109
+ this.groupBy(request, aggregate),
110
+ ]);
111
+ return {
112
+ items: r,
113
+ aggregates,
114
+ };
115
+ }
116
+ catch (err) {
117
+ if (err.isForbiddenError)
118
+ response.forbidden();
119
+ else
120
+ response.error(err, this.repository.metadata);
62
121
  }
63
122
  }
64
123
  static defaultGetLimit = 0;
@@ -92,6 +151,7 @@ export class DataApi {
92
151
  for await (const x of this.repository.query({
93
152
  where,
94
153
  include: this.includeNone(),
154
+ aggregate: undefined,
95
155
  })) {
96
156
  await this.actualDelete(x);
97
157
  deleted++;
@@ -103,7 +163,7 @@ export class DataApi {
103
163
  response.error(err, this.repository.metadata);
104
164
  }
105
165
  }
106
- async groupBy(response, request, body) {
166
+ async groupBy(request, body) {
107
167
  let findOptions = await this.findOptionsFromRequest(request, body);
108
168
  let orderBy = {};
109
169
  if (body?.orderBy) {
@@ -146,9 +206,9 @@ export class DataApi {
146
206
  x[f] = this.repository.fields.find(f).valueConverter.toJson(x[f]);
147
207
  }
148
208
  });
149
- response.success(result);
209
+ return result;
150
210
  }
151
- async getArrayImpl(response, request, body) {
211
+ async getArrayImpl(request, body) {
152
212
  let findOptions = await this.findOptionsFromRequest(request, body);
153
213
  const r = await this.repository.find(findOptions).then(async (r) => {
154
214
  return await Promise.all(r.map(async (y) => this.repository.getEntityRef(y).toApiJson()));
@@ -224,7 +284,7 @@ export class DataApi {
224
284
  return;
225
285
  }
226
286
  try {
227
- const { r } = await this.getArrayImpl(response, request, body);
287
+ const { r } = await this.getArrayImpl(request, body);
228
288
  response.success(r);
229
289
  }
230
290
  catch (err) {
@@ -240,7 +300,7 @@ export class DataApi {
240
300
  return;
241
301
  }
242
302
  try {
243
- const r = await this.getArrayImpl(response, request, body);
303
+ const r = await this.getArrayImpl(request, body);
244
304
  const data = {
245
305
  requestJson: await serializeContext(),
246
306
  findOptionsJson: findOptionsToJson(r.findOptions, this.repository.metadata),
@@ -345,6 +405,7 @@ export class DataApi {
345
405
  for await (const x of this.repository.query({
346
406
  where,
347
407
  include: this.includeNone(),
408
+ aggregate: undefined,
348
409
  })) {
349
410
  await this.actualUpdate(x, body.set);
350
411
  updated++;
@@ -383,35 +444,27 @@ export class DataApi {
383
444
  response.deleted();
384
445
  });
385
446
  }
386
- async post(response, body) {
387
- try {
388
- const insert = async (what) => {
389
- let newr = this.repository.create();
390
- await this.repository.getEntityRef(newr)._updateEntityBasedOnApi(what);
391
- if (!this.repository.getEntityRef(newr).apiInsertAllowed) {
392
- throw new ForbiddenError();
393
- }
394
- await this.repository.getEntityRef(newr).save();
395
- return this.repository.getEntityRef(newr).toApiJson();
396
- };
397
- if (Array.isArray(body)) {
398
- const result = [];
399
- await doTransaction(this.remult, async () => {
400
- for (const item of body) {
401
- result.push(await insert(item));
402
- }
403
- });
404
- response.created(result);
447
+ async post(body) {
448
+ const insert = async (what) => {
449
+ let newr = this.repository.create();
450
+ await this.repository.getEntityRef(newr)._updateEntityBasedOnApi(what);
451
+ if (!this.repository.getEntityRef(newr).apiInsertAllowed) {
452
+ throw new ForbiddenError();
405
453
  }
406
- else
407
- response.created(await insert(body));
408
- }
409
- catch (err) {
410
- if (err.isForbiddenError)
411
- response.forbidden(err.message);
412
- else
413
- response.error(err, this.repository.metadata);
454
+ await this.repository.getEntityRef(newr).save();
455
+ return this.repository.getEntityRef(newr).toApiJson();
456
+ };
457
+ if (Array.isArray(body)) {
458
+ const result = [];
459
+ await doTransaction(this.remult, async () => {
460
+ for (const item of body) {
461
+ result.push(await insert(item));
462
+ }
463
+ });
464
+ return result;
414
465
  }
466
+ else
467
+ return await insert(body);
415
468
  }
416
469
  }
417
470
  export function determineSort(sortUrlParm, dirUrlParam) {
@@ -103,21 +103,24 @@ export class RestEntityDataProvider {
103
103
  this.http = http;
104
104
  this.entity = entity;
105
105
  }
106
+ query(options, aggregateOptions) {
107
+ const r = this.buildFindRequest(options);
108
+ return r
109
+ .run('query', {
110
+ aggregate: this.buildAggregateOptions(aggregateOptions),
111
+ })
112
+ .then(({ items, aggregates }) => ({
113
+ items: items.map((x) => this.translateFromJson(x)),
114
+ aggregates,
115
+ }));
116
+ }
106
117
  async groupBy(options) {
107
118
  const { run } = this.buildFindRequest({
108
119
  where: options?.where,
109
120
  limit: options?.limit,
110
121
  page: options?.page,
111
122
  });
112
- const body = {
113
- groupBy: options?.group?.map((x) => x.key),
114
- sum: options?.sum?.map((x) => x.key),
115
- avg: options?.avg?.map((x) => x.key),
116
- min: options?.min?.map((x) => x.key),
117
- max: options?.max?.map((x) => x.key),
118
- distinctCount: options?.distinctCount?.map((x) => x.key),
119
- orderBy: options?.orderBy?.map((x) => ({ ...x, field: x.field?.key })),
120
- };
123
+ const body = this.buildAggregateOptions(options);
121
124
  const result = await run('groupBy', Object.keys(body).length > 0 ? body : undefined);
122
125
  if (options?.group)
123
126
  result.forEach((row) => {
@@ -127,6 +130,17 @@ export class RestEntityDataProvider {
127
130
  });
128
131
  return result;
129
132
  }
133
+ buildAggregateOptions(options) {
134
+ return {
135
+ groupBy: options?.group?.map((x) => x.key),
136
+ sum: options?.sum?.map((x) => x.key),
137
+ avg: options?.avg?.map((x) => x.key),
138
+ min: options?.min?.map((x) => x.key),
139
+ max: options?.max?.map((x) => x.key),
140
+ distinctCount: options?.distinctCount?.map((x) => x.key),
141
+ orderBy: options?.orderBy?.map((x) => ({ ...x, field: x.field?.key })),
142
+ };
143
+ }
130
144
  translateFromJson(row) {
131
145
  let result = {};
132
146
  for (const col of this.entity.fields) {
@@ -153,6 +167,13 @@ export class RestEntityDataProvider {
153
167
  const { run } = this.buildFindRequest({ where }, 'put');
154
168
  return run('updateMany', this.toJsonOfIncludedKeys(data)).then((r) => +r.updated);
155
169
  }
170
+ async upsertMany(options) {
171
+ const { run } = this.buildFindRequest(undefined);
172
+ return run('upsertMany', options.map((x) => ({
173
+ where: this.toJsonOfIncludedKeys(x.where),
174
+ set: x.set !== undefined ? this.toJsonOfIncludedKeys(x.set) : undefined,
175
+ }))).then((r) => r.map((x) => this.translateFromJson(x)));
176
+ }
156
177
  find(options) {
157
178
  let { run } = this.buildFindRequest(options);
158
179
  return run().then((x) => x.map((y) => this.translateFromJson(y)));
@@ -161,14 +161,18 @@ export class RemultProxy {
161
161
  get metadata() {
162
162
  return remultStatic.remultFactory().repo(...args).metadata;
163
163
  },
164
- query: (...args2) => self
164
+ query: (options) => self
165
165
  .remultFactory()
166
166
  .repo(...args)
167
- .query(...args2),
167
+ .query(options),
168
168
  save: (args2) => self
169
169
  .remultFactory()
170
170
  .repo(...args)
171
171
  .save(args2),
172
+ upsert: (args2) => self
173
+ .remultFactory()
174
+ .repo(...args)
175
+ .upsert(args2),
172
176
  update: (a, b) => self
173
177
  .remultFactory()
174
178
  .repo(...args)
@@ -0,0 +1,147 @@
1
+ import { queryConfig } from '../context.js';
2
+ import { isOfType } from '../isOfType.js';
3
+ import { Sort } from '../sort.js';
4
+ import { RelationLoader } from './relation-loader.js';
5
+ export class QueryResultImpl {
6
+ options;
7
+ repo;
8
+ constructor(options, repo) {
9
+ this.options = options;
10
+ this.repo = repo;
11
+ if (!this.options)
12
+ this.options = {};
13
+ if (!this.options.pageSize) {
14
+ this.options.pageSize = queryConfig.defaultPageSize;
15
+ }
16
+ }
17
+ _count = undefined;
18
+ _aggregates;
19
+ async getPage(page) {
20
+ if ((page ?? 0) < 1)
21
+ page = 1;
22
+ return this.repo.find({
23
+ where: this.options.where,
24
+ orderBy: this.options.orderBy,
25
+ limit: this.options.pageSize,
26
+ page: page,
27
+ load: this.options.load,
28
+ include: this.options.include,
29
+ });
30
+ }
31
+ async count() {
32
+ if (this._count === undefined)
33
+ this._count = await this.repo.count(this.options.where);
34
+ return this._count;
35
+ }
36
+ async forEach(what) {
37
+ let i = 0;
38
+ for await (const x of this) {
39
+ await what(x);
40
+ i++;
41
+ }
42
+ return i;
43
+ }
44
+ async paginator(pNextPageFilter) {
45
+ this.options.orderBy = Sort.createUniqueEntityOrderBy(this.repo.metadata, this.options.orderBy);
46
+ let options = {
47
+ where: {
48
+ $and: [this.options.where, pNextPageFilter],
49
+ },
50
+ orderBy: this.options.orderBy,
51
+ limit: this.options.pageSize,
52
+ load: this.options.load,
53
+ include: this.options.include,
54
+ };
55
+ let getItems = () => this.repo.find(options);
56
+ if (this._aggregates === undefined &&
57
+ isOfType(this.options, 'aggregate')) {
58
+ let agg = this.options.aggregate;
59
+ if (!this.repo._dataProvider.isProxy) {
60
+ let itemsPromise = getItems();
61
+ getItems = async () => {
62
+ this._aggregates = await this.repo.aggregate({
63
+ ...agg,
64
+ where: this.options.where,
65
+ });
66
+ this._count = this._aggregates.$count;
67
+ return itemsPromise;
68
+ };
69
+ }
70
+ else {
71
+ const loader = new RelationLoader();
72
+ getItems = () => this.repo
73
+ ._rawFind(options, false, loader, async (opt) => {
74
+ const r = await this.repo._edp.query(opt, await this.repo.__buildGroupByOptions(agg));
75
+ this._aggregates = r.aggregates;
76
+ return r.items;
77
+ })
78
+ .then(async (y) => {
79
+ await loader.resolveAll();
80
+ return y;
81
+ });
82
+ }
83
+ }
84
+ let items = await getItems();
85
+ let nextPage = () => {
86
+ throw new Error('no more pages');
87
+ };
88
+ let hasNextPage = items.length == this.options.pageSize;
89
+ if (hasNextPage) {
90
+ let nextPageFilter = await this.repo._createAfterFilter(this.options.orderBy, items[items.length - 1]);
91
+ nextPage = () => this.paginator(nextPageFilter);
92
+ }
93
+ return {
94
+ count: () => this.count(),
95
+ hasNextPage,
96
+ items,
97
+ nextPage,
98
+ //@ts-ignore
99
+ aggregates: this._aggregates,
100
+ };
101
+ }
102
+ [Symbol.asyncIterator]() {
103
+ if (!this.options.where) {
104
+ this.options.where = {};
105
+ }
106
+ let ob = this.options.orderBy;
107
+ this.options.orderBy = Sort.createUniqueEntityOrderBy(this.repo.metadata, ob);
108
+ let itemIndex = -1;
109
+ let currentPage = undefined;
110
+ let itStrategy;
111
+ let j = 0;
112
+ itStrategy = async () => {
113
+ if (this.options.progress) {
114
+ this.options.progress.progress(j++ / (await this.count()));
115
+ }
116
+ if (currentPage === undefined || itemIndex == currentPage.items.length) {
117
+ if (currentPage && !currentPage.hasNextPage)
118
+ return { value: undefined, done: true };
119
+ let prev = currentPage;
120
+ if (currentPage)
121
+ currentPage = await currentPage.nextPage();
122
+ else
123
+ currentPage = await this.paginator();
124
+ itemIndex = 0;
125
+ if (currentPage.items.length == 0) {
126
+ return { value: undefined, done: true };
127
+ }
128
+ else {
129
+ if (prev?.items.length ?? 0 > 0) {
130
+ if (this.repo.getEntityRef(prev.items[0]).getId() ==
131
+ this.repo.getEntityRef(currentPage.items[0]).getId())
132
+ throw new Error('pagination failure, returned same first row');
133
+ }
134
+ }
135
+ }
136
+ if (itemIndex < currentPage.items.length)
137
+ return { value: currentPage.items[itemIndex++], done: false };
138
+ return { done: true, value: undefined };
139
+ };
140
+ return {
141
+ next: async () => {
142
+ let r = itStrategy();
143
+ return r;
144
+ },
145
+ };
146
+ }
147
+ }