CacheFacade.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { forEachBail } = require("enhanced-resolve");
  7. const asyncLib = require("neo-async");
  8. const getLazyHashedEtag = require("./cache/getLazyHashedEtag");
  9. const mergeEtags = require("./cache/mergeEtags");
  10. /** @typedef {import("./Cache")} Cache */
  11. /** @typedef {import("./Cache").Etag} Etag */
  12. /** @typedef {import("./cache/getLazyHashedEtag").HashableObject} HashableObject */
  13. /** @typedef {import("./util/Hash").HashFunction} HashFunction */
  14. /**
  15. * Defines the callback cache callback.
  16. * @template T
  17. * @callback CallbackCache
  18. * @param {(Error | null)=} err
  19. * @param {(T | null)=} result
  20. * @returns {void}
  21. */
  22. /**
  23. * Defines the callback normal error cache callback.
  24. * @template T
  25. * @callback CallbackNormalErrorCache
  26. * @param {(Error | null)=} err
  27. * @param {T=} result
  28. * @returns {void}
  29. */
  30. class MultiItemCache {
  31. /**
  32. * Creates an instance of MultiItemCache.
  33. * @param {ItemCacheFacade[]} items item caches
  34. */
  35. constructor(items) {
  36. this._items = items;
  37. // @ts-expect-error expected - returns the single ItemCacheFacade when passed an array of length 1
  38. // eslint-disable-next-line no-constructor-return
  39. if (items.length === 1) return /** @type {ItemCacheFacade} */ (items[0]);
  40. }
  41. /**
  42. * Returns value.
  43. * @template T
  44. * @param {CallbackCache<T>} callback signals when the value is retrieved
  45. * @returns {void}
  46. */
  47. get(callback) {
  48. forEachBail(this._items, (item, callback) => item.get(callback), callback);
  49. }
  50. /**
  51. * Returns promise with the data.
  52. * @template T
  53. * @returns {Promise<T>} promise with the data
  54. */
  55. getPromise() {
  56. /**
  57. * Returns promise with the data.
  58. * @param {number} i index
  59. * @returns {Promise<T>} promise with the data
  60. */
  61. const next = (i) =>
  62. this._items[i].getPromise().then((result) => {
  63. if (result !== undefined) return result;
  64. if (++i < this._items.length) return next(i);
  65. });
  66. return next(0);
  67. }
  68. /**
  69. * Processes the provided data.
  70. * @template T
  71. * @param {T} data the value to store
  72. * @param {CallbackCache<void>} callback signals when the value is stored
  73. * @returns {void}
  74. */
  75. store(data, callback) {
  76. asyncLib.each(
  77. this._items,
  78. (item, callback) => item.store(data, callback),
  79. callback
  80. );
  81. }
  82. /**
  83. * Stores the provided data.
  84. * @template T
  85. * @param {T} data the value to store
  86. * @returns {Promise<void>} promise signals when the value is stored
  87. */
  88. storePromise(data) {
  89. return Promise.all(this._items.map((item) => item.storePromise(data))).then(
  90. () => {}
  91. );
  92. }
  93. }
  94. class ItemCacheFacade {
  95. /**
  96. * Creates an instance of ItemCacheFacade.
  97. * @param {Cache} cache the root cache
  98. * @param {string} name the child cache item name
  99. * @param {Etag | null} etag the etag
  100. */
  101. constructor(cache, name, etag) {
  102. this._cache = cache;
  103. this._name = name;
  104. this._etag = etag;
  105. }
  106. /**
  107. * Returns value.
  108. * @template T
  109. * @param {CallbackCache<T>} callback signals when the value is retrieved
  110. * @returns {void}
  111. */
  112. get(callback) {
  113. this._cache.get(this._name, this._etag, callback);
  114. }
  115. /**
  116. * Returns promise with the data.
  117. * @template T
  118. * @returns {Promise<T>} promise with the data
  119. */
  120. getPromise() {
  121. return new Promise((resolve, reject) => {
  122. this._cache.get(this._name, this._etag, (err, data) => {
  123. if (err) {
  124. reject(err);
  125. } else {
  126. resolve(data);
  127. }
  128. });
  129. });
  130. }
  131. /**
  132. * Processes the provided data.
  133. * @template T
  134. * @param {T} data the value to store
  135. * @param {CallbackCache<void>} callback signals when the value is stored
  136. * @returns {void}
  137. */
  138. store(data, callback) {
  139. this._cache.store(this._name, this._etag, data, callback);
  140. }
  141. /**
  142. * Stores the provided data.
  143. * @template T
  144. * @param {T} data the value to store
  145. * @returns {Promise<void>} promise signals when the value is stored
  146. */
  147. storePromise(data) {
  148. return new Promise((resolve, reject) => {
  149. this._cache.store(this._name, this._etag, data, (err) => {
  150. if (err) {
  151. reject(err);
  152. } else {
  153. resolve();
  154. }
  155. });
  156. });
  157. }
  158. /**
  159. * Processes the provided computer.
  160. * @template T
  161. * @param {(callback: CallbackNormalErrorCache<T>) => void} computer function to compute the value if not cached
  162. * @param {CallbackNormalErrorCache<T>} callback signals when the value is retrieved
  163. * @returns {void}
  164. */
  165. provide(computer, callback) {
  166. this.get((err, cacheEntry) => {
  167. if (err) return callback(err);
  168. if (cacheEntry !== undefined) return cacheEntry;
  169. computer((err, result) => {
  170. if (err) return callback(err);
  171. this.store(result, (err) => {
  172. if (err) return callback(err);
  173. callback(null, result);
  174. });
  175. });
  176. });
  177. }
  178. /**
  179. * Returns promise with the data.
  180. * @template T
  181. * @param {() => Promise<T> | T} computer function to compute the value if not cached
  182. * @returns {Promise<T>} promise with the data
  183. */
  184. async providePromise(computer) {
  185. const cacheEntry = await this.getPromise();
  186. if (cacheEntry !== undefined) return cacheEntry;
  187. const result = await computer();
  188. await this.storePromise(result);
  189. return result;
  190. }
  191. }
  192. class CacheFacade {
  193. /**
  194. * Creates an instance of CacheFacade.
  195. * @param {Cache} cache the root cache
  196. * @param {string} name the child cache name
  197. * @param {HashFunction=} hashFunction the hash function to use
  198. */
  199. constructor(cache, name, hashFunction) {
  200. this._cache = cache;
  201. this._name = name;
  202. this._hashFunction = hashFunction;
  203. }
  204. /**
  205. * Returns child cache.
  206. * @param {string} name the child cache name#
  207. * @returns {CacheFacade} child cache
  208. */
  209. getChildCache(name) {
  210. return new CacheFacade(
  211. this._cache,
  212. `${this._name}|${name}`,
  213. this._hashFunction
  214. );
  215. }
  216. /**
  217. * Returns item cache.
  218. * @param {string} identifier the cache identifier
  219. * @param {Etag | null} etag the etag
  220. * @returns {ItemCacheFacade} item cache
  221. */
  222. getItemCache(identifier, etag) {
  223. return new ItemCacheFacade(
  224. this._cache,
  225. `${this._name}|${identifier}`,
  226. etag
  227. );
  228. }
  229. /**
  230. * Gets lazy hashed etag.
  231. * @param {HashableObject} obj an hashable object
  232. * @returns {Etag} an etag that is lazy hashed
  233. */
  234. getLazyHashedEtag(obj) {
  235. return getLazyHashedEtag(obj, this._hashFunction);
  236. }
  237. /**
  238. * Merges the provided values into a single result.
  239. * @param {Etag} a an etag
  240. * @param {Etag} b another etag
  241. * @returns {Etag} an etag that represents both
  242. */
  243. mergeEtags(a, b) {
  244. return mergeEtags(a, b);
  245. }
  246. /**
  247. * Returns value.
  248. * @template T
  249. * @param {string} identifier the cache identifier
  250. * @param {Etag | null} etag the etag
  251. * @param {CallbackCache<T>} callback signals when the value is retrieved
  252. * @returns {void}
  253. */
  254. get(identifier, etag, callback) {
  255. this._cache.get(`${this._name}|${identifier}`, etag, callback);
  256. }
  257. /**
  258. * Returns promise with the data.
  259. * @template T
  260. * @param {string} identifier the cache identifier
  261. * @param {Etag | null} etag the etag
  262. * @returns {Promise<T>} promise with the data
  263. */
  264. getPromise(identifier, etag) {
  265. return new Promise((resolve, reject) => {
  266. this._cache.get(`${this._name}|${identifier}`, etag, (err, data) => {
  267. if (err) {
  268. reject(err);
  269. } else {
  270. resolve(data);
  271. }
  272. });
  273. });
  274. }
  275. /**
  276. * Processes the provided identifier.
  277. * @template T
  278. * @param {string} identifier the cache identifier
  279. * @param {Etag | null} etag the etag
  280. * @param {T} data the value to store
  281. * @param {CallbackCache<void>} callback signals when the value is stored
  282. * @returns {void}
  283. */
  284. store(identifier, etag, data, callback) {
  285. this._cache.store(`${this._name}|${identifier}`, etag, data, callback);
  286. }
  287. /**
  288. * Stores the provided identifier.
  289. * @template T
  290. * @param {string} identifier the cache identifier
  291. * @param {Etag | null} etag the etag
  292. * @param {T} data the value to store
  293. * @returns {Promise<void>} promise signals when the value is stored
  294. */
  295. storePromise(identifier, etag, data) {
  296. return new Promise((resolve, reject) => {
  297. this._cache.store(`${this._name}|${identifier}`, etag, data, (err) => {
  298. if (err) {
  299. reject(err);
  300. } else {
  301. resolve();
  302. }
  303. });
  304. });
  305. }
  306. /**
  307. * Processes the provided identifier.
  308. * @template T
  309. * @param {string} identifier the cache identifier
  310. * @param {Etag | null} etag the etag
  311. * @param {(callback: CallbackNormalErrorCache<T>) => void} computer function to compute the value if not cached
  312. * @param {CallbackNormalErrorCache<T>} callback signals when the value is retrieved
  313. * @returns {void}
  314. */
  315. provide(identifier, etag, computer, callback) {
  316. this.get(identifier, etag, (err, cacheEntry) => {
  317. if (err) return callback(err);
  318. if (cacheEntry !== undefined) return cacheEntry;
  319. computer((err, result) => {
  320. if (err) return callback(err);
  321. this.store(identifier, etag, result, (err) => {
  322. if (err) return callback(err);
  323. callback(null, result);
  324. });
  325. });
  326. });
  327. }
  328. /**
  329. * Returns promise with the data.
  330. * @template T
  331. * @param {string} identifier the cache identifier
  332. * @param {Etag | null} etag the etag
  333. * @param {() => Promise<T> | T} computer function to compute the value if not cached
  334. * @returns {Promise<T>} promise with the data
  335. */
  336. async providePromise(identifier, etag, computer) {
  337. const cacheEntry = await this.getPromise(identifier, etag);
  338. if (cacheEntry !== undefined) return cacheEntry;
  339. const result = await computer();
  340. await this.storePromise(identifier, etag, result);
  341. return result;
  342. }
  343. }
  344. module.exports = CacheFacade;
  345. module.exports.ItemCacheFacade = ItemCacheFacade;
  346. module.exports.MultiItemCache = MultiItemCache;