QueueStore.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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.mjs';
  8. import {DBWrapper} from 'workbox-core/_private/DBWrapper.mjs';
  9. import '../_version.mjs';
  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 IndexedbDB,
  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. * Preppend 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. } else {
  94. // Otherwise let the auto-incrementor assign the ID.
  95. delete entry.id;
  96. }
  97. entry.queueName = this._queueName;
  98. await this._db.add(OBJECT_STORE_NAME, entry);
  99. }
  100. /**
  101. * Removes and returns the last entry in the queue matching the `queueName`.
  102. *
  103. * @return {Promise<Object>}
  104. * @private
  105. */
  106. async popEntry() {
  107. return this._removeEntry({direction: 'prev'});
  108. }
  109. /**
  110. * Removes and returns the first entry in the queue matching the `queueName`.
  111. *
  112. * @return {Promise<Object>}
  113. * @private
  114. */
  115. async shiftEntry() {
  116. return this._removeEntry({direction: 'next'});
  117. }
  118. /**
  119. * Returns all entries in the store matching the `queueName`.
  120. *
  121. * @param {Object} options See workbox.backgroundSync.Queue~getAll}
  122. * @return {Promise<Array<Object>>}
  123. * @private
  124. */
  125. async getAll() {
  126. return await this._db.getAllMatching(OBJECT_STORE_NAME, {
  127. index: INDEXED_PROP,
  128. query: IDBKeyRange.only(this._queueName),
  129. });
  130. }
  131. /**
  132. * Deletes the entry for the given ID.
  133. *
  134. * WARNING: this method does not ensure the deleted enry belongs to this
  135. * queue (i.e. matches the `queueName`). But this limitation is acceptable
  136. * as this class is not publicly exposed. An additional check would make
  137. * this method slower than it needs to be.
  138. *
  139. * @private
  140. * @param {number} id
  141. */
  142. async deleteEntry(id) {
  143. await this._db.delete(OBJECT_STORE_NAME, id);
  144. }
  145. /**
  146. * Removes and returns the first or last entry in the queue (based on the
  147. * `direction` argument) matching the `queueName`.
  148. *
  149. * @return {Promise<Object>}
  150. * @private
  151. */
  152. async _removeEntry({direction}) {
  153. const [entry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
  154. direction,
  155. index: INDEXED_PROP,
  156. query: IDBKeyRange.only(this._queueName),
  157. count: 1,
  158. });
  159. if (entry) {
  160. await this.deleteEntry(entry.id);
  161. return entry;
  162. }
  163. }
  164. /**
  165. * Upgrades the database given an `upgradeneeded` event.
  166. *
  167. * @param {Event} event
  168. * @private
  169. */
  170. _upgradeDb(event) {
  171. const db = event.target.result;
  172. if (event.oldVersion > 0 && event.oldVersion < DB_VERSION) {
  173. if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
  174. db.deleteObjectStore(OBJECT_STORE_NAME);
  175. }
  176. }
  177. const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
  178. autoIncrement: true,
  179. keyPath: 'id',
  180. });
  181. objStore.createIndex(INDEXED_PROP, INDEXED_PROP, {unique: false});
  182. }
  183. }