CacheFacade.js 8.5 KB

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