index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. function promisifyRequest(request) {
  2. return new Promise((resolve, reject) => {
  3. // @ts-ignore - file size hacks
  4. request.oncomplete = request.onsuccess = () => resolve(request.result);
  5. // @ts-ignore - file size hacks
  6. request.onabort = request.onerror = () => reject(request.error);
  7. });
  8. }
  9. function createStore(dbName, storeName) {
  10. const request = indexedDB.open(dbName);
  11. request.onupgradeneeded = () => request.result.createObjectStore(storeName);
  12. const dbp = promisifyRequest(request);
  13. return (txMode, callback) => dbp.then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName)));
  14. }
  15. let defaultGetStoreFunc;
  16. function defaultGetStore() {
  17. if (!defaultGetStoreFunc) {
  18. defaultGetStoreFunc = createStore('keyval-store', 'keyval');
  19. }
  20. return defaultGetStoreFunc;
  21. }
  22. /**
  23. * Get a value by its key.
  24. *
  25. * @param key
  26. * @param customStore Method to get a custom store. Use with caution (see the docs).
  27. */
  28. function get(key, customStore = defaultGetStore()) {
  29. return customStore('readonly', (store) => promisifyRequest(store.get(key)));
  30. }
  31. /**
  32. * Set a value with a key.
  33. *
  34. * @param key
  35. * @param value
  36. * @param customStore Method to get a custom store. Use with caution (see the docs).
  37. */
  38. function set(key, value, customStore = defaultGetStore()) {
  39. return customStore('readwrite', (store) => {
  40. store.put(value, key);
  41. return promisifyRequest(store.transaction);
  42. });
  43. }
  44. /**
  45. * Set multiple values at once. This is faster than calling set() multiple times.
  46. * It's also atomic – if one of the pairs can't be added, none will be added.
  47. *
  48. * @param entries Array of entries, where each entry is an array of `[key, value]`.
  49. * @param customStore Method to get a custom store. Use with caution (see the docs).
  50. */
  51. function setMany(entries, customStore = defaultGetStore()) {
  52. return customStore('readwrite', (store) => {
  53. entries.forEach((entry) => store.put(entry[1], entry[0]));
  54. return promisifyRequest(store.transaction);
  55. });
  56. }
  57. /**
  58. * Get multiple values by their keys
  59. *
  60. * @param keys
  61. * @param customStore Method to get a custom store. Use with caution (see the docs).
  62. */
  63. function getMany(keys, customStore = defaultGetStore()) {
  64. return customStore('readonly', (store) => Promise.all(keys.map((key) => promisifyRequest(store.get(key)))));
  65. }
  66. /**
  67. * Update a value. This lets you see the old value and update it as an atomic operation.
  68. *
  69. * @param key
  70. * @param updater A callback that takes the old value and returns a new value.
  71. * @param customStore Method to get a custom store. Use with caution (see the docs).
  72. */
  73. function update(key, updater, customStore = defaultGetStore()) {
  74. return customStore('readwrite', (store) =>
  75. // Need to create the promise manually.
  76. // If I try to chain promises, the transaction closes in browsers
  77. // that use a promise polyfill (IE10/11).
  78. new Promise((resolve, reject) => {
  79. store.get(key).onsuccess = function () {
  80. try {
  81. store.put(updater(this.result), key);
  82. resolve(promisifyRequest(store.transaction));
  83. }
  84. catch (err) {
  85. reject(err);
  86. }
  87. };
  88. }));
  89. }
  90. /**
  91. * Delete a particular key from the store.
  92. *
  93. * @param key
  94. * @param customStore Method to get a custom store. Use with caution (see the docs).
  95. */
  96. function del(key, customStore = defaultGetStore()) {
  97. return customStore('readwrite', (store) => {
  98. store.delete(key);
  99. return promisifyRequest(store.transaction);
  100. });
  101. }
  102. /**
  103. * Delete multiple keys at once.
  104. *
  105. * @param keys List of keys to delete.
  106. * @param customStore Method to get a custom store. Use with caution (see the docs).
  107. */
  108. function delMany(keys, customStore = defaultGetStore()) {
  109. return customStore('readwrite', (store) => {
  110. keys.forEach((key) => store.delete(key));
  111. return promisifyRequest(store.transaction);
  112. });
  113. }
  114. /**
  115. * Clear all values in the store.
  116. *
  117. * @param customStore Method to get a custom store. Use with caution (see the docs).
  118. */
  119. function clear(customStore = defaultGetStore()) {
  120. return customStore('readwrite', (store) => {
  121. store.clear();
  122. return promisifyRequest(store.transaction);
  123. });
  124. }
  125. function eachCursor(store, callback) {
  126. store.openCursor().onsuccess = function () {
  127. if (!this.result)
  128. return;
  129. callback(this.result);
  130. this.result.continue();
  131. };
  132. return promisifyRequest(store.transaction);
  133. }
  134. /**
  135. * Get all keys in the store.
  136. *
  137. * @param customStore Method to get a custom store. Use with caution (see the docs).
  138. */
  139. function keys(customStore = defaultGetStore()) {
  140. return customStore('readonly', (store) => {
  141. // Fast path for modern browsers
  142. if (store.getAllKeys) {
  143. return promisifyRequest(store.getAllKeys());
  144. }
  145. const items = [];
  146. return eachCursor(store, (cursor) => items.push(cursor.key)).then(() => items);
  147. });
  148. }
  149. /**
  150. * Get all values in the store.
  151. *
  152. * @param customStore Method to get a custom store. Use with caution (see the docs).
  153. */
  154. function values(customStore = defaultGetStore()) {
  155. return customStore('readonly', (store) => {
  156. // Fast path for modern browsers
  157. if (store.getAll) {
  158. return promisifyRequest(store.getAll());
  159. }
  160. const items = [];
  161. return eachCursor(store, (cursor) => items.push(cursor.value)).then(() => items);
  162. });
  163. }
  164. /**
  165. * Get all entries in the store. Each entry is an array of `[key, value]`.
  166. *
  167. * @param customStore Method to get a custom store. Use with caution (see the docs).
  168. */
  169. function entries(customStore = defaultGetStore()) {
  170. return customStore('readonly', (store) => {
  171. // Fast path for modern browsers
  172. // (although, hopefully we'll get a simpler path some day)
  173. if (store.getAll && store.getAllKeys) {
  174. return Promise.all([
  175. promisifyRequest(store.getAllKeys()),
  176. promisifyRequest(store.getAll()),
  177. ]).then(([keys, values]) => keys.map((key, i) => [key, values[i]]));
  178. }
  179. const items = [];
  180. return customStore('readonly', (store) => eachCursor(store, (cursor) => items.push([cursor.key, cursor.value])).then(() => items));
  181. });
  182. }
  183. export { clear, createStore, del, delMany, entries, get, getMany, keys, promisifyRequest, set, setMany, update, values };