Няма описание
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

wrap-idb-value.cjs 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. 'use strict';
  2. const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
  3. let idbProxyableTypes;
  4. let cursorAdvanceMethods;
  5. // This is a function to prevent it throwing up in node environments.
  6. function getIdbProxyableTypes() {
  7. return (idbProxyableTypes ||
  8. (idbProxyableTypes = [
  9. IDBDatabase,
  10. IDBObjectStore,
  11. IDBIndex,
  12. IDBCursor,
  13. IDBTransaction,
  14. ]));
  15. }
  16. // This is a function to prevent it throwing up in node environments.
  17. function getCursorAdvanceMethods() {
  18. return (cursorAdvanceMethods ||
  19. (cursorAdvanceMethods = [
  20. IDBCursor.prototype.advance,
  21. IDBCursor.prototype.continue,
  22. IDBCursor.prototype.continuePrimaryKey,
  23. ]));
  24. }
  25. const cursorRequestMap = new WeakMap();
  26. const transactionDoneMap = new WeakMap();
  27. const transactionStoreNamesMap = new WeakMap();
  28. const transformCache = new WeakMap();
  29. const reverseTransformCache = new WeakMap();
  30. function promisifyRequest(request) {
  31. const promise = new Promise((resolve, reject) => {
  32. const unlisten = () => {
  33. request.removeEventListener('success', success);
  34. request.removeEventListener('error', error);
  35. };
  36. const success = () => {
  37. resolve(wrap(request.result));
  38. unlisten();
  39. };
  40. const error = () => {
  41. reject(request.error);
  42. unlisten();
  43. };
  44. request.addEventListener('success', success);
  45. request.addEventListener('error', error);
  46. });
  47. promise
  48. .then((value) => {
  49. // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
  50. // (see wrapFunction).
  51. if (value instanceof IDBCursor) {
  52. cursorRequestMap.set(value, request);
  53. }
  54. // Catching to avoid "Uncaught Promise exceptions"
  55. })
  56. .catch(() => { });
  57. // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
  58. // is because we create many promises from a single IDBRequest.
  59. reverseTransformCache.set(promise, request);
  60. return promise;
  61. }
  62. function cacheDonePromiseForTransaction(tx) {
  63. // Early bail if we've already created a done promise for this transaction.
  64. if (transactionDoneMap.has(tx))
  65. return;
  66. const done = new Promise((resolve, reject) => {
  67. const unlisten = () => {
  68. tx.removeEventListener('complete', complete);
  69. tx.removeEventListener('error', error);
  70. tx.removeEventListener('abort', error);
  71. };
  72. const complete = () => {
  73. resolve();
  74. unlisten();
  75. };
  76. const error = () => {
  77. reject(tx.error || new DOMException('AbortError', 'AbortError'));
  78. unlisten();
  79. };
  80. tx.addEventListener('complete', complete);
  81. tx.addEventListener('error', error);
  82. tx.addEventListener('abort', error);
  83. });
  84. // Cache it for later retrieval.
  85. transactionDoneMap.set(tx, done);
  86. }
  87. let idbProxyTraps = {
  88. get(target, prop, receiver) {
  89. if (target instanceof IDBTransaction) {
  90. // Special handling for transaction.done.
  91. if (prop === 'done')
  92. return transactionDoneMap.get(target);
  93. // Polyfill for objectStoreNames because of Edge.
  94. if (prop === 'objectStoreNames') {
  95. return target.objectStoreNames || transactionStoreNamesMap.get(target);
  96. }
  97. // Make tx.store return the only store in the transaction, or undefined if there are many.
  98. if (prop === 'store') {
  99. return receiver.objectStoreNames[1]
  100. ? undefined
  101. : receiver.objectStore(receiver.objectStoreNames[0]);
  102. }
  103. }
  104. // Else transform whatever we get back.
  105. return wrap(target[prop]);
  106. },
  107. set(target, prop, value) {
  108. target[prop] = value;
  109. return true;
  110. },
  111. has(target, prop) {
  112. if (target instanceof IDBTransaction &&
  113. (prop === 'done' || prop === 'store')) {
  114. return true;
  115. }
  116. return prop in target;
  117. },
  118. };
  119. function replaceTraps(callback) {
  120. idbProxyTraps = callback(idbProxyTraps);
  121. }
  122. function wrapFunction(func) {
  123. // Due to expected object equality (which is enforced by the caching in `wrap`), we
  124. // only create one new func per func.
  125. // Edge doesn't support objectStoreNames (booo), so we polyfill it here.
  126. if (func === IDBDatabase.prototype.transaction &&
  127. !('objectStoreNames' in IDBTransaction.prototype)) {
  128. return function (storeNames, ...args) {
  129. const tx = func.call(unwrap(this), storeNames, ...args);
  130. transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);
  131. return wrap(tx);
  132. };
  133. }
  134. // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
  135. // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
  136. // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
  137. // with real promises, so each advance methods returns a new promise for the cursor object, or
  138. // undefined if the end of the cursor has been reached.
  139. if (getCursorAdvanceMethods().includes(func)) {
  140. return function (...args) {
  141. // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
  142. // the original object.
  143. func.apply(unwrap(this), args);
  144. return wrap(cursorRequestMap.get(this));
  145. };
  146. }
  147. return function (...args) {
  148. // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
  149. // the original object.
  150. return wrap(func.apply(unwrap(this), args));
  151. };
  152. }
  153. function transformCachableValue(value) {
  154. if (typeof value === 'function')
  155. return wrapFunction(value);
  156. // This doesn't return, it just creates a 'done' promise for the transaction,
  157. // which is later returned for transaction.done (see idbObjectHandler).
  158. if (value instanceof IDBTransaction)
  159. cacheDonePromiseForTransaction(value);
  160. if (instanceOfAny(value, getIdbProxyableTypes()))
  161. return new Proxy(value, idbProxyTraps);
  162. // Return the same value back if we're not going to transform it.
  163. return value;
  164. }
  165. function wrap(value) {
  166. // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
  167. // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
  168. if (value instanceof IDBRequest)
  169. return promisifyRequest(value);
  170. // If we've already transformed this value before, reuse the transformed value.
  171. // This is faster, but it also provides object equality.
  172. if (transformCache.has(value))
  173. return transformCache.get(value);
  174. const newValue = transformCachableValue(value);
  175. // Not all types are transformed.
  176. // These may be primitive types, so they can't be WeakMap keys.
  177. if (newValue !== value) {
  178. transformCache.set(value, newValue);
  179. reverseTransformCache.set(newValue, value);
  180. }
  181. return newValue;
  182. }
  183. const unwrap = (value) => reverseTransformCache.get(value);
  184. exports.instanceOfAny = instanceOfAny;
  185. exports.replaceTraps = replaceTraps;
  186. exports.reverseTransformCache = reverseTransformCache;
  187. exports.unwrap = unwrap;
  188. exports.wrap = wrap;