1 | const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
|
2 |
|
3 | let idbProxyableTypes;
|
4 | let cursorAdvanceMethods;
|
5 |
|
6 | function getIdbProxyableTypes() {
|
7 | return (idbProxyableTypes ||
|
8 | (idbProxyableTypes = [
|
9 | IDBDatabase,
|
10 | IDBObjectStore,
|
11 | IDBIndex,
|
12 | IDBCursor,
|
13 | IDBTransaction,
|
14 | ]));
|
15 | }
|
16 |
|
17 | function getCursorAdvanceMethods() {
|
18 | return (cursorAdvanceMethods ||
|
19 | (cursorAdvanceMethods = [
|
20 | IDBCursor.prototype.advance,
|
21 | IDBCursor.prototype.continue,
|
22 | IDBCursor.prototype.continuePrimaryKey,
|
23 | ]));
|
24 | }
|
25 | const transactionDoneMap = new WeakMap();
|
26 | const transformCache = new WeakMap();
|
27 | const reverseTransformCache = new WeakMap();
|
28 | function promisifyRequest(request) {
|
29 | const promise = new Promise((resolve, reject) => {
|
30 | const unlisten = () => {
|
31 | request.removeEventListener('success', success);
|
32 | request.removeEventListener('error', error);
|
33 | };
|
34 | const success = () => {
|
35 | resolve(wrap(request.result));
|
36 | unlisten();
|
37 | };
|
38 | const error = () => {
|
39 | reject(request.error);
|
40 | unlisten();
|
41 | };
|
42 | request.addEventListener('success', success);
|
43 | request.addEventListener('error', error);
|
44 | });
|
45 |
|
46 |
|
47 | reverseTransformCache.set(promise, request);
|
48 | return promise;
|
49 | }
|
50 | function cacheDonePromiseForTransaction(tx) {
|
51 |
|
52 | if (transactionDoneMap.has(tx))
|
53 | return;
|
54 | const done = new Promise((resolve, reject) => {
|
55 | const unlisten = () => {
|
56 | tx.removeEventListener('complete', complete);
|
57 | tx.removeEventListener('error', error);
|
58 | tx.removeEventListener('abort', error);
|
59 | };
|
60 | const complete = () => {
|
61 | resolve();
|
62 | unlisten();
|
63 | };
|
64 | const error = () => {
|
65 | reject(tx.error || new DOMException('AbortError', 'AbortError'));
|
66 | unlisten();
|
67 | };
|
68 | tx.addEventListener('complete', complete);
|
69 | tx.addEventListener('error', error);
|
70 | tx.addEventListener('abort', error);
|
71 | });
|
72 |
|
73 | transactionDoneMap.set(tx, done);
|
74 | }
|
75 | let idbProxyTraps = {
|
76 | get(target, prop, receiver) {
|
77 | if (target instanceof IDBTransaction) {
|
78 |
|
79 | if (prop === 'done')
|
80 | return transactionDoneMap.get(target);
|
81 |
|
82 | if (prop === 'store') {
|
83 | return receiver.objectStoreNames[1]
|
84 | ? undefined
|
85 | : receiver.objectStore(receiver.objectStoreNames[0]);
|
86 | }
|
87 | }
|
88 |
|
89 | return wrap(target[prop]);
|
90 | },
|
91 | set(target, prop, value) {
|
92 | target[prop] = value;
|
93 | return true;
|
94 | },
|
95 | has(target, prop) {
|
96 | if (target instanceof IDBTransaction &&
|
97 | (prop === 'done' || prop === 'store')) {
|
98 | return true;
|
99 | }
|
100 | return prop in target;
|
101 | },
|
102 | };
|
103 | function replaceTraps(callback) {
|
104 | idbProxyTraps = callback(idbProxyTraps);
|
105 | }
|
106 | function wrapFunction(func) {
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | if (getCursorAdvanceMethods().includes(func)) {
|
115 | return function (...args) {
|
116 |
|
117 |
|
118 | func.apply(unwrap(this), args);
|
119 | return wrap(this.request);
|
120 | };
|
121 | }
|
122 | return function (...args) {
|
123 |
|
124 |
|
125 | return wrap(func.apply(unwrap(this), args));
|
126 | };
|
127 | }
|
128 | function transformCachableValue(value) {
|
129 | if (typeof value === 'function')
|
130 | return wrapFunction(value);
|
131 |
|
132 |
|
133 | if (value instanceof IDBTransaction)
|
134 | cacheDonePromiseForTransaction(value);
|
135 | if (instanceOfAny(value, getIdbProxyableTypes()))
|
136 | return new Proxy(value, idbProxyTraps);
|
137 |
|
138 | return value;
|
139 | }
|
140 | function wrap(value) {
|
141 |
|
142 |
|
143 | if (value instanceof IDBRequest)
|
144 | return promisifyRequest(value);
|
145 |
|
146 |
|
147 | if (transformCache.has(value))
|
148 | return transformCache.get(value);
|
149 | const newValue = transformCachableValue(value);
|
150 |
|
151 |
|
152 | if (newValue !== value) {
|
153 | transformCache.set(value, newValue);
|
154 | reverseTransformCache.set(newValue, value);
|
155 | }
|
156 | return newValue;
|
157 | }
|
158 | const unwrap = (value) => reverseTransformCache.get(value);
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
|
168 | const request = indexedDB.open(name, version);
|
169 | const openPromise = wrap(request);
|
170 | if (upgrade) {
|
171 | request.addEventListener('upgradeneeded', (event) => {
|
172 | upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
|
173 | });
|
174 | }
|
175 | if (blocked) {
|
176 | request.addEventListener('blocked', (event) => blocked(
|
177 |
|
178 | event.oldVersion, event.newVersion, event));
|
179 | }
|
180 | openPromise
|
181 | .then((db) => {
|
182 | if (terminated)
|
183 | db.addEventListener('close', () => terminated());
|
184 | if (blocking) {
|
185 | db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
|
186 | }
|
187 | })
|
188 | .catch(() => { });
|
189 | return openPromise;
|
190 | }
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 | function deleteDB(name, { blocked } = {}) {
|
197 | const request = indexedDB.deleteDatabase(name);
|
198 | if (blocked) {
|
199 | request.addEventListener('blocked', (event) => blocked(
|
200 |
|
201 | event.oldVersion, event));
|
202 | }
|
203 | return wrap(request).then(() => undefined);
|
204 | }
|
205 |
|
206 | const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
|
207 | const writeMethods = ['put', 'add', 'delete', 'clear'];
|
208 | const cachedMethods = new Map();
|
209 | function getMethod(target, prop) {
|
210 | if (!(target instanceof IDBDatabase &&
|
211 | !(prop in target) &&
|
212 | typeof prop === 'string')) {
|
213 | return;
|
214 | }
|
215 | if (cachedMethods.get(prop))
|
216 | return cachedMethods.get(prop);
|
217 | const targetFuncName = prop.replace(/FromIndex$/, '');
|
218 | const useIndex = prop !== targetFuncName;
|
219 | const isWrite = writeMethods.includes(targetFuncName);
|
220 | if (
|
221 |
|
222 | !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
|
223 | !(isWrite || readMethods.includes(targetFuncName))) {
|
224 | return;
|
225 | }
|
226 | const method = async function (storeName, ...args) {
|
227 |
|
228 | const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
|
229 | let target = tx.store;
|
230 | if (useIndex)
|
231 | target = target.index(args.shift());
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | return (await Promise.all([
|
238 | target[targetFuncName](...args),
|
239 | isWrite && tx.done,
|
240 | ]))[0];
|
241 | };
|
242 | cachedMethods.set(prop, method);
|
243 | return method;
|
244 | }
|
245 | replaceTraps((oldTraps) => ({
|
246 | ...oldTraps,
|
247 | get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
|
248 | has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
|
249 | }));
|
250 |
|
251 | const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
|
252 | const methodMap = {};
|
253 | const advanceResults = new WeakMap();
|
254 | const ittrProxiedCursorToOriginalProxy = new WeakMap();
|
255 | const cursorIteratorTraps = {
|
256 | get(target, prop) {
|
257 | if (!advanceMethodProps.includes(prop))
|
258 | return target[prop];
|
259 | let cachedFunc = methodMap[prop];
|
260 | if (!cachedFunc) {
|
261 | cachedFunc = methodMap[prop] = function (...args) {
|
262 | advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
|
263 | };
|
264 | }
|
265 | return cachedFunc;
|
266 | },
|
267 | };
|
268 | async function* iterate(...args) {
|
269 |
|
270 | let cursor = this;
|
271 | if (!(cursor instanceof IDBCursor)) {
|
272 | cursor = await cursor.openCursor(...args);
|
273 | }
|
274 | if (!cursor)
|
275 | return;
|
276 | cursor = cursor;
|
277 | const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
|
278 | ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
|
279 |
|
280 | reverseTransformCache.set(proxiedCursor, unwrap(cursor));
|
281 | while (cursor) {
|
282 | yield proxiedCursor;
|
283 |
|
284 | cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
|
285 | advanceResults.delete(proxiedCursor);
|
286 | }
|
287 | }
|
288 | function isIteratorProp(target, prop) {
|
289 | return ((prop === Symbol.asyncIterator &&
|
290 | instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
|
291 | (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
|
292 | }
|
293 | replaceTraps((oldTraps) => ({
|
294 | ...oldTraps,
|
295 | get(target, prop, receiver) {
|
296 | if (isIteratorProp(target, prop))
|
297 | return iterate;
|
298 | return oldTraps.get(target, prop, receiver);
|
299 | },
|
300 | has(target, prop) {
|
301 | return isIteratorProp(target, prop) || oldTraps.has(target, prop);
|
302 | },
|
303 | }));
|
304 |
|
305 | export { deleteDB, openDB, unwrap, wrap };
|