remult 0.27.20-next.1 → 0.27.20-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.
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();
@@ -34,35 +34,69 @@ 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 res.success(this.groupBy(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
- case 'query':
61
- return res.success(this.query(res, req, body));
62
- default:
63
- 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);
64
74
  }
65
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
+ }
66
100
  async query(response, request, body) {
67
101
  if (!this.repository.metadata.apiReadAllowed) {
68
102
  response.forbidden();
@@ -410,35 +444,27 @@ export class DataApi {
410
444
  response.deleted();
411
445
  });
412
446
  }
413
- async post(response, body) {
414
- try {
415
- const insert = async (what) => {
416
- let newr = this.repository.create();
417
- await this.repository.getEntityRef(newr)._updateEntityBasedOnApi(what);
418
- if (!this.repository.getEntityRef(newr).apiInsertAllowed) {
419
- throw new ForbiddenError();
420
- }
421
- await this.repository.getEntityRef(newr).save();
422
- return this.repository.getEntityRef(newr).toApiJson();
423
- };
424
- if (Array.isArray(body)) {
425
- const result = [];
426
- await doTransaction(this.remult, async () => {
427
- for (const item of body) {
428
- result.push(await insert(item));
429
- }
430
- });
431
- 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();
432
453
  }
433
- else
434
- response.created(await insert(body));
435
- }
436
- catch (err) {
437
- if (err.isForbiddenError)
438
- response.forbidden(err.message);
439
- else
440
- 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;
441
465
  }
466
+ else
467
+ return await insert(body);
442
468
  }
443
469
  }
444
470
  export function determineSort(sortUrlParm, dirUrlParam) {
@@ -105,9 +105,14 @@ export class RestEntityDataProvider {
105
105
  }
106
106
  query(options, aggregateOptions) {
107
107
  const r = this.buildFindRequest(options);
108
- return r.run('query', {
108
+ return r
109
+ .run('query', {
109
110
  aggregate: this.buildAggregateOptions(aggregateOptions),
110
- });
111
+ })
112
+ .then(({ items, aggregates }) => ({
113
+ items: items.map((x) => this.translateFromJson(x)),
114
+ aggregates,
115
+ }));
111
116
  }
112
117
  async groupBy(options) {
113
118
  const { run } = this.buildFindRequest({
@@ -162,6 +167,13 @@ export class RestEntityDataProvider {
162
167
  const { run } = this.buildFindRequest({ where }, 'put');
163
168
  return run('updateMany', this.toJsonOfIncludedKeys(data)).then((r) => +r.updated);
164
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
+ }
165
177
  find(options) {
166
178
  let { run } = this.buildFindRequest(options);
167
179
  return run().then((x) => x.map((y) => this.translateFromJson(y)));
@@ -169,6 +169,10 @@ export class RemultProxy {
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)
@@ -1,4 +1,5 @@
1
1
  import { queryConfig } from '../context.js';
2
+ import { isOfType } from '../isOfType.js';
2
3
  import { Sort } from '../sort.js';
3
4
  import { RelationLoader } from './relation-loader.js';
4
5
  export class QueryResultImpl {
@@ -52,11 +53,8 @@ export class QueryResultImpl {
52
53
  include: this.options.include,
53
54
  };
54
55
  let getItems = () => this.repo.find(options);
55
- if (this._aggregates === undefined /* &&
56
- (isOfType<QueryOptions<entityType, any, any, any, any, any>>(
57
- this.options,
58
- 'aggregate',
59
- ))*/) {
56
+ if (this._aggregates === undefined &&
57
+ isOfType(this.options, 'aggregate')) {
60
58
  let agg = this.options.aggregate;
61
59
  if (!this.repo._dataProvider.isProxy) {
62
60
  let itemsPromise = getItems();
@@ -65,6 +63,7 @@ export class QueryResultImpl {
65
63
  ...agg,
66
64
  where: this.options.where,
67
65
  });
66
+ this._count = this._aggregates.$count;
68
67
  return itemsPromise;
69
68
  };
70
69
  }
@@ -350,10 +350,22 @@ export class RepositoryImplementation {
350
350
  }
351
351
  }
352
352
  }
353
+ __createDto(basedOn) {
354
+ const ref = this.getEntityRef({
355
+ ...basedOn,
356
+ });
357
+ const r = ref.copyDataToObject(false);
358
+ const keys = Object.keys(basedOn);
359
+ for (const element of this.fields) {
360
+ if (element.dbReadOnly || !keys.includes(element.key))
361
+ delete r[element.key];
362
+ }
363
+ return r;
364
+ }
353
365
  async updateMany({ where, set, }) {
354
366
  Filter.throwErrorIfFilterIsEmpty(where, 'updateMany');
355
367
  if (this._dataProvider.isProxy) {
356
- return this._edp.updateMany(await this._translateWhereToFilter(where), set);
368
+ return this._edp.updateMany(await this._translateWhereToFilter(where), this.__createDto({ ...set }));
357
369
  }
358
370
  else {
359
371
  let updated = 0;
@@ -406,6 +418,40 @@ export class RepositoryImplementation {
406
418
  return await ref.save();
407
419
  }
408
420
  }
421
+ async upsert(options) {
422
+ if (this._dataProvider.isProxy) {
423
+ let rawRows = await this._edp.upsertMany((Array.isArray(options) ? options : [options]).map((x) => ({
424
+ where: this.__createDto(x.where),
425
+ set: x.set ? this.__createDto(x.set) : undefined,
426
+ })));
427
+ const loader = new RelationLoader();
428
+ const result = await this._loadManyToOneForManyRows(rawRows, {}, loader);
429
+ await loader.resolveAll();
430
+ if (Array.isArray(options))
431
+ return result;
432
+ else
433
+ return result[0];
434
+ }
435
+ if (Array.isArray(options)) {
436
+ return promiseAll(options, (x) => this.upsert(x));
437
+ }
438
+ let op = options;
439
+ var row = await this.findFirst(op.where, { createIfNotFound: true });
440
+ var ref = getEntityRef(row, false);
441
+ if (ref.isNew()) {
442
+ if (op.set) {
443
+ assign(row, op.set);
444
+ }
445
+ return await ref.save();
446
+ }
447
+ else {
448
+ if (op.set) {
449
+ assign(row, op.set);
450
+ return await ref.save();
451
+ }
452
+ return row;
453
+ }
454
+ }
409
455
  _getRefForExistingRow(entity, id) {
410
456
  let ref = getEntityRef(entity, false);
411
457
  if (!ref) {
@@ -1460,11 +1506,9 @@ export class rowHelperImplementation extends rowHelperBase {
1460
1506
  await this.__validateEntity();
1461
1507
  this.__assertValidity();
1462
1508
  let d = this.copyDataToObject(this.isNew());
1463
- let ignoreKeys = [];
1464
1509
  for (const field of this.metadata.fields) {
1465
1510
  if (field.dbReadOnly) {
1466
1511
  d[field.key] = undefined;
1467
- ignoreKeys.push(field.key);
1468
1512
  let f = this.fields.find(field);
1469
1513
  f.value = f.originalValue;
1470
1514
  }
@@ -17,16 +17,14 @@ const wnn = [
17
17
  export const flags = {
18
18
  error500RetryCount: 4,
19
19
  };
20
- //p1 - add aggregate to query
21
- //p1 - consider upsert (where,set)
22
- //p1 - add parameter all to deleteMany, and updateMany
23
- //p1 filter.apply ApiPreFilter
20
+ //p2 - add parameter all to deleteMany, and updateMany
21
+ //p2 filter.apply ApiPreFilter
24
22
  //p2 - signIn: (arg) =>withRemult(async () => { - consider if there's a generic way of doing signIn:withRemult(arg=>{})
25
- /*p1 - add id and use uuid by default, but allow changes with Fields.id.defaultIdProvider NO but defaultProvider yes???
26
- //p1 - replace uuid with crypto.randomUUID and allow custom fallback NO
27
- //p1 - Add example for luid
28
- //p1 - add example for nanoid
29
- //p1 - explain the benefits of changing the default provider for testing in docs.
23
+ /*p2 - add id and use uuid by default, but allow changes with Fields.id.defaultIdProvider NO but defaultProvider yes???
24
+ //p2 - replace uuid with crypto.randomUUID and allow custom fallback NO
25
+ //p2 - Add example for luid
26
+ //p2 - add example for nanoid
27
+ //p2 - explain the benefits of changing the default provider for testing in docs.
30
28
  */
31
29
  //p2 - add some kind of options handler that will help with translation etc... like in hagai - something that runs at the stage where options are being built
32
30
  //p2 - enforce api rules in some backend scenarios - https://discord.com/channels/975754286384418847/1292424895338119239
@@ -38,13 +36,11 @@ export const flags = {
38
36
  */
39
37
  //p2 - fix query docs to also explain how it can be used for infinite scroll and pagination.
40
38
  //p2 - when like doesn't have a string - don't send it to the db
41
- //p1 - vite 5.3 supports ts5 decorators - check and adapt.
42
- //p1 - tutorial about controller - and mutable controller
43
- //p1 - docs abount subscription channel
44
- //p1 - Consider enforcing serial operations on JSON database to prevent data loss
45
- //p1 - add LifecycleEvent to documentation
46
- //p1 - fix chaining of saving and saved in multiple entity options args
47
- //p1 - review starter and examples and separate remult * auth from the routes
39
+ //p2 - vite 5.3 supports ts5 decorators - check and adapt.
40
+ //p2 - tutorial about controller - and mutable controller
41
+ //p2 - docs abount subscription channel
42
+ //p2 - add LifecycleEvent to documentation
43
+ //p2 - fix chaining of saving and saved in multiple entity options args
48
44
  //y1 - live query with count #436
49
45
  //y1 TODO - In the esm version of our tutorial - the imports are automatically .ts and not .js in react and not in vue
50
46
  //y1 TODO - fix remult admin not to load the html into memory until used
@@ -66,12 +62,10 @@ const result = await repo.batch(x=>({
66
62
  */
67
63
  //y1 - wait a second to close stream -see pr
68
64
  //p1 - prepare the createEntity discussion
69
- //p1 - article on displayValue including it's definition for entities that is used in relations
70
- //p1 - article auth.js with express - played with it, requires type="module" and a few more configurations - https://github.com/noam-honig/express-auth
71
- //p2 - create foreign key constraints in user code - https://codesandbox.io/p/devbox/fk-validator-tdshcs
65
+ //p2 - article on displayValue including it's definition for entities that is used in relations
66
+ //p2 - create foreign key constraints in user code - https://codesandbox.io/p/devbox/fk-validator-tdshcs, https://gist.github.com/jycouet/8b264e18c4d8605736f4353062a7d81e
72
67
  //y2 - should we validate relations
73
68
  //y1 - dependency of live query tables live query refresh of view on table update
74
- //p1 - see why mongo tests do not run anymore
75
69
  //y2 - consider replacing all errors with error classes that extend the base Error class
76
70
  //y2 - should enforce integer - currently we probably round / truncate it
77
71
  //y1 - talk about filter on objects that are not loaded - {
@@ -100,11 +94,9 @@ const result = await repo.batch(x=>({
100
94
  //y2 - constraints (@JY)
101
95
  //p1 - when a tasks table exists in a different schema - we get many errors
102
96
  //p1 - live query with include
103
- //p1 - adjust angular tutorial starter kit for latest angular (as is in tutorial)
104
97
  //y2 - Fix problem with promise all in sql expression recurssion - when using PromiseAll in row relation loading, some sql expressions appear is recursion call even if they are not
105
98
  //p2 - when subscribe is forbidden - the query still runs after the renew process
106
99
  //p2 - 'update tasks set where id = $1
107
- //y2 - remove __dirname from tutorials
108
100
  //p2 - when value changes for field with error, clear error - so the user will feel comfortable
109
101
  //p2 - allowApiUpdate should be false for include in api false
110
102
  //docs
package/graphql.js CHANGED
@@ -392,14 +392,14 @@ function remultGraphql(options) {
392
392
  root[createResolverKey_1] = handleMutationWithErrors_1(function (dApi, response, setResult, arg1, req) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
393
393
  return tslib_1.__generator(this, function (_a) {
394
394
  switch (_a.label) {
395
- case 0: return [4 /*yield*/, dApi.post(tslib_1.__assign(tslib_1.__assign({}, response), { created: function (y) {
395
+ case 0: return [4 /*yield*/, dApi.httpPost(tslib_1.__assign(tslib_1.__assign({}, response), { created: function (y) {
396
396
  var _a;
397
397
  var orig = tslib_1.__assign({}, y);
398
398
  currentType.query.resultProcessors.forEach(function (z) { return z(y, orig); });
399
399
  setResult((_a = {},
400
400
  _a[toCamelCase(getMetaType(meta))] = y,
401
401
  _a));
402
- } }), arg1.input)];
402
+ } }), { get: function () { return undefined; } }, arg1.input, undefined)];
403
403
  case 1:
404
404
  _a.sent();
405
405
  return [2 /*return*/];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.27.20-next.1",
3
+ "version": "0.27.20-next.3",
4
4
  "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev",
6
6
  "repository": {
package/remult-mongo.js CHANGED
@@ -529,8 +529,11 @@ var MongoEntityDataProvider = /** @class */ (function () {
529
529
  index_js_1.Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
530
530
  _c = (_b = collection).deleteOne;
531
531
  return [4 /*yield*/, f.resolveWhere()];
532
- case 2:
533
- _c.apply(_b, [_d.sent(), { session: this.session }]);
532
+ case 2: return [4 /*yield*/, _c.apply(_b, [_d.sent(), {
533
+ session: this.session,
534
+ }])];
535
+ case 3:
536
+ _d.sent();
534
537
  return [2 /*return*/];
535
538
  }
536
539
  });
package/src/data-api.d.ts CHANGED
@@ -8,6 +8,7 @@ export declare class DataApi<T = unknown> {
8
8
  constructor(repository: Repository<T>, remult: Remult);
9
9
  httpGet(res: DataApiResponse, req: DataApiRequest, serializeContext: () => Promise<any>): void | Promise<void>;
10
10
  httpPost(res: DataApiResponse, req: DataApiRequest, body: any, serializeContext: () => Promise<any>): Promise<void>;
11
+ upsertMany(response: DataApiResponse, request: DataApiRequest, body: any): Promise<void>;
11
12
  query(response: DataApiResponse, request: DataApiRequest, body: any): Promise<{
12
13
  items: any[];
13
14
  aggregates: import("./remult3/remult3.js").GroupByResult<T, any[], any[], any[], any[], any[], any[]>;
@@ -36,7 +37,7 @@ export declare class DataApi<T = unknown> {
36
37
  put(response: DataApiResponse, id: any, body: any): Promise<void>;
37
38
  actualDelete(row: any): Promise<void>;
38
39
  delete(response: DataApiResponse, id: any): Promise<void>;
39
- post(response: DataApiResponse, body: any): Promise<void>;
40
+ post(body: any): Promise<any>;
40
41
  }
41
42
  export interface DataApiResponse {
42
43
  success(data: any): void;