QueueStore.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /*
  2. Copyright 2018 Google LLC
  3. Use of this source code is governed by an MIT-style
  4. license that can be found in the LICENSE file or at
  5. https://opensource.org/licenses/MIT.
  6. */
  7. import { assert } from 'workbox-core/_private/assert.js';
  8. import { DBWrapper } from 'workbox-core/_private/DBWrapper.js';
  9. import '../_version.js';
  10. const DB_VERSION = 3;
  11. const DB_NAME = 'workbox-background-sync';
  12. const OBJECT_STORE_NAME = 'requests';
  13. const INDEXED_PROP = 'queueName';
  14. /**
  15. * A class to manage storing requests from a Queue in IndexedDB,
  16. * indexed by their queue name for easier access.
  17. *
  18. * @private
  19. */
  20. export class QueueStore {
  21. /**
  22. * Associates this instance with a Queue instance, so entries added can be
  23. * identified by their queue name.
  24. *
  25. * @param {string} queueName
  26. * @private
  27. */
  28. constructor(queueName) {
  29. this._queueName = queueName;
  30. this._db = new DBWrapper(DB_NAME, DB_VERSION, {
  31. onupgradeneeded: this._upgradeDb,
  32. });
  33. }
  34. /**
  35. * Append an entry last in the queue.
  36. *
  37. * @param {Object} entry
  38. * @param {Object} entry.requestData
  39. * @param {number} [entry.timestamp]
  40. * @param {Object} [entry.metadata]
  41. * @private
  42. */
  43. async pushEntry(entry) {
  44. if (process.env.NODE_ENV !== 'production') {
  45. assert.isType(entry, 'object', {
  46. moduleName: 'workbox-background-sync',
  47. className: 'QueueStore',
  48. funcName: 'pushEntry',
  49. paramName: 'entry',
  50. });
  51. assert.isType(entry.requestData, 'object', {
  52. moduleName: 'workbox-background-sync',
  53. className: 'QueueStore',
  54. funcName: 'pushEntry',
  55. paramName: 'entry.requestData',
  56. });
  57. }
  58. // Don't specify an ID since one is automatically generated.
  59. delete entry.id;
  60. entry.queueName = this._queueName;
  61. await this._db.add(OBJECT_STORE_NAME, entry);
  62. }
  63. /**
  64. * Prepend an entry first in the queue.
  65. *
  66. * @param {Object} entry
  67. * @param {Object} entry.requestData
  68. * @param {number} [entry.timestamp]
  69. * @param {Object} [entry.metadata]
  70. * @private
  71. */
  72. async unshiftEntry(entry) {
  73. if (process.env.NODE_ENV !== 'production') {
  74. assert.isType(entry, 'object', {
  75. moduleName: 'workbox-background-sync',
  76. className: 'QueueStore',
  77. funcName: 'unshiftEntry',
  78. paramName: 'entry',
  79. });
  80. assert.isType(entry.requestData, 'object', {
  81. moduleName: 'workbox-background-sync',
  82. className: 'QueueStore',
  83. funcName: 'unshiftEntry',
  84. paramName: 'entry.requestData',
  85. });
  86. }
  87. const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
  88. count: 1,
  89. });
  90. if (firstEntry) {
  91. // Pick an ID one less than the lowest ID in the object store.
  92. entry.id = firstEntry.id - 1;
  93. }
  94. else {
  95. // Otherwise let the auto-incrementor assign the ID.
  96. delete entry.id;
  97. }
  98. entry.queueName = this._queueName;
  99. await this._db.add(OBJECT_STORE_NAME, entry);
  100. }
  101. /**
  102. * Removes and returns the last entry in the queue matching the `queueName`.
  103. *
  104. * @return {Promise<Object>}
  105. * @private
  106. */
  107. async popEntry() {
  108. return this._removeEntry({ direction: 'prev' });
  109. }
  110. /**
  111. * Removes and returns the first entry in the queue matching the `queueName`.
  112. *
  113. * @return {Promise<Object>}
  114. * @private
  115. */
  116. async shiftEntry() {
  117. return this._removeEntry({ direction: 'next' });
  118. }
  119. /**
  120. * Returns all entries in the store matching the `queueName`.
  121. *
  122. * @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}
  123. * @return {Promise<Array<Object>>}
  124. * @private
  125. */
  126. async getAll() {
  127. return await this._db.getAllMatching(OBJECT_STORE_NAME, {
  128. index: INDEXED_PROP,
  129. query: IDBKeyRange.only(this._queueName),
  130. });
  131. }
  132. /**
  133. * Deletes the entry for the given ID.
  134. *
  135. * WARNING: this method does not ensure the deleted enry belongs to this
  136. * queue (i.e. matches the `queueName`). But this limitation is acceptable
  137. * as this class is not publicly exposed. An additional check would make
  138. * this method slower than it needs to be.
  139. *
  140. * @private
  141. * @param {number} id
  142. */
  143. async deleteEntry(id) {
  144. await this._db.delete(OBJECT_STORE_NAME, id);
  145. }
  146. /**
  147. * Removes and returns the first or last entry in the queue (based on the
  148. * `direction` argument) matching the `queueName`.
  149. *
  150. * @return {Promise<Object>}
  151. * @private
  152. */
  153. async _removeEntry({ direction }) {
  154. const [entry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
  155. direction,
  156. index: INDEXED_PROP,
  157. query: IDBKeyRange.only(this._queueName),
  158. count: 1,
  159. });
  160. if (entry) {
  161. await this.deleteEntry(entry.id);
  162. return entry;
  163. }
  164. }
  165. /**
  166. * Upgrades the database given an `upgradeneeded` event.
  167. *
  168. * @param {Event} event
  169. * @private
  170. */
  171. _upgradeDb(event) {
  172. const db = event.target.result;
  173. if (event.oldVersion > 0 && event.oldVersion < DB_VERSION) {
  174. if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
  175. db.deleteObjectStore(OBJECT_STORE_NAME);
  176. }
  177. }
  178. const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
  179. autoIncrement: true,
  180. keyPath: 'id',
  181. });
  182. objStore.createIndex(INDEXED_PROP, INDEXED_PROP, { unique: false });
  183. }
  184. }