remult 3.4.0-next.4 → 3.4.0-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.
Files changed (53) hide show
  1. package/SseSubscriptionServer.d.ts +2 -0
  2. package/SseSubscriptionServer.js +40 -0
  3. package/esm/SseSubscriptionServer.js +13 -0
  4. package/esm/index.js +7 -6
  5. package/esm/remult-d1.js +1 -0
  6. package/esm/remult-knex/index.js +112 -2
  7. package/esm/remult-mongo.js +35 -20
  8. package/esm/remult-sqlite-core.js +1 -0
  9. package/esm/server/remult-api-server.js +15 -14
  10. package/esm/src/data-api.js +19 -8
  11. package/esm/src/data-interfaces.js +4 -4
  12. package/esm/src/data-providers/array-entity-data-provider.js +14 -2
  13. package/esm/src/data-providers/in-memory-database.js +12 -1
  14. package/esm/src/data-providers/indexed-db-data-provider.js +844 -0
  15. package/esm/src/data-providers/json-data-provider.js +2 -2
  16. package/esm/src/data-providers/rest-data-provider.js +11 -12
  17. package/esm/src/data-providers/sql-database.js +275 -149
  18. package/esm/src/remult3/RepositoryImplementation.js +137 -60
  19. package/index.d.ts +32 -17
  20. package/index.js +5 -2
  21. package/internals.d.ts +2 -0
  22. package/package.json +1 -1
  23. package/remult-d1.d.ts +1 -0
  24. package/remult-d1.js +1 -0
  25. package/remult-knex/index.js +329 -44
  26. package/remult-mongo.js +103 -52
  27. package/remult-sqlite-core.d.ts +1 -0
  28. package/remult-sqlite-core.js +1 -0
  29. package/server/remult-api-server.d.ts +12 -0
  30. package/server/remult-api-server.js +19 -39
  31. package/src/data-api.d.ts +2 -5
  32. package/src/data-api.js +118 -90
  33. package/src/data-interfaces.d.ts +9 -3
  34. package/src/data-interfaces.js +4 -4
  35. package/src/data-providers/array-entity-data-provider.d.ts +2 -2
  36. package/src/data-providers/array-entity-data-provider.js +92 -14
  37. package/src/data-providers/in-memory-database.d.ts +5 -2
  38. package/src/data-providers/in-memory-database.js +22 -1
  39. package/src/data-providers/indexed-db-data-provider.d.ts +61 -0
  40. package/src/data-providers/indexed-db-data-provider.js +1459 -0
  41. package/src/data-providers/json-data-provider.js +2 -2
  42. package/src/data-providers/rest-data-provider.d.ts +2 -3
  43. package/src/data-providers/rest-data-provider.js +18 -17
  44. package/src/data-providers/sql-database.d.ts +46 -2
  45. package/src/data-providers/sql-database.js +544 -273
  46. package/src/entity.d.ts +12 -0
  47. package/src/live-query/SubscriptionServer.d.ts +4 -0
  48. package/src/remult3/RepositoryImplementation.d.ts +7 -4
  49. package/src/remult3/RepositoryImplementation.js +523 -326
  50. package/src/remult3/remult3.d.ts +6 -4
  51. package/src/remult3/repository-internals.d.ts +4 -2
  52. package/src/sql-command.d.ts +2 -0
  53. package/src/standard-schema/index.d.ts +1 -1
@@ -1,10 +1,12 @@
1
1
  import type { GenericResponse } from './server/index.js';
2
+ import type { SubscriptionServerRouteApi } from './server/remult-api-server.js';
2
3
  import type { Remult } from './src/context.js';
3
4
  import type { SubscriptionServer } from './src/live-query/SubscriptionServer.js';
4
5
  /** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
5
6
  export declare const ssePingMs = 15000;
6
7
  export declare class SseSubscriptionServer implements SubscriptionServer {
7
8
  private canUserConnectToChannel?;
9
+ initApiServer(api: SubscriptionServerRouteApi): void;
8
10
  constructor(canUserConnectToChannel?: ((channel: string, remult: Remult) => boolean) | undefined);
9
11
  publishMessage<T>(channel: string, message: any): Promise<void>;
10
12
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.clientConnection = exports.SseSubscriptionServer = exports.ssePingMs = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var SseSubscriptionClient_js_1 = require("./src/live-query/SseSubscriptionClient.js");
6
+ var SubscriptionChannel_js_1 = require("./src/live-query/SubscriptionChannel.js");
6
7
  /** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
7
8
  exports.ssePingMs = 15000;
8
9
  var SseSubscriptionServer = /** @class */ (function () {
@@ -18,6 +19,45 @@ var SseSubscriptionServer = /** @class */ (function () {
18
19
  this.canUserConnectToChannel = function () { return true; };
19
20
  }
20
21
  }
22
+ SseSubscriptionServer.prototype.initApiServer = function (api) {
23
+ var _this = this;
24
+ var streamPath = '/' + SubscriptionChannel_js_1.streamUrl;
25
+ api.addRoute(streamPath, 'get', function (_a) { return tslib_1.__awaiter(_this, [_a], void 0, function (_b) {
26
+ var req = _b.req, origRes = _b.origRes;
27
+ return tslib_1.__generator(this, function (_c) {
28
+ this.openHttpServerStream(req, origRes);
29
+ return [2 /*return*/];
30
+ });
31
+ }); });
32
+ api.addRoute(streamPath + '/subscribe', 'post', function (_a) { return tslib_1.__awaiter(_this, [_a], void 0, function (_b) {
33
+ var _c;
34
+ var remult = _b.remult, res = _b.res, getBody = _b.getBody;
35
+ return tslib_1.__generator(this, function (_d) {
36
+ switch (_d.label) {
37
+ case 0:
38
+ _c = this.subscribeToChannel;
39
+ return [4 /*yield*/, getBody()];
40
+ case 1:
41
+ _c.apply(this, [_d.sent(), res, remult]);
42
+ return [2 /*return*/];
43
+ }
44
+ });
45
+ }); });
46
+ api.addRoute(streamPath + '/unsubscribe', 'post', function (_a) { return tslib_1.__awaiter(_this, [_a], void 0, function (_b) {
47
+ var _c;
48
+ var remult = _b.remult, res = _b.res, getBody = _b.getBody;
49
+ return tslib_1.__generator(this, function (_d) {
50
+ switch (_d.label) {
51
+ case 0:
52
+ _c = this.subscribeToChannel;
53
+ return [4 /*yield*/, getBody()];
54
+ case 1:
55
+ _c.apply(this, [_d.sent(), res, remult, true]);
56
+ return [2 /*return*/];
57
+ }
58
+ });
59
+ }); });
60
+ };
21
61
  //@internal
22
62
  SseSubscriptionServer.prototype.subscribeToChannel = function (_a, res, remult, remove) {
23
63
  var e_1, _b;
@@ -1,8 +1,21 @@
1
1
  import { ConnectionNotFoundError } from './src/live-query/SseSubscriptionClient.js';
2
+ import { streamUrl } from './src/live-query/SubscriptionChannel.js';
2
3
  /** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
3
4
  export const ssePingMs = 15_000;
4
5
  export class SseSubscriptionServer {
5
6
  canUserConnectToChannel;
7
+ initApiServer(api) {
8
+ const streamPath = '/' + streamUrl;
9
+ api.addRoute(streamPath, 'get', async ({ req, origRes }) => {
10
+ this.openHttpServerStream(req, origRes);
11
+ });
12
+ api.addRoute(streamPath + '/subscribe', 'post', async ({ remult, res, getBody }) => {
13
+ this.subscribeToChannel(await getBody(), res, remult);
14
+ });
15
+ api.addRoute(streamPath + '/unsubscribe', 'post', async ({ remult, res, getBody }) => {
16
+ this.subscribeToChannel(await getBody(), res, remult, true);
17
+ });
18
+ }
6
19
  //@internal
7
20
  subscribeToChannel({ channel, clientId }, res, remult, remove = false) {
8
21
  for (const c of this.connections) {
package/esm/index.js CHANGED
@@ -9,31 +9,32 @@ export { LabelTransformer } from './src/remult3/RepositoryImplementation.js';
9
9
  export const CaptionTransformer = LabelTransformer;
10
10
  export { Entity } from './src/remult3/entity.js';
11
11
  export { getEntityRef } from './src/remult3/getEntityRef.js';
12
- export { Field, Fields, Relations, } from './src/remult3/Fields.js';
12
+ export { Field, Fields, Relations } from './src/remult3/Fields.js';
13
13
  export { IdEntity } from './src/remult3/IdEntity.js';
14
14
  export { describeClass, describeBackendMethods, describeEntity, } from './src/remult3/classDescribers.js';
15
- export { EntityError, } from './src/data-interfaces.js'; //V
15
+ export { EntityError } from './src/data-interfaces.js'; //V
16
16
  export { RestDataProvider } from './src/data-providers/rest-data-provider.js'; //V
17
17
  export { InMemoryDataProvider } from './src/data-providers/in-memory-database.js'; //V
18
18
  export { SqlDatabase } from './src/data-providers/sql-database.js'; //V
19
19
  export { CustomSqlFilterBuilder, dbNamesOf, } from './src/filter/filter-consumer-bridge-to-sql-request.js';
20
- export { JsonDataProvider, } from './src/data-providers/json-data-provider.js'; //V
20
+ export { JsonDataProvider } from './src/data-providers/json-data-provider.js'; //V
21
21
  export { JsonEntityOpfsStorage } from './src/data-providers/json-entity-opfs-storage.js';
22
22
  export { JsonEntityIndexedDbStorage } from './src/data-providers/json-entity-indexed-db-data-provider.js';
23
+ export { IndexedDbDataProvider, IndexedDbIndexBuilder, } from './src/data-providers/indexed-db-data-provider.js';
23
24
  //export * from './src/data-api'; //reconsider if to make internal
24
25
  export { Controller, BackendMethod, ProgressListener, ForbiddenError, } from './src/server-action.js';
25
26
  export { Allow, Remult, withRemult, isBackend, EventSource, } from './src/context.js';
26
27
  export { Sort } from './src/sort.js';
27
28
  export { CompoundIdField } from './src/CompoundIdField.js';
28
- export { Filter, } from './src/filter/filter-interfaces.js';
29
+ export { Filter } from './src/filter/filter-interfaces.js';
29
30
  export { UrlBuilder } from './urlBuilder.js';
30
31
  export { Validators, valueValidator, createValidator, createValidatorWithArgs, createValueValidatorWithArgs, createValueValidator, } from './src/validators.js';
31
32
  export { ValueConverters } from './src/valueConverters.js';
32
33
  export { remult } from './src/remult-proxy.js';
33
34
  import { remult } from './src/remult-proxy.js';
34
35
  //export { getId } from './src/remult3/getId';
35
- export { InMemoryLiveQueryStorage, } from './src/live-query/SubscriptionServer.js';
36
- export { SubscriptionChannel, } from './src/live-query/SubscriptionChannel.js';
36
+ export { InMemoryLiveQueryStorage } from './src/live-query/SubscriptionServer.js';
37
+ export { SubscriptionChannel } from './src/live-query/SubscriptionChannel.js';
37
38
  /**
38
39
  * A convenient shortcut function to quickly obtain a repository for a specific entity type in Remult.
39
40
  *
package/esm/remult-d1.js CHANGED
@@ -6,6 +6,7 @@ export function createD1DataProvider(d1) {
6
6
  }
7
7
  export class D1DataProvider extends SqliteCoreDataProvider {
8
8
  d1;
9
+ maxParametersInOneSqlStatement = 100;
9
10
  /**
10
11
  * For production or local d1 using binding
11
12
  *
@@ -234,16 +234,87 @@ class KnexEntityDataProvider {
234
234
  return undefined;
235
235
  return getRowAfterUpdate(this.entity, this, data, id, 'update');
236
236
  }
237
- async delete(id) {
237
+ async delete(ids) {
238
+ if (ids.length === 0)
239
+ return;
238
240
  const e = await this.init();
239
241
  let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
240
- Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
242
+ Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
241
243
  let where = await f.resolveWhere();
242
244
  await this.getEntityFrom(e)
243
245
  .delete()
244
246
  .where((b) => where.forEach((w) => w(b)));
245
247
  }
246
248
  async insert(data, options) {
249
+ if (data.length === 0)
250
+ return [];
251
+ if (isMysqlKnex(this.knex) &&
252
+ isAutoIncrement(this.entity.idMetadata.field) &&
253
+ options?.select !== 'none') {
254
+ const result = [];
255
+ for (const row of data)
256
+ result.push(await this.insertOne(row, options));
257
+ return result;
258
+ }
259
+ const e = await this.init();
260
+ const batches = splitByMaxParameters(data, (row) => countKnexInsertBindParameters(this.entity, e, row), knexMaxParametersInOneSqlStatement(this.knex));
261
+ const result = [];
262
+ for (const batch of batches)
263
+ result.push(...(await this.insertBatch(batch, e, options)));
264
+ return result;
265
+ }
266
+ async insertBatch(batch, e, options) {
267
+ const cols = [];
268
+ for (const x of this.entity.fields) {
269
+ if (isDbReadonly(x, e))
270
+ continue;
271
+ if (batch.some((row) => translateValueAndHandleArrayAndHandleArray(x, row[x.key]) !=
272
+ undefined))
273
+ cols.push(x);
274
+ }
275
+ const insertObjects = [];
276
+ for (const row of batch) {
277
+ const insertObject = {};
278
+ for (const x of cols) {
279
+ const v = translateValueAndHandleArrayAndHandleArray(x, row[x.key]);
280
+ const key = await e.$dbNameOf(x);
281
+ if (v != undefined)
282
+ insertObject[key] = v;
283
+ else if (x.allowNull)
284
+ insertObject[key] = null;
285
+ else
286
+ insertObject[key] = this.knex.raw('DEFAULT');
287
+ }
288
+ insertObjects.push(insertObject);
289
+ }
290
+ let insert = this.getEntityFrom(e).insert(insertObjects);
291
+ const wantRows = options?.select !== 'none';
292
+ if (isAutoIncrement(this.entity.idMetadata.field) && wantRows) {
293
+ const result = await insert.returning(this.entity.idMetadata.field.dbName);
294
+ const ids = result.map((r) => r && typeof r === 'object'
295
+ ? r[this.entity.idMetadata.field.dbName]
296
+ : r);
297
+ return this.loadRowsByIds(ids);
298
+ }
299
+ await insert;
300
+ if (!wantRows)
301
+ return batch.map(() => undefined);
302
+ return this.loadRowsByIds(batch.map((row) => this.entity.idMetadata.getId(row)));
303
+ }
304
+ async loadRowsByIds(ids) {
305
+ const found = await this.find({
306
+ where: Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)),
307
+ });
308
+ const byId = new Map(found.map((row) => [this.entity.idMetadata.getId(row) + '', row]));
309
+ return ids.map((id) => {
310
+ const row = byId.get(id + '');
311
+ if (!row)
312
+ throw new Error(`Failed to insert row - result contained ${found.length} rows`);
313
+ return row;
314
+ });
315
+ }
316
+ //@internal
317
+ async insertOne(data, options) {
247
318
  const e = await this.init();
248
319
  let insertObject = {};
249
320
  for (const x of this.entity.fields) {
@@ -591,6 +662,45 @@ export async function createKnexDataProvider(config) {
591
662
  let result = new KnexDataProvider(k);
592
663
  return result;
593
664
  }
665
+ const defaultMaxParametersInOneSqlStatement = 2000;
666
+ function isMysqlKnex(knex) {
667
+ const client = knex.client.config.client;
668
+ return client === 'mysql2' || client === 'mysql';
669
+ }
670
+ function knexMaxParametersInOneSqlStatement(knex) {
671
+ const client = String(knex.client.config.client ?? '');
672
+ if (client.includes('sqlite'))
673
+ return 999;
674
+ return defaultMaxParametersInOneSqlStatement;
675
+ }
676
+ function splitByMaxParameters(items, parametersInItem, maxParameters) {
677
+ const batches = [];
678
+ let batch = [];
679
+ let params = 0;
680
+ for (const item of items) {
681
+ const n = parametersInItem(item);
682
+ if (batch.length > 0 && params + n > maxParameters) {
683
+ batches.push(batch);
684
+ batch = [];
685
+ params = 0;
686
+ }
687
+ batch.push(item);
688
+ params += n;
689
+ }
690
+ if (batch.length)
691
+ batches.push(batch);
692
+ return batches;
693
+ }
694
+ function countKnexInsertBindParameters(entity, e, row) {
695
+ let n = 0;
696
+ for (const x of entity.fields) {
697
+ if (isDbReadonly(x, e))
698
+ continue;
699
+ if (translateValueAndHandleArrayAndHandleArray(x, row[x.key]) != undefined)
700
+ n++;
701
+ }
702
+ return n;
703
+ }
594
704
  function translateValueAndHandleArrayAndHandleArray(field, val) {
595
705
  let result = field.valueConverter.toDb(val);
596
706
  if (Array.isArray(result))
@@ -119,6 +119,8 @@ class MongoEntityDataProvider {
119
119
  translateToDb(row, nameProvider) {
120
120
  let result = {};
121
121
  for (const col of this.entity.fields) {
122
+ if (col.dbReadOnly || col.isServerExpression)
123
+ continue;
122
124
  let val = toDb(col, row[col.key]);
123
125
  if (val === null)
124
126
  val = NULL;
@@ -255,7 +257,7 @@ class MongoEntityDataProvider {
255
257
  for (const f of this.entity.fields) {
256
258
  if (!f.dbReadOnly && !f.isServerExpression) {
257
259
  if (keys.includes(f.key)) {
258
- newR[f.key] = toDb(f, data[f.key]);
260
+ newR[e.$dbNameOf(f)] = toDb(f, data[f.key]);
259
261
  }
260
262
  }
261
263
  }
@@ -266,22 +268,31 @@ class MongoEntityDataProvider {
266
268
  return undefined;
267
269
  return getRowAfterUpdate(this.entity, this, data, id, 'update');
268
270
  }
269
- async delete(id) {
271
+ async delete(ids) {
272
+ if (ids.length === 0)
273
+ return;
270
274
  const { e, collection } = await this.collection();
271
275
  let f = new FilterConsumerBridgeToMongo(e);
272
- Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
273
- await collection.deleteOne(await f.resolveWhere(), {
276
+ Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
277
+ await collection.deleteMany(await f.resolveWhere(), {
274
278
  session: this.session,
275
279
  });
276
280
  }
277
281
  async insert(data, options) {
282
+ if (data.length === 0)
283
+ return [];
278
284
  let { collection, e } = await this.collection();
279
- let r = await collection.insertOne(await this.translateToDb(data, e), {
280
- session: this.session,
281
- });
285
+ const docs = [];
286
+ for (const row of data)
287
+ docs.push(await this.translateToDb(row, e));
288
+ const r = await collection.insertMany(docs, { session: this.session });
282
289
  if (options?.select === 'none')
283
- return undefined;
284
- return await this.translateFromDb(await collection.findOne({ _id: r.insertedId }, { session: this.session }), e);
290
+ return data.map(() => undefined);
291
+ const result = [];
292
+ for (let i = 0; i < data.length; i++) {
293
+ result.push(await this.translateFromDb(await collection.findOne({ _id: r.insertedIds[i] }, { session: this.session }), e));
294
+ }
295
+ return result;
285
296
  }
286
297
  async collection() {
287
298
  const e = await dbNamesOf(this.entity);
@@ -377,23 +388,27 @@ class FilterConsumerBridgeToMongo {
377
388
  this.add(col, val, '$lt');
378
389
  }
379
390
  containsCaseInsensitive(col, val) {
380
- this.add(col, val, '$regex', { $options: 'i' });
391
+ this.addRegex(col, val);
381
392
  }
382
393
  notContainsCaseInsensitive(col, val) {
383
- this.result.push(() => ({
384
- [this.nameProvider.$dbNameOf(col)]: {
385
- $not: {
386
- $regex: isNull(val) ? val : toDb(col, val),
387
- $options: 'i',
388
- },
389
- },
390
- }));
394
+ this.addRegex(col, val, '', '', true);
391
395
  }
392
396
  startsWithCaseInsensitive(col, val) {
393
- this.add(col, `^${val}`, '$regex', { $options: 'i' });
397
+ this.addRegex(col, val, '^');
394
398
  }
395
399
  endsWithCaseInsensitive(col, val) {
396
- this.add(col, `${val}$`, '$regex', { $options: 'i' });
400
+ this.addRegex(col, val, '', '$');
401
+ }
402
+ addRegex(col, val, start = '', end = '', negate = false) {
403
+ this.result.push(() => {
404
+ const regex = {
405
+ $regex: start + toDb(col, val).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + end,
406
+ $options: 'i',
407
+ };
408
+ return {
409
+ [this.nameProvider.$dbNameOf(col)]: negate ? { $not: regex } : regex,
410
+ };
411
+ });
397
412
  }
398
413
  add(col, val, operator, moreOptions) {
399
414
  this.result.push(() => ({
@@ -13,6 +13,7 @@ export class SqliteCoreDataProvider {
13
13
  this.doesNotSupportReturningSyntaxOnlyForUpdate = doesNotSupportReturningSyntaxOnlyForUpdate;
14
14
  }
15
15
  orderByNullsFirst;
16
+ maxParametersInOneSqlStatement = 999;
16
17
  getLimitSqlSyntax(limit, offset) {
17
18
  return ' limit ' + limit + ' offset ' + offset;
18
19
  }
@@ -2,7 +2,7 @@ import { __decorate, __metadata } from "tslib";
2
2
  import { SseSubscriptionServer } from '../SseSubscriptionServer.js';
3
3
  import { Remult, RemultAsyncLocalStorage, withRemult } from '../src/context.js';
4
4
  import { DataApi, serializeError } from '../src/data-api.js';
5
- import { liveQueryKeepAliveRoute, streamUrl, } from '../src/live-query/SubscriptionChannel.js';
5
+ import { liveQueryKeepAliveRoute } from '../src/live-query/SubscriptionChannel.js';
6
6
  import { InMemoryLiveQueryStorage, LiveQueryPublisher, } from '../src/live-query/SubscriptionServer.js';
7
7
  import { IdEntity } from '../src/remult3/IdEntity.js';
8
8
  import { Fields } from '../src/remult3/Fields.js';
@@ -266,19 +266,20 @@ export class RemultServerImplementation {
266
266
  r.route(this.options.rootPath + '/admin').get(admin());
267
267
  }
268
268
  r.route(this.options.rootPath + '/me').get(this.process(async (remult, req, res) => res.success(remult.user ?? null)));
269
- if (this.options.subscriptionServer instanceof SseSubscriptionServer) {
270
- const streamPath = this.options.rootPath + '/' + streamUrl;
271
- r.route(streamPath).get(this.process(async (remult, req, res, origReq, origRes) => {
272
- ;
273
- remult.subscriptionServer.openHttpServerStream(origReq, origRes);
274
- }));
275
- r.route(streamPath + '/subscribe').post(this.process(async (remult, _2, res, _, _1, origReq) => {
276
- const body = remult.subscriptionServer.subscribeToChannel(await this.coreOptions.getRequestBody(origReq), res, remult);
277
- }));
278
- r.route(streamPath + '/unsubscribe').post(this.process(async (remult, req, res, reqInfo, origRes, origReq) => {
279
- ;
280
- remult.subscriptionServer.subscribeToChannel(await this.coreOptions.getRequestBody(origReq), res, remult, true);
281
- }));
269
+ const subscriptionServer = this.options.subscriptionServer;
270
+ if (subscriptionServer?.initApiServer) {
271
+ subscriptionServer.initApiServer({
272
+ rootPath: this.options.rootPath,
273
+ addRoute: (relativePath, method, handler) => {
274
+ r.route(this.options.rootPath + relativePath)[method](this.process(async (remult, _, res, req, origRes, origReq) => handler({
275
+ remult,
276
+ res,
277
+ req,
278
+ origRes,
279
+ getBody: () => this.coreOptions.getRequestBody(origReq),
280
+ })));
281
+ },
282
+ });
282
283
  }
283
284
  r.route(this.options.rootPath + '/' + liveQueryKeepAliveRoute).post(this.process(async (remult, req, res, reqInfo, origRes, origReq) => {
284
285
  const body = await this.coreOptions.getRequestBody(origReq);
@@ -12,7 +12,7 @@ export class DataApi {
12
12
  this.repository = repository;
13
13
  this.remult = remult;
14
14
  }
15
- httpGet(res, req, serializeContext) {
15
+ async httpGet(res, req, serializeContext) {
16
16
  try {
17
17
  const action = req?.get('__action');
18
18
  if (action?.startsWith(liveQueryAction))
@@ -22,13 +22,13 @@ export class DataApi {
22
22
  case 'count':
23
23
  return this.count(res, req, undefined);
24
24
  case 'groupBy':
25
- return res.success(this.groupBy(req, undefined));
25
+ return res.success(await this.groupBy(req, undefined));
26
26
  }
27
27
  return this.getArray(res, req, undefined);
28
28
  }
29
29
  catch (err) {
30
30
  if (err.isForbiddenError)
31
- res.forbidden(err);
31
+ res.forbidden(err.message);
32
32
  else
33
33
  res.error(err, this.repository.metadata);
34
34
  }
@@ -70,7 +70,7 @@ export class DataApi {
70
70
  res.success('ok');
71
71
  return;
72
72
  case 'query':
73
- return res.success(await this.query(res, req, body));
73
+ return this.query(res, req, body);
74
74
  default:
75
75
  return res.created(await this.post(body, req));
76
76
  }
@@ -124,10 +124,10 @@ export class DataApi {
124
124
  this.getArrayImpl(request, rest),
125
125
  this.groupBy(aggregateRequest, { ...aggregate, where: body.where }),
126
126
  ]);
127
- return {
127
+ return response.success({
128
128
  items: r,
129
129
  aggregates,
130
- };
130
+ });
131
131
  }
132
132
  catch (err) {
133
133
  if (err.isForbiddenError)
@@ -513,11 +513,22 @@ export class DataApi {
513
513
  return this.repository.getEntityRef(newr).toApiJson();
514
514
  };
515
515
  if (Array.isArray(body)) {
516
- const result = [];
516
+ let result = [];
517
517
  await doTransaction(this.remult, async () => {
518
+ const items = [];
518
519
  for (const item of body) {
519
- result.push(await insert(item));
520
+ let newr = this.repository.create();
521
+ await this.repository.getEntityRef(newr)._updateEntityBasedOnApi(item);
522
+ if (!this.repository.getEntityRef(newr).apiInsertAllowed) {
523
+ throw new ForbiddenError();
524
+ }
525
+ items.push(newr);
520
526
  }
527
+ const inserted = await this.repository.insert(items, options);
528
+ if (options?.select === 'none')
529
+ result = items.map(() => undefined);
530
+ else
531
+ result = inserted.map((row) => this.repository.getEntityRef(row).toApiJson());
521
532
  });
522
533
  return result;
523
534
  }
@@ -40,11 +40,11 @@ class EntityDataProviderPromiseWrapper {
40
40
  constructor(dataProvider) {
41
41
  this.dataProvider = dataProvider;
42
42
  }
43
- delete(id) {
44
- return this.dataProvider.then((dp) => dp.delete(id));
43
+ delete(ids) {
44
+ return this.dataProvider.then((dp) => dp.delete(ids));
45
45
  }
46
- insert(data) {
47
- return this.dataProvider.then((dp) => dp.insert(data));
46
+ insert(data, options) {
47
+ return this.dataProvider.then((dp) => dp.insert(data, options));
48
48
  }
49
49
  count(where) {
50
50
  return this.dataProvider.then((dp) => dp.count(where));
@@ -318,18 +318,30 @@ export class ArrayEntityDataProvider {
318
318
  }
319
319
  throw new Error(`ArrayEntityDataProvider: Couldn't find row with id "${id}" in entity "${this.entity.key}" to update`);
320
320
  }
321
- async delete(id) {
321
+ async delete(ids) {
322
+ for (const id of ids)
323
+ await this.deleteOne(id);
324
+ }
325
+ //@internal
326
+ async deleteOne(id) {
322
327
  const names = await this.init();
323
328
  let idMatches = this.idMatches(id, names);
324
329
  for (let i = 0; i < this.rows().length; i++) {
325
330
  if (idMatches(this.rows()[i])) {
326
331
  this.rows().splice(i, 1);
327
- return Promise.resolve();
332
+ return;
328
333
  }
329
334
  }
330
335
  throw new Error(`ArrayEntityDataProvider: Couldn't find row with id "${id}" in entity "${this.entity.key}" to delete`);
331
336
  }
332
337
  async insert(data) {
338
+ const result = [];
339
+ for (const row of data)
340
+ result.push(await this.insertOne(row));
341
+ return result;
342
+ }
343
+ //@internal
344
+ async insertOne(data) {
333
345
  const names = await this.init();
334
346
  let j = this.translateToJson(data, names);
335
347
  let idf = this.entity.idMetadata.field;
@@ -1,3 +1,4 @@
1
+ import { getEntityMetadata, } from '../remult3/RepositoryImplementation.js';
1
2
  import { ArrayEntityDataProvider } from './array-entity-data-provider.js';
2
3
  export class InMemoryDataProvider {
3
4
  async transaction(action) {
@@ -15,7 +16,17 @@ export class InMemoryDataProvider {
15
16
  let name = entity.dbName;
16
17
  if (!this.rows[name])
17
18
  this.rows[name] = [];
18
- return new ArrayEntityDataProvider(entity, () => this.rows[name]);
19
+ return new ArrayEntityDataProvider(entity, () => {
20
+ if (!this.rows[name])
21
+ this.rows[name] = [];
22
+ return this.rows[name];
23
+ });
24
+ }
25
+ async dropDatabase() {
26
+ this.rows = {};
27
+ }
28
+ async dropTable(entity) {
29
+ delete this.rows[getEntityMetadata(entity).dbName];
19
30
  }
20
31
  toString() {
21
32
  return 'InMemoryDataProvider';