CacheFacade.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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. /** @import Cache, { Etag } from "./Cache" */
  11. /** @import { HashableObject } from "./cache/getLazyHashedEtag" */
  12. /** @import { HashFunction } from "./util/Hash" */
  13. /**
  14. * Defines the callback cache callback.
  15. * @template T
  16. * @callback CallbackCache
  17. * @param {(Error | null)=} err
  18. * @param {(T | null)=} result
  19. * @returns {void}
  20. */
  21. /**
  22. * Defines the callback normal error cache callback.
  23. * @template T
  24. * @callback CallbackNormalErrorCache
  25. * @param {(Error | null)=} err
  26. * @param {T=} result
  27. * @returns {void}
  28. */
  29. class MultiItemCache {
  30. /**
  31. * Creates an instance of MultiItemCache.
  32. * @param {ItemCacheFacade[]} items item caches
  33. */
  34. constructor(items) {
  35. /** @type {ItemCacheFacade[]} */
  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. /** @type {Cache} */
  103. this._cache = cache;
  104. /** @type {string} */
  105. this._name = name;
  106. /** @type {Etag | null} */
  107. this._etag = etag;
  108. }
  109. /**
  110. * Returns value.
  111. * @template T
  112. * @param {CallbackCache<T>} callback signals when the value is retrieved
  113. * @returns {void}
  114. */
  115. get(callback) {
  116. this._cache.get(this._name, this._etag, callback);
  117. }
  118. /**
  119. * Returns promise with the data.
  120. * @template T
  121. * @returns {Promise<T>} promise with the data
  122. */
  123. getPromise() {
  124. return new Promise((resolve, reject) => {
  125. this._cache.get(this._name, this._etag, (err, data) => {
  126. if (err) {
  127. reject(err);
  128. } else {
  129. resolve(data);
  130. }
  131. });
  132. });
  133. }
  134. /**
  135. * Processes the provided data.
  136. * @template T
  137. * @param {T} data the value to store
  138. * @param {CallbackCache<void>} callback signals when the value is stored
  139. * @returns {void}
  140. */
  141. store(data, callback) {
  142. this._cache.store(this._name, this._etag, data, callback);
  143. }
  144. /**
  145. * Stores the provided data.
  146. * @template T
  147. * @param {T} data the value to store
  148. * @returns {Promise<void>} promise signals when the value is stored
  149. */
  150. storePromise(data) {
  151. return new Promise((resolve, reject) => {
  152. this._cache.store(this._name, this._etag, data, (err) => {
  153. if (err) {
  154. reject(err);
  155. } else {
  156. resolve();
  157. }
  158. });
  159. });
  160. }
  161. /**
  162. * Processes the provided computer.
  163. * @template T
  164. * @param {(callback: CallbackNormalErrorCache<T>) => void} computer function to compute the value if not cached
  165. * @param {CallbackNormalErrorCache<T>} callback signals when the value is retrieved
  166. * @returns {void}
  167. */
  168. provide(computer, callback) {
  169. this.get((err, cacheEntry) => {
  170. if (err) return callback(err);
  171. if (cacheEntry !== undefined) return cacheEntry;
  172. computer((err, result) => {
  173. if (err) return callback(err);
  174. this.store(result, (err) => {
  175. if (err) return callback(err);
  176. callback(null, result);
  177. });
  178. });
  179. });
  180. }
  181. /**
  182. * Returns promise with the data.
  183. * @template T
  184. * @param {() => Promise<T> | T} computer function to compute the value if not cached
  185. * @returns {Promise<T>} promise with the data
  186. */
  187. async providePromise(computer) {
  188. const cacheEntry = await this.getPromise();
  189. if (cacheEntry !== undefined) return cacheEntry;
  190. const result = await computer();
  191. await this.storePromise(result);
  192. return result;
  193. }
  194. }
  195. class CacheFacade {
  196. /**
  197. * Creates an instance of CacheFacade.
  198. * @param {Cache} cache the root cache
  199. * @param {string} name the child cache name
  200. * @param {HashFunction=} hashFunction the hash function to use
  201. */
  202. constructor(cache, name, hashFunction) {
  203. /** @type {Cache} */
  204. this._cache = cache;
  205. /** @type {string} */
  206. this._name = name;
  207. /** @type {HashFunction | undefined} */
  208. this._hashFunction = hashFunction;
  209. }
  210. /**
  211. * Returns whether a cache backend is active.
  212. * @returns {boolean} true, when get or store can have an effect
  213. */
  214. isEnabled() {
  215. return this._cache.hooks.get.isUsed() || this._cache.hooks.store.isUsed();
  216. }
  217. /**
  218. * Returns child cache.
  219. * @param {string} name the child cache name#
  220. * @returns {CacheFacade} child cache
  221. */
  222. getChildCache(name) {
  223. return new CacheFacade(
  224. this._cache,
  225. `${this._name}|${name}`,
  226. this._hashFunction
  227. );
  228. }
  229. /**
  230. * Returns item cache.
  231. * @param {string} identifier the cache identifier
  232. * @param {Etag | null} etag the etag
  233. * @returns {ItemCacheFacade} item cache
  234. */
  235. getItemCache(identifier, etag) {
  236. return new ItemCacheFacade(
  237. this._cache,
  238. `${this._name}|${identifier}`,
  239. etag
  240. );
  241. }
  242. /**
  243. * Gets lazy hashed etag.
  244. * @param {HashableObject} obj an hashable object
  245. * @returns {Etag} an etag that is lazy hashed
  246. */
  247. getLazyHashedEtag(obj) {
  248. return getLazyHashedEtag(obj, this._hashFunction);
  249. }
  250. /**
  251. * Merges the provided values into a single result.
  252. * @param {Etag} a an etag
  253. * @param {Etag} b another etag
  254. * @returns {Etag} an etag that represents both
  255. */
  256. mergeEtags(a, b) {
  257. return mergeEtags(a, b);
  258. }
  259. /**
  260. * Returns value.
  261. * @template T
  262. * @param {string} identifier the cache identifier
  263. * @param {Etag | null} etag the etag
  264. * @param {CallbackCache<T>} callback signals when the value is retrieved
  265. * @returns {void}
  266. */
  267. get(identifier, etag, callback) {
  268. this._cache.get(`${this._name}|${identifier}`, etag, callback);
  269. }
  270. /**
  271. * Returns promise with the data.
  272. * @template T
  273. * @param {string} identifier the cache identifier
  274. * @param {Etag | null} etag the etag
  275. * @returns {Promise<T>} promise with the data
  276. */
  277. getPromise(identifier, etag) {
  278. return new Promise((resolve, reject) => {
  279. this._cache.get(`${this._name}|${identifier}`, etag, (err, data) => {
  280. if (err) {
  281. reject(err);
  282. } else {
  283. resolve(data);
  284. }
  285. });
  286. });
  287. }
  288. /**
  289. * Processes the provided identifier.
  290. * @template T
  291. * @param {string} identifier the cache identifier
  292. * @param {Etag | null} etag the etag
  293. * @param {T} data the value to store
  294. * @param {CallbackCache<void>} callback signals when the value is stored
  295. * @returns {void}
  296. */
  297. store(identifier, etag, data, callback) {
  298. this._cache.store(`${this._name}|${identifier}`, etag, data, callback);
  299. }
  300. /**
  301. * Stores the provided identifier.
  302. * @template T
  303. * @param {string} identifier the cache identifier
  304. * @param {Etag | null} etag the etag
  305. * @param {T} data the value to store
  306. * @returns {Promise<void>} promise signals when the value is stored
  307. */
  308. storePromise(identifier, etag, data) {
  309. return new Promise((resolve, reject) => {
  310. this._cache.store(`${this._name}|${identifier}`, etag, data, (err) => {
  311. if (err) {
  312. reject(err);
  313. } else {
  314. resolve();
  315. }
  316. });
  317. });
  318. }
  319. /**
  320. * Processes the provided identifier.
  321. * @template T
  322. * @param {string} identifier the cache identifier
  323. * @param {Etag | null} etag the etag
  324. * @param {(callback: CallbackNormalErrorCache<T>) => void} computer function to compute the value if not cached
  325. * @param {CallbackNormalErrorCache<T>} callback signals when the value is retrieved
  326. * @returns {void}
  327. */
  328. provide(identifier, etag, computer, callback) {
  329. this.get(identifier, etag, (err, cacheEntry) => {
  330. if (err) return callback(err);
  331. if (cacheEntry !== undefined) return cacheEntry;
  332. computer((err, result) => {
  333. if (err) return callback(err);
  334. this.store(identifier, etag, result, (err) => {
  335. if (err) return callback(err);
  336. callback(null, result);
  337. });
  338. });
  339. });
  340. }
  341. /**
  342. * Returns promise with the data.
  343. * @template T
  344. * @param {string} identifier the cache identifier
  345. * @param {Etag | null} etag the etag
  346. * @param {() => Promise<T> | T} computer function to compute the value if not cached
  347. * @returns {Promise<T>} promise with the data
  348. */
  349. async providePromise(identifier, etag, computer) {
  350. const cacheEntry = await this.getPromise(identifier, etag);
  351. if (cacheEntry !== undefined) return cacheEntry;
  352. const result = await computer();
  353. await this.storePromise(identifier, etag, result);
  354. return result;
  355. }
  356. }
  357. CacheFacade.ItemCacheFacade = ItemCacheFacade;
  358. CacheFacade.MultiItemCache = MultiItemCache;
  359. module.exports = CacheFacade;