CacheFacade.js 8.4 KB

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