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