UNPKG

ng-http-caching

Version:

Cache for HTTP requests in Angular application.

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