idb-ts 3.10.0 → 3.12.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.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,92 +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
- let 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
  }
436
+ const INTERNAL_CREATED_AT_FIELD = '__idb_createdAt';
437
+ const INTERNAL_UPDATED_AT_FIELD = '__idb_updatedAt';
158
438
  class KeyGenerators {
159
439
  static uuid() {
160
440
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
161
- const r = Math.random() * 16 | 0;
162
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
441
+ const r = (Math.random() * 16) | 0;
442
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
163
443
  return v.toString(16);
164
444
  });
165
445
  }
@@ -174,64 +454,104 @@ exports.KeyGenerators = KeyGenerators;
174
454
  function KeyPath(options) {
175
455
  return (target, propertyKey) => {
176
456
  const constructor = target.constructor;
177
- const existingKeypaths = Reflect.getMetadata("individual_keypaths", constructor) || [];
457
+ const existingKeypaths = Reflect.getMetadata('individual_keypaths', constructor) || [];
178
458
  existingKeypaths.push(propertyKey);
179
- Reflect.defineMetadata("individual_keypaths", existingKeypaths, constructor);
459
+ Reflect.defineMetadata('individual_keypaths', existingKeypaths, constructor);
180
460
  const metadata = {
181
461
  fields: propertyKey,
182
- options: options
462
+ options: options,
183
463
  };
184
- Reflect.defineMetadata("keypath", metadata, constructor);
464
+ Reflect.defineMetadata('keypath', metadata, constructor);
185
465
  };
186
466
  }
187
- exports.KeyPath = KeyPath;
188
467
  function CompositeKeyPath(fields, options) {
189
468
  return (target) => {
190
469
  const metadata = {
191
470
  fields: fields,
192
- options: options
471
+ options: options,
193
472
  };
194
- Reflect.defineMetadata("keypath", metadata, target);
473
+ Reflect.defineMetadata('keypath', metadata, target);
195
474
  };
196
475
  }
197
- exports.CompositeKeyPath = CompositeKeyPath;
198
- function Index() {
476
+ function Index(options) {
199
477
  return (target, propertyKey) => {
200
478
  const constructor = target.constructor;
201
- const existing = Reflect.getMetadata("indexes", constructor) || [];
202
- Reflect.defineMetadata("indexes", [...existing, propertyKey], constructor);
479
+ const existing = Reflect.getMetadata('indexes', constructor) || [];
480
+ const nextIndexes = [
481
+ ...existing,
482
+ { field: propertyKey, options },
483
+ ];
484
+ Reflect.defineMetadata('indexes', nextIndexes, constructor);
485
+ };
486
+ }
487
+ function Validate(predicate, message) {
488
+ return (target, propertyKey) => {
489
+ const constructor = target.constructor;
490
+ const existing = Reflect.getMetadata('validators', constructor) || [];
491
+ const nextRules = [
492
+ ...existing,
493
+ { field: propertyKey, predicate, message },
494
+ ];
495
+ Reflect.defineMetadata('validators', nextRules, constructor);
496
+ };
497
+ }
498
+ function RetentionPolicy(options) {
499
+ return (target) => {
500
+ var _a, _b;
501
+ if (!Number.isInteger(options.seconds) || options.seconds <= 0) {
502
+ throw new Error('RetentionPolicy.seconds must be a positive integer.');
503
+ }
504
+ const metadata = {
505
+ seconds: options.seconds,
506
+ enabled: (_a = options.enabled) !== null && _a !== void 0 ? _a : true,
507
+ field: (_b = options.field) !== null && _b !== void 0 ? _b : INTERNAL_CREATED_AT_FIELD,
508
+ };
509
+ Reflect.defineMetadata('retention_policy', metadata, target);
203
510
  };
204
511
  }
205
- exports.Index = Index;
206
512
  function DataClass(options = {}) {
207
513
  return (target) => {
208
- const keyPathMetadata = Reflect.getMetadata("keypath", target);
514
+ const keyPathMetadata = Reflect.getMetadata('keypath', target);
209
515
  if (!keyPathMetadata) {
210
516
  throw new Error(`No keypath field defined for the class ${target.name}.`);
211
517
  }
212
- const individualKeypaths = Reflect.getMetadata("individual_keypaths", target) || [];
518
+ const individualKeypaths = Reflect.getMetadata('individual_keypaths', target) || [];
213
519
  if (individualKeypaths.length > 1) {
214
520
  throw new Error(`Only one keypath field can be defined for the class ${target.name}.`);
215
521
  }
216
522
  const version = options.version || 1;
217
- Reflect.defineMetadata("dataclass", true, target);
218
- Reflect.defineMetadata("version", version, target);
523
+ Reflect.defineMetadata('dataclass', true, target);
524
+ Reflect.defineMetadata('version', version, target);
219
525
  };
220
526
  }
221
- exports.DataClass = DataClass;
222
527
  class Database {
223
528
  constructor(dbName, classes) {
224
529
  this.db = null;
225
530
  this.entityRepositories = new Map();
531
+ this.retentionTimer = null;
532
+ this.retentionCleanupRunning = false;
226
533
  this.dbName = dbName;
227
- if (!classes.every(cls => Reflect.getMetadata("dataclass", cls))) {
228
- 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.');
229
536
  }
230
537
  this.classes = classes;
231
538
  this.dbVersion = this.calculateDatabaseVersion();
539
+ this.retentionPolicies = this.classes
540
+ .map((cls) => {
541
+ const policy = Reflect.getMetadata('retention_policy', cls);
542
+ if (!(policy === null || policy === void 0 ? void 0 : policy.enabled)) {
543
+ return null;
544
+ }
545
+ return {
546
+ className: cls.name,
547
+ storeName: cls.name.toLowerCase(),
548
+ policy,
549
+ };
550
+ })
551
+ .filter((policy) => policy !== null);
232
552
  }
233
553
  calculateDatabaseVersion() {
234
- const versions = this.classes.map(cls => Reflect.getMetadata("version", cls) || 1);
554
+ const versions = this.classes.map((cls) => Reflect.getMetadata('version', cls) || 1);
235
555
  return Math.max(...versions);
236
556
  }
237
557
  static build(dbName, classes) {
@@ -253,9 +573,9 @@ class Database {
253
573
  console.debug(`Database upgrade from version ${oldVersion} to ${newVersion}`);
254
574
  this.classes.forEach((cls) => {
255
575
  var _a;
256
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
257
- const indexFields = Reflect.getMetadata("indexes", cls) || [];
258
- 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;
259
579
  const storeName = cls.name.toLowerCase();
260
580
  if (classVersion > oldVersion) {
261
581
  if (!db.objectStoreNames.contains(storeName)) {
@@ -269,8 +589,15 @@ class Database {
269
589
  }
270
590
  const store = db.createObjectStore(storeName, storeOptions);
271
591
  indexFields.forEach((indexField) => {
272
- if (!store.indexNames.contains(indexField)) {
273
- store.createIndex(indexField, indexField, { unique: false });
592
+ var _a;
593
+ const indexName = typeof indexField === 'string'
594
+ ? indexField
595
+ : indexField.field;
596
+ const indexOptions = typeof indexField === 'string'
597
+ ? { unique: false }
598
+ : ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
599
+ if (!store.indexNames.contains(indexName)) {
600
+ store.createIndex(indexName, indexName, indexOptions);
274
601
  }
275
602
  });
276
603
  }
@@ -280,9 +607,16 @@ class Database {
280
607
  if (transaction) {
281
608
  const store = transaction.objectStore(storeName);
282
609
  indexFields.forEach((indexField) => {
283
- if (!store.indexNames.contains(indexField)) {
284
- console.debug(`Adding index: ${indexField} to ${storeName}`);
285
- store.createIndex(indexField, indexField, { unique: false });
610
+ var _a;
611
+ const indexName = typeof indexField === 'string'
612
+ ? indexField
613
+ : indexField.field;
614
+ const indexOptions = typeof indexField === 'string'
615
+ ? { unique: false }
616
+ : ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
617
+ if (!store.indexNames.contains(indexName)) {
618
+ console.debug(`Adding index: ${indexName} to ${storeName}`);
619
+ store.createIndex(indexName, indexName, indexOptions);
286
620
  }
287
621
  });
288
622
  }
@@ -292,18 +626,19 @@ class Database {
292
626
  };
293
627
  request.onsuccess = () => {
294
628
  this.db = request.result;
295
- 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(', ')}`);
630
+ this.startRetentionCleanup();
296
631
  resolve();
297
632
  };
298
633
  request.onerror = () => {
299
- console.error("Error initializing database:", request.error);
634
+ console.error('Error initializing database:', request.error);
300
635
  reject(request.error);
301
636
  };
302
637
  });
303
638
  });
304
639
  }
305
640
  generateEntityRepositories() {
306
- this.classes.forEach(cls => {
641
+ this.classes.forEach((cls) => {
307
642
  const entityName = cls.name;
308
643
  const repository = this.createEntityRepository(cls);
309
644
  this.entityRepositories.set(entityName, repository);
@@ -311,15 +646,133 @@ class Database {
311
646
  value: repository,
312
647
  writable: false,
313
648
  enumerable: true,
314
- configurable: false
649
+ configurable: false,
315
650
  });
316
651
  });
317
652
  }
318
- createEntityRepository(cls) {
653
+ calculateRetentionCleanupIntervalMs() {
654
+ if (!this.retentionPolicies.length) {
655
+ return undefined;
656
+ }
657
+ const gcd = (left, right) => {
658
+ let a = left;
659
+ let b = right;
660
+ while (b !== 0) {
661
+ const remainder = a % b;
662
+ a = b;
663
+ b = remainder;
664
+ }
665
+ return Math.abs(a);
666
+ };
667
+ const seconds = this.retentionPolicies.map(({ policy }) => policy.seconds);
668
+ return (seconds.reduce((accumulator, value) => gcd(accumulator, value)) * 1000);
669
+ }
670
+ startRetentionCleanup() {
671
+ const cleanupIntervalMs = this.calculateRetentionCleanupIntervalMs();
672
+ if (!cleanupIntervalMs || !this.db || this.retentionTimer) {
673
+ return;
674
+ }
675
+ console.debug(`Retention cleanup enabled for ${this.retentionPolicies.length} entities every ${cleanupIntervalMs}ms`);
676
+ void this.runRetentionCleanup();
677
+ this.retentionTimer = setInterval(() => {
678
+ void this.runRetentionCleanup();
679
+ }, cleanupIntervalMs);
680
+ }
681
+ runRetentionCleanup() {
682
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
683
+ if (!this.db ||
684
+ this.retentionCleanupRunning ||
685
+ !this.retentionPolicies.length) {
686
+ return;
687
+ }
688
+ this.retentionCleanupRunning = true;
689
+ try {
690
+ console.debug('Retention cleanup tick started');
691
+ for (const { storeName, className, policy } of this.retentionPolicies) {
692
+ yield this.cleanupExpiredRecords(storeName, className, policy);
693
+ }
694
+ console.debug('Retention cleanup tick finished');
695
+ }
696
+ finally {
697
+ this.retentionCleanupRunning = false;
698
+ }
699
+ });
700
+ }
701
+ cleanupExpiredRecords(storeName, className, policy) {
702
+ if (!this.db) {
703
+ return Promise.resolve();
704
+ }
705
+ return new Promise((resolve, reject) => {
706
+ try {
707
+ const transaction = this.db.transaction(storeName, 'readwrite');
708
+ const store = transaction.objectStore(storeName);
709
+ const cutoff = Date.now() - policy.seconds * 1000;
710
+ const request = store.openCursor();
711
+ request.onsuccess = () => {
712
+ const cursor = request.result;
713
+ if (!cursor) {
714
+ return;
715
+ }
716
+ const value = cursor.value;
717
+ const timestamp = value === null || value === void 0 ? void 0 : value[policy.field];
718
+ console.debug(`Retention cleanup inspecting ${className}.${policy.field}:`, timestamp, 'cutoff:', cutoff);
719
+ if (typeof timestamp === 'number' && timestamp <= cutoff) {
720
+ const deleteRequest = cursor.delete();
721
+ deleteRequest.onsuccess = () => {
722
+ console.debug(`Retention cleanup removed expired record from ${className}`);
723
+ cursor.continue();
724
+ };
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
+ };
729
+ return;
730
+ }
731
+ cursor.continue();
732
+ };
733
+ transaction.oncomplete = () => resolve();
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
+ };
742
+ }
743
+ catch (error) {
744
+ reject(error);
745
+ }
746
+ });
747
+ }
748
+ createEntityRepository(cls, transaction) {
319
749
  const self = this;
750
+ const creationTimestampField = INTERNAL_CREATED_AT_FIELD;
751
+ const updateTimestampField = INTERNAL_UPDATED_AT_FIELD;
752
+ const validators = (Reflect.getMetadata('validators', cls) ||
753
+ []);
754
+ const validateItem = (item) => {
755
+ const failures = [];
756
+ validators.forEach((rule) => {
757
+ const value = item[rule.field];
758
+ let valid = false;
759
+ try {
760
+ valid = rule.predicate(value, item);
761
+ }
762
+ catch (_a) {
763
+ valid = false;
764
+ }
765
+ if (!valid) {
766
+ failures.push(`${rule.field}: ${rule.message}`);
767
+ }
768
+ });
769
+ if (failures.length) {
770
+ throw new Error(`Validation failed for ${cls.name}: ${failures.join('; ')}`);
771
+ }
772
+ };
320
773
  const generateKey = (item) => {
321
774
  var _a;
322
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
775
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
323
776
  if (!((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.generator))
324
777
  return undefined;
325
778
  const generator = keyPathMetadata.options.generator;
@@ -337,20 +790,63 @@ class Database {
337
790
  return undefined;
338
791
  }
339
792
  };
793
+ const applyTimestampFields = (item, existingItem) => {
794
+ const now = Date.now();
795
+ const existingCreationValue = existingItem
796
+ ? existingItem[creationTimestampField]
797
+ : undefined;
798
+ item[creationTimestampField] =
799
+ existingCreationValue !== undefined ? existingCreationValue : now;
800
+ item[updateTimestampField] = now;
801
+ };
802
+ const readExistingItem = (store, key) => {
803
+ return new Promise((resolve, reject) => {
804
+ const request = store.get(key);
805
+ request.onsuccess = () => resolve(request.result);
806
+ request.onerror = () => reject(request.error);
807
+ });
808
+ };
809
+ const createStoredItem = (store, item) => {
810
+ return new Promise((resolve, reject) => {
811
+ const request = store.add(item);
812
+ request.onsuccess = () => resolve();
813
+ request.onerror = () => reject(request.error);
814
+ });
815
+ };
816
+ const updateStoredItem = (store, item) => tslib_1.__awaiter(this, void 0, void 0, function* () {
817
+ const key = extractKey(item);
818
+ let existingItem;
819
+ if (key !== undefined && key !== null) {
820
+ existingItem = yield readExistingItem(store, key);
821
+ }
822
+ applyTimestampFields(item, existingItem);
823
+ yield new Promise((resolve, reject) => {
824
+ const request = store.put(item);
825
+ request.onsuccess = () => resolve();
826
+ request.onerror = () => reject(request.error);
827
+ });
828
+ });
829
+ const deleteStoredItem = (store, key) => {
830
+ return new Promise((resolve, reject) => {
831
+ const request = store.delete(key);
832
+ request.onsuccess = () => resolve();
833
+ request.onerror = () => reject(request.error);
834
+ });
835
+ };
340
836
  const extractKey = (item) => {
341
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
837
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
342
838
  if (!keyPathMetadata)
343
839
  return undefined;
344
840
  const fields = keyPathMetadata.fields;
345
841
  if (Array.isArray(fields)) {
346
- return fields.map(field => item[field]);
842
+ return fields.map((field) => item[field]);
347
843
  }
348
844
  else {
349
845
  return item[fields];
350
846
  }
351
847
  };
352
848
  const setKey = (item, key) => {
353
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
849
+ const keyPathMetadata = Reflect.getMetadata('keypath', cls);
354
850
  if (!keyPathMetadata)
355
851
  return;
356
852
  const fields = keyPathMetadata.fields;
@@ -363,30 +859,36 @@ class Database {
363
859
  if (!self.db)
364
860
  throw new Error('Database not initialized.');
365
861
  const storeName = cls.name.toLowerCase();
366
- return new QueryBuilder(self.db, storeName);
862
+ return new QueryBuilder(self.db, storeName, transaction);
367
863
  },
368
864
  create: (item) => tslib_1.__awaiter(this, void 0, void 0, function* () {
369
865
  var _a;
370
- const keyPathMetadata = Reflect.getMetadata("keypath", cls);
371
- 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) {
372
869
  const currentKey = extractKey(item);
373
- if (currentKey === undefined || currentKey === null || currentKey === '') {
870
+ if (currentKey === undefined ||
871
+ currentKey === null ||
872
+ currentKey === '') {
374
873
  const generatedKey = generateKey(item);
375
874
  if (generatedKey !== undefined) {
376
875
  setKey(item, generatedKey);
377
876
  }
378
877
  }
379
878
  }
879
+ validateItem(item);
880
+ applyTimestampFields(item);
380
881
  return this.performOperation(cls.name, 'readwrite', (store) => {
381
- const request = store.add(item);
382
- return new Promise((resolve, reject) => {
383
- request.onsuccess = () => {
384
- console.debug(`Item added to ${cls.name}:`, item);
385
- resolve();
386
- };
387
- request.onerror = () => reject(request.error);
882
+ return createStoredItem(store, item).then(() => {
883
+ console.debug(`Item added to ${cls.name}:`, item);
388
884
  });
389
- });
885
+ }, transaction);
886
+ }),
887
+ createMany: (items) => tslib_1.__awaiter(this, void 0, void 0, function* () {
888
+ const repository = this.createEntityRepository(cls, transaction);
889
+ for (const item of items) {
890
+ yield repository.create(item);
891
+ }
390
892
  }),
391
893
  read: (key) => tslib_1.__awaiter(this, void 0, void 0, function* () {
392
894
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -398,31 +900,45 @@ class Database {
398
900
  };
399
901
  request.onerror = () => reject(request.error);
400
902
  });
401
- });
903
+ }, transaction);
402
904
  }),
403
905
  update: (item) => tslib_1.__awaiter(this, void 0, void 0, function* () {
906
+ validateItem(item);
404
907
  return this.performOperation(cls.name, 'readwrite', (store) => {
405
- const request = store.put(item);
406
- return new Promise((resolve, reject) => {
407
- request.onsuccess = () => {
408
- console.debug(`Item updated in ${cls.name}:`, item);
409
- resolve();
410
- };
411
- request.onerror = () => reject(request.error);
908
+ return updateStoredItem(store, item).then(() => {
909
+ console.debug(`Item updated in ${cls.name}:`, item);
412
910
  });
413
- });
911
+ }, transaction);
912
+ }),
913
+ updateMany: (items) => tslib_1.__awaiter(this, void 0, void 0, function* () {
914
+ const repository = this.createEntityRepository(cls, transaction);
915
+ for (const item of items) {
916
+ yield repository.update(item);
917
+ }
414
918
  }),
415
919
  delete: (key) => tslib_1.__awaiter(this, void 0, void 0, function* () {
416
920
  return this.performOperation(cls.name, 'readwrite', (store) => {
417
- const request = store.delete(key);
418
- return new Promise((resolve, reject) => {
419
- request.onsuccess = () => {
420
- console.debug(`Item deleted from ${cls.name}:`, key);
421
- resolve();
422
- };
423
- request.onerror = () => reject(request.error);
921
+ return deleteStoredItem(store, key).then(() => {
922
+ console.debug(`Item deleted from ${cls.name}:`, key);
424
923
  });
425
- });
924
+ }, transaction);
925
+ }),
926
+ deleteMany: (keys) => tslib_1.__awaiter(this, void 0, void 0, function* () {
927
+ const repository = this.createEntityRepository(cls, transaction);
928
+ for (const key of keys) {
929
+ yield repository.delete(key);
930
+ }
931
+ }),
932
+ deleteWhere: (predicate) => tslib_1.__awaiter(this, void 0, void 0, function* () {
933
+ var _a;
934
+ const repository = this.createEntityRepository(cls, transaction);
935
+ const query = repository.query();
936
+ const resolvedQuery = (_a = predicate(query)) !== null && _a !== void 0 ? _a : query;
937
+ const matches = yield resolvedQuery.execute();
938
+ const keys = matches
939
+ .map((item) => extractKey(item))
940
+ .filter((key) => key !== undefined && key !== null);
941
+ yield repository.deleteMany(keys);
426
942
  }),
427
943
  list: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
428
944
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -434,7 +950,7 @@ class Database {
434
950
  };
435
951
  request.onerror = () => reject(request.error);
436
952
  });
437
- });
953
+ }, transaction);
438
954
  }),
439
955
  listPaginated: (page, pageSize) => tslib_1.__awaiter(this, void 0, void 0, function* () {
440
956
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -448,7 +964,7 @@ class Database {
448
964
  };
449
965
  request.onerror = () => reject(request.error);
450
966
  });
451
- });
967
+ }, transaction);
452
968
  }),
453
969
  findByIndex: (indexName, value) => tslib_1.__awaiter(this, void 0, void 0, function* () {
454
970
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -464,7 +980,7 @@ class Database {
464
980
  };
465
981
  request.onerror = () => reject(request.error);
466
982
  });
467
- });
983
+ }, transaction);
468
984
  }),
469
985
  findOneByIndex: (indexName, value) => tslib_1.__awaiter(this, void 0, void 0, function* () {
470
986
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -480,7 +996,7 @@ class Database {
480
996
  };
481
997
  request.onerror = () => reject(request.error);
482
998
  });
483
- });
999
+ }, transaction);
484
1000
  }),
485
1001
  count: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
486
1002
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -492,7 +1008,7 @@ class Database {
492
1008
  };
493
1009
  request.onerror = () => reject(request.error);
494
1010
  });
495
- });
1011
+ }, transaction);
496
1012
  }),
497
1013
  exists: (key) => tslib_1.__awaiter(this, void 0, void 0, function* () {
498
1014
  return this.performOperation(cls.name, 'readonly', (store) => {
@@ -505,7 +1021,7 @@ class Database {
505
1021
  };
506
1022
  request.onerror = () => reject(request.error);
507
1023
  });
508
- });
1024
+ }, transaction);
509
1025
  }),
510
1026
  clear: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
511
1027
  return this.performOperation(cls.name, 'readwrite', (store) => {
@@ -517,21 +1033,107 @@ class Database {
517
1033
  };
518
1034
  request.onerror = () => reject(request.error);
519
1035
  });
520
- });
521
- })
1036
+ }, transaction);
1037
+ }),
522
1038
  };
523
1039
  }
524
- performOperation(className, mode, operation) {
1040
+ performOperation(className, mode, operation, transaction) {
525
1041
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
526
- if (!this.db) {
527
- throw new Error("Database not initialized.");
1042
+ if (!this.db && !transaction) {
1043
+ throw new Error('Database not initialized.');
528
1044
  }
529
1045
  const storeName = className.toLowerCase();
530
- const transaction = this.db.transaction(storeName, mode);
531
- 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);
532
1048
  return operation(store);
533
1049
  });
534
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
+ }
1127
+ close() {
1128
+ if (this.retentionTimer) {
1129
+ clearInterval(this.retentionTimer);
1130
+ this.retentionTimer = null;
1131
+ }
1132
+ if (this.db) {
1133
+ this.db.close();
1134
+ this.db = null;
1135
+ }
1136
+ }
535
1137
  getAvailableEntities() {
536
1138
  return Array.from(this.entityRepositories.keys());
537
1139
  }
@@ -540,15 +1142,15 @@ class Database {
540
1142
  }
541
1143
  getEntityVersions() {
542
1144
  const versions = new Map();
543
- this.classes.forEach(cls => {
544
- const version = Reflect.getMetadata("version", cls) || 1;
1145
+ this.classes.forEach((cls) => {
1146
+ const version = Reflect.getMetadata('version', cls) || 1;
545
1147
  versions.set(cls.name, version);
546
1148
  });
547
1149
  return versions;
548
1150
  }
549
1151
  getEntityVersion(entityName) {
550
- const cls = this.classes.find(c => c.name === entityName);
551
- 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;
552
1154
  }
553
1155
  }
554
1156
  exports.Database = Database;