123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
-
- let idbProxyableTypes;
- let cursorAdvanceMethods;
- // This is a function to prevent it throwing up in node environments.
- function getIdbProxyableTypes() {
- return (idbProxyableTypes ||
- (idbProxyableTypes = [
- IDBDatabase,
- IDBObjectStore,
- IDBIndex,
- IDBCursor,
- IDBTransaction,
- ]));
- }
- // This is a function to prevent it throwing up in node environments.
- function getCursorAdvanceMethods() {
- return (cursorAdvanceMethods ||
- (cursorAdvanceMethods = [
- IDBCursor.prototype.advance,
- IDBCursor.prototype.continue,
- IDBCursor.prototype.continuePrimaryKey,
- ]));
- }
- const cursorRequestMap = new WeakMap();
- const transactionDoneMap = new WeakMap();
- const transactionStoreNamesMap = new WeakMap();
- const transformCache = new WeakMap();
- const reverseTransformCache = new WeakMap();
- function promisifyRequest(request) {
- const promise = new Promise((resolve, reject) => {
- const unlisten = () => {
- request.removeEventListener('success', success);
- request.removeEventListener('error', error);
- };
- const success = () => {
- resolve(wrap(request.result));
- unlisten();
- };
- const error = () => {
- reject(request.error);
- unlisten();
- };
- request.addEventListener('success', success);
- request.addEventListener('error', error);
- });
- promise
- .then((value) => {
- // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
- // (see wrapFunction).
- if (value instanceof IDBCursor) {
- cursorRequestMap.set(value, request);
- }
- // Catching to avoid "Uncaught Promise exceptions"
- })
- .catch(() => { });
- // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
- // is because we create many promises from a single IDBRequest.
- reverseTransformCache.set(promise, request);
- return promise;
- }
- function cacheDonePromiseForTransaction(tx) {
- // Early bail if we've already created a done promise for this transaction.
- if (transactionDoneMap.has(tx))
- return;
- const done = new Promise((resolve, reject) => {
- const unlisten = () => {
- tx.removeEventListener('complete', complete);
- tx.removeEventListener('error', error);
- tx.removeEventListener('abort', error);
- };
- const complete = () => {
- resolve();
- unlisten();
- };
- const error = () => {
- reject(tx.error || new DOMException('AbortError', 'AbortError'));
- unlisten();
- };
- tx.addEventListener('complete', complete);
- tx.addEventListener('error', error);
- tx.addEventListener('abort', error);
- });
- // Cache it for later retrieval.
- transactionDoneMap.set(tx, done);
- }
- let idbProxyTraps = {
- get(target, prop, receiver) {
- if (target instanceof IDBTransaction) {
- // Special handling for transaction.done.
- if (prop === 'done')
- return transactionDoneMap.get(target);
- // Polyfill for objectStoreNames because of Edge.
- if (prop === 'objectStoreNames') {
- return target.objectStoreNames || transactionStoreNamesMap.get(target);
- }
- // Make tx.store return the only store in the transaction, or undefined if there are many.
- if (prop === 'store') {
- return receiver.objectStoreNames[1]
- ? undefined
- : receiver.objectStore(receiver.objectStoreNames[0]);
- }
- }
- // Else transform whatever we get back.
- return wrap(target[prop]);
- },
- set(target, prop, value) {
- target[prop] = value;
- return true;
- },
- has(target, prop) {
- if (target instanceof IDBTransaction &&
- (prop === 'done' || prop === 'store')) {
- return true;
- }
- return prop in target;
- },
- };
- function replaceTraps(callback) {
- idbProxyTraps = callback(idbProxyTraps);
- }
- function wrapFunction(func) {
- // Due to expected object equality (which is enforced by the caching in `wrap`), we
- // only create one new func per func.
- // Edge doesn't support objectStoreNames (booo), so we polyfill it here.
- if (func === IDBDatabase.prototype.transaction &&
- !('objectStoreNames' in IDBTransaction.prototype)) {
- return function (storeNames, ...args) {
- const tx = func.call(unwrap(this), storeNames, ...args);
- transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);
- return wrap(tx);
- };
- }
- // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
- // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
- // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
- // with real promises, so each advance methods returns a new promise for the cursor object, or
- // undefined if the end of the cursor has been reached.
- if (getCursorAdvanceMethods().includes(func)) {
- return function (...args) {
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
- // the original object.
- func.apply(unwrap(this), args);
- return wrap(cursorRequestMap.get(this));
- };
- }
- return function (...args) {
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
- // the original object.
- return wrap(func.apply(unwrap(this), args));
- };
- }
- function transformCachableValue(value) {
- if (typeof value === 'function')
- return wrapFunction(value);
- // This doesn't return, it just creates a 'done' promise for the transaction,
- // which is later returned for transaction.done (see idbObjectHandler).
- if (value instanceof IDBTransaction)
- cacheDonePromiseForTransaction(value);
- if (instanceOfAny(value, getIdbProxyableTypes()))
- return new Proxy(value, idbProxyTraps);
- // Return the same value back if we're not going to transform it.
- return value;
- }
- function wrap(value) {
- // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
- // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
- if (value instanceof IDBRequest)
- return promisifyRequest(value);
- // If we've already transformed this value before, reuse the transformed value.
- // This is faster, but it also provides object equality.
- if (transformCache.has(value))
- return transformCache.get(value);
- const newValue = transformCachableValue(value);
- // Not all types are transformed.
- // These may be primitive types, so they can't be WeakMap keys.
- if (newValue !== value) {
- transformCache.set(value, newValue);
- reverseTransformCache.set(newValue, value);
- }
- return newValue;
- }
- const unwrap = (value) => reverseTransformCache.get(value);
-
- export { reverseTransformCache as a, instanceOfAny as i, replaceTraps as r, unwrap as u, wrap as w };
|