feat(ajax): add maxCacheSize option

This commit is contained in:
Martin Pool 2022-05-13 18:55:50 +02:00 committed by Ahmet Yeşil
parent 915de370d7
commit 447383bd14
11 changed files with 355 additions and 100 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/ajax': minor
---
Add a `maxCacheSize` cache option to specify a max size for the whole cache

View file

@ -207,7 +207,7 @@ export class Ajax {
for (const intercept of this._responseInterceptors) {
// In this instance we actually do want to await for each sequence
// eslint-disable-next-line no-await-in-loop
interceptedResponse = await intercept(interceptedResponse);
interceptedResponse = await intercept(/** @type CacheResponse */ (interceptedResponse));
}
return interceptedResponse;
}

View file

@ -3,47 +3,86 @@ import './typedef.js';
export default class Cache {
constructor() {
/**
* @type {{ [requestId: string]: { createdAt: number, response: CacheResponse } }}
* @type CachedRequests
* @private
*/
this._cachedRequests = {};
/**
* @type {number}
* @private
*/
this._size = 0;
}
/**
* Store an item in the cache
* @param {string} requestId key by which the request is stored
* @param {Response} response the cached response
* @param {CacheResponse} response the cached response
* @param {number} size the response size
*/
set(requestId, response) {
set(requestId, response, size = 0) {
if (this._cachedRequests[requestId]) {
this.delete(requestId);
}
this._cachedRequests[requestId] = {
createdAt: Date.now(),
size,
response,
};
this._size += size;
}
/**
* Retrieve an item from the cache
* @param {string} requestId key by which the cache is stored
* @param {number} maxAge maximum age of a cached request to serve from cache, in milliseconds
* @param {object} options
* @param {number} [options.maxAge] maximum age of a cached request to serve from cache, in milliseconds
* @param {number} [options.maxResponseSize] maximum size of a cached request to serve from cache, in bytes
* @returns {CacheResponse | undefined}
*/
get(requestId, maxAge = 0) {
get(requestId, { maxAge = Infinity, maxResponseSize = Infinity } = {}) {
const isNumber = (/** @type any */ num) => Number(num) === num;
const cachedRequest = this._cachedRequests[requestId];
if (!cachedRequest) {
return;
return undefined;
}
const cachedRequestAge = Date.now() - cachedRequest.createdAt;
if (Number.isFinite(maxAge) && cachedRequestAge < maxAge) {
// eslint-disable-next-line consistent-return
// maxAge and maxResponseSize should both be numbers
if (!isNumber(maxAge)) {
return undefined;
}
if (!isNumber(maxResponseSize)) {
return undefined;
}
if (Date.now() >= cachedRequest.createdAt + maxAge) {
return undefined;
}
if (cachedRequest.size > maxResponseSize) {
return undefined;
}
return cachedRequest.response;
}
}
/**
* Delete the item with the given requestId from the cache
* @param {string } requestId the request id to delete from the cache
*/
delete(requestId) {
const cachedRequest = this._cachedRequests[requestId];
if (!cachedRequest) {
return;
}
this._size -= cachedRequest.size;
delete this._cachedRequests[requestId];
}
@ -59,7 +98,28 @@ export default class Cache {
});
}
/**
* Truncate the cache to the given size, according to a First-In-First-Out (FIFO) policy
*
* @param {number} maxAllowedCacheSize
*/
truncateTo(maxAllowedCacheSize) {
if (this._size <= maxAllowedCacheSize) return;
const requests = this._cachedRequests;
const sortedRequestIds = Object.keys(requests).sort(
(a, b) => requests[a].createdAt - requests[b].createdAt,
);
for (const requestId of sortedRequestIds) {
this.delete(requestId);
if (this._size <= maxAllowedCacheSize) return;
}
}
reset() {
this._cachedRequests = {};
this._size = 0;
}
}

View file

@ -84,6 +84,7 @@ export const extendCacheOptions = ({
invalidateUrlsRegex,
contentTypes,
maxResponseSize,
maxCacheSize,
}) => ({
useCache,
methods,
@ -93,6 +94,7 @@ export const extendCacheOptions = ({
invalidateUrlsRegex,
contentTypes,
maxResponseSize,
maxCacheSize,
});
/**
@ -107,6 +109,7 @@ export const validateCacheOptions = ({
invalidateUrlsRegex,
contentTypes,
maxResponseSize,
maxCacheSize,
} = {}) => {
if (useCache !== undefined && typeof useCache !== 'boolean') {
throw new Error('Property `useCache` must be a `boolean`');
@ -132,6 +135,9 @@ export const validateCacheOptions = ({
if (maxResponseSize !== undefined && !Number.isFinite(maxResponseSize)) {
throw new Error('Property `maxResponseSize` must be a finite `number`');
}
if (maxCacheSize !== undefined && !Number.isFinite(maxCacheSize)) {
throw new Error('Property `maxCacheSize` must be a finite `number`');
}
};
/**

View file

@ -2,44 +2,48 @@
import '../typedef.js';
import {
ajaxCache,
resetCacheSession,
extendCacheOptions,
validateCacheOptions,
invalidateMatchingCache,
pendingRequestStore,
isCurrentSessionId,
pendingRequestStore,
resetCacheSession,
validateCacheOptions,
} from '../cacheManager.js';
/**
* Tests whether the request method is supported according to the `cacheOptions`
* @param {ValidatedCacheOptions} cacheOptions
* @param {string[]} methods
* @param {string} method
* @returns {boolean}
*/
const isMethodSupported = (cacheOptions, method) =>
cacheOptions.methods.includes(method.toLowerCase());
const isMethodSupported = (methods, method) => methods.includes(method.toLowerCase());
/**
* Tests whether the response content type is supported by the `contentTypes` whitelist
* @param {Response} response
* @param {CacheOptions} cacheOptions
* @param {string[]|undefined} contentTypes
* @returns {boolean} `true` if the contentTypes property is not an array, or if the value of the Content-Type header is in the array
*/
const isResponseContentTypeSupported = (response, { contentTypes } = {}) => {
const isResponseContentTypeSupported = (response, contentTypes) => {
if (!Array.isArray(contentTypes)) return true;
return contentTypes.includes(String(response.headers.get('Content-Type')));
};
/**
* Tests whether the response size is not too large to be cached according to the `maxResponseSize` property
* @param {Response} response
* @param {CacheOptions} cacheOptions
* @returns {boolean} `true` if the `maxResponseSize` property is not larger than zero, or if the Content-Length header is not present, or if the value of the header is not larger than the `maxResponseSize` property
* @returns {Promise<number>}
*/
const isResponseSizeSupported = (response, { maxResponseSize } = {}) => {
const responseSize = +(response.headers.get('Content-Length') || 0);
const getResponseSize = async response =>
Number(response.headers.get('Content-Length')) || (await response.clone().blob()).size || 0;
/**
* Tests whether the response size is not too large to be cached according to the `maxResponseSize` property
* @param {number|undefined} responseSize
* @param {number|undefined} maxResponseSize
* @returns {boolean} `true` if the `maxResponseSize` property is not larger than zero, or if the response size is not known, or if the value of the header is not larger than the `maxResponseSize` property
*/
const isResponseSizeSupported = (responseSize, maxResponseSize) => {
if (!maxResponseSize) return true;
if (!responseSize) return true;
@ -63,17 +67,20 @@ const createCacheRequestInterceptor =
...request.cacheOptions,
});
const { useCache, requestIdFunction, methods, contentTypes, maxAge, maxResponseSize } =
cacheOptions;
// store cacheOptions and cacheSessionId in the request, to use it in the response interceptor.
request.cacheOptions = cacheOptions;
request.cacheSessionId = cacheSessionId;
if (!cacheOptions.useCache) {
if (!useCache) {
return request;
}
const requestId = cacheOptions.requestIdFunction(request);
const requestId = requestIdFunction(request);
if (!isMethodSupported(cacheOptions, request.method)) {
if (!isMethodSupported(methods, request.method)) {
invalidateMatchingCache(requestId, cacheOptions);
return request;
}
@ -84,15 +91,11 @@ const createCacheRequestInterceptor =
await pendingRequest;
}
const cachedResponse = ajaxCache.get(requestId, cacheOptions.maxAge);
if (
cachedResponse &&
isResponseContentTypeSupported(cachedResponse, cacheOptions) &&
isResponseSizeSupported(cachedResponse, cacheOptions)
) {
const cachedResponse = ajaxCache.get(requestId, { maxAge, maxResponseSize });
if (cachedResponse && isResponseContentTypeSupported(cachedResponse, contentTypes)) {
// Return the response from cache
request.cacheOptions = request.cacheOptions ?? { useCache: false };
/** @type {CacheResponse} */
const response = cachedResponse.clone();
response.request = request;
response.fromCache = true;
@ -109,27 +112,35 @@ const createCacheRequestInterceptor =
* @param {CacheOptions} globalCacheOptions
* @returns {ResponseInterceptor}
*/
const createCacheResponseInterceptor =
globalCacheOptions => /** @param {CacheResponse} response */ async response => {
const createCacheResponseInterceptor = globalCacheOptions => async responseParam => {
const response = /** @type {CacheResponse} */ (responseParam);
if (!response.request) {
throw new Error('Missing request in response');
}
const cacheOptions = extendCacheOptions({
const { requestIdFunction, methods, contentTypes, maxResponseSize, maxCacheSize } =
extendCacheOptions({
...globalCacheOptions,
...response.request.cacheOptions,
});
if (!response.fromCache && isMethodSupported(cacheOptions, response.request.method)) {
const requestId = cacheOptions.requestIdFunction(response.request);
if (!response.fromCache && isMethodSupported(methods, response.request.method)) {
const requestId = requestIdFunction(response.request);
const responseSize = maxCacheSize || maxResponseSize ? await getResponseSize(response) : 0;
if (
isCurrentSessionId(response.request.cacheSessionId) &&
isResponseContentTypeSupported(response, cacheOptions) &&
isResponseSizeSupported(response, cacheOptions)
isResponseContentTypeSupported(response, contentTypes) &&
isResponseSizeSupported(responseSize, maxResponseSize)
) {
// Cache the response
ajaxCache.set(requestId, response.clone());
ajaxCache.set(requestId, response.clone(), responseSize);
// Truncate the cache if needed
if (maxCacheSize) {
ajaxCache.truncateTo(maxCacheSize);
}
}
// Mark the pending request as resolved

View file

@ -13,6 +13,7 @@
* @typedef {import('../types/types').CacheResponseRequest} CacheResponseRequest
* @typedef {import('../types/types').CacheRequest} CacheRequest
* @typedef {import('../types/types').CacheResponse} CacheResponse
* @typedef {import('../types/types').CachedRequests} CachedRequests
* @typedef {import('../types/types').CachedRequestInterceptor} CachedRequestInterceptor
* @typedef {import('../types/types').CachedResponseInterceptor} CachedResponseInterceptor
*/

View file

@ -71,10 +71,23 @@ describe('Ajax', () => {
// When
// @ts-expect-error
const ajax1 = new Ajax(config);
const result = ajax1.options?.cacheOptions?.getCacheIdentifier;
const defaultCacheIdentifierFunction = ajax1.options?.cacheOptions?.getCacheIdentifier;
// Then
expect(result).not.to.be.undefined;
expect(result).to.be.a('function');
expect(defaultCacheIdentifierFunction).not.to.be.undefined;
expect(defaultCacheIdentifierFunction).to.be.a('function');
expect(defaultCacheIdentifierFunction()).to.equal('_default');
});
it('can set options through a setter after the object has been created', () => {
// Given
const ajax1 = new Ajax({ jsonPrefix: 'prefix1' });
expect(ajax1.options.jsonPrefix).to.equal('prefix1');
// When
ajax1.options = { ...ajax1.options, jsonPrefix: 'prefix2' };
// Then
expect(ajax1.options.jsonPrefix).to.equal('prefix2');
});
});
@ -168,6 +181,22 @@ describe('Ajax', () => {
expect(response.body).to.eql({ a: 1, b: 2 });
});
});
it('throws on invalid JSON responses', async () => {
fetchStub.returns(Promise.resolve(new Response('invalid-json')));
let thrown = false;
try {
await ajax.fetchJson('/foo');
} catch (e) {
// https://github.com/microsoft/TypeScript/issues/20024 open issue, can't type catch clause in param
const _e = /** @type {Error} */ (e);
expect(_e).to.be.an.instanceOf(Error);
expect(_e.message).to.equal('Failed to parse response from as JSON.');
thrown = true;
}
expect(thrown).to.be.true;
});
});
describe('request and response interceptors', () => {

View file

@ -32,8 +32,12 @@ describe('Cache', () => {
const cache = new Cache();
cache._cachedRequests = {
requestId1: { createdAt: Date.now() - TWO_MINUTES_IN_MS, response: 'cached data 1' },
requestId2: { createdAt: Date.now(), response: 'cached data 2' },
requestId1: {
createdAt: Date.now() - TWO_MINUTES_IN_MS,
response: 'cached data 1',
size: 1000,
},
requestId2: { createdAt: Date.now(), response: 'cached data 2', size: 100 },
};
it('returns undefined if no cached request found for requestId', () => {
@ -41,7 +45,7 @@ describe('Cache', () => {
const maxAge = TEN_MINUTES_IN_MS;
const expected = undefined;
// When
const result = cache.get('nonCachedRequestId', maxAge);
const result = cache.get('nonCachedRequestId', { maxAge });
// Then
expect(result).to.equal(expected);
});
@ -51,17 +55,7 @@ describe('Cache', () => {
const maxAge = 'some string';
const expected = undefined;
// When
const result = cache.get('requestId1', maxAge);
// Then
expect(result).to.equal(expected);
});
it('returns undefined if maxAge is not finite', () => {
// Given
const maxAge = 1 / 0;
const expected = undefined;
// When
const result = cache.get('requestId1', maxAge);
const result = cache.get('requestId1', { maxAge });
// Then
expect(result).to.equal(expected);
});
@ -71,7 +65,7 @@ describe('Cache', () => {
const maxAge = -10;
const expected = undefined;
// When
const result = cache.get('requestId1', maxAge);
const result = cache.get('requestId1', { maxAge });
// Then
expect(result).to.equal(expected);
});
@ -81,7 +75,7 @@ describe('Cache', () => {
const maxAge = A_MINUTE_IN_MS;
const expected = undefined;
// When
const result = cache.get('requestId1', maxAge);
const result = cache.get('requestId1', { maxAge });
// Then
expect(result).to.equal(expected);
});
@ -91,7 +85,59 @@ describe('Cache', () => {
const maxAge = TEN_MINUTES_IN_MS;
const expected = cache._cachedRequests?.requestId1?.response;
// When
const result = cache.get('requestId1', maxAge);
const result = cache.get('requestId1', { maxAge });
// Then
expect(result).to.deep.equal(expected);
});
it('returns the cached value if maxAge is Infinity', () => {
// Given
const maxAge = 1 / 0;
const expected = 'cached data 1';
// When
const result = cache.get('requestId1', { maxAge });
// Then
expect(result).to.equal(expected);
});
it('returns the cached value if neither maxAge nor maxSize is specified', () => {
// Given
const expected = 'cached data 1';
// When
const result = cache.get('requestId1');
// Then
expect(result).to.equal(expected);
});
it('returns undefined if maxResponseSize is set and is smaller than the recorded size of the cache item', () => {
// Given
const maxAge = TEN_MINUTES_IN_MS;
const maxResponseSize = 100;
const expected = undefined;
// When
const result = cache.get('requestId1', { maxAge, maxResponseSize });
// Then
expect(result).to.equal(expected);
});
it('returns undefined if maxResponseSize is set and is not a number', () => {
// Given
const maxAge = TEN_MINUTES_IN_MS;
const maxResponseSize = 'nine thousand';
const expected = undefined;
// When
const result = cache.get('requestId1', { maxAge, maxResponseSize });
// Then
expect(result).to.equal(expected);
});
it('gets the cached request by requestId if maxResponseSize is set and is greater than the recorded size of the cache item', () => {
// Given
const maxAge = TEN_MINUTES_IN_MS;
const maxResponseSize = 10000;
const expected = cache._cachedRequests?.requestId1?.response;
// When
const result = cache.get('requestId1', { maxAge, maxResponseSize });
// Then
expect(result).to.deep.equal(expected);
});
@ -108,8 +154,8 @@ describe('Cache', () => {
cache.set('requestId1', response1);
cache.set('requestId2', response2);
// Then
expect(cache.get('requestId1', maxAge)).to.equal(response1);
expect(cache.get('requestId2', maxAge)).to.equal(response2);
expect(cache.get('requestId1', { maxAge })).to.equal(response1);
expect(cache.get('requestId2', { maxAge })).to.equal(response2);
});
it('updates the `response` for the given `requestId`, if already cached', () => {
@ -121,11 +167,11 @@ describe('Cache', () => {
// When
cache.set('requestId1', response);
// Then
expect(cache.get('requestId1', maxAge)).to.equal(response);
expect(cache.get('requestId1', { maxAge })).to.equal(response);
// When
cache.set('requestId1', updatedResponse);
// Then
expect(cache.get('requestId1', maxAge)).to.equal(updatedResponse);
expect(cache.get('requestId1', { maxAge })).to.equal(updatedResponse);
});
});
@ -140,13 +186,13 @@ describe('Cache', () => {
cache.set('requestId1', response1);
cache.set('requestId2', response2);
// Then
expect(cache.get('requestId1', maxAge)).to.equal(response1);
expect(cache.get('requestId2', maxAge)).to.equal(response2);
expect(cache.get('requestId1', { maxAge })).to.equal(response1);
expect(cache.get('requestId2', { maxAge })).to.equal(response2);
// When
cache.delete('requestId1');
// Then
expect(cache.get('requestId1', maxAge)).to.be.undefined;
expect(cache.get('requestId2', maxAge)).to.equal(response2);
expect(cache.get('requestId1', { maxAge })).to.be.undefined;
expect(cache.get('requestId2', { maxAge })).to.equal(response2);
});
it('deletes cache by regex', () => {
@ -161,15 +207,15 @@ describe('Cache', () => {
cache.set('requestId2', response2);
cache.set('anotherRequestId', response3);
// Then
expect(cache.get('requestId1', maxAge)).to.equal(response1);
expect(cache.get('requestId2', maxAge)).to.equal(response2);
expect(cache.get('anotherRequestId', maxAge)).to.equal(response3);
expect(cache.get('requestId1', { maxAge })).to.equal(response1);
expect(cache.get('requestId2', { maxAge })).to.equal(response2);
expect(cache.get('anotherRequestId', { maxAge })).to.equal(response3);
// When
cache.deleteMatching(/^requestId/);
// Then
expect(cache.get('requestId1', maxAge)).to.be.undefined;
expect(cache.get('requestId2', maxAge)).to.be.undefined;
expect(cache.get('anotherRequestId', maxAge)).to.equal(response3);
expect(cache.get('requestId1', { maxAge })).to.be.undefined;
expect(cache.get('requestId2', { maxAge })).to.be.undefined;
expect(cache.get('anotherRequestId', { maxAge })).to.equal(response3);
});
});
@ -181,16 +227,54 @@ describe('Cache', () => {
const response1 = 'response of request1';
const response2 = 'response of request2';
// When
cache.set('requestId1', response1);
cache.set('requestId2', response2);
cache.set('requestId1', response1, { maxAge: 1 });
cache.set('requestId2', response2, { maxAge: 2 });
// Then
expect(cache.get('requestId1', maxAge)).to.equal(response1);
expect(cache.get('requestId2', maxAge)).to.equal(response2);
expect(cache.get('requestId1', { maxAge })).to.equal(response1, 3);
expect(cache.get('requestId2', { maxAge })).to.equal(response2, 4);
// When
cache.reset();
// Then
expect(cache.get('requestId1', maxAge)).to.be.undefined;
expect(cache.get('requestId2', maxAge)).to.be.undefined;
expect(cache.get('requestId1', { maxAge })).to.be.undefined;
expect(cache.get('requestId2', { maxAge })).to.be.undefined;
});
});
describe('cache.truncateTo', () => {
let cache;
beforeEach(() => {
cache = new Cache();
cache.set('requestId1', 'response1', 123);
cache.set('requestId2', 'response2', 321);
cache.set('requestId3', 'response3', 111);
expect(cache._size).to.equal(555);
});
it('removes the oldest item if the cache is too large', () => {
cache.truncateTo(500);
expect(cache._size).to.equal(432);
});
it('removes items until the specified size is reached', () => {
cache.truncateTo(200);
expect(cache._size).to.equal(111);
});
it('removes everything if the size is smaller than the newest thing', () => {
cache.truncateTo(100);
expect(cache._size).to.equal(0);
});
it('does nothing when the size is larger than the current size', () => {
cache.truncateTo(600);
expect(cache._size).to.equal(555);
});
});
});

View file

@ -76,6 +76,7 @@ describe('cacheManager', () => {
invalidateUrlsRegex: invalidateUrlsRegexResult,
contentTypes,
maxResponseSize,
maxCacheSize,
} = extendCacheOptions({ invalidateUrls, invalidateUrlsRegex });
// Assert
expect(useCache).to.be.false;
@ -86,6 +87,7 @@ describe('cacheManager', () => {
expect(invalidateUrlsRegexResult).to.equal(invalidateUrlsRegex);
expect(contentTypes).to.be.undefined;
expect(maxResponseSize).to.be.undefined;
expect(maxCacheSize).to.be.undefined;
});
it('the DEFAULT_GET_REQUEST_ID function throws when called with no arguments', () => {
@ -295,6 +297,26 @@ describe('cacheManager', () => {
);
});
});
describe('the maxCacheSize property', () => {
it('accepts a finite number', () => {
expect(() => validateCacheOptions({ maxCacheSize: 42 })).not.to.throw;
});
it('accepts undefined', () => {
expect(() => validateCacheOptions({ maxCacheSize: undefined })).not.to.throw;
});
it('does not accept anything else', () => {
// @ts-ignore
expect(() => validateCacheOptions({ maxCacheSize: 'string' })).to.throw(
'Property `maxCacheSize` must be a finite `number`',
);
expect(() => validateCacheOptions({ maxCacheSize: Infinity })).to.throw(
'Property `maxCacheSize` must be a finite `number`',
);
});
});
});
describe('invalidateMatchingCache', () => {

View file

@ -5,6 +5,10 @@ import { Ajax } from '../../index.js';
import { extendCacheOptions, resetCacheSession, ajaxCache } from '../../src/cacheManager.js';
import { createCacheInterceptors } from '../../src/interceptors/cacheInterceptors.js';
const MOCK_RESPONSE = 'mock response';
const getUrl = (/** @type {string} */ url) => new URL(url, window.location.href).toString();
/** @type {Ajax} */
let ajax;
@ -53,7 +57,7 @@ describe('cache interceptors', () => {
beforeEach(() => {
ajax = new Ajax();
mockResponse = new Response('mock response');
mockResponse = new Response(MOCK_RESPONSE);
fetchStub = sinon.stub(window, 'fetch');
fetchStub.resolves(mockResponse);
ajaxRequestSpy = sinon.spy(ajax, 'fetch');
@ -361,7 +365,7 @@ describe('cache interceptors', () => {
newCacheId();
fetchStub.returns(
Promise.resolve(
new Response('mock response', { status: 206, headers: { 'x-foo': 'x-bar' } }),
new Response(MOCK_RESPONSE, { status: 206, headers: { 'x-foo': 'x-bar' } }),
),
);
@ -654,7 +658,7 @@ describe('cache interceptors', () => {
const cacheOptions = {
invalidateUrls: [
requestIdFunction({
url: new URL('/test-invalid-url', window.location.href).toString(),
url: getUrl('/test-invalid-url'),
params: { foo: 1, bar: 2 },
}),
],
@ -719,10 +723,39 @@ describe('cache interceptors', () => {
const firstResponse = await firstRequest;
expect(firstResponse).to.equal('mock response');
expect(firstResponse).to.equal(MOCK_RESPONSE);
// @ts-ignore
expect(ajaxCache._cachedRequests).to.deep.equal({});
expect(fetchStub.callCount).to.equal(1);
});
describe('when maxCacheSize is set', () => {
beforeEach(() => {
newCacheId();
addCacheInterceptors(ajax, {
useCache: true,
maxCacheSize: MOCK_RESPONSE.length * 3 + 1,
});
});
it('discards the last added request when the cache is full', async () => {
// @ts-ignore use this private field
expect(ajaxCache._size).to.equal(0);
await ajax.fetch('/test1');
await ajax.fetch('/test2');
await ajax.fetch('/test3');
await ajax.fetch('/test4');
// @ts-ignore use this private field
expect(Object.keys(ajaxCache._cachedRequests)).to.deep.equal(
['/test2', '/test3', '/test4'].map(getUrl),
);
expect(fetchStub.callCount).to.equal(4);
// @ts-ignore use this private field
expect(ajaxCache._size).to.equal(MOCK_RESPONSE.length * 3);
});
});
});
});

View file

@ -42,6 +42,7 @@ export interface CacheOptions {
requestIdFunction?: RequestIdFunction;
contentTypes?: string[];
maxResponseSize?: number;
maxCacheSize?: number;
}
export interface CacheOptionsWithIdentifier extends CacheOptions {
@ -72,13 +73,16 @@ export interface CacheResponseRequest {
export interface CacheResponseExtension {
request: CacheResponseRequest;
data: object | string;
fromCache?: boolean;
}
export type CacheRequest = Request & Partial<CacheRequestExtension>;
export type CacheResponse = Response & Partial<CacheResponseExtension>;
export interface CacheResponse extends Response, CacheResponseExtension {
clone: () => CacheResponse;
}
export type CachedRequests = { [requestId: string]: { createdAt: number, size: number, response: CacheResponse } };
export type CachedRequestInterceptor = (
request: CacheRequest,