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