feat: buildKeyGenerator and ids with req.data by default

This commit is contained in:
arthurfiorette 2022-01-14 18:46:20 -03:00
parent fa2c6e3204
commit 9379fce100
No known key found for this signature in database
GPG Key ID: 9D190CD53C53C555
8 changed files with 197 additions and 48 deletions

View File

@ -18,7 +18,11 @@ See more about storages [here](pages/storages).
The function used to create different keys for each request. Defaults to a function that
priorizes the id, and if not specified, a string is generated using the `method`,
`baseURL`, `params`, and `url`.
`baseURL`, `params`, `data` and `url`.
The
[default](https://github.com/arthurfiorette/axios-cache-interceptor/blob/main/src/util/key-generator.ts)
id generation can clarify this idea.
## `waiting`

View File

@ -36,3 +36,33 @@ console.log('Cache 2:', await Axios.storage.get(id2));
The
[default](https://github.com/arthurfiorette/axios-cache-interceptor/blob/main/src/util/key-generator.ts)
id generation can clarify this idea.
## Joining requests
Everything that is used to treat two requests as same or not, is done by the `generateKey`
property.
By default, it uses the `method`, `baseURL`, `params`, `data` and `url` properties from
the request object into an hashcode generated by the `object-code` library.
You can make two "different" requests share the same cache with this property.
An example:
```js #runkit
const axios = require('axios');
const { setupCache, buildKeyGenerator } = require('axios-cache-interceptor');
const generator = buildKeyGenerator(({ headers }) => {
// In this imaginary example, two requests will
// be treated as the same if their x-cache-server header is the same.
// The result of this function, being a object or not, will be
// hashed by `object-code` library.
return headers?.['x-cache-server'] || 'not-set';
});
const axios = mockAxios({
generateKey: keyGenerator
});
```

View File

@ -47,7 +47,8 @@
"homepage": "https://axios-cache-interceptor.js.org",
"dependencies": {
"cache-parser": "^1.1.2",
"fast-defer": "^1.1.3"
"fast-defer": "^1.1.3",
"object-code": "^1.0.1"
},
"resolutions": {
"colors": "1.4.0"

View File

@ -1,41 +1,70 @@
import type { Method } from 'axios';
import { code } from 'object-code';
import type { CacheRequestConfig } from '../cache/axios';
import type { KeyGenerator } from './types';
// Remove first and last '/' char, if present
const SLASHES_REGEX = /^\/|\/$/g;
const stringifyObject = (obj?: unknown) =>
obj !== undefined
? JSON.stringify(obj, obj === null ? undefined : Object.keys(obj as object).sort())
: '{}';
/**
* Builds an generator that received the {@link CacheRequestConfig} and should return a
* string id for it.
*/
export function buildKeyGenerator<R = unknown, D = unknown>(
hash: false,
generator: KeyGenerator
): KeyGenerator<R, D>;
export const defaultKeyGenerator: KeyGenerator = ({
baseURL = '',
url = '',
method = 'get',
params,
data,
id
}) => {
if (id) {
return id;
/**
* Builds an generator that received the {@link CacheRequestConfig} and has it's return
* value hashed by {@link code}.
*
* ### You can return an object that is hashed into an unique number, example:
*
* ```js
* // This generator will return a hash code.
* // The code will only be the same if url, method and data are the same.
* const generator = buildKeyGenerator(true, ({ url, method, data }) => ({
* url,
* method,
* data
* }));
* ```
*/
export function buildKeyGenerator<R = unknown, D = unknown>(
hash: true,
generator: (options: CacheRequestConfig<R, D>) => unknown
): KeyGenerator<R, D>;
export function buildKeyGenerator<R = unknown, D = unknown>(
hash: boolean,
generator: (options: CacheRequestConfig<R, D>) => unknown
): KeyGenerator<R, D> {
return (request) => {
if (request.id) {
return request.id;
}
// Remove trailing slashes
request.baseURL && (request.baseURL = request.baseURL.replace(SLASHES_REGEX, ''));
request.url && (request.url = request.url.replace(SLASHES_REGEX, ''));
// lowercase method
request.method && (request.method = request.method.toLowerCase() as Method);
const result = generator(request) as string;
return hash ? code(result).toString() : result;
};
}
export const defaultKeyGenerator = buildKeyGenerator(
true,
({ baseURL = '', url = '', method = 'get', params, data }) => {
return {
url: baseURL + (baseURL && url ? '/' : '') + url,
method,
params: params as unknown,
data
};
}
// Remove trailing slashes
baseURL = baseURL.replace(SLASHES_REGEX, '');
url = url.replace(SLASHES_REGEX, '');
return `${
// method
method.toLowerCase()
}::${
// complete url
baseURL + (baseURL && url ? '/' : '') + url
}::${
// query
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
stringifyObject(params)
}::${
// request body
stringifyObject(data)
}`;
};
);

View File

@ -29,7 +29,7 @@ export type CachePredicateObject<R = unknown, D = unknown> = {
};
/** A simple function that receives a cache request config and should return a string id for it. */
export type KeyGenerator = <R = unknown, D = unknown>(
export type KeyGenerator<R = unknown, D = unknown> = (
options: CacheRequestConfig<R, D>
) => string;

View File

@ -234,4 +234,24 @@ describe('tests cache predicate object', () => {
expect(result).toBeDefined();
});
it('request have id no matter what', async () => {
const axios = mockAxios({
methods: ['post'] // only post
});
const req1 = await axios.post('url', { a: 1 });
const req2 = await axios.post('url', { a: 1 });
const req3 = await axios.get('url-2');
expect(req1.id).toBeDefined();
expect(req1.cached).toBe(false);
expect(req2.id).toBeDefined();
expect(req2.cached).toBe(true);
expect(req3.id).toBeDefined();
expect(req3.cached).toBe(false);
});
});

View File

@ -1,4 +1,5 @@
import { defaultKeyGenerator } from '../../src/util/key-generator';
import { buildKeyGenerator, defaultKeyGenerator } from '../../src/util/key-generator';
import { mockAxios } from '../mocks/axios';
describe('tests key generation', () => {
it('should generate different key for and id', () => {
@ -89,14 +90,6 @@ describe('tests key generation', () => {
});
it('tests argument replacement', () => {
const key = defaultKeyGenerator({
baseURL: 'http://example.com',
url: '',
params: { a: 1, b: 2 }
});
expect(key).toBe('get::http://example.com::{"a":1,"b":2}::{}');
const groups = [
['http://example.com', '/http://example.com'],
['http://example.com', '/http://example.com/'],
@ -127,7 +120,7 @@ describe('tests key generation', () => {
defaultKeyGenerator({ ...def, data: undefined })
];
expect(dataProps).toStrictEqual([...new Set(dataProps)]);
expect(new Set(dataProps).size).toBe(dataProps.length);
const paramsProps = [
defaultKeyGenerator({ ...def, params: 23 }),
@ -135,10 +128,74 @@ describe('tests key generation', () => {
defaultKeyGenerator({ ...def, params: -453 }),
defaultKeyGenerator({ ...def, params: 'string' }),
defaultKeyGenerator({ ...def, params: new Date() }),
defaultKeyGenerator({ ...def, params: Symbol() }),
defaultKeyGenerator({ ...def, params: null }),
defaultKeyGenerator({ ...def, params: undefined })
];
expect(paramsProps).toStrictEqual([...new Set(paramsProps)]);
expect(new Set(paramsProps).size).toBe(paramsProps.length);
});
it('tests buildKeyGenerator & hash: false', async () => {
const keyGenerator = buildKeyGenerator(false, ({ headers }) => {
return headers?.['x-req-header'] || 'not-set';
});
const axios = mockAxios({ generateKey: keyGenerator });
const { id } = await axios.get('random-url', {
data: Math.random(),
headers: {
'x-req-header': 'my-custom-id'
}
});
const { id: id2 } = await axios.get('other-url', {
data: Math.random() * 2,
headers: {
'x-req-header': 'my-custom-id'
}
});
const { id: id3 } = await axios.get('other-url', {
data: Math.random() * 2
});
expect(id).toBe('my-custom-id');
expect(id).toBe(id2);
expect(id3).toBe('not-set');
});
it('tests buildKeyGenerator & hash: true', async () => {
const keyGenerator = buildKeyGenerator(true, ({ headers }) => {
return headers?.['x-req-header'] || 'not-set';
});
const axios = mockAxios({ generateKey: keyGenerator });
const { id } = await axios.get('random-url', {
data: Math.random(),
headers: {
'x-req-header': 'my-custom-id'
}
});
const { id: id2 } = await axios.get('other-url', {
data: Math.random() * 2,
headers: {
'x-req-header': 'my-custom-id'
}
});
const { id: id3 } = await axios.get('other-url', {
data: Math.random() * 2
});
expect(id).toBe(id2);
expect(id).not.toBe('my-custom-id'); // hashed value
expect(id3).not.toBe(id);
expect(id3).not.toBe(id2);
expect(id3).not.toBe('not-set');
});
});

View File

@ -1878,6 +1878,7 @@ __metadata:
eslint-plugin-prettier: ^4.0.0
fast-defer: ^1.1.3
jest: ^27.4.7
object-code: ^1.0.1
prettier: ^2.5.1
prettier-plugin-jsdoc: ^0.3.30
prettier-plugin-organize-imports: ^2.3.4
@ -4859,6 +4860,13 @@ __metadata:
languageName: node
linkType: hard
"object-code@npm:^1.0.1":
version: 1.0.1
resolution: "object-code@npm:1.0.1"
checksum: 0a914caf04b28c0baa6b7423a16475a7f6bcbdbda79dc0035e8977462abe82549b33c8135c850f06359234cd1ea0504eab3743905994dbae2ea465ff737a55de
languageName: node
linkType: hard
"once@npm:^1.3.0":
version: 1.4.0
resolution: "once@npm:1.4.0"