UNPKG

ng-http-caching

Version:

Cache for HTTP requests in Angular application.

1 lines 61.7 kB
{"version":3,"file":"ng-http-caching.mjs","sources":["../../../projects/ng-http-caching/src/lib/storage/ng-http-caching-memory-storage.ts","../../../projects/ng-http-caching/src/lib/storage/ng-http-caching-ng-simple-state-sentinel.ts","../../../projects/ng-http-caching/src/lib/ng-http-caching.service.ts","../../../projects/ng-http-caching/src/lib/ng-http-caching-interceptor.service.ts","../../../projects/ng-http-caching/src/lib/ng-http-caching.module.ts","../../../projects/ng-http-caching/src/lib/ng-http-caching-provider.ts","../../../projects/ng-http-caching/src/lib/storage/ng-http-caching-browser-storage.ts","../../../projects/ng-http-caching/src/lib/storage/ng-http-caching-local-storage.ts","../../../projects/ng-http-caching/src/lib/storage/ng-http-caching-session-storage.ts","../../../projects/ng-http-caching/src/public-api.ts","../../../projects/ng-http-caching/src/ng-http-caching.ts"],"sourcesContent":["import { NgHttpCachingStorageInterface } from './ng-http-caching-storage.interface';\nimport { NgHttpCachingEntry } from '../ng-http-caching.service';\n\nexport class NgHttpCachingMemoryStorage extends Map<string, NgHttpCachingEntry<any, any>> implements NgHttpCachingStorageInterface { }\n\nexport const withNgHttpCachingMemoryStorage = () => new NgHttpCachingMemoryStorage();\n","import { InjectionToken, type Type } from '@angular/core';\nimport { NgHttpCachingEntry } from '../ng-http-caching.service';\nimport { NgHttpCachingStorageInterface } from './ng-http-caching-storage.interface';\n\n/**\n * State shape expected by the adapter.\n */\nexport interface NgHttpCachingNgSimpleState<K = unknown, T = unknown> {\n entries: Record<string, NgHttpCachingEntry<K, T>>;\n}\n\n/**\n * User-facing configuration for the ng-simple-state adapter.\n */\nexport interface NgHttpCachingNgSimpleStateAdapterConfig {\n storeName?: string;\n [key: string]: any;\n}\n\n/**\n * InjectionToken used to pass the user-provided adapter configuration.\n */\nexport const NG_HTTP_CACHING_NG_SIMPLE_STATE_CONFIG =\n new InjectionToken<NgHttpCachingNgSimpleStateAdapterConfig>('ng-http-caching-ng-simple-state.config');\n\n/**\n * Lightweight sentinel / marker class.\n */\nexport class NgHttpCachingNgSimpleStateSentinel {\n constructor(\n public readonly adapterClass: Type<NgHttpCachingStorageInterface>,\n public readonly adapterConfig?: NgHttpCachingNgSimpleStateAdapterConfig\n ) { }\n}\n","import { Injectable, InjectionToken, VERSION, isDevMode, inject, OnDestroy } from '@angular/core';\nimport { HttpRequest, HttpResponse, HttpEvent, HttpContextToken, HttpContext, HttpHeaders } from '@angular/common/http';\nimport type { Observable } from 'rxjs';\nimport { NgHttpCachingStorageInterface } from './storage/ng-http-caching-storage.interface';\nimport { NgHttpCachingMemoryStorage } from './storage/ng-http-caching-memory-storage';\nimport { NgHttpCachingNgSimpleStateSentinel } from './storage/ng-http-caching-ng-simple-state-sentinel';\n\nexport type NgHttpCachingContext = Pick<NgHttpCachingConfig, 'getKey' | 'isCacheable' | 'isExpired' | 'isValid' | 'clearCacheOnMutation'>;\n\nexport const NG_HTTP_CACHING_CONTEXT = new HttpContextToken<NgHttpCachingContext>(() => ({}));\n\nexport const withNgHttpCachingContext = (value: NgHttpCachingContext, context: HttpContext = new HttpContext()) => context.set(NG_HTTP_CACHING_CONTEXT, value)\n\nexport const checkCacheHeaders = (headers: HttpHeaders): boolean | number => {\n // check Cache-Control\n const cacheControlHeader = headers.get('cache-control');\n if (cacheControlHeader) {\n const cacheControl = cacheControlHeader.toLowerCase();\n if (cacheControl.includes('no-store')) {\n return false;\n } else if (cacheControl.includes('no-cache')) {\n return false;\n }\n // extract max-age value if present\n const maxAgeMatch = cacheControl.match(/max-age\\s*=\\s*(\\d+)/);\n if (maxAgeMatch) {\n const maxAgeSec = parseInt(maxAgeMatch[1], 10);\n // return the max-age in milliseconds so the caller can use it as lifetime\n return maxAgeSec * 1000;\n }\n return true;\n }\n\n // check Expires header if response is without Cache-Control\n const expiresHeader = headers.get('expires');\n if (expiresHeader) {\n const expires = Date.parse(expiresHeader);\n if (!isNaN(expires)) {\n return expires > Date.now();\n }\n }\n\n return true;\n}\n\nexport interface NgHttpCachingEntry<K = any, T = any> {\n /**\n * URL\n */\n url: string;\n /**\n * HttpResponse\n */\n response: HttpResponse<T>;\n /**\n * HttpRequest\n */\n request: HttpRequest<K>;\n /**\n * Timestamp of add to cache time\n */\n addedTime: number;\n /**\n * Cache version\n */\n version: string;\n}\n\nexport const NG_HTTP_CACHING_CONFIG = new InjectionToken<NgHttpCachingConfig>(\n 'ng-http-caching.config'\n);\n\nexport const NgHttpCachingStrategy = {\n /**\n * All request are cacheable if HTTP method is into `allowedMethod`\n */\n ALLOW_ALL: 'ALLOW_ALL',\n /**\n * Only the request with `X-NG-HTTP-CACHING-ALLOW-CACHE` header are cacheable if HTTP method is into `allowedMethod`\n */\n DISALLOW_ALL: 'DISALLOW_ALL'\n}\nexport type NgHttpCachingStrategy = typeof NgHttpCachingStrategy[keyof typeof NgHttpCachingStrategy];\n\nexport const NgHttpCachingMutationStrategy = {\n /**\n * No invalidation on mutation\n */\n NONE: 'NONE',\n /**\n * Clear all cache on mutation\n */\n ALL: 'ALL',\n /**\n * Clear only the cache entries with the same URL as the mutation request\n */\n IDENTICAL: 'IDENTICAL',\n /**\n * Clear the cache entries with the same URL or the parent URL as the mutation request\n */\n COLLECTION: 'COLLECTION',\n}\nexport type NgHttpCachingMutationStrategy = typeof NgHttpCachingMutationStrategy[keyof typeof NgHttpCachingMutationStrategy];\n\nexport const NgHttpCachingHeaders = {\n /**\n * Request is cacheable if HTTP method is into `allowedMethod`\n */\n ALLOW_CACHE: 'X-NG-HTTP-CACHING-ALLOW-CACHE',\n /**\n * Request isn't cacheable\n */\n DISALLOW_CACHE: 'X-NG-HTTP-CACHING-DISALLOW-CACHE',\n /**\n * Specific cache lifetime for the request\n */\n LIFETIME: 'X-NG-HTTP-CACHING-LIFETIME',\n /**\n * You can tag multiple request by adding this header with the same tag and \n * using `NgHttpCachingService.clearCacheByTag(tag: string)` for delete all the tagged request\n */\n TAG: 'X-NG-HTTP-CACHING-TAG'\n}\nexport type NgHttpCachingHeaders = typeof NgHttpCachingHeaders[keyof typeof NgHttpCachingHeaders];\n\nexport const NgHttpCachingHeadersList = Object.values(NgHttpCachingHeaders);\n\nexport const NG_HTTP_CACHING_SECOND_IN_MS = 1000;\nexport const NG_HTTP_CACHING_MINUTE_IN_MS = NG_HTTP_CACHING_SECOND_IN_MS * 60;\nexport const NG_HTTP_CACHING_HOUR_IN_MS = NG_HTTP_CACHING_MINUTE_IN_MS * 60;\nexport const NG_HTTP_CACHING_DAY_IN_MS = NG_HTTP_CACHING_HOUR_IN_MS * 24;\nexport const NG_HTTP_CACHING_WEEK_IN_MS = NG_HTTP_CACHING_DAY_IN_MS * 7;\nexport const NG_HTTP_CACHING_MONTH_IN_MS = NG_HTTP_CACHING_DAY_IN_MS * 30;\nexport const NG_HTTP_CACHING_YEAR_IN_MS = NG_HTTP_CACHING_DAY_IN_MS * 365;\n\nexport interface NgHttpCachingConfig {\n /**\n * Set the cache store. You can implement your custom store by implement the `NgHttpCachingStorageInterface` interface, eg.:\n */\n store?: NgHttpCachingStorageInterface | NgHttpCachingNgSimpleStateSentinel;\n /**\n * Number of millisecond that a response is stored in the cache. \n * You can set specific \"lifetime\" for each request by add the header `X-NG-HTTP-CACHING-LIFETIME` (see example below).\n */\n lifetime?: number;\n /**\n * Array of allowed HTTP methods to cache. \n * You can allow multiple methods, eg.: `['GET', 'POST', 'PUT', 'DELETE', 'HEAD']` or \n * allow all methods by: `['ALL']`. If `allowedMethod` is an empty array (`[]`), no response are cached.\n * *Warning!* `NgHttpCaching` use the full url (url with query parameters) as unique key for the cached response,\n * this is correct for the `GET` request but is _potentially_ wrong for other type of request (eg. `POST`, `PUT`). \n * You can set a different \"key\" by customizing the `getKey` config method (see `getKey` section).\n */\n allowedMethod?: string[];\n /**\n * Set the cache strategy, possible strategies are:\n * - `NgHttpCachingStrategy.ALLOW_ALL`: All request are cacheable if HTTP method is into `allowedMethod`;\n * - `NgHttpCachingStrategy.DISALLOW_ALL`: Only the request with `X-NG-HTTP-CACHING-ALLOW-CACHE` header are cacheable if HTTP method is into `allowedMethod`;\n */\n cacheStrategy?: NgHttpCachingStrategy;\n /**\n * Cache version. When you have a breaking change, change the version, and it'll delete the current cache automatically.\n * The default value is Angular major version (eg. 13), in this way, the cache is invalidated on every Angular upgrade.\n */\n version?: string;\n /**\n * If true response headers cache-control and expires are respected.\n */\n checkResponseHeaders?: boolean;\n /**\n * If this function return `true` the request is expired and a new request is send to backend, if return `false` isn't expired. \n * If the result is `undefined`, the normal behaviour is provided.\n */\n isExpired?: <K, T>(entry: NgHttpCachingEntry<K, T>) => boolean | undefined | void;\n /**\n * If this function return `true` the request is cacheable, if return `false` isn't cacheable. \n * If the result is `undefined`, the normal behaviour is provided.\n */\n isCacheable?: <K>(req: HttpRequest<K>) => boolean | undefined | void;\n /**\n * This function return the unique key (`string`) for store the response into the cache. \n * If the result is `undefined`, the normal behaviour is provided.\n */\n getKey?: <K>(req: HttpRequest<K>) => string | undefined | void;\n /**\n * If this function return `true` the cache entry is valid and can be stored, if return `false` isn't valid. \n * If the result is `undefined`, the normal behaviour is provided.\n */\n isValid?: <K, T>(entry: NgHttpCachingEntry<K, T>) => boolean | undefined | void;\n /**\n * Set the mutation strategy.\n * If `true`, it behaves like `NgHttpCachingMutationStrategy.ALL`.\n * If `false`, it behaves like `NgHttpCachingMutationStrategy.NONE`.\n * If a custom function is provided, returning `true` will clear the **entire** cache store.\n * Returning `false` (or `undefined`) will skip invalidation for that request.\n */\n clearCacheOnMutation?: NgHttpCachingMutationStrategy | boolean | (<K>(req: HttpRequest<K>) => boolean | undefined | void);\n}\n\nexport interface NgHttpCachingDefaultConfig extends NgHttpCachingConfig {\n store: NgHttpCachingStorageInterface;\n lifetime: number;\n allowedMethod: string[];\n cacheStrategy: NgHttpCachingStrategy;\n version: string;\n checkResponseHeaders: boolean;\n}\n\nexport const NgHttpCachingConfigDefault: Readonly<NgHttpCachingDefaultConfig> = {\n store: new NgHttpCachingMemoryStorage(),\n lifetime: NG_HTTP_CACHING_HOUR_IN_MS,\n version: VERSION.major,\n allowedMethod: ['GET', 'HEAD'],\n cacheStrategy: NgHttpCachingStrategy.ALLOW_ALL,\n checkResponseHeaders: false,\n clearCacheOnMutation: NgHttpCachingMutationStrategy.NONE\n};\n\n/**\n * Creates a fresh default config with a new store instance.\n * This avoids sharing a single Map across multiple service instances (important in tests).\n */\nfunction createDefaultConfig(): NgHttpCachingDefaultConfig {\n return { ...NgHttpCachingConfigDefault, store: new NgHttpCachingMemoryStorage() };\n}\n\n@Injectable({ providedIn: 'root' })\nexport class NgHttpCachingService implements OnDestroy {\n\n private readonly queue = new Map<string, Observable<HttpEvent<any>>>();\n\n private readonly config: NgHttpCachingDefaultConfig;\n\n private gcLock = false;\n private gcLastRun: number = 0;\n\n private devMode: boolean = isDevMode();\n\n constructor() {\n const userConfig: Readonly<NgHttpCachingConfig | null> = inject(NG_HTTP_CACHING_CONFIG, { optional: true });\n if (userConfig) {\n const config: NgHttpCachingConfig = { ...userConfig };\n if (config.store instanceof NgHttpCachingNgSimpleStateSentinel) {\n config.store = inject(config.store.adapterClass);\n }\n this.config = { ...createDefaultConfig(), ...config } as NgHttpCachingDefaultConfig;\n } else {\n this.config = createDefaultConfig();\n }\n // start cache clean\n this.runGc();\n }\n\n /**\n * Return the config\n */\n getConfig(): Readonly<NgHttpCachingConfig> {\n return this.config;\n }\n\n /**\n * Return the queue map\n */\n getQueue(): Readonly<Map<string, Observable<HttpEvent<any>>>> {\n return this.queue;\n }\n\n /**\n * Return the cache store\n */\n getStore(): Readonly<NgHttpCachingStorageInterface> {\n return this.config.store;\n }\n\n /**\n * Return response from cache\n */\n getFromCache<K, T>(req: HttpRequest<K>): Readonly<HttpResponse<T>> | undefined {\n const key: string = this.getKey(req);\n const cached: NgHttpCachingEntry<K, T> | undefined = this.config.store.get<K, T>(key);\n\n if (!cached) {\n return undefined;\n }\n\n if (this.isExpired(cached)) {\n this.clearCacheByKey(key);\n return undefined;\n }\n\n return this.deepFreeze(cached.response);\n }\n\n /**\n * Add response to cache\n */\n addToCache<K, T>(req: HttpRequest<K>, res: HttpResponse<T>): boolean {\n const entry: NgHttpCachingEntry<K, T> = {\n url: req.urlWithParams,\n response: res,\n request: req,\n addedTime: Date.now(),\n version: this.config.version,\n };\n if (this.isValid(entry)) {\n const key: string = this.getKey(req);\n this.config.store.set(key, entry);\n return true;\n }\n return false;\n }\n\n /**\n * Delete response from cache\n */\n deleteFromCache<K>(req: HttpRequest<K>): boolean {\n const key: string = this.getKey(req);\n return this.clearCacheByKey(key);\n }\n\n /**\n * Clear the cache\n */\n clearCache(): void {\n this.config.store.clear();\n }\n\n /**\n * Clear the cache by key\n */\n clearCacheByKey(key: string): boolean {\n return this.config.store.delete(key);\n }\n\n /**\n * Clear the cache by keys\n */\n clearCacheByKeys(keys: Array<string>): number {\n let counter = 0;\n if (keys) {\n for (const key of keys) {\n if (this.clearCacheByKey(key)) {\n counter++;\n }\n }\n }\n return counter;\n }\n\n /**\n * Clear the cache by regex\n */\n clearCacheByRegex<K, T>(regex: RegExp): number {\n const keys: Array<string> = [];\n this.config.store.forEach<K, T>((_: NgHttpCachingEntry<K, T>, key: string) => {\n if (regex.test(key)) {\n keys.push(key);\n }\n });\n return this.clearCacheByKeys(keys);\n }\n\n /**\n * Clear the cache by TAG\n */\n clearCacheByTag<K, T>(tag: string): number {\n const keys: Array<string> = [];\n this.config.store.forEach<K, T>((entry: NgHttpCachingEntry<K, T>, key: string) => {\n const tagHeader = entry.request.headers.get(NgHttpCachingHeaders.TAG);\n if (tagHeader && tagHeader.split(',').includes(tag)) {\n keys.push(key);\n }\n });\n return this.clearCacheByKeys(keys);\n }\n\n /**\n * Run garbage collector (delete expired cache entry)\n */\n runGc<K, T>(): boolean {\n if (this.gcLock || (this.gcLastRun && (Date.now() - this.gcLastRun < 1000))) {\n return false;\n }\n this.gcLock = true;\n this.gcLastRun = Date.now();\n try {\n const keys: Array<string> = [];\n this.config.store.forEach<K, T>((entry: NgHttpCachingEntry<K, T>, key: string) => {\n if (this.isExpired(entry)) {\n keys.push(key);\n }\n });\n this.clearCacheByKeys(keys);\n } finally {\n this.gcLock = false;\n }\n return true;\n }\n\n /**\n * Clear the cache by mutation\n */\n clearCacheByMutation<K>(req: HttpRequest<K>): boolean {\n const context = req.context.get(NG_HTTP_CACHING_CONTEXT);\n const strategy = context.clearCacheOnMutation !== undefined ? context.clearCacheOnMutation : this.config.clearCacheOnMutation;\n\n if (typeof strategy === 'function') {\n const result = strategy(req);\n if (result === true) {\n this.clearCache();\n return true;\n }\n return false;\n }\n\n if (strategy === false || strategy === NgHttpCachingMutationStrategy.NONE) {\n return false;\n }\n\n if (!['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {\n return false;\n }\n\n if (strategy === true || strategy === NgHttpCachingMutationStrategy.ALL) {\n this.clearCache();\n return true;\n }\n\n const url = req.urlWithParams.split('?')[0];\n\n if (strategy === NgHttpCachingMutationStrategy.IDENTICAL) {\n const regex = new RegExp('^.*@' + this.escapeRegExp(url) + '(\\\\?|$)');\n this.clearCacheByRegex(regex);\n return true;\n }\n\n if (strategy === NgHttpCachingMutationStrategy.COLLECTION) {\n const regexStr = '^.*@' + this.escapeRegExp(url) + '(\\\\?|$)';\n const parts = url.split('/');\n if (parts.length > 1) {\n parts.pop();\n const parentUrl = parts.join('/');\n const parentRegexStr = '^.*@' + this.escapeRegExp(parentUrl) + '(\\\\?|$)';\n this.clearCacheByRegex(new RegExp(`(${regexStr})|(${parentRegexStr})`));\n } else {\n this.clearCacheByRegex(new RegExp(regexStr));\n }\n return true;\n }\n\n return false;\n }\n\n /**\n * Escape regex special characters\n */\n private escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n\n /**\n * Return true if cache entry is expired\n */\n isExpired<K, T>(entry: NgHttpCachingEntry<K, T>): boolean {\n // if user provide custom method, use it\n const context = entry.request.context.get(NG_HTTP_CACHING_CONTEXT);\n if (typeof context?.isExpired === 'function') {\n const result = context.isExpired(entry);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // if user provide custom method, use it\n if (typeof this.config.isExpired === 'function') {\n const result = this.config.isExpired(entry);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // if version change, always expire\n if (this.config.version !== entry.version) {\n return true;\n }\n // config/default lifetime\n let lifetime: number = this.config.lifetime;\n // request has own lifetime header (takes highest priority)\n const headerLifetime = entry.request.headers.get(NgHttpCachingHeaders.LIFETIME);\n if (headerLifetime) {\n lifetime = +headerLifetime;\n } else if (this.config.checkResponseHeaders) {\n // check response headers for max-age\n const headerResult = checkCacheHeaders(entry.response.headers);\n if (typeof headerResult === 'number') {\n lifetime = headerResult;\n }\n }\n // never expire if 0\n if (lifetime === 0) {\n return false;\n }\n // wrong lifetime\n if (lifetime < 0 || isNaN(lifetime)) {\n throw new Error('lifetime must be greater than or equal 0');\n }\n return entry.addedTime + lifetime < Date.now();\n }\n\n /**\n * Return true if cache entry is valid for store in the cache\n * Default behaviour is whether the status code falls in the 2xx range and response headers cache-control and expires allow cache.\n */\n isValid<K, T>(entry: NgHttpCachingEntry<K, T>): boolean {\n const context = entry.request.context.get(NG_HTTP_CACHING_CONTEXT);\n // if user provide custom method, use it\n if (typeof context.isValid === 'function') {\n const result = context.isValid(entry);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // if user provide custom method, use it\n if (typeof this.config.isValid === 'function') {\n const result = this.config.isValid(entry);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // different version\n if (this.config.version !== entry.version) {\n return false;\n }\n\n if (this.config.checkResponseHeaders) {\n // check if response headers allow cache\n const headerResult = checkCacheHeaders(entry.response.headers);\n if (headerResult === false) {\n return false;\n }\n }\n return entry.response.ok;\n }\n\n /**\n * Return true if the request is cacheable\n */\n isCacheable<K>(req: HttpRequest<K>): boolean {\n const context = req.context.get(NG_HTTP_CACHING_CONTEXT);\n // if user provide custom method, use it\n if (typeof context?.isCacheable === 'function') {\n const result = context.isCacheable(req);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // if user provide custom method, use it\n if (typeof this.config.isCacheable === 'function') {\n const result = this.config.isCacheable(req);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // request has disallow cache header\n if (req.headers.has(NgHttpCachingHeaders.DISALLOW_CACHE)) {\n return false;\n }\n // strategy is disallow all...\n if (this.config.cacheStrategy === NgHttpCachingStrategy.DISALLOW_ALL) {\n // request isn't allowed if come without allow header\n if (!req.headers.has(NgHttpCachingHeaders.ALLOW_CACHE)) {\n return false;\n }\n }\n // if allowed method is only ALL, allow all http methods\n if (this.config.allowedMethod.length === 1) {\n if (this.config.allowedMethod[0] === 'ALL') {\n return true;\n }\n }\n // request is allowed if method is in allowedMethod\n return this.config.allowedMethod.includes(req.method);\n }\n\n /**\n * Return the cache key.\n * Default key is http method plus url with query parameters, eg.:\n * `GET@https://github.com/nigrosimone/ng-http-caching`\n */\n getKey<K>(req: HttpRequest<K>): string {\n // if user provide custom method, use it\n const context = req.context.get(NG_HTTP_CACHING_CONTEXT);\n if (typeof context.getKey === 'function') {\n const result = context.getKey(req);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // if user provide custom method, use it\n if (typeof this.config.getKey === 'function') {\n const result = this.config.getKey(req);\n // if result is undefined, normal behaviour is provided\n if (result !== undefined) {\n return result;\n }\n }\n // default key is req.method plus url with query parameters\n return req.method + '@' + req.urlWithParams;\n }\n\n /**\n * Return observable from cache\n */\n getFromQueue<K, T>(req: HttpRequest<K>): Observable<HttpEvent<T>> | undefined {\n const key: string = this.getKey(req);\n const cached: Observable<HttpEvent<T>> | undefined = this.queue.get(key);\n\n if (!cached) {\n return undefined;\n }\n\n return cached;\n }\n\n /**\n * Add observable to cache\n */\n addToQueue<K, T>(req: HttpRequest<K>, obs: Observable<HttpEvent<T>>): void {\n const key: string = this.getKey(req);\n this.queue.set(key, obs);\n }\n\n /**\n * Delete observable from cache\n */\n deleteFromQueue<K>(req: HttpRequest<K>): boolean {\n const key: string = this.getKey(req);\n return this.queue.delete(key);\n }\n\n /**\n * Recursively Object.freeze simple Javascript structures consisting of plain objects, arrays, and primitives.\n * Make the data immutable.\n * @returns immutable object\n */\n private deepFreeze<S>(object: S): Readonly<S> {\n // No freezing in production (for better performance).\n if (!this.devMode || !object || typeof object !== 'object') {\n return object as Readonly<S>;\n }\n\n // When already frozen, we assume its children are frozen (for better performance).\n // This should be true if you always use `deepFreeze` to freeze objects.\n //\n // Note that Object.isFrozen will also return `true` for primitives (numbers,\n // strings, booleans, undefined, null), so there is no need to check for\n // those explicitly.\n if (Object.isFrozen(object)) {\n return object as Readonly<S>;\n }\n\n // At this point we know that we're dealing with either an array or plain object, so\n // just freeze it and recurse on its values.\n Object.freeze(object);\n Object.keys(object).forEach(key => this.deepFreeze((object as any)[key]));\n\n return object as Readonly<S>;\n }\n\n ngOnDestroy(): void {\n this.queue.clear();\n }\n}\n","import { HttpEvent, HttpEventType, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\nimport { asyncScheduler, Observable, of, scheduled } from 'rxjs';\nimport { tap, shareReplay, finalize } from 'rxjs/operators';\nimport { NgHttpCachingService, NgHttpCachingHeadersList } from './ng-http-caching.service';\n\n@Injectable()\nexport class NgHttpCachingInterceptorService implements HttpInterceptor {\n\n private readonly cacheService: NgHttpCachingService = inject(NgHttpCachingService);\n\n intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n // run garbage collector\n this.cacheService.runGc();\n\n // Don't cache if it's not cacheable\n if (!this.cacheService.isCacheable(req)) {\n return this.sendRequest(req, next).pipe(\n tap(event => {\n if (event.type === HttpEventType.Response && event.ok) {\n this.cacheService.clearCacheByMutation(req);\n }\n })\n );\n }\n\n // Checked if there is pending response for this request\n const cachedObservable: Observable<HttpEvent<any>> | undefined = this.cacheService.getFromQueue(req);\n if (cachedObservable) {\n\n return cachedObservable;\n }\n\n // Checked if there is cached response for this request\n const cachedResponse: HttpResponse<any> | undefined = this.cacheService.getFromCache(req);\n if (cachedResponse) {\n\n return scheduled(of(cachedResponse.clone()), asyncScheduler);\n }\n\n // If the request of going through for first time\n // then let the request proceed and cache the response\n\n const shared = this.sendRequest(req, next).pipe(\n tap(event => {\n if (event.type === HttpEventType.Response) {\n this.cacheService.addToCache(req, event);\n }\n }),\n finalize(() => {\n // delete pending request\n this.cacheService.deleteFromQueue(req);\n }),\n shareReplay({\n bufferSize: 1, refCount: true\n })\n );\n\n // add pending request to queue for cache parallel request\n this.cacheService.addToQueue(req, shared);\n\n return shared;\n }\n\n /**\n * Send http request (next handler)\n */\n sendRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n // trim custom headers before send request\n let headers: HttpHeaders = req.headers;\n let needClone = false;\n for (const header of NgHttpCachingHeadersList) {\n if (headers.has(header)) {\n needClone = true;\n headers = headers.delete(header);\n }\n }\n if (needClone) {\n req = req.clone({ headers });\n }\n return next.handle(req);\n }\n}\n","import { NgModule, ModuleWithProviders, Provider } from '@angular/core';\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\nimport {\n NG_HTTP_CACHING_CONFIG,\n NgHttpCachingConfig,\n NgHttpCachingService,\n} from './ng-http-caching.service';\nimport { NgHttpCachingInterceptorService } from './ng-http-caching-interceptor.service';\nimport {\n NG_HTTP_CACHING_NG_SIMPLE_STATE_CONFIG,\n NgHttpCachingNgSimpleStateSentinel,\n} from './storage/ng-http-caching-ng-simple-state-sentinel';\n\n/** @deprecated use provideNgHttpCaching */\n@NgModule({\n providers: [\n NgHttpCachingService,\n NgHttpCachingInterceptorService,\n {\n provide: HTTP_INTERCEPTORS,\n useClass: NgHttpCachingInterceptorService,\n multi: true,\n },\n ]\n})\nexport class NgHttpCachingModule {\n static forRoot(\n ngHttpCachingConfig?: NgHttpCachingConfig\n ): ModuleWithProviders<NgHttpCachingModule> {\n const providers: Provider[] = [\n {\n provide: NG_HTTP_CACHING_CONFIG,\n useValue: ngHttpCachingConfig,\n },\n ];\n // Forward optional ng-simple-state adapter config\n if (ngHttpCachingConfig?.store instanceof NgHttpCachingNgSimpleStateSentinel) {\n providers.push(ngHttpCachingConfig.store.adapterClass);\n if (ngHttpCachingConfig.store.adapterConfig) {\n providers.push({\n provide: NG_HTTP_CACHING_NG_SIMPLE_STATE_CONFIG,\n useValue: ngHttpCachingConfig.store.adapterConfig,\n });\n }\n }\n return {\n ngModule: NgHttpCachingModule,\n providers,\n };\n }\n}\n","import { makeEnvironmentProviders, Provider } from \"@angular/core\";\nimport { HTTP_INTERCEPTORS } from \"@angular/common/http\";\nimport { NgHttpCachingInterceptorService } from \"./ng-http-caching-interceptor.service\";\nimport { NG_HTTP_CACHING_CONFIG, NgHttpCachingConfig, NgHttpCachingService } from \"./ng-http-caching.service\";\nimport {\n NG_HTTP_CACHING_NG_SIMPLE_STATE_CONFIG,\n NgHttpCachingNgSimpleStateSentinel,\n} from \"./storage/ng-http-caching-ng-simple-state-sentinel\";\n\nexport function provideNgHttpCaching(ngHttpCachingConfig?: NgHttpCachingConfig) {\n const providers: Provider[] = [\n NgHttpCachingService,\n {\n provide: HTTP_INTERCEPTORS,\n useClass: NgHttpCachingInterceptorService,\n multi: true,\n },\n NgHttpCachingInterceptorService,\n ];\n if (ngHttpCachingConfig) {\n providers.push({\n provide: NG_HTTP_CACHING_CONFIG,\n useValue: ngHttpCachingConfig,\n });\n // If the user chose the ng-simple-state adapter, forward\n // the optional adapter config so it's available via DI.\n if (ngHttpCachingConfig.store instanceof NgHttpCachingNgSimpleStateSentinel) {\n providers.push(ngHttpCachingConfig.store.adapterClass);\n if (ngHttpCachingConfig.store.adapterConfig) {\n providers.push({\n provide: NG_HTTP_CACHING_NG_SIMPLE_STATE_CONFIG,\n useValue: ngHttpCachingConfig.store.adapterConfig,\n });\n }\n }\n }\n return makeEnvironmentProviders(providers);\n}\n","import { NgHttpCachingStorageInterface } from './ng-http-caching-storage.interface';\nimport { NgHttpCachingEntry } from '../ng-http-caching.service';\nimport { HttpHeaders, HttpParams, HttpRequest, HttpResponse } from '@angular/common/http';\n\nconst KEY_PREFIX = 'NgHttpCaching::';\n\nexport interface NgHttpCachingStorageEntry {\n url: string;\n response: string;\n request: string;\n addedTime: number;\n version: string;\n}\n\nexport const serializeRequest = (req: HttpRequest<any>): string => {\n const request = req.clone(); // Make a clone, useful for doing destructive things\n return JSON.stringify({\n headers: Object.fromEntries( // Just a helper to make this into an object, not really required but makes the output nicer\n request.headers.keys().map( // Get all of the headers\n (key: string) => [key, request.headers.getAll(key)] // Get all of the corresponding values for the headers\n )\n ),\n method: request.method, // The Request Method, e.g. GET, POST, DELETE\n url: request.url, // The URL\n params: Object.fromEntries( // Just a helper to make this into an object, not really required but makes the output nicer\n Array.from(request.params.keys()).map(\n (key: string) => [key, request.params.getAll(key)]\n )\n ), // The request parameters\n withCredentials: request.withCredentials, // Whether credentials are being sent\n responseType: request.responseType, // The response type\n body: request.serializeBody() // Serialize the body, all well and good since we are working on a clone\n });\n}\n\nexport const serializeResponse = (res: HttpResponse<any>): string => {\n const response = res.clone();\n return JSON.stringify({\n headers: Object.fromEntries( // Just a helper to make this into an object, not really required but makes the output nicer\n response.headers.keys().map( // Get all of the headers\n (key: string) => [key, response.headers.getAll(key)] // Get all of the corresponding values for the headers\n )\n ),\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n body: response.body // Serialize the body, all well and good since we are working on a clone\n });\n}\n\nexport const deserializeRequest = <T = unknown>(req: string): HttpRequest<T> => {\n const request = JSON.parse(req);\n const headers = new HttpHeaders(request.headers);\n let params = new HttpParams();\n for (const parameter in request.params) {\n for (const paramValue of request.params[parameter]) {\n params = params.append(parameter, paramValue);\n }\n }\n return new HttpRequest(request.method, request.url, request.body, {\n headers,\n params,\n responseType: request.responseType,\n withCredentials: request.withCredentials\n });\n}\n\nexport const deserializeResponse = <T = unknown>(res: string): HttpResponse<T> => {\n const response = JSON.parse(res);\n return new HttpResponse<T>({\n url: response.url,\n headers: new HttpHeaders(response.headers),\n body: response.body,\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nexport class NgHttpCachingBrowserStorage implements NgHttpCachingStorageInterface {\n\n constructor(private storage: Storage) { }\n\n get size(): number {\n let count = 0;\n for (let i = 0, e = this.storage.length; i < e; i++) {\n const key = this.storage.key(i);\n if (key && key.startsWith(KEY_PREFIX)) {\n count++;\n }\n }\n return count;\n }\n\n clear(): void {\n for (let i = this.storage.length - 1; i >= 0; i--) {\n const key = this.storage.key(i);\n if (key && key.startsWith(KEY_PREFIX)) {\n this.storage.removeItem(key);\n }\n }\n }\n\n delete(key: string): boolean {\n if (!key) {\n return false;\n }\n if (!key.startsWith(KEY_PREFIX)) {\n key = KEY_PREFIX + key;\n }\n this.storage.removeItem(key);\n return true;\n }\n\n forEach(callbackfn: (value: NgHttpCachingEntry, key: string) => void): void {\n // iterate this.storage\n for (let i = 0, e = this.storage.length; i < e; i++) {\n const keyWithPrefix = this.storage.key(i);\n if (keyWithPrefix && keyWithPrefix.startsWith(KEY_PREFIX)) {\n const value = this.get(keyWithPrefix);\n if (value) {\n const keyWithoutPrefix = keyWithPrefix.substring(KEY_PREFIX.length);\n callbackfn(value, keyWithoutPrefix);\n }\n }\n }\n }\n\n get(key: string): Readonly<NgHttpCachingEntry> | undefined {\n if (!key) {\n return undefined;\n }\n if (!key.startsWith(KEY_PREFIX)) {\n key = KEY_PREFIX + key;\n }\n const item = this.storage.getItem(key);\n if (item) {\n try {\n const parsedItem: NgHttpCachingStorageEntry = JSON.parse(item);\n return this.deserialize(parsedItem);\n } catch (e) {\n console.error('Failed to parse cached entry:', key, e);\n this.storage.removeItem(key);\n return undefined;\n }\n }\n return undefined;\n }\n\n has(key: string): boolean {\n if (!key) {\n return false;\n }\n if (!key.startsWith(KEY_PREFIX)) {\n key = KEY_PREFIX + key;\n }\n return !!this.storage.getItem(key);\n }\n\n set(key: string, value: NgHttpCachingEntry): void {\n if (!key) {\n return;\n }\n if (!key.startsWith(KEY_PREFIX)) {\n key = KEY_PREFIX + key;\n }\n try {\n const unParsedItem: NgHttpCachingStorageEntry = this.serialize(value);\n this.storage.setItem(key, JSON.stringify(unParsedItem));\n } catch (error) {\n if ((error as Error).name === 'QuotaExceededError') {\n // Handle storage quota exceeded\n this.clear(); // Clear all cache entries\n }\n console.error('Failed to serialize cache entry:', key, error);\n }\n }\n\n protected serialize(value: NgHttpCachingEntry): NgHttpCachingStorageEntry {\n return {\n url: value.url,\n response: serializeResponse(value.response),\n request: serializeRequest(value.request),\n addedTime: value.addedTime,\n version: value.version\n };\n }\n\n protected deserialize(value: NgHttpCachingStorageEntry): NgHttpCachingEntry {\n return {\n url: value.url,\n response: deserializeResponse(value.response),\n request: deserializeRequest(value.request),\n addedTime: value.addedTime,\n version: value.version\n };\n }\n}\n\n","import { NgHttpCachingBrowserStorage } from './ng-http-caching-browser-storage';\r\n\r\nexport class NgHttpCachingLocalStorage extends NgHttpCachingBrowserStorage {\r\n\r\n constructor() {\r\n super(localStorage);\r\n }\r\n}\r\n\r\nexport const withNgHttpCachingLocalStorage = () => new NgHttpCachingLocalStorage();","import { NgHttpCachingBrowserStorage } from './ng-http-caching-browser-storage';\r\n\r\nexport class NgHttpCachingSessionStorage extends NgHttpCachingBrowserStorage {\r\n\r\n constructor() {\r\n super(sessionStorage);\r\n }\r\n}\r\n\r\nexport const withNgHttpCachingSessionStorage = () => new NgHttpCachingSessionStorage();","/*\n * Public API Surface of ng-http-caching\n */\nexport * from './lib/ng-http-caching-interceptor.service';\nexport * from './lib/ng-http-caching.service';\nexport * from './lib/ng-http-caching.module';\nexport * from './lib/ng-http-caching-provider';\nexport * from './lib/storage/ng-http-caching-storage.interface';\nexport * from './lib/storage/ng-http-caching-memory-storage';\nexport * from './lib/storage/ng-http-caching-local-storage';\nexport * from './lib/storage/ng-http-caching-session-storage';\nexport * from './lib/storage/ng-http-caching-browser-storage';\nexport * from './lib/storage/ng-http-caching-ng-simple-state-sentinel';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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