{"version":3,"file":"throttlingCache.min.mjs","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 = (url, opts) => false\nconst defaultInvalidate = (url, opts) => 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(([resolve, 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"],"names":["defaultSkip","url","opts","skipCache","method","defaultKey","defaultClear","defaultInvalidate","defaultCondition","response","ok","throttlingCache","throttle","skip","key","clear","invalidate","condition","flagResponseOnCacheHit","cache","Map","inflight","throttling","Set","throttleRequest","_key","has","add","setTimeout","delete","middleware","next","invalidatePatterns","Array","forEach","pattern","RegExp","_","test","cachedClone","get","clone","Object","defineProperty","value","enumerable","Promise","resolve","reject","push","set","then","catch","error","cacheResponse"],"mappings":"AAgEA,MAAMA,EAAc,CAACC,EAAKC,IACxBA,EAAKC,WAA6B,QAAhBD,EAAKE,OAEnBC,EAAa,CAACJ,EAAKC,IAASA,EAAKE,OAAS,IAAMH,EAChDK,EAAe,CAACL,EAAKC,IAAS,EAC9BK,EAAoB,CAACN,EAAKC,IAAS,KACnCM,EAAmBC,GAAYA,EAASC,GAEjCC,EAA6C,EACxDC,WAAW,IACXC,OAAOb,EACPc,MAAMT,EACNU,QAAQT,EACRU,aAAaT,EACbU,YAAYT,EACZU,yBAAyB,YACvB,MAEF,MAAMC,EAAQ,IAAIC,IACZC,EAAW,IAAID,IACfE,EAAa,IAAIC,IAEjBC,EAAkBC,IAClBb,IAAaU,EAAWI,IAAID,KAC9BH,EAAWK,IAAIF,GACfG,YAAW,KAAQN,EAAWO,OAAOJ,EAAK,GAAIb,GAC/C,EAGGkB,EAAaC,GAAQ,CAAC9B,EAAKC,KAC/B,MAAMuB,EAAOX,EAAIb,EAAKC,GAEtB,IAAI8B,EAAqBhB,EAAWf,EAAKC,GAqBzC,GApBI8B,IACIA,aAA8BC,QAClCD,EAAqB,CAACA,IAExBA,EAAmBE,SAAQC,IACF,iBAAZA,EACThB,EAAMU,OAAOM,GACJA,aAAmBC,QAC5BjB,EAAMe,SAAQ,CAACG,EAAGvB,KACZqB,EAAQG,KAAKxB,IACfK,EAAMU,OAAOf,EACd,GAEJ,KAGDC,EAAMd,EAAKC,IACbiB,EAAMJ,QAGJF,EAAKZ,EAAKC,GACZ,OAAO6B,EAAK9B,EAAKC,GAGnB,GAAIoB,EAAWI,IAAID,GAAO,CAExB,GAAIN,EAAMO,IAAID,GAAO,CACnB,MAAMc,EAAcpB,EAAMqB,IAAIf,GAAMgB,QAQpC,OAPIvB,GAEFwB,OAAOC,eAAeJ,EAAarB,EAAwB,CACzD0B,MAAOnB,EACPoB,WAAY,IAGTC,QAAQC,QAAQR,EAExB,CAAM,GAAIlB,EAASK,IAAID,GACtB,OAAO,IAAIqB,SAAQ,CAACC,EAASC,KAC3B3B,EAASmB,IAAIf,GAAMwB,KAAK,CAACF,EAASC,GAAQ,GAG/C,CAUD,OAPK3B,EAASK,IAAID,IAChBJ,EAAS6B,IAAIzB,EAAM,IAGrBD,EAAgBC,GAGTM,EAAK9B,EAAKC,GACdiD,MAAK1C,IAEAQ,EAAUR,EAASgC,UACrBtB,EAAM+B,IAAIzB,EAAMhB,EAASgC,SAG3BpB,EAASmB,IAAIf,GAAMS,SAAQ,EAAEa,KAAaA,EAAQtC,EAASgC,WAE3DpB,EAASQ,OAAOJ,GAEThB,KAER2C,OAAMC,IAIL,MAFAhC,EAASmB,IAAIf,GAAMS,SAAQ,EAAEa,EAASC,KAAYA,EAAOK,KACzDhC,EAASQ,OAAOJ,GACV4B,CAAK,GACX,EAYN,OARAvB,EAAWwB,cAAgB,SAAUxC,EAAKL,GACxCe,EAAgBV,GAChBK,EAAM+B,IAAIpC,EAAKL,EACjB,EACAqB,EAAWX,MAAQA,EACnBW,EAAWT,SAAWA,EACtBS,EAAWR,WAAaA,EAEjBQ,CAAU"}