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