remult 3.3.18 → 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 (38) 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-interfaces.js +4 -4
  7. package/esm/src/data-providers/array-entity-data-provider.js +14 -2
  8. package/esm/src/data-providers/indexed-db-data-provider.js +644 -0
  9. package/esm/src/data-providers/json-data-provider.js +2 -2
  10. package/esm/src/data-providers/rest-data-provider.js +11 -12
  11. package/esm/src/data-providers/sql-database.js +110 -43
  12. package/esm/src/remult3/RepositoryImplementation.js +90 -60
  13. package/index.d.ts +31 -17
  14. package/index.js +5 -2
  15. package/package.json +1 -1
  16. package/remult-d1.d.ts +1 -0
  17. package/remult-d1.js +1 -0
  18. package/remult-knex/index.d.ts +2 -2
  19. package/remult-knex/index.js +329 -44
  20. package/remult-mongo.js +80 -36
  21. package/remult-sqlite-core.d.ts +1 -0
  22. package/remult-sqlite-core.js +1 -0
  23. package/src/column-interfaces.d.ts +4 -0
  24. package/src/data-interfaces.d.ts +2 -3
  25. package/src/data-interfaces.js +4 -4
  26. package/src/data-providers/array-entity-data-provider.d.ts +2 -2
  27. package/src/data-providers/array-entity-data-provider.js +92 -14
  28. package/src/data-providers/indexed-db-data-provider.d.ts +37 -0
  29. package/src/data-providers/indexed-db-data-provider.js +1048 -0
  30. package/src/data-providers/json-data-provider.js +2 -2
  31. package/src/data-providers/rest-data-provider.d.ts +2 -3
  32. package/src/data-providers/rest-data-provider.js +18 -17
  33. package/src/data-providers/sql-database.js +254 -105
  34. package/src/remult3/RepositoryImplementation.d.ts +1 -1
  35. package/src/remult3/RepositoryImplementation.js +384 -270
  36. package/src/remult3/remult3.d.ts +2 -2
  37. package/src/sql-command.d.ts +2 -0
  38. package/src/standard-schema/index.d.ts +1 -1
@@ -0,0 +1,644 @@
1
+ import { dbNamesOf, isDbReadonly, } from '../filter/filter-consumer-bridge-to-sql-request.js';
2
+ import { isAutoIncrement } from '../remult3/RepositoryImplementation.js';
3
+ import { ArrayEntityDataProvider } from './array-entity-data-provider.js';
4
+ export class IndexedDbIndexBuilder {
5
+ //@internal
6
+ entries = [];
7
+ ensureIndexes(entity, indexes) {
8
+ this.entries.push({ entity, indexes: [...indexes] });
9
+ return this;
10
+ }
11
+ }
12
+ export class IndexedDbDataProvider {
13
+ dbName;
14
+ //@internal
15
+ declaredIndexes = [];
16
+ constructor(dbName = 'remult', options) {
17
+ this.dbName = dbName;
18
+ if (options?.indexes) {
19
+ const builder = new IndexedDbIndexBuilder();
20
+ options.indexes(builder);
21
+ this.declaredIndexes = builder.entries;
22
+ }
23
+ }
24
+ //@internal
25
+ db;
26
+ //@internal
27
+ lastFetch;
28
+ //@internal
29
+ tail = Promise.resolve();
30
+ getEntityDataProvider(entity) {
31
+ return new IndexedDbEntityDataProvider(this, entity);
32
+ }
33
+ async transaction(action) {
34
+ await action(this);
35
+ }
36
+ async ensureSchema(entities) {
37
+ await this.enqueue(() => this.ensureStores(entities));
38
+ }
39
+ close() {
40
+ this.db?.close();
41
+ this.db = undefined;
42
+ }
43
+ //@internal
44
+ enqueue(fn) {
45
+ const run = this.tail.then(fn, fn);
46
+ this.tail = run.then(() => { }, () => { });
47
+ return run;
48
+ }
49
+ //@internal
50
+ async runWithRows(entity, where, what) {
51
+ return this.enqueue(async () => {
52
+ await this.ensureStores([entity]);
53
+ const rows = await this.loadRows(entity, where);
54
+ return what(new ArrayEntityDataProvider(entity, () => rows));
55
+ });
56
+ }
57
+ //@internal
58
+ async fetchRows(entity, where) {
59
+ return this.enqueue(async () => {
60
+ await this.ensureStores([entity]);
61
+ return this.loadRows(entity, where);
62
+ });
63
+ }
64
+ //@internal
65
+ async loadRows(entity, where) {
66
+ const storeName = storeNameOf(entity);
67
+ const prefetch = await idbPrefetchFromFilter(entity, where, this.storeIndexes(storeName));
68
+ this.lastFetch = prefetch;
69
+ if (prefetch.type === 'all')
70
+ return this.getAll(storeName);
71
+ const ops = prefetch.type === 'or' ? prefetch.parts : [prefetch];
72
+ return this.fetchOps(storeName, entity, ops);
73
+ }
74
+ //@internal
75
+ storeIndexes(storeName) {
76
+ const db = this.db;
77
+ if (!db?.objectStoreNames.contains(storeName))
78
+ return [];
79
+ const store = db.transaction(storeName).objectStore(storeName);
80
+ const names = store.indexNames;
81
+ const result = [];
82
+ for (let i = 0; i < names.length; i++) {
83
+ const name = names[i];
84
+ result.push({
85
+ name,
86
+ keyPath: store.index(name).keyPath,
87
+ });
88
+ }
89
+ return result;
90
+ }
91
+ //@internal
92
+ indexesFor(entity) {
93
+ return this.declaredIndexes
94
+ .filter((e) => e.entity === entity.entityType)
95
+ .flatMap((e) => e.indexes);
96
+ }
97
+ //@internal
98
+ async ensureStores(entities) {
99
+ const wanted = await Promise.all(entities.map(async (entity) => {
100
+ const keyPath = await keyPathOf(entity);
101
+ return {
102
+ name: storeNameOf(entity),
103
+ keyPath,
104
+ autoIncrement: isAutoIncrement(entity.idMetadata.field),
105
+ indexes: await resolveIndexDefs(entity, this.indexesFor(entity), keyPath),
106
+ };
107
+ }));
108
+ const db = await this.open();
109
+ const needsUpgrade = wanted.some((w) => {
110
+ if (!db.objectStoreNames.contains(w.name))
111
+ return true;
112
+ const existing = this.storeIndexes(w.name).map((i) => i.name);
113
+ return w.indexes.some((idx) => !existing.includes(idx.name));
114
+ });
115
+ if (!needsUpgrade)
116
+ return;
117
+ const nextVersion = db.version + 1;
118
+ this.forget(db);
119
+ db.close();
120
+ await this.open(nextVersion, (upgradeDb, tx) => {
121
+ for (const { name, keyPath, autoIncrement, indexes } of wanted) {
122
+ const store = upgradeDb.objectStoreNames.contains(name)
123
+ ? tx.objectStore(name)
124
+ : upgradeDb.createObjectStore(name, { keyPath, autoIncrement });
125
+ for (const idx of indexes) {
126
+ if (!store.indexNames.contains(idx.name)) {
127
+ store.createIndex(idx.name, idx.keyPath);
128
+ }
129
+ }
130
+ }
131
+ });
132
+ }
133
+ //@internal
134
+ forget(db) {
135
+ if (this.db === db)
136
+ this.db = undefined;
137
+ }
138
+ //@internal
139
+ async open(version, upgrade) {
140
+ if (this.db && version == null)
141
+ return this.db;
142
+ this.db = await new Promise((resolve, reject) => {
143
+ const request = version == null
144
+ ? indexedDB.open(this.dbName)
145
+ : indexedDB.open(this.dbName, version);
146
+ request.onerror = () => reject(request.error);
147
+ request.onupgradeneeded = () => upgrade?.(request.result, request.transaction);
148
+ request.onsuccess = () => {
149
+ const db = request.result;
150
+ const forget = () => this.forget(db);
151
+ db.onclose = forget;
152
+ db.onversionchange = () => {
153
+ db.close();
154
+ forget();
155
+ };
156
+ resolve(db);
157
+ };
158
+ });
159
+ return this.db;
160
+ }
161
+ //@internal
162
+ getAll(storeName, range) {
163
+ return this.withStore(storeName, 'readonly', (store) => idbReq(range ? store.getAll(range) : store.getAll()));
164
+ }
165
+ //@internal
166
+ fetchOps(storeName, entity, ops) {
167
+ return this.withStore(storeName, 'readonly', async (store) => {
168
+ const idKey = entity.idMetadata.fields[0].key;
169
+ const seen = new Set();
170
+ const rows = [];
171
+ for (const op of ops) {
172
+ const source = op.index ? store.index(op.index) : store;
173
+ const chunk = op.type === 'keys'
174
+ ? op.index
175
+ ? (await Promise.all(op.keys.map((key) => idbReq(source.getAll(key))))).flat()
176
+ : await Promise.all(op.keys.map((key) => idbReq(store.get(key))))
177
+ : await idbReq(source.getAll(op.range));
178
+ for (const row of chunk) {
179
+ if (row == null)
180
+ continue;
181
+ const k = JSON.stringify(row[idKey]);
182
+ if (seen.has(k))
183
+ continue;
184
+ seen.add(k);
185
+ rows.push(row);
186
+ }
187
+ }
188
+ return rows;
189
+ });
190
+ }
191
+ //@internal
192
+ withStore(storeName, mode, op) {
193
+ return new Promise((resolve, reject) => {
194
+ const tx = this.db.transaction(storeName, mode);
195
+ let result;
196
+ let opErr;
197
+ tx.oncomplete = () => {
198
+ if (opErr)
199
+ reject(opErr);
200
+ else
201
+ resolve(result);
202
+ };
203
+ tx.onabort = () => reject(opErr ?? tx.error);
204
+ tx.onerror = () => reject(opErr ?? tx.error);
205
+ Promise.resolve(op(tx.objectStore(storeName))).then((r) => {
206
+ result = r;
207
+ }, (e) => {
208
+ opErr = e;
209
+ try {
210
+ tx.abort();
211
+ }
212
+ catch {
213
+ /* already aborted */
214
+ }
215
+ });
216
+ });
217
+ }
218
+ }
219
+ class IndexedDbEntityDataProvider {
220
+ provider;
221
+ entity;
222
+ constructor(provider, entity) {
223
+ this.provider = provider;
224
+ this.entity = entity;
225
+ }
226
+ find(options) {
227
+ return this.provider.runWithRows(this.entity, options?.where, (dp) => dp.find(options));
228
+ }
229
+ count(where) {
230
+ return this.provider.runWithRows(this.entity, where, (dp) => dp.count(where));
231
+ }
232
+ groupBy(options) {
233
+ return this.provider.runWithRows(this.entity, options?.where, (dp) => dp.groupBy(options));
234
+ }
235
+ insert(data) {
236
+ return this.provider.enqueue(async () => {
237
+ await this.provider.ensureStores([this.entity]);
238
+ const helper = new ArrayEntityDataProvider(this.entity, () => []);
239
+ const names = await helper.init();
240
+ const auto = isAutoIncrement(this.entity.idMetadata.field);
241
+ const idName = names.$dbNameOf(this.entity.idMetadata.field);
242
+ const rows = data.map((row) => {
243
+ const json = helper.translateToJson(row, names);
244
+ helper.verifyThatRowHasAllNotNullColumns(json, names);
245
+ if (auto)
246
+ delete json[idName];
247
+ return json;
248
+ });
249
+ return this.provider.withStore(storeNameOf(this.entity), 'readwrite', async (store) => {
250
+ const result = [];
251
+ for (const json of rows) {
252
+ try {
253
+ const key = await idbReq(store.add(json));
254
+ if (auto)
255
+ json[idName] = json[idName] ?? key;
256
+ }
257
+ catch (e) {
258
+ if (e?.name === 'ConstraintError')
259
+ throw Error('id already exists');
260
+ throw e;
261
+ }
262
+ result.push(helper.translateFromJson(json, names));
263
+ }
264
+ return result;
265
+ });
266
+ });
267
+ }
268
+ update(id, data) {
269
+ return this.provider.enqueue(async () => {
270
+ await this.provider.ensureStores([this.entity]);
271
+ const helper = new ArrayEntityDataProvider(this.entity, () => []);
272
+ const names = await helper.init();
273
+ const key = toIdbKey(this.entity, id);
274
+ return this.provider.withStore(storeNameOf(this.entity), 'readwrite', async (store) => {
275
+ const existing = await idbReq(store.get(key));
276
+ if (existing == null)
277
+ throw new Error(`Couldn't find row with id "${id}" in entity "${this.entity.key}" to update`);
278
+ const json = { ...existing };
279
+ const keys = Object.keys(data);
280
+ for (const f of this.entity.fields) {
281
+ if (!isDbReadonly(f, names) && keys.includes(f.key)) {
282
+ json[names.$dbNameOf(f)] = f.valueConverter.toJson(data[f.key]);
283
+ }
284
+ }
285
+ helper.verifyThatRowHasAllNotNullColumns(json, names);
286
+ const newKey = keyFromRow(this.entity, names, json);
287
+ if (!idbKeysEqual(key, newKey)) {
288
+ const conflict = await idbReq(store.get(newKey));
289
+ if (conflict != null)
290
+ throw Error('id already exists');
291
+ await idbReq(store.delete(key));
292
+ }
293
+ await idbReq(store.put(json));
294
+ return helper.translateFromJson(json, names);
295
+ });
296
+ });
297
+ }
298
+ delete(ids) {
299
+ if (ids.length === 0)
300
+ return Promise.resolve();
301
+ return this.provider.enqueue(async () => {
302
+ await this.provider.ensureStores([this.entity]);
303
+ return this.provider.withStore(storeNameOf(this.entity), 'readwrite', async (store) => {
304
+ for (const id of ids) {
305
+ const key = toIdbKey(this.entity, id);
306
+ const existing = await idbReq(store.get(key));
307
+ if (existing == null)
308
+ throw new Error(`Couldn't find row with id "${id}" in entity "${this.entity.key}" to delete`);
309
+ await idbReq(store.delete(key));
310
+ }
311
+ });
312
+ });
313
+ }
314
+ }
315
+ function storeNameOf(entity) {
316
+ return entity.dbName;
317
+ }
318
+ //@internal
319
+ export async function idbPrefetchFromFilter(entity, where, indexes = []) {
320
+ if (!where)
321
+ return { type: 'all' };
322
+ const names = await dbNamesOf(entity, (x) => x);
323
+ if (entity.idMetadata.fields.length === 1) {
324
+ const c = new IdbPrefetchCollector(entity.idMetadata.fields[0]);
325
+ where.__applyToConsumer(c);
326
+ const r = c.result();
327
+ if (r.type !== 'all')
328
+ return r;
329
+ }
330
+ const ordered = [...indexes].sort((a, b) => pathLen(b.keyPath) - pathLen(a.keyPath));
331
+ for (const idx of ordered) {
332
+ const r = prefetchForIndex(entity, names, where, idx);
333
+ if (r.type !== 'all')
334
+ return withIndex(r, idx.name);
335
+ }
336
+ return { type: 'all' };
337
+ }
338
+ function prefetchForIndex(entity, names, where, idx) {
339
+ const path = Array.isArray(idx.keyPath) ? idx.keyPath : [idx.keyPath];
340
+ if (path.length === 1) {
341
+ const field = fieldByDbName(entity, names, path[0]);
342
+ if (!field)
343
+ return { type: 'all' };
344
+ const c = new IdbPrefetchCollector(field);
345
+ where.__applyToConsumer(c);
346
+ return c.result();
347
+ }
348
+ const parts = path.map((dbName) => {
349
+ const field = fieldByDbName(entity, names, dbName);
350
+ if (!field)
351
+ return { type: 'all' };
352
+ const c = new IdbPrefetchCollector(field);
353
+ where.__applyToConsumer(c);
354
+ return c.result();
355
+ });
356
+ if (parts.every((p) => p.type === 'keys')) {
357
+ return {
358
+ type: 'keys',
359
+ keys: cartesian(parts.map((p) => p.keys)),
360
+ };
361
+ }
362
+ const last = parts[parts.length - 1];
363
+ const prefix = parts.slice(0, -1);
364
+ if (last.type === 'range' &&
365
+ prefix.every((p) => p.type === 'keys' && p.keys.length === 1)) {
366
+ const pre = prefix.map((p) => p.keys[0]);
367
+ const r = last.range;
368
+ try {
369
+ if (r.lower !== undefined && r.upper !== undefined)
370
+ return {
371
+ type: 'range',
372
+ range: IDBKeyRange.bound([...pre, r.lower], [...pre, r.upper], r.lowerOpen, r.upperOpen),
373
+ };
374
+ if (r.lower !== undefined)
375
+ return {
376
+ type: 'range',
377
+ range: IDBKeyRange.lowerBound([...pre, r.lower], r.lowerOpen),
378
+ };
379
+ if (r.upper !== undefined)
380
+ return {
381
+ type: 'range',
382
+ range: IDBKeyRange.upperBound([...pre, r.upper], r.upperOpen),
383
+ };
384
+ }
385
+ catch {
386
+ return { type: 'keys', keys: [] };
387
+ }
388
+ }
389
+ return { type: 'all' };
390
+ }
391
+ function withIndex(prefetch, index) {
392
+ if (prefetch.type === 'all')
393
+ return prefetch;
394
+ if (prefetch.type === 'or')
395
+ return { type: 'or', parts: prefetch.parts.map((p) => ({ ...p, index })) };
396
+ return { ...prefetch, index };
397
+ }
398
+ function pathLen(keyPath) {
399
+ return Array.isArray(keyPath) ? keyPath.length : 1;
400
+ }
401
+ function fieldByDbName(entity, names, dbName) {
402
+ for (const f of entity.fields) {
403
+ if (names.$dbNameOf(f) === dbName)
404
+ return f;
405
+ }
406
+ }
407
+ function cartesian(lists) {
408
+ let acc = [[]];
409
+ for (const list of lists) {
410
+ acc = acc.flatMap((prefix) => list.map((v) => [...prefix, v]));
411
+ }
412
+ return acc;
413
+ }
414
+ class IdbPrefetchCollector {
415
+ idField;
416
+ keys;
417
+ lower;
418
+ upper;
419
+ orOps;
420
+ cannotNarrow = false;
421
+ constructor(idField) {
422
+ this.idField = idField;
423
+ }
424
+ result() {
425
+ if (this.cannotNarrow)
426
+ return { type: 'all' };
427
+ const andOp = this.andOp();
428
+ if (this.keys && andOp)
429
+ return andOp;
430
+ if (this.orOps)
431
+ return mergeOps(this.orOps);
432
+ return andOp ?? { type: 'all' };
433
+ }
434
+ hasIdConstraint() {
435
+ return this.keys != null || this.hasBounds || this.orOps != null;
436
+ }
437
+ get hasBounds() {
438
+ return this.lower != null || this.upper != null;
439
+ }
440
+ andOp() {
441
+ if (this.keys) {
442
+ if (!this.hasBounds)
443
+ return { type: 'keys', keys: this.keys };
444
+ try {
445
+ const range = this.toKeyRange();
446
+ return {
447
+ type: 'keys',
448
+ keys: this.keys.filter((k) => range.includes(k)),
449
+ };
450
+ }
451
+ catch {
452
+ return { type: 'keys', keys: [] };
453
+ }
454
+ }
455
+ if (!this.hasBounds)
456
+ return undefined;
457
+ try {
458
+ return { type: 'range', range: this.toKeyRange() };
459
+ }
460
+ catch {
461
+ return { type: 'keys', keys: [] };
462
+ }
463
+ }
464
+ toKeyRange() {
465
+ if (this.lower && this.upper)
466
+ return IDBKeyRange.bound(this.lower.value, this.upper.value, this.lower.open, this.upper.open);
467
+ if (this.lower)
468
+ return IDBKeyRange.lowerBound(this.lower.value, this.lower.open);
469
+ return IDBKeyRange.upperBound(this.upper.value, this.upper.open);
470
+ }
471
+ isId(col) {
472
+ return col.key === this.idField.key;
473
+ }
474
+ toKey(col, val) {
475
+ return col.valueConverter.toJson(val);
476
+ }
477
+ intersectKeys(vals) {
478
+ if (!this.keys)
479
+ this.keys = [...vals];
480
+ else
481
+ this.keys = this.keys.filter((k) => vals.some((v) => idbKeysEqual(k, v)));
482
+ }
483
+ addLower(value, open) {
484
+ if (!this.lower)
485
+ this.lower = { value, open };
486
+ else if (value > this.lower.value)
487
+ this.lower = { value, open };
488
+ else if (value === this.lower.value)
489
+ this.lower = { value, open: this.lower.open || open };
490
+ }
491
+ addUpper(value, open) {
492
+ if (!this.upper)
493
+ this.upper = { value, open };
494
+ else if (value < this.upper.value)
495
+ this.upper = { value, open };
496
+ else if (value === this.upper.value)
497
+ this.upper = { value, open: this.upper.open || open };
498
+ }
499
+ isEqualTo(col, val) {
500
+ if (this.isId(col))
501
+ this.intersectKeys([this.toKey(col, val)]);
502
+ }
503
+ isIn(col, val) {
504
+ if (this.isId(col))
505
+ this.intersectKeys(val.map((v) => this.toKey(col, v)));
506
+ }
507
+ isGreaterThan(col, val) {
508
+ if (this.isId(col))
509
+ this.addLower(this.toKey(col, val), true);
510
+ }
511
+ isGreaterOrEqualTo(col, val) {
512
+ if (this.isId(col))
513
+ this.addLower(this.toKey(col, val), false);
514
+ }
515
+ isLessThan(col, val) {
516
+ if (this.isId(col))
517
+ this.addUpper(this.toKey(col, val), true);
518
+ }
519
+ isLessOrEqualTo(col, val) {
520
+ if (this.isId(col))
521
+ this.addUpper(this.toKey(col, val), false);
522
+ }
523
+ or(orElements) {
524
+ const results = orElements.map((el) => {
525
+ const c = new IdbPrefetchCollector(this.idField);
526
+ el.__applyToConsumer(c);
527
+ return c.result();
528
+ });
529
+ if (results.some((r) => r.type === 'all'))
530
+ return;
531
+ const ops = results.flatMap((r) => r.type === 'or' ? r.parts : r.type === 'all' ? [] : [r]);
532
+ this.orOps = this.orOps ? [...this.orOps, ...ops] : ops;
533
+ }
534
+ not(filter) {
535
+ const c = new IdbPrefetchCollector(this.idField);
536
+ filter.__applyToConsumer(c);
537
+ if (c.cannotNarrow || c.hasIdConstraint())
538
+ this.cannotNarrow = true;
539
+ }
540
+ isNull(col) {
541
+ if (this.isId(col))
542
+ this.cannotNarrow = true;
543
+ }
544
+ isNotNull(col) {
545
+ if (this.isId(col))
546
+ this.cannotNarrow = true;
547
+ }
548
+ isDifferentFrom(_col, _val) { }
549
+ containsCaseInsensitive(_col, _val) { }
550
+ notContainsCaseInsensitive(_col, _val) { }
551
+ startsWithCaseInsensitive(_col, _val) { }
552
+ endsWithCaseInsensitive(_col, _val) { }
553
+ custom(_key, _customItem) {
554
+ this.cannotNarrow = true;
555
+ }
556
+ databaseCustom(_databaseCustom) {
557
+ this.cannotNarrow = true;
558
+ }
559
+ }
560
+ function mergeOps(ops) {
561
+ const keyGroups = new Map();
562
+ const ranges = [];
563
+ for (const op of ops) {
564
+ if (op.type === 'keys') {
565
+ const k = op.index ?? '';
566
+ keyGroups.set(k, [...(keyGroups.get(k) ?? []), ...op.keys]);
567
+ }
568
+ else
569
+ ranges.push(op);
570
+ }
571
+ const parts = [
572
+ ...[...keyGroups.entries()].map(([index, keys]) => ({
573
+ type: 'keys',
574
+ keys: keys.filter((k, i) => keys.findIndex((x) => idbKeysEqual(x, k)) === i),
575
+ ...(index ? { index } : {}),
576
+ })),
577
+ ...ranges,
578
+ ];
579
+ if (parts.length === 1)
580
+ return parts[0];
581
+ return { type: 'or', parts };
582
+ }
583
+ async function resolveIndexDefs(entity, defs, storeKeyPath) {
584
+ if (defs.length === 0)
585
+ return [];
586
+ const names = await dbNamesOf(entity, (x) => x);
587
+ const result = [];
588
+ const seen = new Set();
589
+ const pk = JSON.stringify(storeKeyPath);
590
+ for (const def of defs) {
591
+ const keys = Array.isArray(def) ? [...def] : [def];
592
+ const keyPath = keys.map((key) => {
593
+ const field = [...entity.fields].find((f) => f.key === key);
594
+ if (!field)
595
+ throw new Error(`IndexedDB index field "${key}" not found on entity "${entity.key}"`);
596
+ return names.$dbNameOf(field);
597
+ });
598
+ const path = keyPath.length === 1 ? keyPath[0] : keyPath;
599
+ if (JSON.stringify(path) === pk)
600
+ continue;
601
+ const name = keyPath.join('_');
602
+ if (seen.has(name))
603
+ continue;
604
+ seen.add(name);
605
+ result.push({ name, keyPath: path });
606
+ }
607
+ return result;
608
+ }
609
+ async function keyPathOf(entity) {
610
+ const names = await dbNamesOf(entity, (x) => x);
611
+ const fields = entity.idMetadata.fields;
612
+ if (fields.length === 1)
613
+ return names.$dbNameOf(fields[0]);
614
+ return fields.map((f) => names.$dbNameOf(f));
615
+ }
616
+ function toIdbKey(entity, id) {
617
+ const fields = entity.idMetadata.fields;
618
+ if (fields.length === 1)
619
+ return fields[0].valueConverter.toJson(id);
620
+ const values = typeof id === 'object' && id !== null
621
+ ? id
622
+ : Object.fromEntries(String(id)
623
+ .split(',')
624
+ .map((part, i) => [
625
+ fields[i].key,
626
+ fields[i].valueConverter.fromJson(part),
627
+ ]));
628
+ return fields.map((f) => f.valueConverter.toJson(values[f.key]));
629
+ }
630
+ function keyFromRow(entity, names, row) {
631
+ const fields = entity.idMetadata.fields;
632
+ if (fields.length === 1)
633
+ return row[names.$dbNameOf(fields[0])];
634
+ return fields.map((f) => row[names.$dbNameOf(f)]);
635
+ }
636
+ function idbKeysEqual(a, b) {
637
+ return JSON.stringify(a) === JSON.stringify(b);
638
+ }
639
+ function idbReq(request) {
640
+ return new Promise((resolve, reject) => {
641
+ request.onsuccess = () => resolve(request.result);
642
+ request.onerror = () => reject(request.error);
643
+ });
644
+ }
@@ -49,8 +49,8 @@ class JsonEntityDataProvider {
49
49
  return x;
50
50
  }))));
51
51
  }
52
- delete(id) {
53
- return (this.p = this.p.then(() => this.loadEntityData((dp, save) => dp.delete(id).then(async (x) => {
52
+ delete(ids) {
53
+ return (this.p = this.p.then(() => this.loadEntityData((dp, save) => dp.delete(ids).then(async (x) => {
54
54
  await save();
55
55
  return x;
56
56
  }))));
@@ -303,21 +303,20 @@ export class RestEntityDataProvider {
303
303
  }
304
304
  return result;
305
305
  }
306
- async delete(id) {
307
- if (id == '')
308
- await this.deleteMany(Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)));
309
- else
310
- return this.http().delete(this.url() + '/' + encodeURIComponent(id));
306
+ async delete(ids) {
307
+ if (ids.length === 0)
308
+ return;
309
+ if (ids.length === 1 && ids[0] !== '')
310
+ return this.http().delete(this.url() + '/' + encodeURIComponent(ids[0]));
311
+ await this.deleteMany(Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)));
311
312
  }
312
313
  insert(data, options) {
313
314
  return this.http()
314
- .post(this.url() + (options?.select === 'none' ? '?_select=$none' : ''), this.translateToJson(data))
315
- .then((y) => this.translateFromJson(y));
316
- }
317
- insertMany(data, options) {
318
- return this.http()
319
- .post(this.url() + (options?.select === 'none' ? '?_select=$none' : ''), data.map((data) => this.translateToJson(data)))
320
- .then((y) => y.map((y) => this.translateFromJson(y)));
315
+ .post(this.url() + (options?.select === 'none' ? '?_select=$none' : ''), data.map((row) => this.translateToJson(row)))
316
+ .then((y) => {
317
+ const rows = Array.isArray(y) ? y : [y];
318
+ return rows.map((row) => row ? this.translateFromJson(row) : undefined);
319
+ });
321
320
  }
322
321
  }
323
322
  export function addFilterToUrlAndReturnTrueIfSuccessful(filter, url) {