CacheFacade.js 8.6 KB

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