idb-ts 3.11.1 → 3.13.0

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/lib/index.esm.js CHANGED
@@ -1,56 +1,202 @@
1
1
  Object.defineProperty(exports, "__esModule", { value: true });
2
- exports.KeyGenerators = exports.RetentionPolicy = exports.Validate = exports.Index = exports.DataClass = exports.CompositeKeyPath = exports.KeyPath = exports.Database = void 0;
2
+ exports.KeyGenerators = exports.Database = void 0;
3
+ exports.KeyPath = KeyPath;
4
+ exports.CompositeKeyPath = CompositeKeyPath;
5
+ exports.DataClass = DataClass;
6
+ exports.Index = Index;
7
+ exports.Validate = Validate;
8
+ exports.RetentionPolicy = RetentionPolicy;
3
9
  const tslib_1 = require("tslib");
4
10
  require("reflect-metadata");
11
+ class FieldQueryBuilder {
12
+ constructor(parent, field) {
13
+ this.parent = parent;
14
+ this.field = field;
15
+ }
16
+ equals(value) {
17
+ this.parent.appendCondition(this.field, 'equals', value);
18
+ this.parent.clearCurrentField();
19
+ return this.parent;
20
+ }
21
+ gt(value) {
22
+ this.parent.appendCondition(this.field, 'gt', value);
23
+ this.parent.clearCurrentField();
24
+ return this.parent;
25
+ }
26
+ gte(value) {
27
+ this.parent.appendCondition(this.field, 'gte', value);
28
+ this.parent.clearCurrentField();
29
+ return this.parent;
30
+ }
31
+ lt(value) {
32
+ this.parent.appendCondition(this.field, 'lt', value);
33
+ this.parent.clearCurrentField();
34
+ return this.parent;
35
+ }
36
+ lte(value) {
37
+ this.parent.appendCondition(this.field, 'lte', value);
38
+ this.parent.clearCurrentField();
39
+ return this.parent;
40
+ }
41
+ startsWith(value) {
42
+ this.parent.appendCondition(this.field, 'startsWith', value);
43
+ this.parent.clearCurrentField();
44
+ return this.parent;
45
+ }
46
+ endsWith(value) {
47
+ this.parent.appendCondition(this.field, 'endsWith', value);
48
+ this.parent.clearCurrentField();
49
+ return this.parent;
50
+ }
51
+ contains(value) {
52
+ this.parent.appendCondition(this.field, 'contains', value);
53
+ this.parent.clearCurrentField();
54
+ return this.parent;
55
+ }
56
+ matches(value) {
57
+ this.parent.appendCondition(this.field, 'matches', value);
58
+ this.parent.clearCurrentField();
59
+ return this.parent;
60
+ }
61
+ between(start, end) {
62
+ this.parent.appendCondition(this.field, 'between', [start, end]);
63
+ this.parent.clearCurrentField();
64
+ return this.parent;
65
+ }
66
+ notBetween(start, end) {
67
+ this.parent.appendCondition(this.field, 'notBetween', [start, end]);
68
+ this.parent.clearCurrentField();
69
+ return this.parent;
70
+ }
71
+ in(values) {
72
+ this.parent.appendCondition(this.field, 'in', values);
73
+ this.parent.clearCurrentField();
74
+ return this.parent;
75
+ }
76
+ notIn(values) {
77
+ this.parent.appendCondition(this.field, 'notIn', values);
78
+ this.parent.clearCurrentField();
79
+ return this.parent;
80
+ }
81
+ containsAny(values) {
82
+ this.parent.appendCondition(this.field, 'containsAny', values);
83
+ this.parent.clearCurrentField();
84
+ return this.parent;
85
+ }
86
+ containsAll(values) {
87
+ this.parent.appendCondition(this.field, 'containsAll', values);
88
+ this.parent.clearCurrentField();
89
+ return this.parent;
90
+ }
91
+ and(field) {
92
+ this.parent.clearCurrentField();
93
+ return this.parent.and(field);
94
+ }
95
+ or() {
96
+ this.parent.clearCurrentField();
97
+ return this.parent.or();
98
+ }
99
+ }
5
100
  class QueryBuilder {
6
- constructor(db, storeName) {
7
- this.conditions = [];
101
+ constructor(db, storeName, transaction) {
102
+ this.clauses = [];
8
103
  this.orderDirection = 'asc';
104
+ this.pendingConnector = 'and';
9
105
  this.db = db;
10
106
  this.storeName = storeName;
107
+ this.transaction = transaction;
108
+ }
109
+ where(fieldOrBuilder) {
110
+ if (typeof fieldOrBuilder === 'function') {
111
+ this.clearCurrentField();
112
+ return this.addNestedGroup(fieldOrBuilder);
113
+ }
114
+ this.currentField = fieldOrBuilder;
115
+ return new FieldQueryBuilder(this, fieldOrBuilder);
116
+ }
117
+ and(fieldOrBuilder) {
118
+ return this.where(fieldOrBuilder);
11
119
  }
12
- where(field) {
13
- this.currentField = field;
120
+ or() {
121
+ this.pendingConnector = 'or';
14
122
  return this;
15
123
  }
16
- and(field) {
17
- this.currentField = field;
124
+ addNestedGroup(builder) {
125
+ var _a;
126
+ const nested = new QueryBuilder(this.db, this.storeName, this.transaction);
127
+ const returned = (_a = builder(nested)) !== null && _a !== void 0 ? _a : nested;
128
+ const clauses = returned instanceof QueryBuilder ? returned.clauses : nested.clauses;
129
+ this.appendClause({ kind: 'group', clauses });
18
130
  return this;
19
131
  }
20
- equals(value) {
21
- if (!this.currentField)
22
- throw new Error('No field specified for equals');
23
- this.conditions.push({ field: this.currentField, op: 'equals', value });
132
+ clearCurrentField() {
24
133
  this.currentField = undefined;
25
- return this;
26
134
  }
27
- gt(value) {
28
- if (!this.currentField)
29
- throw new Error('No field specified for gt');
30
- this.conditions.push({ field: this.currentField, op: 'gt', value });
135
+ appendCondition(field, op, value) {
136
+ this.appendClause({ kind: 'condition', field, op, value });
137
+ }
138
+ appendClause(clause) {
139
+ const connector = this.clauses.length === 0 ? null : this.pendingConnector;
140
+ this.clauses.push(Object.assign(Object.assign({}, clause), { connector }));
141
+ this.pendingConnector = 'and';
142
+ }
143
+ requireCurrentField(operation) {
144
+ if (!this.currentField) {
145
+ throw new Error(`No field specified for ${operation}`);
146
+ }
147
+ const field = this.currentField;
31
148
  this.currentField = undefined;
149
+ return field;
150
+ }
151
+ addCondition(op, value) {
152
+ const field = this.requireCurrentField(op);
153
+ this.appendCondition(field, op, value);
32
154
  return this;
33
155
  }
156
+ equals(value) {
157
+ return this.addCondition('equals', value);
158
+ }
159
+ gt(value) {
160
+ return this.addCondition('gt', value);
161
+ }
34
162
  gte(value) {
35
- if (!this.currentField)
36
- throw new Error('No field specified for gte');
37
- this.conditions.push({ field: this.currentField, op: 'gte', value });
38
- this.currentField = undefined;
39
- return this;
163
+ return this.addCondition('gte', value);
40
164
  }
41
165
  lt(value) {
42
- if (!this.currentField)
43
- throw new Error('No field specified for lt');
44
- this.conditions.push({ field: this.currentField, op: 'lt', value });
45
- this.currentField = undefined;
46
- return this;
166
+ return this.addCondition('lt', value);
47
167
  }
48
168
  lte(value) {
49
- if (!this.currentField)
50
- throw new Error('No field specified for lte');
51
- this.conditions.push({ field: this.currentField, op: 'lte', value });
52
- this.currentField = undefined;
53
- return this;
169
+ return this.addCondition('lte', value);
170
+ }
171
+ startsWith(value) {
172
+ return this.addCondition('startsWith', value);
173
+ }
174
+ endsWith(value) {
175
+ return this.addCondition('endsWith', value);
176
+ }
177
+ contains(value) {
178
+ return this.addCondition('contains', value);
179
+ }
180
+ matches(value) {
181
+ return this.addCondition('matches', value);
182
+ }
183
+ between(start, end) {
184
+ return this.addCondition('between', [start, end]);
185
+ }
186
+ notBetween(start, end) {
187
+ return this.addCondition('notBetween', [start, end]);
188
+ }
189
+ ['in'](values) {
190
+ return this.addCondition('in', values);
191
+ }
192
+ notIn(values) {
193
+ return this.addCondition('notIn', values);
194
+ }
195
+ containsAny(values) {
196
+ return this.addCondition('containsAny', values);
197
+ }
198
+ containsAll(values) {
199
+ return this.addCondition('containsAll', values);
54
200
  }
55
201
  orderBy(field, direction = 'asc') {
56
202
  this.orderField = field;
@@ -74,94 +220,226 @@ class QueryBuilder {
74
220
  this.rangeEnd = end;
75
221
  return this;
76
222
  }
77
- execute() {
223
+ groupBy(field) {
224
+ this.groupField = field;
225
+ return this;
226
+ }
227
+ loadCandidates() {
78
228
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
229
+ const store = this.transaction
230
+ ? this.transaction.objectStore(this.storeName)
231
+ : this.db
232
+ .transaction(this.storeName, 'readonly')
233
+ .objectStore(this.storeName);
234
+ const request = this.createReadRequest(store);
79
235
  return new Promise((resolve, reject) => {
80
- const tx = this.db.transaction(this.storeName, 'readonly');
81
- const store = tx.objectStore(this.storeName);
82
- let request;
83
- let results = [];
84
- if (this.indexName) {
85
- if (!store.indexNames.contains(this.indexName)) {
86
- reject(new Error(`Index '${this.indexName}' does not exist on ${this.storeName}`));
87
- return;
88
- }
89
- const index = store.index(this.indexName);
90
- let keyRange;
91
- if (this.rangeStart !== undefined && this.rangeEnd !== undefined) {
92
- keyRange = IDBKeyRange.bound(this.rangeStart, this.rangeEnd);
93
- }
94
- else if (this.rangeStart !== undefined) {
95
- keyRange = IDBKeyRange.lowerBound(this.rangeStart);
96
- }
97
- else if (this.rangeEnd !== undefined) {
98
- keyRange = IDBKeyRange.upperBound(this.rangeEnd);
99
- }
100
- request = index.openCursor(keyRange);
101
- }
102
- else {
103
- request = store.openCursor();
104
- }
105
- request.onsuccess = () => {
106
- const cursor = request.result;
107
- if (cursor) {
108
- let match = true;
109
- const value = cursor.value;
110
- for (const cond of this.conditions) {
111
- const val = value[cond.field];
112
- switch (cond.op) {
113
- case 'equals':
114
- match = match && val === cond.value;
115
- break;
116
- case 'gt':
117
- match = match && val > cond.value;
118
- break;
119
- case 'gte':
120
- match = match && val >= cond.value;
121
- break;
122
- case 'lt':
123
- match = match && val < cond.value;
124
- break;
125
- case 'lte':
126
- match = match && val <= cond.value;
127
- break;
128
- }
129
- }
130
- if (match)
131
- results.push(value);
132
- cursor.continue();
133
- }
134
- else {
135
- if (this.orderField) {
136
- results.sort((a, b) => {
137
- const va = a[this.orderField];
138
- const vb = b[this.orderField];
139
- if (va < vb)
140
- return this.orderDirection === 'asc' ? -1 : 1;
141
- if (va > vb)
142
- return this.orderDirection === 'asc' ? 1 : -1;
143
- return 0;
144
- });
145
- }
146
- if (this.offsetCount !== undefined)
147
- results = results.slice(this.offsetCount);
148
- if (this.limitCount !== undefined)
149
- results = results.slice(0, this.limitCount);
150
- resolve(results);
151
- }
152
- };
236
+ request.onsuccess = () => { var _a; return resolve((_a = request.result) !== null && _a !== void 0 ? _a : []); };
153
237
  request.onerror = () => reject(request.error);
154
238
  });
155
239
  });
156
240
  }
241
+ createReadRequest(store) {
242
+ if (!this.indexName) {
243
+ return store.getAll();
244
+ }
245
+ if (!store.indexNames.contains(this.indexName)) {
246
+ throw new Error(`Index '${this.indexName}' does not exist on ${this.storeName}`);
247
+ }
248
+ const index = store.index(this.indexName);
249
+ let keyRange;
250
+ if (this.rangeStart !== undefined && this.rangeEnd !== undefined) {
251
+ keyRange = IDBKeyRange.bound(this.rangeStart, this.rangeEnd);
252
+ }
253
+ else if (this.rangeStart !== undefined) {
254
+ keyRange = IDBKeyRange.lowerBound(this.rangeStart);
255
+ }
256
+ else if (this.rangeEnd !== undefined) {
257
+ keyRange = IDBKeyRange.upperBound(this.rangeEnd);
258
+ }
259
+ return keyRange ? index.getAll(keyRange) : index.getAll();
260
+ }
261
+ matchesClause(item, clause) {
262
+ if (clause.kind === 'group') {
263
+ return this.evaluateClauses(item, clause.clauses);
264
+ }
265
+ const value = item[clause.field];
266
+ switch (clause.op) {
267
+ case 'equals':
268
+ return value === clause.value;
269
+ case 'gt':
270
+ return value > clause.value;
271
+ case 'gte':
272
+ return value >= clause.value;
273
+ case 'lt':
274
+ return value < clause.value;
275
+ case 'lte':
276
+ return value <= clause.value;
277
+ case 'startsWith':
278
+ return (typeof value === 'string' && value.startsWith(String(clause.value)));
279
+ case 'endsWith':
280
+ return (typeof value === 'string' && value.endsWith(String(clause.value)));
281
+ case 'contains':
282
+ if (typeof value === 'string') {
283
+ return value.includes(String(clause.value));
284
+ }
285
+ return Array.isArray(value) && value.includes(clause.value);
286
+ case 'matches': {
287
+ const pattern = clause.value instanceof RegExp
288
+ ? new RegExp(clause.value.source, clause.value.flags)
289
+ : new RegExp(String(clause.value));
290
+ return typeof value === 'string' && pattern.test(value);
291
+ }
292
+ case 'between': {
293
+ const [start, end] = clause.value;
294
+ return value >= start && value <= end;
295
+ }
296
+ case 'notBetween': {
297
+ const [start, end] = clause.value;
298
+ return value < start || value > end;
299
+ }
300
+ case 'in':
301
+ return Array.isArray(clause.value) && clause.value.includes(value);
302
+ case 'notIn':
303
+ return Array.isArray(clause.value) && !clause.value.includes(value);
304
+ case 'containsAny':
305
+ return (Array.isArray(value) &&
306
+ Array.isArray(clause.value) &&
307
+ clause.value.some((entry) => value.includes(entry)));
308
+ case 'containsAll':
309
+ return (Array.isArray(value) &&
310
+ Array.isArray(clause.value) &&
311
+ clause.value.every((entry) => value.includes(entry)));
312
+ }
313
+ }
314
+ evaluateClauses(item, clauses) {
315
+ if (!clauses.length) {
316
+ return true;
317
+ }
318
+ let result = this.matchesClause(item, clauses[0]);
319
+ for (let index = 1; index < clauses.length; index += 1) {
320
+ const clause = clauses[index];
321
+ const matches = this.matchesClause(item, clause);
322
+ result =
323
+ clause.connector === 'or' ? result || matches : result && matches;
324
+ }
325
+ return result;
326
+ }
327
+ collectMatches() {
328
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
329
+ const candidates = yield this.loadCandidates();
330
+ return candidates.filter((item) => this.evaluateClauses(item, this.clauses));
331
+ });
332
+ }
333
+ sortResults(results) {
334
+ if (!this.orderField) {
335
+ return results;
336
+ }
337
+ return [...results].sort((left, right) => {
338
+ const leftValue = left[this.orderField];
339
+ const rightValue = right[this.orderField];
340
+ if (leftValue < rightValue)
341
+ return this.orderDirection === 'asc' ? -1 : 1;
342
+ if (leftValue > rightValue)
343
+ return this.orderDirection === 'asc' ? 1 : -1;
344
+ return 0;
345
+ });
346
+ }
347
+ applyPagination(results) {
348
+ let nextResults = results;
349
+ if (this.offsetCount !== undefined) {
350
+ nextResults = nextResults.slice(this.offsetCount);
351
+ }
352
+ if (this.limitCount !== undefined) {
353
+ nextResults = nextResults.slice(0, this.limitCount);
354
+ }
355
+ return nextResults;
356
+ }
357
+ aggregateValues(field, reducer) {
358
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
359
+ const results = yield this.collectMatches();
360
+ const values = field
361
+ ? results.map((item) => item[field])
362
+ : results;
363
+ return reducer(values);
364
+ });
365
+ }
366
+ aggregateGroupedCount(field) {
367
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
368
+ var _a;
369
+ const results = yield this.collectMatches();
370
+ const groups = new Map();
371
+ for (const item of results) {
372
+ const key = item[field];
373
+ groups.set(key, ((_a = groups.get(key)) !== null && _a !== void 0 ? _a : 0) + 1);
374
+ }
375
+ return Array.from(groups.entries())
376
+ .sort(([left], [right]) => {
377
+ if (left < right)
378
+ return -1;
379
+ if (left > right)
380
+ return 1;
381
+ return 0;
382
+ })
383
+ .map(([key, count]) => ({ [field]: key, count }));
384
+ });
385
+ }
386
+ execute() {
387
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
388
+ const results = yield this.collectMatches();
389
+ return this.applyPagination(this.sortResults(results));
390
+ });
391
+ }
392
+ count() {
393
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
394
+ if (this.groupField) {
395
+ return this.aggregateGroupedCount(this.groupField);
396
+ }
397
+ const results = yield this.collectMatches();
398
+ return results.length;
399
+ });
400
+ }
401
+ sum(field) {
402
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
403
+ const total = yield this.aggregateValues(field, (values) => values.reduce((accumulator, value) => accumulator + (Number(value) || 0), 0));
404
+ return total;
405
+ });
406
+ }
407
+ avg(field) {
408
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
409
+ const values = yield this.aggregateValues(field, (items) => items);
410
+ if (!values.length) {
411
+ return 0;
412
+ }
413
+ const total = values.reduce((accumulator, value) => accumulator + (Number(value) || 0), 0);
414
+ return total / values.length;
415
+ });
416
+ }
417
+ min(field) {
418
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
419
+ const values = yield this.aggregateValues(field, (items) => items);
420
+ if (!values.length) {
421
+ return null;
422
+ }
423
+ return values.reduce((currentMin, value) => value < currentMin ? value : currentMin);
424
+ });
425
+ }
426
+ max(field) {
427
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
428
+ const values = yield this.aggregateValues(field, (items) => items);
429
+ if (!values.length) {
430
+ return null;
431
+ }
432
+ return values.reduce((currentMax, value) => value > currentMax ? value : currentMax);
433
+ });
434
+ }
157
435
  }
158
436
  const INTERNAL_CREATED_AT_FIELD = '__idb_createdAt';
159
437
  const INTERNAL_UPDATED_AT_FIELD = '__idb_updatedAt';
160
438
  class KeyGenerators {
161
439
  static uuid() {
162
440
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
163
- const r = Math.random() * 16 | 0;
164
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
441
+ const r = (Math.random() * 16) | 0;
442
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
165
443
  return v.toString(16);
166
444
  });
167
445
  }
@@ -176,45 +454,47 @@ exports.KeyGenerators = KeyGenerators;
176
454
  function KeyPath(options) {
177
455
  return (target, propertyKey) => {
178
456
  const constructor = target.constructor;
179
- const existingKeypaths = Reflect.getMetadata("individual_keypaths", constructor) || [];
457
+ const existingKeypaths = Reflect.getMetadata('individual_keypaths', constructor) || [];
180
458
  existingKeypaths.push(propertyKey);
181
- Reflect.defineMetadata("individual_keypaths", existingKeypaths, constructor);
459
+ Reflect.defineMetadata('individual_keypaths', existingKeypaths, constructor);
182
460
  const metadata = {
183
461
  fields: propertyKey,
184
- options: options
462
+ options: options,
185
463
  };
186
- Reflect.defineMetadata("keypath", metadata, constructor);
464
+ Reflect.defineMetadata('keypath', metadata, constructor);
187
465
  };
188
466
  }
189
- exports.KeyPath = KeyPath;
190
467
  function CompositeKeyPath(fields, options) {
191
468
  return (target) => {
192
469
  const metadata = {
193
470
  fields: fields,
194
- options: options
471
+ options: options,
195
472
  };
196
- Reflect.defineMetadata("keypath", metadata, target);
473
+ Reflect.defineMetadata('keypath', metadata, target);
197
474
  };
198
475
  }
199
- exports.CompositeKeyPath = CompositeKeyPath;
200
476
  function Index(options) {
201
477
  return (target, propertyKey) => {
202
478
  const constructor = target.constructor;
203
- const existing = Reflect.getMetadata("indexes", constructor) || [];
204
- const nextIndexes = [...existing, { field: propertyKey, options }];
205
- Reflect.defineMetadata("indexes", nextIndexes, constructor);
479
+ const existing = Reflect.getMetadata('indexes', constructor) || [];
480
+ const nextIndexes = [
481
+ ...existing,
482
+ { field: propertyKey, options },
483
+ ];
484
+ Reflect.defineMetadata('indexes', nextIndexes, constructor);
206
485
  };
207
486
  }
208
- exports.Index = Index;
209
487
  function Validate(predicate, message) {
210
488
  return (target, propertyKey) => {
211
489
  const constructor = target.constructor;
212
490
  const existing = Reflect.getMetadata('validators', constructor) || [];
213
- const nextRules = [...existing, { field: propertyKey, predicate, message }];
491
+ const nextRules = [
492
+ ...existing,
493
+ { field: propertyKey, predicate, message },
494
+ ];
214
495
  Reflect.defineMetadata('validators', nextRules, constructor);
215
496
  };
216
497
  }
217
- exports.Validate = Validate;
218
498
  function RetentionPolicy(options) {
219
499
  return (target) => {
220
500
  var _a, _b;
@@ -224,28 +504,26 @@ function RetentionPolicy(options) {
224
504
  const metadata = {
225
505
  seconds: options.seconds,
226
506
  enabled: (_a = options.enabled) !== null && _a !== void 0 ? _a : true,
227
- field: (_b = options.field) !== null && _b !== void 0 ? _b : INTERNAL_CREATED_AT_FIELD
507
+ field: (_b = options.field) !== null && _b !== void 0 ? _b : INTERNAL_CREATED_AT_FIELD,
228
508
  };
229
509
  Reflect.defineMetadata('retention_policy', metadata, target);
230
510
  };
231
511
  }
232
- exports.RetentionPolicy = RetentionPolicy;
233
512
  function DataClass(options = {}) {
234
513
  return (target) => {
235
- const keyPathMetadata = Reflect.getMetadata("keypath", target);
514
+ const keyPathMetadata = Reflect.getMetadata('keypath', target);
236
515
  if (!keyPathMetadata) {
237
516
  throw new Error(`No keypath field defined for the class ${target.name}.`);
238
517
  }
239
- const individualKeypaths = Reflect.getMetadata("individual_keypaths", target) || [];
518
+ const individualKeypaths = Reflect.getMetadata('individual_keypaths', target) || [];
240
519
  if (individualKeypaths.length > 1) {
241
520
  throw new Error(`Only one keypath field can be defined for the class ${target.name}.`);
242
521
  }
243
522
  const version = options.version || 1;
244
- Reflect.defineMetadata("dataclass", true, target);
245
- Reflect.defineMetadata("version", version, target);
523
+ Reflect.defineMetadata('dataclass', true, target);
524
+ Reflect.defineMetadata('version', version, target);
246
525
  };
247
526
  }
248
- exports.DataClass = DataClass;
249
527
  class Database {
250
528
  constructor(dbName, classes) {
251
529
  this.db = null;
@@ -253,8 +531,8 @@ class Database {
253
531
  this.retentionTimer = null;
254
532
  this.retentionCleanupRunning = false;
255
533
  this.dbName = dbName;
256
- if (!classes.every(cls => Reflect.getMetadata("dataclass", cls))) {
257
- throw new Error("All classes should be decorated with @DataClass.");
534
+ if (!classes.every((cls) => Reflect.getMetadata('dataclass', cls))) {
535
+ throw new Error('All classes should be decorated with @DataClass.');
258
536
  }
259
537
  this.classes = classes;
260
538
  this.dbVersion = this.calculateDatabaseVersion();
@@ -267,13 +545,13 @@ class Database {
267
545
  return {
268
546
  className: cls.name,
269
547
  storeName: cls.name.toLowerCase(),
270
- policy
548
+ policy,
271
549
  };
272
550
  })
273
551
  .filter((policy) => policy !== null);
274
552
  }
275
553
  calculateDatabaseVersion() {
276
- const versions = this.classes.map(cls => Reflect.getMetadata("version", cls) || 1);
554
+ const versions = this.classes.map((cls) => Reflect.getMetadata('version', cls) || 1);
277
555
  return Math.max(...versions);
278
556
  }
279
557
  static build(dbName, classes) {
@@ -295,9 +573,9 @@ class Database {
295
573
  console.debug(`Database upgrade from version ${oldVersion} to ${newVersion}`);
296
574
  this.classes.forEach((cls) => {
297
575
  var _a;
298
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
299
- const indexFields = Reflect.getMetadata("indexes", cls) || [];
300
- const classVersion = Reflect.getMetadata("version", cls) || 1;
576
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
577
+ const indexFields = Reflect.getMetadata('indexes', cls) || [];
578
+ const classVersion = Reflect.getMetadata('version', cls) || 1;
301
579
  const storeName = cls.name.toLowerCase();
302
580
  if (classVersion > oldVersion) {
303
581
  if (!db.objectStoreNames.contains(storeName)) {
@@ -312,7 +590,9 @@ class Database {
312
590
  const store = db.createObjectStore(storeName, storeOptions);
313
591
  indexFields.forEach((indexField) => {
314
592
  var _a;
315
- const indexName = typeof indexField === 'string' ? indexField : indexField.field;
593
+ const indexName = typeof indexField === 'string'
594
+ ? indexField
595
+ : indexField.field;
316
596
  const indexOptions = typeof indexField === 'string'
317
597
  ? { unique: false }
318
598
  : ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
@@ -328,7 +608,9 @@ class Database {
328
608
  const store = transaction.objectStore(storeName);
329
609
  indexFields.forEach((indexField) => {
330
610
  var _a;
331
- const indexName = typeof indexField === 'string' ? indexField : indexField.field;
611
+ const indexName = typeof indexField === 'string'
612
+ ? indexField
613
+ : indexField.field;
332
614
  const indexOptions = typeof indexField === 'string'
333
615
  ? { unique: false }
334
616
  : ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
@@ -344,19 +626,19 @@ class Database {
344
626
  };
345
627
  request.onsuccess = () => {
346
628
  this.db = request.result;
347
- console.debug(`Database initialized (version ${this.dbVersion}) with object stores for: ${this.classes.map(cls => `${cls.name}(v${Reflect.getMetadata("version", cls) || 1})`).join(", ")}`);
629
+ console.debug(`Database initialized (version ${this.dbVersion}) with object stores for: ${this.classes.map((cls) => `${cls.name}(v${Reflect.getMetadata('version', cls) || 1})`).join(', ')}`);
348
630
  this.startRetentionCleanup();
349
631
  resolve();
350
632
  };
351
633
  request.onerror = () => {
352
- console.error("Error initializing database:", request.error);
634
+ console.error('Error initializing database:', request.error);
353
635
  reject(request.error);
354
636
  };
355
637
  });
356
638
  });
357
639
  }
358
640
  generateEntityRepositories() {
359
- this.classes.forEach(cls => {
641
+ this.classes.forEach((cls) => {
360
642
  const entityName = cls.name;
361
643
  const repository = this.createEntityRepository(cls);
362
644
  this.entityRepositories.set(entityName, repository);
@@ -364,7 +646,7 @@ class Database {
364
646
  value: repository,
365
647
  writable: false,
366
648
  enumerable: true,
367
- configurable: false
649
+ configurable: false,
368
650
  });
369
651
  });
370
652
  }
@@ -383,7 +665,7 @@ class Database {
383
665
  return Math.abs(a);
384
666
  };
385
667
  const seconds = this.retentionPolicies.map(({ policy }) => policy.seconds);
386
- return seconds.reduce((accumulator, value) => gcd(accumulator, value)) * 1000;
668
+ return (seconds.reduce((accumulator, value) => gcd(accumulator, value)) * 1000);
387
669
  }
388
670
  startRetentionCleanup() {
389
671
  const cleanupIntervalMs = this.calculateRetentionCleanupIntervalMs();
@@ -398,7 +680,9 @@ class Database {
398
680
  }
399
681
  runRetentionCleanup() {
400
682
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
401
- if (!this.db || this.retentionCleanupRunning || !this.retentionPolicies.length) {
683
+ if (!this.db ||
684
+ this.retentionCleanupRunning ||
685
+ !this.retentionPolicies.length) {
402
686
  return;
403
687
  }
404
688
  this.retentionCleanupRunning = true;
@@ -422,7 +706,7 @@ class Database {
422
706
  try {
423
707
  const transaction = this.db.transaction(storeName, 'readwrite');
424
708
  const store = transaction.objectStore(storeName);
425
- const cutoff = Date.now() - (policy.seconds * 1000);
709
+ const cutoff = Date.now() - policy.seconds * 1000;
426
710
  const request = store.openCursor();
427
711
  request.onsuccess = () => {
428
712
  const cursor = request.result;
@@ -438,25 +722,35 @@ class Database {
438
722
  console.debug(`Retention cleanup removed expired record from ${className}`);
439
723
  cursor.continue();
440
724
  };
441
- deleteRequest.onerror = () => { var _a; return reject((_a = deleteRequest.error) !== null && _a !== void 0 ? _a : new Error(`Retention cleanup delete failed for ${className}`)); };
725
+ deleteRequest.onerror = () => {
726
+ var _a;
727
+ return reject((_a = deleteRequest.error) !== null && _a !== void 0 ? _a : new Error(`Retention cleanup delete failed for ${className}`));
728
+ };
442
729
  return;
443
730
  }
444
731
  cursor.continue();
445
732
  };
446
733
  transaction.oncomplete = () => resolve();
447
- transaction.onerror = () => { var _a; return reject((_a = transaction.error) !== null && _a !== void 0 ? _a : new Error(`Retention cleanup failed for ${className}`)); };
448
- transaction.onabort = () => { var _a; return reject((_a = transaction.error) !== null && _a !== void 0 ? _a : new Error(`Retention cleanup aborted for ${className}`)); };
734
+ transaction.onerror = () => {
735
+ var _a;
736
+ return reject((_a = transaction.error) !== null && _a !== void 0 ? _a : new Error(`Retention cleanup failed for ${className}`));
737
+ };
738
+ transaction.onabort = () => {
739
+ var _a;
740
+ return reject((_a = transaction.error) !== null && _a !== void 0 ? _a : new Error(`Retention cleanup aborted for ${className}`));
741
+ };
449
742
  }
450
743
  catch (error) {
451
744
  reject(error);
452
745
  }
453
746
  });
454
747
  }
455
- createEntityRepository(cls) {
748
+ createEntityRepository(cls, transaction) {
456
749
  const self = this;
457
750
  const creationTimestampField = INTERNAL_CREATED_AT_FIELD;
458
751
  const updateTimestampField = INTERNAL_UPDATED_AT_FIELD;
459
- const validators = (Reflect.getMetadata('validators', cls) || []);
752
+ const validators = (Reflect.getMetadata('validators', cls) ||
753
+ []);
460
754
  const validateItem = (item) => {
461
755
  const failures = [];
462
756
  validators.forEach((rule) => {
@@ -478,7 +772,7 @@ class Database {
478
772
  };
479
773
  const generateKey = (item) => {
480
774
  var _a;
481
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
775
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
482
776
  if (!((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.generator))
483
777
  return undefined;
484
778
  const generator = keyPathMetadata.options.generator;
@@ -498,8 +792,11 @@ class Database {
498
792
  };
499
793
  const applyTimestampFields = (item, existingItem) => {
500
794
  const now = Date.now();
501
- const existingCreationValue = existingItem ? existingItem[creationTimestampField] : undefined;
502
- item[creationTimestampField] = existingCreationValue !== undefined ? existingCreationValue : now;
795
+ const existingCreationValue = existingItem
796
+ ? existingItem[creationTimestampField]
797
+ : undefined;
798
+ item[creationTimestampField] =
799
+ existingCreationValue !== undefined ? existingCreationValue : now;
503
800
  item[updateTimestampField] = now;
504
801
  };
505
802
  const readExistingItem = (store, key) => {
@@ -537,19 +834,19 @@ class Database {
537
834
  });
538
835
  };
539
836
  const extractKey = (item) => {
540
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
837
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
541
838
  if (!keyPathMetadata)
542
839
  return undefined;
543
840
  const fields = keyPathMetadata.fields;
544
841
  if (Array.isArray(fields)) {
545
- return fields.map(field => item[field]);
842
+ return fields.map((field) => item[field]);
546
843
  }
547
844
  else {
548
845
  return item[fields];
549
846
  }
550
847
  };
551
848
  const setKey = (item, key) => {
552
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
849
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
553
850
  if (!keyPathMetadata)
554
851
  return;
555
852
  const fields = keyPathMetadata.fields;
@@ -562,14 +859,17 @@ class Database {
562
859
  if (!self.db)
563
860
  throw new Error('Database not initialized.');
564
861
  const storeName = cls.name.toLowerCase();
565
- return new QueryBuilder(self.db, storeName);
862
+ return new QueryBuilder(self.db, storeName, transaction);
566
863
  },
567
864
  create: (item) => tslib_1.__awaiter(this, void 0, void 0, function* () {
568
865
  var _a;
569
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
570
- if (((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.generator) && !keyPathMetadata.options.autoIncrement) {
866
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
867
+ if (((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.generator) &&
868
+ !keyPathMetadata.options.autoIncrement) {
571
869
  const currentKey = extractKey(item);
572
- if (currentKey === undefined || currentKey === null || currentKey === '') {
870
+ if (currentKey === undefined ||
871
+ currentKey === null ||
872
+ currentKey === '') {
573
873
  const generatedKey = generateKey(item);
574
874
  if (generatedKey !== undefined) {
575
875
  setKey(item, generatedKey);
@@ -582,10 +882,10 @@ class Database {
582
882
  return createStoredItem(store, item).then(() => {
583
883
  console.debug(`Item added to ${cls.name}:`, item);
584
884
  });
585
- });
885
+ }, transaction);
586
886
  }),
587
887
  createMany: (items) => tslib_1.__awaiter(this, void 0, void 0, function* () {
588
- const repository = this.createEntityRepository(cls);
888
+ const repository = this.createEntityRepository(cls, transaction);
589
889
  for (const item of items) {
590
890
  yield repository.create(item);
591
891
  }
@@ -600,7 +900,7 @@ class Database {
600
900
  };
601
901
  request.onerror = () => reject(request.error);
602
902
  });
603
- });
903
+ }, transaction);
604
904
  }),
605
905
  update: (item) => tslib_1.__awaiter(this, void 0, void 0, function* () {
606
906
  validateItem(item);
@@ -608,10 +908,10 @@ class Database {
608
908
  return updateStoredItem(store, item).then(() => {
609
909
  console.debug(`Item updated in ${cls.name}:`, item);
610
910
  });
611
- });
911
+ }, transaction);
612
912
  }),
613
913
  updateMany: (items) => tslib_1.__awaiter(this, void 0, void 0, function* () {
614
- const repository = this.createEntityRepository(cls);
914
+ const repository = this.createEntityRepository(cls, transaction);
615
915
  for (const item of items) {
616
916
  yield repository.update(item);
617
917
  }
@@ -621,19 +921,19 @@ class Database {
621
921
  return deleteStoredItem(store, key).then(() => {
622
922
  console.debug(`Item deleted from ${cls.name}:`, key);
623
923
  });
624
- });
924
+ }, transaction);
625
925
  }),
626
926
  deleteMany: (keys) => tslib_1.__awaiter(this, void 0, void 0, function* () {
627
- const repository = this.createEntityRepository(cls);
927
+ const repository = this.createEntityRepository(cls, transaction);
628
928
  for (const key of keys) {
629
929
  yield repository.delete(key);
630
930
  }
631
931
  }),
632
932
  deleteWhere: (predicate) => tslib_1.__awaiter(this, void 0, void 0, function* () {
633
- var _b;
634
- const repository = this.createEntityRepository(cls);
933
+ var _a;
934
+ const repository = this.createEntityRepository(cls, transaction);
635
935
  const query = repository.query();
636
- const resolvedQuery = (_b = predicate(query)) !== null && _b !== void 0 ? _b : query;
936
+ const resolvedQuery = (_a = predicate(query)) !== null && _a !== void 0 ? _a : query;
637
937
  const matches = yield resolvedQuery.execute();
638
938
  const keys = matches
639
939
  .map((item) => extractKey(item))
@@ -650,7 +950,7 @@ class Database {
650
950
  };
651
951
  request.onerror = () => reject(request.error);
652
952
  });
653
- });
953
+ }, transaction);
654
954
  }),
655
955
  listPaginated: (page, pageSize) => tslib_1.__awaiter(this, void 0, void 0, function* () {
656
956
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -664,7 +964,7 @@ class Database {
664
964
  };
665
965
  request.onerror = () => reject(request.error);
666
966
  });
667
- });
967
+ }, transaction);
668
968
  }),
669
969
  findByIndex: (indexName, value) => tslib_1.__awaiter(this, void 0, void 0, function* () {
670
970
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -680,7 +980,7 @@ class Database {
680
980
  };
681
981
  request.onerror = () => reject(request.error);
682
982
  });
683
- });
983
+ }, transaction);
684
984
  }),
685
985
  findOneByIndex: (indexName, value) => tslib_1.__awaiter(this, void 0, void 0, function* () {
686
986
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -696,7 +996,7 @@ class Database {
696
996
  };
697
997
  request.onerror = () => reject(request.error);
698
998
  });
699
- });
999
+ }, transaction);
700
1000
  }),
701
1001
  count: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
702
1002
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -708,7 +1008,7 @@ class Database {
708
1008
  };
709
1009
  request.onerror = () => reject(request.error);
710
1010
  });
711
- });
1011
+ }, transaction);
712
1012
  }),
713
1013
  exists: (key) => tslib_1.__awaiter(this, void 0, void 0, function* () {
714
1014
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -721,7 +1021,7 @@ class Database {
721
1021
  };
722
1022
  request.onerror = () => reject(request.error);
723
1023
  });
724
- });
1024
+ }, transaction);
725
1025
  }),
726
1026
  clear: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
727
1027
  return this.performOperation(cls.name, 'readwrite', (store) => {
@@ -733,21 +1033,97 @@ class Database {
733
1033
  };
734
1034
  request.onerror = () => reject(request.error);
735
1035
  });
736
- });
737
- })
1036
+ }, transaction);
1037
+ }),
738
1038
  };
739
1039
  }
740
- performOperation(className, mode, operation) {
1040
+ performOperation(className, mode, operation, transaction) {
741
1041
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
742
- if (!this.db) {
743
- throw new Error("Database not initialized.");
1042
+ if (!this.db && !transaction) {
1043
+ throw new Error('Database not initialized.');
744
1044
  }
745
1045
  const storeName = className.toLowerCase();
746
- const transaction = this.db.transaction(storeName, mode);
747
- const store = transaction.objectStore(storeName);
1046
+ const activeTransaction = transaction !== null && transaction !== void 0 ? transaction : this.db.transaction(storeName, mode);
1047
+ const store = activeTransaction.objectStore(storeName);
748
1048
  return operation(store);
749
1049
  });
750
1050
  }
1051
+ createTransactionHandle(entityNames, mode) {
1052
+ if (!this.db) {
1053
+ throw new Error('Database not initialized.');
1054
+ }
1055
+ const uniqueEntityNames = [...new Set(entityNames)];
1056
+ const storeNames = uniqueEntityNames.map((entityName) => {
1057
+ const entityClass = this.classes.find((cls) => cls.name === entityName);
1058
+ if (!entityClass) {
1059
+ throw new Error(`Entity '${entityName}' is not registered in ${this.dbName}.`);
1060
+ }
1061
+ return entityClass.name.toLowerCase();
1062
+ });
1063
+ const nativeTransaction = this.db.transaction(storeNames, mode);
1064
+ let rollbackRequested = false;
1065
+ const completion = new Promise((resolve, reject) => {
1066
+ nativeTransaction.oncomplete = () => resolve();
1067
+ nativeTransaction.onabort = () => {
1068
+ var _a;
1069
+ if (rollbackRequested) {
1070
+ resolve();
1071
+ return;
1072
+ }
1073
+ reject((_a = nativeTransaction.error) !== null && _a !== void 0 ? _a : new Error('Transaction aborted.'));
1074
+ };
1075
+ nativeTransaction.onerror = () => {
1076
+ var _a;
1077
+ if (rollbackRequested) {
1078
+ resolve();
1079
+ return;
1080
+ }
1081
+ reject((_a = nativeTransaction.error) !== null && _a !== void 0 ? _a : new Error('Transaction failed.'));
1082
+ };
1083
+ });
1084
+ const handle = {};
1085
+ uniqueEntityNames.forEach((entityName) => {
1086
+ const entityClass = this.classes.find((cls) => cls.name === entityName);
1087
+ if (entityClass) {
1088
+ handle[entityName] = this.createEntityRepository(entityClass, nativeTransaction);
1089
+ }
1090
+ });
1091
+ handle.commit = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1092
+ if (typeof nativeTransaction.commit === 'function') {
1093
+ nativeTransaction.commit();
1094
+ }
1095
+ yield completion;
1096
+ });
1097
+ handle.rollback = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1098
+ rollbackRequested = true;
1099
+ try {
1100
+ nativeTransaction.abort();
1101
+ }
1102
+ catch (_a) {
1103
+ }
1104
+ yield completion.catch(() => undefined);
1105
+ });
1106
+ return handle;
1107
+ }
1108
+ beginTransaction(entityNames_1) {
1109
+ return tslib_1.__awaiter(this, arguments, void 0, function* (entityNames, mode = 'readwrite') {
1110
+ return this.createTransactionHandle(entityNames, mode);
1111
+ });
1112
+ }
1113
+ transaction(callback) {
1114
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1115
+ const tx = this.createTransactionHandle(this.classes.map((cls) => cls.name), 'readwrite');
1116
+ try {
1117
+ const result = yield callback(tx);
1118
+ yield tx.commit();
1119
+ return result;
1120
+ }
1121
+ catch (error) {
1122
+ yield tx.rollback();
1123
+ throw error;
1124
+ }
1125
+ });
1126
+ }
751
1127
  close() {
752
1128
  if (this.retentionTimer) {
753
1129
  clearInterval(this.retentionTimer);
@@ -766,15 +1142,15 @@ class Database {
766
1142
  }
767
1143
  getEntityVersions() {
768
1144
  const versions = new Map();
769
- this.classes.forEach(cls => {
770
- const version = Reflect.getMetadata("version", cls) || 1;
1145
+ this.classes.forEach((cls) => {
1146
+ const version = Reflect.getMetadata('version', cls) || 1;
771
1147
  versions.set(cls.name, version);
772
1148
  });
773
1149
  return versions;
774
1150
  }
775
1151
  getEntityVersion(entityName) {
776
- const cls = this.classes.find(c => c.name === entityName);
777
- return cls ? (Reflect.getMetadata("version", cls) || 1) : undefined;
1152
+ const cls = this.classes.find((c) => c.name === entityName);
1153
+ return cls ? Reflect.getMetadata('version', cls) || 1 : undefined;
778
1154
  }
779
1155
  }
780
1156
  exports.Database = Database;