asljs-dali 0.1.1 → 0.1.2
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 +7 -1
- package/dist/delete-strategy-uuid-soft-delete-strategy.d.ts +18 -0
- package/dist/delete-strategy-uuid-soft-delete-strategy.js +23 -0
- package/dist/delete-strategy.d.ts +8 -0
- package/dist/delete-strategy.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/table.d.ts +5 -1
- package/dist/table.js +104 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,8 @@ const db =
|
|
|
43
43
|
const notes =
|
|
44
44
|
new Table<Note>(
|
|
45
45
|
'notes',
|
|
46
|
-
db
|
|
46
|
+
db,
|
|
47
|
+
{ /* options */ });
|
|
47
48
|
|
|
48
49
|
await notes.add(
|
|
49
50
|
{ id: '1',
|
|
@@ -69,6 +70,11 @@ Versioning:
|
|
|
69
70
|
- `IncrementTableVersionStrategy<T>`
|
|
70
71
|
- `UuidTableVersionStrategy<T>`
|
|
71
72
|
|
|
73
|
+
Delete strategies:
|
|
74
|
+
|
|
75
|
+
- `TableDeleteStrategy<T>`
|
|
76
|
+
- `UuidSoftDeleteTableDeleteStrategy<T>`
|
|
77
|
+
|
|
72
78
|
Transactions:
|
|
73
79
|
|
|
74
80
|
- `TxMode`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type DeleteStrategy } from './delete-strategy.js';
|
|
2
|
+
type IndexQueryMapping = {
|
|
3
|
+
index: string;
|
|
4
|
+
key: IDBValidKey;
|
|
5
|
+
};
|
|
6
|
+
type IndexQueryMapper = (index: string, key: IDBValidKey) => IndexQueryMapping | null;
|
|
7
|
+
export declare class UuidSoftDeleteStrategy<T extends Record<string, any>> implements DeleteStrategy<T> {
|
|
8
|
+
private readonly deletedField;
|
|
9
|
+
private readonly mapActiveIndexQuery?;
|
|
10
|
+
constructor(deletedField: keyof T & string, mapActiveIndexQuery?: IndexQueryMapper | undefined);
|
|
11
|
+
isDeleted(record: T): boolean;
|
|
12
|
+
delete(record: T): T;
|
|
13
|
+
mapIndexQuery(index: string, key: IDBValidKey): {
|
|
14
|
+
index: string;
|
|
15
|
+
key: IDBValidKey;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class UuidSoftDeleteStrategy {
|
|
2
|
+
constructor(deletedField, mapActiveIndexQuery) {
|
|
3
|
+
this.deletedField = deletedField;
|
|
4
|
+
this.mapActiveIndexQuery = mapActiveIndexQuery;
|
|
5
|
+
}
|
|
6
|
+
isDeleted(record) {
|
|
7
|
+
const marker = record[this.deletedField];
|
|
8
|
+
return typeof marker === 'string'
|
|
9
|
+
&& marker.length > 0;
|
|
10
|
+
}
|
|
11
|
+
delete(record) {
|
|
12
|
+
if (this.isDeleted(record))
|
|
13
|
+
return record;
|
|
14
|
+
return {
|
|
15
|
+
...record,
|
|
16
|
+
[this.deletedField]: crypto.randomUUID()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
mapIndexQuery(index, key) {
|
|
20
|
+
return this.mapActiveIndexQuery?.(index, key)
|
|
21
|
+
?? null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { dbDelete, dbOpen, dbRequestAsync, } from './db.js';
|
|
2
2
|
export { IncrementVersionStrategy as IncrementTableVersionStrategy, } from './version-strategy-increment.js';
|
|
3
|
+
export { type DeleteStrategy as TableDeleteStrategy, } from './delete-strategy.js';
|
|
3
4
|
export { Table, type TableEvents, type TableEventsReceiver, } from './table.js';
|
|
4
5
|
export { VersionConflictError as TableVersionConflictError, } from './version-conflict-error.js';
|
|
5
6
|
export { type VersionStrategy as TableVersionStrategy, } from './version-strategy.js';
|
|
6
7
|
export { txDone, txRead, txReuseOrCreate, txWrite, TxMode, txEnsure, } from './transactions.js';
|
|
8
|
+
export { UuidSoftDeleteStrategy as UuidSoftDeleteTableDeleteStrategy, } from './delete-strategy-uuid-soft-delete-strategy.js';
|
|
7
9
|
export { UuidVersionStrategy as UuidTableVersionStrategy, } from './version-strategy-uuid.js';
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export { IncrementVersionStrategy as IncrementTableVersionStrategy, } from './ve
|
|
|
3
3
|
export { Table, } from './table.js';
|
|
4
4
|
export { VersionConflictError as TableVersionConflictError, } from './version-conflict-error.js';
|
|
5
5
|
export { txDone, txRead, txReuseOrCreate, txWrite, TxMode, txEnsure, } from './transactions.js';
|
|
6
|
+
export { UuidSoftDeleteStrategy as UuidSoftDeleteTableDeleteStrategy, } from './delete-strategy-uuid-soft-delete-strategy.js';
|
|
6
7
|
export { UuidVersionStrategy as UuidTableVersionStrategy, } from './version-strategy-uuid.js';
|
package/dist/table.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventfulBase } from 'asljs-eventful';
|
|
2
2
|
import { type KeyPath } from './keys.js';
|
|
3
|
+
import { type DeleteStrategy } from './delete-strategy.js';
|
|
3
4
|
import { type VersionStrategy } from './version-strategy.js';
|
|
4
5
|
export type TableEvents<T extends Record<string, any>> = {
|
|
5
6
|
add: [record: T];
|
|
@@ -15,7 +16,10 @@ export declare class Table<T extends Record<string, any>> extends EventfulBase<T
|
|
|
15
16
|
readonly storeName: string;
|
|
16
17
|
readonly db: IDBDatabase;
|
|
17
18
|
readonly key: KeyPath<T>;
|
|
18
|
-
constructor(storeName: string, db: IDBDatabase,
|
|
19
|
+
constructor(storeName: string, db: IDBDatabase, options?: {
|
|
20
|
+
versionStrategy?: VersionStrategy<T>;
|
|
21
|
+
deleteStrategy?: DeleteStrategy<T>;
|
|
22
|
+
});
|
|
19
23
|
getOne(key: IDBValidKey, tx?: IDBTransaction | null): Promise<T | null>;
|
|
20
24
|
get(index: string, key: IDBValidKey, tx?: IDBTransaction | null): Promise<T[]>;
|
|
21
25
|
notify(receiver: TableEventsReceiver<T>): () => boolean;
|
package/dist/table.js
CHANGED
|
@@ -9,21 +9,23 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _Table_instances, _Table_receivers,
|
|
12
|
+
var _Table_instances, _Table_receivers, _Table_versionStrategy, _Table_deleteStrategy, _Table_activeRecords, _Table_getActiveByIndex, _Table_getByIndex, _Table_isDeleted, _Table_canFallbackMappedQuery, _Table_onTransactionCompleted, _Table_notify;
|
|
13
13
|
import { EventfulBase } from 'asljs-eventful';
|
|
14
14
|
import { dbRequestAsync } from './db.js';
|
|
15
15
|
import { keyAssert, keyGet, keyPathAssert, } from './keys.js';
|
|
16
16
|
import { VersionConflictError } from './version-conflict-error.js';
|
|
17
17
|
import { txDone, txRead, txWrite, } from './transactions.js';
|
|
18
18
|
export class Table extends EventfulBase {
|
|
19
|
-
constructor(storeName, db,
|
|
19
|
+
constructor(storeName, db, options) {
|
|
20
20
|
super();
|
|
21
21
|
_Table_instances.add(this);
|
|
22
22
|
this.storeName = storeName;
|
|
23
23
|
this.db = db;
|
|
24
24
|
_Table_receivers.set(this, []);
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
_Table_versionStrategy.set(this, void 0);
|
|
26
|
+
_Table_deleteStrategy.set(this, void 0);
|
|
27
|
+
__classPrivateFieldSet(this, _Table_versionStrategy, options?.versionStrategy, "f");
|
|
28
|
+
__classPrivateFieldSet(this, _Table_deleteStrategy, options?.deleteStrategy, "f");
|
|
27
29
|
const key = txRead(this.db, this.storeName)
|
|
28
30
|
.objectStore(this.storeName)
|
|
29
31
|
.keyPath;
|
|
@@ -43,6 +45,10 @@ export class Table extends EventfulBase {
|
|
|
43
45
|
resolve(null);
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
48
|
+
if (__classPrivateFieldGet(this, _Table_instances, "m", _Table_isDeleted).call(this, fields)) {
|
|
49
|
+
resolve(null);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
46
52
|
resolve(fields);
|
|
47
53
|
};
|
|
48
54
|
request.onerror =
|
|
@@ -53,25 +59,9 @@ export class Table extends EventfulBase {
|
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
get(index, key, tx = null) {
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
const keyPath = idx.keyPath;
|
|
60
|
-
keyPathAssert(keyPath);
|
|
61
|
-
keyAssert(keyPath, key);
|
|
62
|
-
return new Promise((resolve, reject) => {
|
|
63
|
-
const request = idx.getAll(key);
|
|
64
|
-
request.onsuccess =
|
|
65
|
-
() => {
|
|
66
|
-
const records = request.result;
|
|
67
|
-
resolve(records);
|
|
68
|
-
};
|
|
69
|
-
request.onerror =
|
|
70
|
-
() => {
|
|
71
|
-
reject(request.error
|
|
72
|
-
?? new Error(`${this.storeName}: get request failed for index ${index}`));
|
|
73
|
-
};
|
|
74
|
-
});
|
|
62
|
+
if (__classPrivateFieldGet(this, _Table_deleteStrategy, "f") === undefined)
|
|
63
|
+
return __classPrivateFieldGet(this, _Table_instances, "m", _Table_getByIndex).call(this, index, key, tx);
|
|
64
|
+
return __classPrivateFieldGet(this, _Table_instances, "m", _Table_getActiveByIndex).call(this, index, key, tx);
|
|
75
65
|
}
|
|
76
66
|
notify(receiver) {
|
|
77
67
|
__classPrivateFieldGet(this, _Table_receivers, "f").push(receiver);
|
|
@@ -87,7 +77,7 @@ export class Table extends EventfulBase {
|
|
|
87
77
|
const records = await dbRequestAsync(txRead(this.db, this.storeName, tx)
|
|
88
78
|
.objectStore(this.storeName)
|
|
89
79
|
.getAll());
|
|
90
|
-
return records;
|
|
80
|
+
return __classPrivateFieldGet(this, _Table_instances, "m", _Table_activeRecords).call(this, records);
|
|
91
81
|
}
|
|
92
82
|
scan(predicate, tx = null) {
|
|
93
83
|
return new Promise((resolve, reject) => {
|
|
@@ -103,6 +93,10 @@ export class Table extends EventfulBase {
|
|
|
103
93
|
return;
|
|
104
94
|
}
|
|
105
95
|
const record = cursor.value;
|
|
96
|
+
if (__classPrivateFieldGet(this, _Table_instances, "m", _Table_isDeleted).call(this, record)) {
|
|
97
|
+
cursor.continue();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
106
100
|
try {
|
|
107
101
|
if (predicate(record)) {
|
|
108
102
|
result.push(record);
|
|
@@ -122,8 +116,8 @@ export class Table extends EventfulBase {
|
|
|
122
116
|
});
|
|
123
117
|
}
|
|
124
118
|
async add(record, tx = null) {
|
|
125
|
-
const storedRecord = __classPrivateFieldGet(this,
|
|
126
|
-
? __classPrivateFieldGet(this,
|
|
119
|
+
const storedRecord = __classPrivateFieldGet(this, _Table_versionStrategy, "f")
|
|
120
|
+
? __classPrivateFieldGet(this, _Table_versionStrategy, "f").initialise(record)
|
|
127
121
|
: record;
|
|
128
122
|
const ltx = txWrite(this.db, this.storeName, tx);
|
|
129
123
|
const store = ltx.objectStore(this.storeName);
|
|
@@ -145,14 +139,14 @@ export class Table extends EventfulBase {
|
|
|
145
139
|
throw new Error(`Record with key ${String(key)} not found.`);
|
|
146
140
|
}
|
|
147
141
|
let storedRecord = record;
|
|
148
|
-
if (__classPrivateFieldGet(this,
|
|
142
|
+
if (__classPrivateFieldGet(this, _Table_versionStrategy, "f")) {
|
|
149
143
|
if (expectedVersion === undefined) {
|
|
150
144
|
throw new Error(`${this.storeName}: expectedVersion is required when a version strategy is configured.`);
|
|
151
145
|
}
|
|
152
|
-
if (!__classPrivateFieldGet(this,
|
|
153
|
-
throw new VersionConflictError(key, expectedVersion, __classPrivateFieldGet(this,
|
|
146
|
+
if (!__classPrivateFieldGet(this, _Table_versionStrategy, "f").verify(existing, expectedVersion)) {
|
|
147
|
+
throw new VersionConflictError(key, expectedVersion, __classPrivateFieldGet(this, _Table_versionStrategy, "f").getVersion(existing));
|
|
154
148
|
}
|
|
155
|
-
storedRecord = __classPrivateFieldGet(this,
|
|
149
|
+
storedRecord = __classPrivateFieldGet(this, _Table_versionStrategy, "f").update(record);
|
|
156
150
|
}
|
|
157
151
|
await dbRequestAsync(store.put(storedRecord));
|
|
158
152
|
__classPrivateFieldGet(this, _Table_instances, "m", _Table_onTransactionCompleted).call(this, store.transaction, () => {
|
|
@@ -171,18 +165,41 @@ export class Table extends EventfulBase {
|
|
|
171
165
|
const existing = await dbRequestAsync(store.get(key));
|
|
172
166
|
if (existing === undefined)
|
|
173
167
|
return;
|
|
174
|
-
if (__classPrivateFieldGet(this,
|
|
168
|
+
if (__classPrivateFieldGet(this, _Table_deleteStrategy, "f") === undefined) {
|
|
169
|
+
if (__classPrivateFieldGet(this, _Table_versionStrategy, "f")) {
|
|
170
|
+
if (expectedVersion === undefined) {
|
|
171
|
+
throw new Error(`${this.storeName}: expectedVersion is required when a version strategy is configured.`);
|
|
172
|
+
}
|
|
173
|
+
if (!__classPrivateFieldGet(this, _Table_versionStrategy, "f").verify(existing, expectedVersion)) {
|
|
174
|
+
throw new VersionConflictError(key, expectedVersion, __classPrivateFieldGet(this, _Table_versionStrategy, "f").getVersion(existing));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
await dbRequestAsync(store.delete(key));
|
|
178
|
+
__classPrivateFieldGet(this, _Table_instances, "m", _Table_onTransactionCompleted).call(this, store.transaction, () => {
|
|
179
|
+
this.emit('delete', existing);
|
|
180
|
+
__classPrivateFieldGet(this, _Table_instances, "m", _Table_notify).call(this, 'delete', [existing]);
|
|
181
|
+
});
|
|
182
|
+
if (tx === null)
|
|
183
|
+
await txDone(ltx);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (__classPrivateFieldGet(this, _Table_deleteStrategy, "f").isDeleted(existing))
|
|
187
|
+
return;
|
|
188
|
+
if (__classPrivateFieldGet(this, _Table_versionStrategy, "f")) {
|
|
175
189
|
if (expectedVersion === undefined) {
|
|
176
190
|
throw new Error(`${this.storeName}: expectedVersion is required when a version strategy is configured.`);
|
|
177
191
|
}
|
|
178
|
-
if (!__classPrivateFieldGet(this,
|
|
179
|
-
throw new VersionConflictError(key, expectedVersion, __classPrivateFieldGet(this,
|
|
192
|
+
if (!__classPrivateFieldGet(this, _Table_versionStrategy, "f").verify(existing, expectedVersion)) {
|
|
193
|
+
throw new VersionConflictError(key, expectedVersion, __classPrivateFieldGet(this, _Table_versionStrategy, "f").getVersion(existing));
|
|
180
194
|
}
|
|
181
195
|
}
|
|
182
|
-
|
|
196
|
+
let storedRecord = __classPrivateFieldGet(this, _Table_deleteStrategy, "f").delete(existing);
|
|
197
|
+
if (__classPrivateFieldGet(this, _Table_versionStrategy, "f"))
|
|
198
|
+
storedRecord = __classPrivateFieldGet(this, _Table_versionStrategy, "f").update(storedRecord);
|
|
199
|
+
await dbRequestAsync(store.put(storedRecord));
|
|
183
200
|
__classPrivateFieldGet(this, _Table_instances, "m", _Table_onTransactionCompleted).call(this, store.transaction, () => {
|
|
184
|
-
this.emit('delete',
|
|
185
|
-
__classPrivateFieldGet(this, _Table_instances, "m", _Table_notify).call(this, 'delete', [
|
|
201
|
+
this.emit('delete', storedRecord);
|
|
202
|
+
__classPrivateFieldGet(this, _Table_instances, "m", _Table_notify).call(this, 'delete', [storedRecord]);
|
|
186
203
|
});
|
|
187
204
|
if (tx === null)
|
|
188
205
|
await txDone(ltx);
|
|
@@ -200,7 +217,57 @@ export class Table extends EventfulBase {
|
|
|
200
217
|
await txDone(ltx);
|
|
201
218
|
}
|
|
202
219
|
}
|
|
203
|
-
_Table_receivers = new WeakMap(),
|
|
220
|
+
_Table_receivers = new WeakMap(), _Table_versionStrategy = new WeakMap(), _Table_deleteStrategy = new WeakMap(), _Table_instances = new WeakSet(), _Table_activeRecords = function _Table_activeRecords(records) {
|
|
221
|
+
const deleteStrategy = __classPrivateFieldGet(this, _Table_deleteStrategy, "f");
|
|
222
|
+
if (deleteStrategy === undefined)
|
|
223
|
+
return records;
|
|
224
|
+
return records.filter(record => !deleteStrategy.isDeleted(record));
|
|
225
|
+
}, _Table_getActiveByIndex = async function _Table_getActiveByIndex(index, key, tx) {
|
|
226
|
+
const mapped = __classPrivateFieldGet(this, _Table_deleteStrategy, "f")?.mapIndexQuery?.(index, key);
|
|
227
|
+
if (mapped) {
|
|
228
|
+
try {
|
|
229
|
+
const records = await __classPrivateFieldGet(this, _Table_instances, "m", _Table_getByIndex).call(this, mapped.index, mapped.key, tx, false);
|
|
230
|
+
return __classPrivateFieldGet(this, _Table_instances, "m", _Table_activeRecords).call(this, records);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
// Degrade to filtering when mapped query cannot be executed.
|
|
234
|
+
if (!__classPrivateFieldGet(this, _Table_instances, "m", _Table_canFallbackMappedQuery).call(this, error))
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const records = await __classPrivateFieldGet(this, _Table_instances, "m", _Table_getByIndex).call(this, index, key, tx);
|
|
239
|
+
return __classPrivateFieldGet(this, _Table_instances, "m", _Table_activeRecords).call(this, records);
|
|
240
|
+
}, _Table_getByIndex = function _Table_getByIndex(index, key, tx, validateKey = true) {
|
|
241
|
+
const store = txRead(this.db, this.storeName, tx)
|
|
242
|
+
.objectStore(this.storeName);
|
|
243
|
+
const idx = store.index(index);
|
|
244
|
+
const keyPath = idx.keyPath;
|
|
245
|
+
if (validateKey) {
|
|
246
|
+
keyPathAssert(keyPath);
|
|
247
|
+
keyAssert(keyPath, key);
|
|
248
|
+
}
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
const request = idx.getAll(key);
|
|
251
|
+
request.onsuccess =
|
|
252
|
+
() => {
|
|
253
|
+
const records = request.result;
|
|
254
|
+
resolve(records);
|
|
255
|
+
};
|
|
256
|
+
request.onerror =
|
|
257
|
+
() => {
|
|
258
|
+
reject(request.error
|
|
259
|
+
?? new Error(`${this.storeName}: get request failed for index ${index}`));
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
}, _Table_isDeleted = function _Table_isDeleted(record) {
|
|
263
|
+
return __classPrivateFieldGet(this, _Table_deleteStrategy, "f")?.isDeleted(record)
|
|
264
|
+
?? false;
|
|
265
|
+
}, _Table_canFallbackMappedQuery = function _Table_canFallbackMappedQuery(error) {
|
|
266
|
+
if (!(error instanceof DOMException))
|
|
267
|
+
return false;
|
|
268
|
+
return error.name === 'NotFoundError'
|
|
269
|
+
|| error.name === 'DataError';
|
|
270
|
+
}, _Table_onTransactionCompleted = function _Table_onTransactionCompleted(tx, action) {
|
|
204
271
|
tx.addEventListener('complete', () => {
|
|
205
272
|
try {
|
|
206
273
|
action();
|