remult 3.3.18-next.0 → 3.3.19-next.1

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 (51) hide show
  1. package/esm/index.js +7 -6
  2. package/esm/internals.js +0 -1
  3. package/esm/remult-d1.js +1 -0
  4. package/esm/remult-knex/index.js +112 -2
  5. package/esm/remult-mongo.js +17 -8
  6. package/esm/remult-sqlite-core.js +1 -0
  7. package/esm/server/initAsyncHooks.js +3 -7
  8. package/esm/server/test-api-data-provider.js +0 -1
  9. package/esm/src/context.js +7 -66
  10. package/esm/src/data-interfaces.js +4 -4
  11. package/esm/src/data-providers/array-entity-data-provider.js +22 -4
  12. package/esm/src/data-providers/indexed-db-data-provider.js +644 -0
  13. package/esm/src/data-providers/json-data-provider.js +2 -2
  14. package/esm/src/data-providers/rest-data-provider.js +11 -12
  15. package/esm/src/data-providers/sql-database.js +110 -43
  16. package/esm/src/remult-proxy.js +0 -4
  17. package/esm/src/remult3/RepositoryImplementation.js +90 -60
  18. package/index.d.ts +31 -17
  19. package/index.js +5 -2
  20. package/internals.d.ts +0 -1
  21. package/internals.js +1 -3
  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.d.ts +2 -2
  26. package/remult-knex/index.js +329 -44
  27. package/remult-mongo.js +80 -36
  28. package/remult-sqlite-core.d.ts +1 -0
  29. package/remult-sqlite-core.js +1 -0
  30. package/server/initAsyncHooks.d.ts +11 -15
  31. package/server/initAsyncHooks.js +15 -13
  32. package/server/test-api-data-provider.js +0 -1
  33. package/src/column-interfaces.d.ts +4 -0
  34. package/src/context.d.ts +12 -37
  35. package/src/context.js +21 -114
  36. package/src/data-interfaces.d.ts +2 -3
  37. package/src/data-interfaces.js +4 -4
  38. package/src/data-providers/array-entity-data-provider.d.ts +2 -2
  39. package/src/data-providers/array-entity-data-provider.js +100 -16
  40. package/src/data-providers/indexed-db-data-provider.d.ts +37 -0
  41. package/src/data-providers/indexed-db-data-provider.js +1048 -0
  42. package/src/data-providers/json-data-provider.js +2 -2
  43. package/src/data-providers/rest-data-provider.d.ts +2 -3
  44. package/src/data-providers/rest-data-provider.js +18 -17
  45. package/src/data-providers/sql-database.js +254 -105
  46. package/src/remult-proxy.js +0 -8
  47. package/src/remult3/RepositoryImplementation.d.ts +1 -1
  48. package/src/remult3/RepositoryImplementation.js +384 -270
  49. package/src/remult3/remult3.d.ts +2 -2
  50. package/src/sql-command.d.ts +2 -0
  51. 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/internals.js CHANGED
@@ -9,4 +9,3 @@ export { isOfType } from './src/isOfType.js';
9
9
  export { flags } from './src/remult3/remult3.js';
10
10
  export { DataProviderPromiseWrapper } from './src/data-interfaces.js';
11
11
  export { pagedQueryResult } from './src/remult3/pagedQueryResult.js';
12
- export { withFetch } from './src/context.js';
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
  }
@@ -16,7 +16,7 @@ export function initAsyncHooks() {
16
16
  }
17
17
  });
18
18
  }
19
- export class AsyncLocalStorageBridgeToRemultAsyncLocalStorageCore {
19
+ export class AsyncLocalStorageBridgeToRemultAsyncLocalStorageCoreImpl {
20
20
  asyncLocalStorage = new AsyncLocalStorage();
21
21
  wasImplemented = 'yes';
22
22
  run(store, callback) {
@@ -36,14 +36,8 @@ export class AsyncLocalStorageBridgeToRemultAsyncLocalStorageCore {
36
36
  getStore() {
37
37
  return this.asyncLocalStorage.getStore();
38
38
  }
39
- createDataProviderAsyncLocalStorage() {
40
- return new AsyncLocalStorage();
41
- }
42
39
  }
43
40
  export class StubRemultAsyncLocalStorageCore {
44
- createDataProviderAsyncLocalStorage() {
45
- return undefined;
46
- }
47
41
  isStub = true;
48
42
  wasImplemented = 'yes';
49
43
  async run(store, callback) {
@@ -56,3 +50,5 @@ export class StubRemultAsyncLocalStorageCore {
56
50
  lastPromise = Promise.resolve(undefined);
57
51
  currentValue;
58
52
  }
53
+ export class AsyncLocalStorageBridgeToRemultAsyncLocalStorageCore extends AsyncLocalStorageBridgeToRemultAsyncLocalStorageCoreImpl {
54
+ }
@@ -98,7 +98,6 @@ async function MakeServerCallWithDifferentStaticRemult(what) {
98
98
  store.remult.user = user;
99
99
  return callback();
100
100
  },
101
- createDataProviderAsyncLocalStorage: () => undefined,
102
101
  wasImplemented: 'yes',
103
102
  });
104
103
  return await what();
@@ -45,9 +45,6 @@ export class RemultAsyncLocalStorage {
45
45
  if (val || this.remultObjectStorage?.isStub)
46
46
  store.inInitRequest = val;
47
47
  }
48
- createDataProviderAsyncLocalStorage() {
49
- return this.remultObjectStorage?.createDataProviderAsyncLocalStorage();
50
- }
51
48
  getStore() {
52
49
  if (!this.remultObjectStorage) {
53
50
  throw new Error("can't use static remult in this environment, `async_hooks` were not initialized");
@@ -186,15 +183,8 @@ export class Remult {
186
183
  httpClient: fetch,
187
184
  }));
188
185
  }
189
- /* @internal */
190
- _myDataProviderStore = new RemultInstanceDataProviderStore(new RestDataProvider(() => this.apiClient));
191
186
  /** The current data provider */
192
- set dataProvider(dataProvider) {
193
- this._myDataProviderStore.getStore().dataProvider = dataProvider;
194
- }
195
- get dataProvider() {
196
- return this._myDataProviderStore.getStore().dataProvider;
197
- }
187
+ dataProvider = new RestDataProvider(() => this.apiClient);
198
188
  /* @internal */
199
189
  repCache = new Map();
200
190
  constructor(provider) {
@@ -321,11 +311,10 @@ export async function doTransaction(remult, what) {
321
311
  const prev = remult.dataProvider;
322
312
  try {
323
313
  await remult.dataProvider.transaction(async (ds) => {
324
- await withRemultDataProvider({ dataProvider: ds, remult }, async () => {
325
- remult.liveQueryPublisher = trans;
326
- await what(ds);
327
- ok = true;
328
- });
314
+ remult.dataProvider = ds;
315
+ remult.liveQueryPublisher = trans;
316
+ await what(ds);
317
+ ok = true;
329
318
  });
330
319
  if (ok)
331
320
  await trans.flush();
@@ -369,54 +358,6 @@ class transactionLiveQueryPublisher {
369
358
  }
370
359
  export async function withRemult(callback, options) {
371
360
  const remult = new Remult();
372
- return remultStatic.asyncContext.run(remult, async (r) => {
373
- remult.dataProvider = await initDataProvider(options?.dataProvider, true, async () => remult.dataProvider);
374
- return await callback(r);
375
- });
376
- }
377
- export class RemultInstanceDataProviderStore {
378
- constructor(dataProvider) {
379
- this._myDataProviderStore = { dataProvider };
380
- }
381
- _myDataProviderStore;
382
- getStore = () => this._myDataProviderStore;
383
- asyncStore;
384
- async withDataProvider(dataProvider, callback) {
385
- if (!this.asyncStore) {
386
- this.asyncStore =
387
- remultStatic.asyncContext.createDataProviderAsyncLocalStorage();
388
- if (this.asyncStore) {
389
- this.getStore = () => this.asyncStore.getStore() ?? this._myDataProviderStore;
390
- }
391
- }
392
- if (this.asyncStore) {
393
- return this.asyncStore.run({ dataProvider }, async () => {
394
- return await callback();
395
- });
396
- }
397
- else {
398
- const prev = this._myDataProviderStore;
399
- try {
400
- this._myDataProviderStore = { dataProvider };
401
- return await callback();
402
- }
403
- finally {
404
- this._myDataProviderStore = prev;
405
- }
406
- }
407
- }
408
- }
409
- export function withRemultDataProvider({ dataProvider, remult }, callback) {
410
- remult = remult ?? remultStatic.remultFactory();
411
- return remult._myDataProviderStore.withDataProvider(dataProvider, callback);
412
- }
413
- export function withFetch(fetch, callback) {
414
- const remult = remultStatic.remultFactory();
415
- return withRemultDataProvider({
416
- remult,
417
- dataProvider: new RestDataProvider(() => ({
418
- ...remult.apiClient,
419
- httpClient: fetch,
420
- })),
421
- }, callback);
361
+ remult.dataProvider = await initDataProvider(options?.dataProvider, true, async () => remult.dataProvider);
362
+ return remultStatic.asyncContext.run(remult, (r) => callback(r));
422
363
  }
@@ -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));
@@ -267,8 +267,14 @@ export class ArrayEntityDataProvider {
267
267
  translateToJson(row, dbNames) {
268
268
  let result = {};
269
269
  for (const col of this.entity.fields) {
270
- if (!isDbReadonly(col, dbNames))
271
- result[dbNames.$dbNameOf(col)] = col.valueConverter.toJson(row[col.key]);
270
+ if (!isDbReadonly(col, dbNames)) {
271
+ let val = row[col.key];
272
+ if (val === undefined) {
273
+ if (col.allowNull)
274
+ val = null;
275
+ }
276
+ result[dbNames.$dbNameOf(col)] = col.valueConverter.toJson(val);
277
+ }
272
278
  }
273
279
  return result;
274
280
  }
@@ -312,18 +318,30 @@ export class ArrayEntityDataProvider {
312
318
  }
313
319
  throw new Error(`ArrayEntityDataProvider: Couldn't find row with id "${id}" in entity "${this.entity.key}" to update`);
314
320
  }
315
- 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) {
316
327
  const names = await this.init();
317
328
  let idMatches = this.idMatches(id, names);
318
329
  for (let i = 0; i < this.rows().length; i++) {
319
330
  if (idMatches(this.rows()[i])) {
320
331
  this.rows().splice(i, 1);
321
- return Promise.resolve();
332
+ return;
322
333
  }
323
334
  }
324
335
  throw new Error(`ArrayEntityDataProvider: Couldn't find row with id "${id}" in entity "${this.entity.key}" to delete`);
325
336
  }
326
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) {
327
345
  const names = await this.init();
328
346
  let j = this.translateToJson(data, names);
329
347
  let idf = this.entity.idMetadata.field;