UNPKG

wretch

Version:

A tiny wrapper built around fetch with an intuitive syntax.

1 lines 7.39 kB
{"version":3,"file":"throttlingCache.min.mjs","names":[],"sources":["../../../src/middlewares/throttlingCache.ts"],"sourcesContent":["import type { WretchOptions, ConfiguredMiddleware } from \"../types.js\"\n\n/* Types */\n\nexport type ThrottlingCacheSkipFunction = (url: string, opts: WretchOptions) => boolean\nexport type ThrottlingCacheKeyFunction = (url: string, opts: WretchOptions) => string\nexport type ThrottlingCacheClearFunction = (url: string, opts: WretchOptions) => boolean\nexport type ThrottlingCacheInvalidateFunction = (url: string, opts: WretchOptions) => string | RegExp | void\nexport type ThrottlingCacheConditionFunction = (response: WretchOptions) => boolean\nexport type ThrottlingCacheOptions = {\n throttle?: number,\n skip?: ThrottlingCacheSkipFunction,\n key?: ThrottlingCacheKeyFunction,\n clear?: ThrottlingCacheClearFunction,\n invalidate?: ThrottlingCacheInvalidateFunction,\n condition?: ThrottlingCacheConditionFunction,\n flagResponseOnCacheHit?: string\n}\n\n/**\n * ## Throttling cache middleware\n *\n * #### A throttling cache which stores and serves server responses for a certain amount of time.\n *\n * **Options**\n *\n * - *throttle* `milliseconds`\n *\n * > the response will be stored for this amount of time before being deleted from the cache.\n *\n * - *skip* `(url, opts) => boolean`\n *\n * > If skip returns true, then the request is performed even if present in the cache.\n *\n * - *key* `(url, opts) => string`\n *\n * > Returns a key that is used to identify the request.\n *\n * - *clear* `(url, opts) => boolean`\n *\n * > Clears the cache if true.\n *\n * - *invalidate* `(url, opts) => string | RegExp | null`\n *\n * > Removes url(s) matching the string/RegExp from the cache.\n *\n * - *condition* `response => boolean`\n *\n * > If false then the response will not be added to the cache.\n *\n * - *flagResponseOnCacheHit* `string`\n *\n * > If set, a Response returned from the cache whill be flagged with a property name equal to this option.\n *\n */\nexport type ThrottlingCacheMiddleware = (options?: ThrottlingCacheOptions) => ConfiguredMiddleware & {\n cacheResponse(key: any, response: any): void;\n cache: Map<any, any>;\n inflight: Map<any, any>;\n throttling: Set<unknown>;\n}\n\n/* Defaults */\n\nconst defaultSkip = (_url, opts) => (\n opts.skipCache || opts.method !== \"GET\"\n)\nconst defaultKey = (url, opts) => opts.method + \"@\" + url\nconst defaultClear = () => false\nconst defaultInvalidate = () => null\nconst defaultCondition = response => response.ok\n\nexport const throttlingCache: ThrottlingCacheMiddleware = ({\n throttle = 1000,\n skip = defaultSkip,\n key = defaultKey,\n clear = defaultClear,\n invalidate = defaultInvalidate,\n condition = defaultCondition,\n flagResponseOnCacheHit = \"__cached\"\n} = {}) => {\n\n const cache = new Map()\n const inflight = new Map()\n const throttling = new Set()\n\n const throttleRequest = _key => {\n if (throttle && !throttling.has(_key)) {\n throttling.add(_key)\n setTimeout(() => { throttling.delete(_key) }, throttle)\n }\n }\n\n const middleware = next => (url, opts) => {\n const _key = key(url, opts)\n\n let invalidatePatterns = invalidate(url, opts)\n if (invalidatePatterns) {\n if (!(invalidatePatterns instanceof Array)) {\n invalidatePatterns = [invalidatePatterns]\n }\n invalidatePatterns.forEach(pattern => {\n if (typeof pattern === \"string\") {\n cache.delete(pattern)\n } else if (pattern instanceof RegExp) {\n cache.forEach((_, key) => {\n if (pattern.test(key)) {\n cache.delete(key)\n }\n })\n }\n })\n }\n if (clear(url, opts)) {\n cache.clear()\n }\n\n if (skip(url, opts)) {\n return next(url, opts)\n }\n\n if (throttling.has(_key)) {\n // If the cache contains a previous response and we are throttling, serve it and bypass the chain.\n if (cache.has(_key)) {\n const cachedClone = cache.get(_key).clone()\n if (flagResponseOnCacheHit) {\n // Flag the Response as cached\n Object.defineProperty(cachedClone, flagResponseOnCacheHit, {\n value: _key,\n enumerable: false\n })\n }\n return Promise.resolve(cachedClone)\n // If the request in already in-flight, wait until it is resolved\n } else if (inflight.has(_key)) {\n return new Promise((resolve, reject) => {\n inflight.get(_key).push([resolve, reject])\n })\n }\n }\n\n // Init. the pending promises Map\n if (!inflight.has(_key))\n inflight.set(_key, [])\n\n // If we are not throttling, activate the throttle for X milliseconds\n throttleRequest(_key)\n\n // We call the next middleware in the chain.\n return next(url, opts)\n .then(response => {\n // Add a cloned response to the cache\n if (condition(response.clone())) {\n cache.set(_key, response.clone())\n }\n // Resolve pending promises\n inflight.get(_key).forEach(([resolve]) => resolve(response.clone()))\n // Remove the inflight pending promises\n inflight.delete(_key)\n // Return the original response\n return response\n })\n .catch(error => {\n // Reject pending promises on error\n inflight.get(_key).forEach(([, reject]) => reject(error))\n inflight.delete(_key)\n throw error\n })\n }\n\n // Programmatically cache a response\n middleware.cacheResponse = function (key, response) {\n throttleRequest(key)\n cache.set(key, response)\n }\n middleware.cache = cache\n middleware.inflight = inflight\n middleware.throttling = throttling\n\n return middleware\n}\n"],"mappings":"AAgEA,MAAM,GAAe,EAAM,IACzB,EAAK,WAAa,EAAK,SAAW,MAE9B,GAAc,EAAK,IAAS,EAAK,OAAS,IAAM,EAChD,MAAqB,GACrB,MAA0B,KAC1B,EAAmB,GAAY,EAAS,GAEjC,GAA8C,CACzD,WAAW,IACX,OAAO,EACP,MAAM,EACN,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,yBAAyB,YACvB,EAAE,GAAK,CAET,IAAM,EAAQ,IAAI,IACZ,EAAW,IAAI,IACf,EAAa,IAAI,IAEjB,EAAkB,GAAQ,CAC1B,GAAY,CAAC,EAAW,IAAI,EAAK,GACnC,EAAW,IAAI,EAAK,CACpB,eAAiB,CAAE,EAAW,OAAO,EAAK,EAAI,EAAS,GAIrD,EAAa,IAAS,EAAK,IAAS,CACxC,IAAM,EAAO,EAAI,EAAK,EAAK,CAEvB,EAAqB,EAAW,EAAK,EAAK,CAqB9C,GApBI,IACI,aAA8B,QAClC,EAAqB,CAAC,EAAmB,EAE3C,EAAmB,QAAQ,GAAW,CAChC,OAAO,GAAY,SACrB,EAAM,OAAO,EAAQ,CACZ,aAAmB,QAC5B,EAAM,SAAS,EAAG,IAAQ,CACpB,EAAQ,KAAK,EAAI,EACnB,EAAM,OAAO,EAAI,EAEnB,EAEJ,EAEA,EAAM,EAAK,EAAK,EAClB,EAAM,OAAO,CAGX,EAAK,EAAK,EAAK,CACjB,OAAO,EAAK,EAAK,EAAK,CAGxB,GAAI,EAAW,IAAI,EAAK,KAElB,EAAM,IAAI,EAAK,CAAE,CACnB,IAAM,EAAc,EAAM,IAAI,EAAK,CAAC,OAAO,CAQ3C,OAPI,GAEF,OAAO,eAAe,EAAa,EAAwB,CACzD,MAAO,EACP,WAAY,GACb,CAAC,CAEG,QAAQ,QAAQ,EAAY,SAE1B,EAAS,IAAI,EAAK,CAC3B,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,EAAS,IAAI,EAAK,CAAC,KAAK,CAAC,EAAS,EAAO,CAAC,EAC1C,CAYN,OAPK,EAAS,IAAI,EAAK,EACrB,EAAS,IAAI,EAAM,EAAE,CAAC,CAGxB,EAAgB,EAAK,CAGd,EAAK,EAAK,EAAK,CACnB,KAAK,IAEA,EAAU,EAAS,OAAO,CAAC,EAC7B,EAAM,IAAI,EAAM,EAAS,OAAO,CAAC,CAGnC,EAAS,IAAI,EAAK,CAAC,SAAS,CAAC,KAAa,EAAQ,EAAS,OAAO,CAAC,CAAC,CAEpE,EAAS,OAAO,EAAK,CAEd,GACP,CACD,MAAM,GAAS,CAId,MAFA,EAAS,IAAI,EAAK,CAAC,SAAS,EAAG,KAAY,EAAO,EAAM,CAAC,CACzD,EAAS,OAAO,EAAK,CACf,GACN,EAYN,MARA,GAAW,cAAgB,SAAU,EAAK,EAAU,CAClD,EAAgB,EAAI,CACpB,EAAM,IAAI,EAAK,EAAS,EAE1B,EAAW,MAAQ,EACnB,EAAW,SAAW,EACtB,EAAW,WAAa,EAEjB"}