remult 3.4.0-next.4 → 3.4.0-next.5

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 (42) hide show
  1. package/esm/index.js +7 -6
  2. package/esm/remult-d1.js +1 -0
  3. package/esm/remult-knex/index.js +112 -2
  4. package/esm/remult-mongo.js +17 -8
  5. package/esm/remult-sqlite-core.js +1 -0
  6. package/esm/src/data-api.js +13 -2
  7. package/esm/src/data-interfaces.js +4 -4
  8. package/esm/src/data-providers/array-entity-data-provider.js +14 -2
  9. package/esm/src/data-providers/in-memory-database.js +12 -1
  10. package/esm/src/data-providers/indexed-db-data-provider.js +844 -0
  11. package/esm/src/data-providers/json-data-provider.js +2 -2
  12. package/esm/src/data-providers/rest-data-provider.js +11 -12
  13. package/esm/src/data-providers/sql-database.js +110 -43
  14. package/esm/src/remult3/RepositoryImplementation.js +135 -60
  15. package/index.d.ts +31 -17
  16. package/index.js +5 -2
  17. package/package.json +1 -1
  18. package/remult-d1.d.ts +1 -0
  19. package/remult-d1.js +1 -0
  20. package/remult-knex/index.js +329 -44
  21. package/remult-mongo.js +80 -36
  22. package/remult-sqlite-core.d.ts +1 -0
  23. package/remult-sqlite-core.js +1 -0
  24. package/src/data-api.js +38 -21
  25. package/src/data-interfaces.d.ts +9 -3
  26. package/src/data-interfaces.js +4 -4
  27. package/src/data-providers/array-entity-data-provider.d.ts +2 -2
  28. package/src/data-providers/array-entity-data-provider.js +92 -14
  29. package/src/data-providers/in-memory-database.d.ts +5 -2
  30. package/src/data-providers/in-memory-database.js +22 -1
  31. package/src/data-providers/indexed-db-data-provider.d.ts +61 -0
  32. package/src/data-providers/indexed-db-data-provider.js +1459 -0
  33. package/src/data-providers/json-data-provider.js +2 -2
  34. package/src/data-providers/rest-data-provider.d.ts +2 -3
  35. package/src/data-providers/rest-data-provider.js +18 -17
  36. package/src/data-providers/sql-database.js +254 -105
  37. package/src/entity.d.ts +12 -0
  38. package/src/remult3/RepositoryImplementation.d.ts +7 -4
  39. package/src/remult3/RepositoryImplementation.js +521 -326
  40. package/src/remult3/remult3.d.ts +6 -4
  41. package/src/sql-command.d.ts +2 -0
  42. package/src/standard-schema/index.d.ts +1 -1
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))
@@ -266,22 +266,31 @@ class MongoEntityDataProvider {
266
266
  return undefined;
267
267
  return getRowAfterUpdate(this.entity, this, data, id, 'update');
268
268
  }
269
- async delete(id) {
269
+ async delete(ids) {
270
+ if (ids.length === 0)
271
+ return;
270
272
  const { e, collection } = await this.collection();
271
273
  let f = new FilterConsumerBridgeToMongo(e);
272
- Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
273
- await collection.deleteOne(await f.resolveWhere(), {
274
+ Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
275
+ await collection.deleteMany(await f.resolveWhere(), {
274
276
  session: this.session,
275
277
  });
276
278
  }
277
279
  async insert(data, options) {
280
+ if (data.length === 0)
281
+ return [];
278
282
  let { collection, e } = await this.collection();
279
- let r = await collection.insertOne(await this.translateToDb(data, e), {
280
- session: this.session,
281
- });
283
+ const docs = [];
284
+ for (const row of data)
285
+ docs.push(await this.translateToDb(row, e));
286
+ const r = await collection.insertMany(docs, { session: this.session });
282
287
  if (options?.select === 'none')
283
- return undefined;
284
- return await this.translateFromDb(await collection.findOne({ _id: r.insertedId }, { session: this.session }), e);
288
+ return data.map(() => undefined);
289
+ const result = [];
290
+ for (let i = 0; i < data.length; i++) {
291
+ result.push(await this.translateFromDb(await collection.findOne({ _id: r.insertedIds[i] }, { session: this.session }), e));
292
+ }
293
+ return result;
285
294
  }
286
295
  async collection() {
287
296
  const e = await dbNamesOf(this.entity);
@@ -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
  }
@@ -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';