/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const { forEachBail } = require("enhanced-resolve"); const asyncLib = require("neo-async"); const getLazyHashedEtag = require("./cache/getLazyHashedEtag"); const mergeEtags = require("./cache/mergeEtags"); /** @typedef {import("./Cache")} Cache */ /** @typedef {import("./Cache").Etag} Etag */ /** @typedef {import("./WebpackError")} WebpackError */ /** @typedef {import("./cache/getLazyHashedEtag").HashableObject} HashableObject */ /** @typedef {typeof import("./util/Hash")} HashConstructor */ /** * @template T * @callback CallbackCache * @param {(Error | null)=} err * @param {(T | null)=} result * @returns {void} */ /** * @template T * @callback CallbackNormalErrorCache * @param {(Error | null)=} err * @param {T=} result * @returns {void} */ class MultiItemCache { /** * @param {ItemCacheFacade[]} items item caches */ constructor(items) { this._items = items; // eslint-disable-next-line no-constructor-return if (items.length === 1) return /** @type {any} */ (items[0]); } /** * @template T * @param {CallbackCache} callback signals when the value is retrieved * @returns {void} */ get(callback) { forEachBail(this._items, (item, callback) => item.get(callback), callback); } /** * @template T * @returns {Promise} promise with the data */ getPromise() { /** * @param {number} i index * @returns {Promise} promise with the data */ const next = i => this._items[i].getPromise().then(result => { if (result !== undefined) return result; if (++i < this._items.length) return next(i); }); return next(0); } /** * @template T * @param {T} data the value to store * @param {CallbackCache} callback signals when the value is stored * @returns {void} */ store(data, callback) { asyncLib.each( this._items, (item, callback) => item.store(data, callback), callback ); } /** * @template T * @param {T} data the value to store * @returns {Promise} promise signals when the value is stored */ storePromise(data) { return Promise.all(this._items.map(item => item.storePromise(data))).then( () => {} ); } } class ItemCacheFacade { /** * @param {Cache} cache the root cache * @param {string} name the child cache item name * @param {Etag | null} etag the etag */ constructor(cache, name, etag) { this._cache = cache; this._name = name; this._etag = etag; } /** * @template T * @param {CallbackCache} callback signals when the value is retrieved * @returns {void} */ get(callback) { this._cache.get(this._name, this._etag, callback); } /** * @template T * @returns {Promise} promise with the data */ getPromise() { return new Promise((resolve, reject) => { this._cache.get(this._name, this._etag, (err, data) => { if (err) { reject(err); } else { resolve(data); } }); }); } /** * @template T * @param {T} data the value to store * @param {CallbackCache} callback signals when the value is stored * @returns {void} */ store(data, callback) { this._cache.store(this._name, this._etag, data, callback); } /** * @template T * @param {T} data the value to store * @returns {Promise} promise signals when the value is stored */ storePromise(data) { return new Promise((resolve, reject) => { this._cache.store(this._name, this._etag, data, err => { if (err) { reject(err); } else { resolve(); } }); }); } /** * @template T * @param {function(CallbackNormalErrorCache): void} computer function to compute the value if not cached * @param {CallbackNormalErrorCache} callback signals when the value is retrieved * @returns {void} */ provide(computer, callback) { this.get((err, cacheEntry) => { if (err) return callback(err); if (cacheEntry !== undefined) return cacheEntry; computer((err, result) => { if (err) return callback(err); this.store(result, err => { if (err) return callback(err); callback(null, result); }); }); }); } /** * @template T * @param {function(): Promise | T} computer function to compute the value if not cached * @returns {Promise} promise with the data */ async providePromise(computer) { const cacheEntry = await this.getPromise(); if (cacheEntry !== undefined) return cacheEntry; const result = await computer(); await this.storePromise(result); return result; } } class CacheFacade { /** * @param {Cache} cache the root cache * @param {string} name the child cache name * @param {(string | HashConstructor)=} hashFunction the hash function to use */ constructor(cache, name, hashFunction) { this._cache = cache; this._name = name; this._hashFunction = hashFunction; } /** * @param {string} name the child cache name# * @returns {CacheFacade} child cache */ getChildCache(name) { return new CacheFacade( this._cache, `${this._name}|${name}`, this._hashFunction ); } /** * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @returns {ItemCacheFacade} item cache */ getItemCache(identifier, etag) { return new ItemCacheFacade( this._cache, `${this._name}|${identifier}`, etag ); } /** * @param {HashableObject} obj an hashable object * @returns {Etag} an etag that is lazy hashed */ getLazyHashedEtag(obj) { return getLazyHashedEtag(obj, this._hashFunction); } /** * @param {Etag} a an etag * @param {Etag} b another etag * @returns {Etag} an etag that represents both */ mergeEtags(a, b) { return mergeEtags(a, b); } /** * @template T * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @param {CallbackCache} callback signals when the value is retrieved * @returns {void} */ get(identifier, etag, callback) { this._cache.get(`${this._name}|${identifier}`, etag, callback); } /** * @template T * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @returns {Promise} promise with the data */ getPromise(identifier, etag) { return new Promise((resolve, reject) => { this._cache.get(`${this._name}|${identifier}`, etag, (err, data) => { if (err) { reject(err); } else { resolve(data); } }); }); } /** * @template T * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @param {T} data the value to store * @param {CallbackCache} callback signals when the value is stored * @returns {void} */ store(identifier, etag, data, callback) { this._cache.store(`${this._name}|${identifier}`, etag, data, callback); } /** * @template T * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @param {T} data the value to store * @returns {Promise} promise signals when the value is stored */ storePromise(identifier, etag, data) { return new Promise((resolve, reject) => { this._cache.store(`${this._name}|${identifier}`, etag, data, err => { if (err) { reject(err); } else { resolve(); } }); }); } /** * @template T * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @param {function(CallbackNormalErrorCache): void} computer function to compute the value if not cached * @param {CallbackNormalErrorCache} callback signals when the value is retrieved * @returns {void} */ provide(identifier, etag, computer, callback) { this.get(identifier, etag, (err, cacheEntry) => { if (err) return callback(err); if (cacheEntry !== undefined) return cacheEntry; computer((err, result) => { if (err) return callback(err); this.store(identifier, etag, result, err => { if (err) return callback(err); callback(null, result); }); }); }); } /** * @template T * @param {string} identifier the cache identifier * @param {Etag | null} etag the etag * @param {function(): Promise | T} computer function to compute the value if not cached * @returns {Promise} promise with the data */ async providePromise(identifier, etag, computer) { const cacheEntry = await this.getPromise(identifier, etag); if (cacheEntry !== undefined) return cacheEntry; const result = await computer(); await this.storePromise(identifier, etag, result); return result; } } module.exports = CacheFacade; module.exports.ItemCacheFacade = ItemCacheFacade; module.exports.MultiItemCache = MultiItemCache;