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.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.
|
|
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.
|
|
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
|
-
|
|
22
|
-
this.
|
|
129
|
+
or() {
|
|
130
|
+
this.pendingConnector = 'or';
|
|
23
131
|
return this;
|
|
24
132
|
}
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
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,94 +229,226 @@ class QueryBuilder {
|
|
|
83
229
|
this.rangeEnd = end;
|
|
84
230
|
return this;
|
|
85
231
|
}
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
const 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
|
}
|
|
167
445
|
const INTERNAL_CREATED_AT_FIELD = '__idb_createdAt';
|
|
168
446
|
const INTERNAL_UPDATED_AT_FIELD = '__idb_updatedAt';
|
|
169
447
|
class KeyGenerators {
|
|
170
448
|
static uuid() {
|
|
171
449
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
172
|
-
const r = Math.random() * 16 | 0;
|
|
173
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8
|
|
450
|
+
const r = (Math.random() * 16) | 0;
|
|
451
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
174
452
|
return v.toString(16);
|
|
175
453
|
});
|
|
176
454
|
}
|
|
@@ -185,45 +463,47 @@ exports.KeyGenerators = KeyGenerators;
|
|
|
185
463
|
function KeyPath(options) {
|
|
186
464
|
return (target, propertyKey) => {
|
|
187
465
|
const constructor = target.constructor;
|
|
188
|
-
const existingKeypaths = Reflect.getMetadata(
|
|
466
|
+
const existingKeypaths = Reflect.getMetadata('individual_keypaths', constructor) || [];
|
|
189
467
|
existingKeypaths.push(propertyKey);
|
|
190
|
-
Reflect.defineMetadata(
|
|
468
|
+
Reflect.defineMetadata('individual_keypaths', existingKeypaths, constructor);
|
|
191
469
|
const metadata = {
|
|
192
470
|
fields: propertyKey,
|
|
193
|
-
options: options
|
|
471
|
+
options: options,
|
|
194
472
|
};
|
|
195
|
-
Reflect.defineMetadata(
|
|
473
|
+
Reflect.defineMetadata('keypath', metadata, constructor);
|
|
196
474
|
};
|
|
197
475
|
}
|
|
198
|
-
exports.KeyPath = KeyPath;
|
|
199
476
|
function CompositeKeyPath(fields, options) {
|
|
200
477
|
return (target) => {
|
|
201
478
|
const metadata = {
|
|
202
479
|
fields: fields,
|
|
203
|
-
options: options
|
|
480
|
+
options: options,
|
|
204
481
|
};
|
|
205
|
-
Reflect.defineMetadata(
|
|
482
|
+
Reflect.defineMetadata('keypath', metadata, target);
|
|
206
483
|
};
|
|
207
484
|
}
|
|
208
|
-
exports.CompositeKeyPath = CompositeKeyPath;
|
|
209
485
|
function Index(options) {
|
|
210
486
|
return (target, propertyKey) => {
|
|
211
487
|
const constructor = target.constructor;
|
|
212
|
-
const existing = Reflect.getMetadata(
|
|
213
|
-
const nextIndexes = [
|
|
214
|
-
|
|
488
|
+
const existing = Reflect.getMetadata('indexes', constructor) || [];
|
|
489
|
+
const nextIndexes = [
|
|
490
|
+
...existing,
|
|
491
|
+
{ field: propertyKey, options },
|
|
492
|
+
];
|
|
493
|
+
Reflect.defineMetadata('indexes', nextIndexes, constructor);
|
|
215
494
|
};
|
|
216
495
|
}
|
|
217
|
-
exports.Index = Index;
|
|
218
496
|
function Validate(predicate, message) {
|
|
219
497
|
return (target, propertyKey) => {
|
|
220
498
|
const constructor = target.constructor;
|
|
221
499
|
const existing = Reflect.getMetadata('validators', constructor) || [];
|
|
222
|
-
const nextRules = [
|
|
500
|
+
const nextRules = [
|
|
501
|
+
...existing,
|
|
502
|
+
{ field: propertyKey, predicate, message },
|
|
503
|
+
];
|
|
223
504
|
Reflect.defineMetadata('validators', nextRules, constructor);
|
|
224
505
|
};
|
|
225
506
|
}
|
|
226
|
-
exports.Validate = Validate;
|
|
227
507
|
function RetentionPolicy(options) {
|
|
228
508
|
return (target) => {
|
|
229
509
|
var _a, _b;
|
|
@@ -233,28 +513,26 @@ function RetentionPolicy(options) {
|
|
|
233
513
|
const metadata = {
|
|
234
514
|
seconds: options.seconds,
|
|
235
515
|
enabled: (_a = options.enabled) !== null && _a !== void 0 ? _a : true,
|
|
236
|
-
field: (_b = options.field) !== null && _b !== void 0 ? _b : INTERNAL_CREATED_AT_FIELD
|
|
516
|
+
field: (_b = options.field) !== null && _b !== void 0 ? _b : INTERNAL_CREATED_AT_FIELD,
|
|
237
517
|
};
|
|
238
518
|
Reflect.defineMetadata('retention_policy', metadata, target);
|
|
239
519
|
};
|
|
240
520
|
}
|
|
241
|
-
exports.RetentionPolicy = RetentionPolicy;
|
|
242
521
|
function DataClass(options = {}) {
|
|
243
522
|
return (target) => {
|
|
244
|
-
const keyPathMetadata = Reflect.getMetadata(
|
|
523
|
+
const keyPathMetadata = Reflect.getMetadata('keypath', target);
|
|
245
524
|
if (!keyPathMetadata) {
|
|
246
525
|
throw new Error(`No keypath field defined for the class ${target.name}.`);
|
|
247
526
|
}
|
|
248
|
-
const individualKeypaths = Reflect.getMetadata(
|
|
527
|
+
const individualKeypaths = Reflect.getMetadata('individual_keypaths', target) || [];
|
|
249
528
|
if (individualKeypaths.length > 1) {
|
|
250
529
|
throw new Error(`Only one keypath field can be defined for the class ${target.name}.`);
|
|
251
530
|
}
|
|
252
531
|
const version = options.version || 1;
|
|
253
|
-
Reflect.defineMetadata(
|
|
254
|
-
Reflect.defineMetadata(
|
|
532
|
+
Reflect.defineMetadata('dataclass', true, target);
|
|
533
|
+
Reflect.defineMetadata('version', version, target);
|
|
255
534
|
};
|
|
256
535
|
}
|
|
257
|
-
exports.DataClass = DataClass;
|
|
258
536
|
class Database {
|
|
259
537
|
constructor(dbName, classes) {
|
|
260
538
|
this.db = null;
|
|
@@ -262,8 +540,8 @@ class Database {
|
|
|
262
540
|
this.retentionTimer = null;
|
|
263
541
|
this.retentionCleanupRunning = false;
|
|
264
542
|
this.dbName = dbName;
|
|
265
|
-
if (!classes.every(cls => Reflect.getMetadata(
|
|
266
|
-
throw new Error(
|
|
543
|
+
if (!classes.every((cls) => Reflect.getMetadata('dataclass', cls))) {
|
|
544
|
+
throw new Error('All classes should be decorated with @DataClass.');
|
|
267
545
|
}
|
|
268
546
|
this.classes = classes;
|
|
269
547
|
this.dbVersion = this.calculateDatabaseVersion();
|
|
@@ -276,13 +554,13 @@ class Database {
|
|
|
276
554
|
return {
|
|
277
555
|
className: cls.name,
|
|
278
556
|
storeName: cls.name.toLowerCase(),
|
|
279
|
-
policy
|
|
557
|
+
policy,
|
|
280
558
|
};
|
|
281
559
|
})
|
|
282
560
|
.filter((policy) => policy !== null);
|
|
283
561
|
}
|
|
284
562
|
calculateDatabaseVersion() {
|
|
285
|
-
const versions = this.classes.map(cls => Reflect.getMetadata(
|
|
563
|
+
const versions = this.classes.map((cls) => Reflect.getMetadata('version', cls) || 1);
|
|
286
564
|
return Math.max(...versions);
|
|
287
565
|
}
|
|
288
566
|
static build(dbName, classes) {
|
|
@@ -304,9 +582,9 @@ class Database {
|
|
|
304
582
|
console.debug(`Database upgrade from version ${oldVersion} to ${newVersion}`);
|
|
305
583
|
this.classes.forEach((cls) => {
|
|
306
584
|
var _a;
|
|
307
|
-
const keyPathMetadata = Reflect.getMetadata(
|
|
308
|
-
const indexFields = Reflect.getMetadata(
|
|
309
|
-
const classVersion = Reflect.getMetadata(
|
|
585
|
+
const keyPathMetadata = Reflect.getMetadata('keypath', cls);
|
|
586
|
+
const indexFields = Reflect.getMetadata('indexes', cls) || [];
|
|
587
|
+
const classVersion = Reflect.getMetadata('version', cls) || 1;
|
|
310
588
|
const storeName = cls.name.toLowerCase();
|
|
311
589
|
if (classVersion > oldVersion) {
|
|
312
590
|
if (!db.objectStoreNames.contains(storeName)) {
|
|
@@ -321,7 +599,9 @@ class Database {
|
|
|
321
599
|
const store = db.createObjectStore(storeName, storeOptions);
|
|
322
600
|
indexFields.forEach((indexField) => {
|
|
323
601
|
var _a;
|
|
324
|
-
const indexName = typeof indexField === 'string'
|
|
602
|
+
const indexName = typeof indexField === 'string'
|
|
603
|
+
? indexField
|
|
604
|
+
: indexField.field;
|
|
325
605
|
const indexOptions = typeof indexField === 'string'
|
|
326
606
|
? { unique: false }
|
|
327
607
|
: ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
|
|
@@ -337,7 +617,9 @@ class Database {
|
|
|
337
617
|
const store = transaction.objectStore(storeName);
|
|
338
618
|
indexFields.forEach((indexField) => {
|
|
339
619
|
var _a;
|
|
340
|
-
const indexName = typeof indexField === 'string'
|
|
620
|
+
const indexName = typeof indexField === 'string'
|
|
621
|
+
? indexField
|
|
622
|
+
: indexField.field;
|
|
341
623
|
const indexOptions = typeof indexField === 'string'
|
|
342
624
|
? { unique: false }
|
|
343
625
|
: ((_a = indexField.options) !== null && _a !== void 0 ? _a : { unique: false });
|
|
@@ -353,19 +635,19 @@ class Database {
|
|
|
353
635
|
};
|
|
354
636
|
request.onsuccess = () => {
|
|
355
637
|
this.db = request.result;
|
|
356
|
-
console.debug(`Database initialized (version ${this.dbVersion}) with object stores for: ${this.classes.map(cls => `${cls.name}(v${Reflect.getMetadata(
|
|
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(', ')}`);
|
|
357
639
|
this.startRetentionCleanup();
|
|
358
640
|
resolve();
|
|
359
641
|
};
|
|
360
642
|
request.onerror = () => {
|
|
361
|
-
console.error(
|
|
643
|
+
console.error('Error initializing database:', request.error);
|
|
362
644
|
reject(request.error);
|
|
363
645
|
};
|
|
364
646
|
});
|
|
365
647
|
});
|
|
366
648
|
}
|
|
367
649
|
generateEntityRepositories() {
|
|
368
|
-
this.classes.forEach(cls => {
|
|
650
|
+
this.classes.forEach((cls) => {
|
|
369
651
|
const entityName = cls.name;
|
|
370
652
|
const repository = this.createEntityRepository(cls);
|
|
371
653
|
this.entityRepositories.set(entityName, repository);
|
|
@@ -373,7 +655,7 @@ class Database {
|
|
|
373
655
|
value: repository,
|
|
374
656
|
writable: false,
|
|
375
657
|
enumerable: true,
|
|
376
|
-
configurable: false
|
|
658
|
+
configurable: false,
|
|
377
659
|
});
|
|
378
660
|
});
|
|
379
661
|
}
|
|
@@ -392,7 +674,7 @@ class Database {
|
|
|
392
674
|
return Math.abs(a);
|
|
393
675
|
};
|
|
394
676
|
const seconds = this.retentionPolicies.map(({ policy }) => policy.seconds);
|
|
395
|
-
return seconds.reduce((accumulator, value) => gcd(accumulator, value)) * 1000;
|
|
677
|
+
return (seconds.reduce((accumulator, value) => gcd(accumulator, value)) * 1000);
|
|
396
678
|
}
|
|
397
679
|
startRetentionCleanup() {
|
|
398
680
|
const cleanupIntervalMs = this.calculateRetentionCleanupIntervalMs();
|
|
@@ -407,7 +689,9 @@ class Database {
|
|
|
407
689
|
}
|
|
408
690
|
runRetentionCleanup() {
|
|
409
691
|
return __awaiter(this, void 0, void 0, function* () {
|
|
410
|
-
if (!this.db ||
|
|
692
|
+
if (!this.db ||
|
|
693
|
+
this.retentionCleanupRunning ||
|
|
694
|
+
!this.retentionPolicies.length) {
|
|
411
695
|
return;
|
|
412
696
|
}
|
|
413
697
|
this.retentionCleanupRunning = true;
|
|
@@ -431,7 +715,7 @@ class Database {
|
|
|
431
715
|
try {
|
|
432
716
|
const transaction = this.db.transaction(storeName, 'readwrite');
|
|
433
717
|
const store = transaction.objectStore(storeName);
|
|
434
|
-
const cutoff = Date.now() -
|
|
718
|
+
const cutoff = Date.now() - policy.seconds * 1000;
|
|
435
719
|
const request = store.openCursor();
|
|
436
720
|
request.onsuccess = () => {
|
|
437
721
|
const cursor = request.result;
|
|
@@ -447,25 +731,35 @@ class Database {
|
|
|
447
731
|
console.debug(`Retention cleanup removed expired record from ${className}`);
|
|
448
732
|
cursor.continue();
|
|
449
733
|
};
|
|
450
|
-
deleteRequest.onerror = () => {
|
|
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
|
+
};
|
|
451
738
|
return;
|
|
452
739
|
}
|
|
453
740
|
cursor.continue();
|
|
454
741
|
};
|
|
455
742
|
transaction.oncomplete = () => resolve();
|
|
456
|
-
transaction.onerror = () => {
|
|
457
|
-
|
|
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
|
+
};
|
|
458
751
|
}
|
|
459
752
|
catch (error) {
|
|
460
753
|
reject(error);
|
|
461
754
|
}
|
|
462
755
|
});
|
|
463
756
|
}
|
|
464
|
-
createEntityRepository(cls) {
|
|
757
|
+
createEntityRepository(cls, transaction) {
|
|
465
758
|
const self = this;
|
|
466
759
|
const creationTimestampField = INTERNAL_CREATED_AT_FIELD;
|
|
467
760
|
const updateTimestampField = INTERNAL_UPDATED_AT_FIELD;
|
|
468
|
-
const validators = (Reflect.getMetadata('validators', cls) ||
|
|
761
|
+
const validators = (Reflect.getMetadata('validators', cls) ||
|
|
762
|
+
[]);
|
|
469
763
|
const validateItem = (item) => {
|
|
470
764
|
const failures = [];
|
|
471
765
|
validators.forEach((rule) => {
|
|
@@ -487,7 +781,7 @@ class Database {
|
|
|
487
781
|
};
|
|
488
782
|
const generateKey = (item) => {
|
|
489
783
|
var _a;
|
|
490
|
-
const keyPathMetadata = Reflect.getMetadata(
|
|
784
|
+
const keyPathMetadata = Reflect.getMetadata('keypath', cls);
|
|
491
785
|
if (!((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.generator))
|
|
492
786
|
return undefined;
|
|
493
787
|
const generator = keyPathMetadata.options.generator;
|
|
@@ -507,8 +801,11 @@ class Database {
|
|
|
507
801
|
};
|
|
508
802
|
const applyTimestampFields = (item, existingItem) => {
|
|
509
803
|
const now = Date.now();
|
|
510
|
-
const existingCreationValue = existingItem
|
|
511
|
-
|
|
804
|
+
const existingCreationValue = existingItem
|
|
805
|
+
? existingItem[creationTimestampField]
|
|
806
|
+
: undefined;
|
|
807
|
+
item[creationTimestampField] =
|
|
808
|
+
existingCreationValue !== undefined ? existingCreationValue : now;
|
|
512
809
|
item[updateTimestampField] = now;
|
|
513
810
|
};
|
|
514
811
|
const readExistingItem = (store, key) => {
|
|
@@ -546,19 +843,19 @@ class Database {
|
|
|
546
843
|
});
|
|
547
844
|
};
|
|
548
845
|
const extractKey = (item) => {
|
|
549
|
-
const keyPathMetadata = Reflect.getMetadata(
|
|
846
|
+
const keyPathMetadata = Reflect.getMetadata('keypath', cls);
|
|
550
847
|
if (!keyPathMetadata)
|
|
551
848
|
return undefined;
|
|
552
849
|
const fields = keyPathMetadata.fields;
|
|
553
850
|
if (Array.isArray(fields)) {
|
|
554
|
-
return fields.map(field => item[field]);
|
|
851
|
+
return fields.map((field) => item[field]);
|
|
555
852
|
}
|
|
556
853
|
else {
|
|
557
854
|
return item[fields];
|
|
558
855
|
}
|
|
559
856
|
};
|
|
560
857
|
const setKey = (item, key) => {
|
|
561
|
-
const keyPathMetadata = Reflect.getMetadata(
|
|
858
|
+
const keyPathMetadata = Reflect.getMetadata('keypath', cls);
|
|
562
859
|
if (!keyPathMetadata)
|
|
563
860
|
return;
|
|
564
861
|
const fields = keyPathMetadata.fields;
|
|
@@ -571,14 +868,17 @@ class Database {
|
|
|
571
868
|
if (!self.db)
|
|
572
869
|
throw new Error('Database not initialized.');
|
|
573
870
|
const storeName = cls.name.toLowerCase();
|
|
574
|
-
return new QueryBuilder(self.db, storeName);
|
|
871
|
+
return new QueryBuilder(self.db, storeName, transaction);
|
|
575
872
|
},
|
|
576
873
|
create: (item) => __awaiter(this, void 0, void 0, function* () {
|
|
577
874
|
var _a;
|
|
578
|
-
const keyPathMetadata = Reflect.getMetadata(
|
|
579
|
-
if (((_a = keyPathMetadata === null || keyPathMetadata === void 0 ? void 0 : keyPathMetadata.options) === null || _a === void 0 ? void 0 : _a.generator) &&
|
|
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) {
|
|
580
878
|
const currentKey = extractKey(item);
|
|
581
|
-
if (currentKey === undefined ||
|
|
879
|
+
if (currentKey === undefined ||
|
|
880
|
+
currentKey === null ||
|
|
881
|
+
currentKey === '') {
|
|
582
882
|
const generatedKey = generateKey(item);
|
|
583
883
|
if (generatedKey !== undefined) {
|
|
584
884
|
setKey(item, generatedKey);
|
|
@@ -591,10 +891,10 @@ class Database {
|
|
|
591
891
|
return createStoredItem(store, item).then(() => {
|
|
592
892
|
console.debug(`Item added to ${cls.name}:`, item);
|
|
593
893
|
});
|
|
594
|
-
});
|
|
894
|
+
}, transaction);
|
|
595
895
|
}),
|
|
596
896
|
createMany: (items) => __awaiter(this, void 0, void 0, function* () {
|
|
597
|
-
const repository = this.createEntityRepository(cls);
|
|
897
|
+
const repository = this.createEntityRepository(cls, transaction);
|
|
598
898
|
for (const item of items) {
|
|
599
899
|
yield repository.create(item);
|
|
600
900
|
}
|
|
@@ -609,7 +909,7 @@ class Database {
|
|
|
609
909
|
};
|
|
610
910
|
request.onerror = () => reject(request.error);
|
|
611
911
|
});
|
|
612
|
-
});
|
|
912
|
+
}, transaction);
|
|
613
913
|
}),
|
|
614
914
|
update: (item) => __awaiter(this, void 0, void 0, function* () {
|
|
615
915
|
validateItem(item);
|
|
@@ -617,10 +917,10 @@ class Database {
|
|
|
617
917
|
return updateStoredItem(store, item).then(() => {
|
|
618
918
|
console.debug(`Item updated in ${cls.name}:`, item);
|
|
619
919
|
});
|
|
620
|
-
});
|
|
920
|
+
}, transaction);
|
|
621
921
|
}),
|
|
622
922
|
updateMany: (items) => __awaiter(this, void 0, void 0, function* () {
|
|
623
|
-
const repository = this.createEntityRepository(cls);
|
|
923
|
+
const repository = this.createEntityRepository(cls, transaction);
|
|
624
924
|
for (const item of items) {
|
|
625
925
|
yield repository.update(item);
|
|
626
926
|
}
|
|
@@ -630,19 +930,19 @@ class Database {
|
|
|
630
930
|
return deleteStoredItem(store, key).then(() => {
|
|
631
931
|
console.debug(`Item deleted from ${cls.name}:`, key);
|
|
632
932
|
});
|
|
633
|
-
});
|
|
933
|
+
}, transaction);
|
|
634
934
|
}),
|
|
635
935
|
deleteMany: (keys) => __awaiter(this, void 0, void 0, function* () {
|
|
636
|
-
const repository = this.createEntityRepository(cls);
|
|
936
|
+
const repository = this.createEntityRepository(cls, transaction);
|
|
637
937
|
for (const key of keys) {
|
|
638
938
|
yield repository.delete(key);
|
|
639
939
|
}
|
|
640
940
|
}),
|
|
641
941
|
deleteWhere: (predicate) => __awaiter(this, void 0, void 0, function* () {
|
|
642
|
-
var
|
|
643
|
-
const repository = this.createEntityRepository(cls);
|
|
942
|
+
var _a;
|
|
943
|
+
const repository = this.createEntityRepository(cls, transaction);
|
|
644
944
|
const query = repository.query();
|
|
645
|
-
const resolvedQuery = (
|
|
945
|
+
const resolvedQuery = (_a = predicate(query)) !== null && _a !== void 0 ? _a : query;
|
|
646
946
|
const matches = yield resolvedQuery.execute();
|
|
647
947
|
const keys = matches
|
|
648
948
|
.map((item) => extractKey(item))
|
|
@@ -659,7 +959,7 @@ class Database {
|
|
|
659
959
|
};
|
|
660
960
|
request.onerror = () => reject(request.error);
|
|
661
961
|
});
|
|
662
|
-
});
|
|
962
|
+
}, transaction);
|
|
663
963
|
}),
|
|
664
964
|
listPaginated: (page, pageSize) => __awaiter(this, void 0, void 0, function* () {
|
|
665
965
|
return this.performOperation(cls.name, 'readonly', (store) => {
|
|
@@ -673,7 +973,7 @@ class Database {
|
|
|
673
973
|
};
|
|
674
974
|
request.onerror = () => reject(request.error);
|
|
675
975
|
});
|
|
676
|
-
});
|
|
976
|
+
}, transaction);
|
|
677
977
|
}),
|
|
678
978
|
findByIndex: (indexName, value) => __awaiter(this, void 0, void 0, function* () {
|
|
679
979
|
return this.performOperation(cls.name, 'readonly', (store) => {
|
|
@@ -689,7 +989,7 @@ class Database {
|
|
|
689
989
|
};
|
|
690
990
|
request.onerror = () => reject(request.error);
|
|
691
991
|
});
|
|
692
|
-
});
|
|
992
|
+
}, transaction);
|
|
693
993
|
}),
|
|
694
994
|
findOneByIndex: (indexName, value) => __awaiter(this, void 0, void 0, function* () {
|
|
695
995
|
return this.performOperation(cls.name, 'readonly', (store) => {
|
|
@@ -705,7 +1005,7 @@ class Database {
|
|
|
705
1005
|
};
|
|
706
1006
|
request.onerror = () => reject(request.error);
|
|
707
1007
|
});
|
|
708
|
-
});
|
|
1008
|
+
}, transaction);
|
|
709
1009
|
}),
|
|
710
1010
|
count: () => __awaiter(this, void 0, void 0, function* () {
|
|
711
1011
|
return this.performOperation(cls.name, 'readonly', (store) => {
|
|
@@ -717,7 +1017,7 @@ class Database {
|
|
|
717
1017
|
};
|
|
718
1018
|
request.onerror = () => reject(request.error);
|
|
719
1019
|
});
|
|
720
|
-
});
|
|
1020
|
+
}, transaction);
|
|
721
1021
|
}),
|
|
722
1022
|
exists: (key) => __awaiter(this, void 0, void 0, function* () {
|
|
723
1023
|
return this.performOperation(cls.name, 'readonly', (store) => {
|
|
@@ -730,7 +1030,7 @@ class Database {
|
|
|
730
1030
|
};
|
|
731
1031
|
request.onerror = () => reject(request.error);
|
|
732
1032
|
});
|
|
733
|
-
});
|
|
1033
|
+
}, transaction);
|
|
734
1034
|
}),
|
|
735
1035
|
clear: () => __awaiter(this, void 0, void 0, function* () {
|
|
736
1036
|
return this.performOperation(cls.name, 'readwrite', (store) => {
|
|
@@ -742,21 +1042,97 @@ class Database {
|
|
|
742
1042
|
};
|
|
743
1043
|
request.onerror = () => reject(request.error);
|
|
744
1044
|
});
|
|
745
|
-
});
|
|
746
|
-
})
|
|
1045
|
+
}, transaction);
|
|
1046
|
+
}),
|
|
747
1047
|
};
|
|
748
1048
|
}
|
|
749
|
-
performOperation(className, mode, operation) {
|
|
1049
|
+
performOperation(className, mode, operation, transaction) {
|
|
750
1050
|
return __awaiter(this, void 0, void 0, function* () {
|
|
751
|
-
if (!this.db) {
|
|
752
|
-
throw new Error(
|
|
1051
|
+
if (!this.db && !transaction) {
|
|
1052
|
+
throw new Error('Database not initialized.');
|
|
753
1053
|
}
|
|
754
1054
|
const storeName = className.toLowerCase();
|
|
755
|
-
const
|
|
756
|
-
const store =
|
|
1055
|
+
const activeTransaction = transaction !== null && transaction !== void 0 ? transaction : this.db.transaction(storeName, mode);
|
|
1056
|
+
const store = activeTransaction.objectStore(storeName);
|
|
757
1057
|
return operation(store);
|
|
758
1058
|
});
|
|
759
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
|
+
}
|
|
760
1136
|
close() {
|
|
761
1137
|
if (this.retentionTimer) {
|
|
762
1138
|
clearInterval(this.retentionTimer);
|
|
@@ -775,15 +1151,15 @@ class Database {
|
|
|
775
1151
|
}
|
|
776
1152
|
getEntityVersions() {
|
|
777
1153
|
const versions = new Map();
|
|
778
|
-
this.classes.forEach(cls => {
|
|
779
|
-
const version = Reflect.getMetadata(
|
|
1154
|
+
this.classes.forEach((cls) => {
|
|
1155
|
+
const version = Reflect.getMetadata('version', cls) || 1;
|
|
780
1156
|
versions.set(cls.name, version);
|
|
781
1157
|
});
|
|
782
1158
|
return versions;
|
|
783
1159
|
}
|
|
784
1160
|
getEntityVersion(entityName) {
|
|
785
|
-
const cls = this.classes.find(c => c.name === entityName);
|
|
786
|
-
return cls ?
|
|
1161
|
+
const cls = this.classes.find((c) => c.name === entityName);
|
|
1162
|
+
return cls ? Reflect.getMetadata('version', cls) || 1 : undefined;
|
|
787
1163
|
}
|
|
788
1164
|
}
|
|
789
1165
|
exports.Database = Database;
|