Skip to content

Commit

Permalink
fix: caching with different payload
Browse files Browse the repository at this point in the history
  • Loading branch information
denbon05 committed Apr 13, 2024
1 parent 0951fc9 commit 862dbdc
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 33 deletions.
14 changes: 10 additions & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"plugins": [
"@typescript-eslint"
],
"rules": {
"import/extensions": "off",
"no-console": "warn",
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/no-shadow": "error",
"no-underscore-dangle": "off",
"indent": "off",
"@typescript-eslint/indent": ["error", 2]
"@typescript-eslint/indent": [
"error",
2
],
"@typescript-eslint/consistent-type-imports": "warn"
}
}
}
2 changes: 1 addition & 1 deletion __tests__/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable import/prefer-default-export */
import fastify from 'fastify';
import lcache from '../../lib';
import { ICacheOptions } from '../../lib/types/lcache';
import type { ICacheOptions } from '../../lib/types/lcache';

export const getApp = (options: ICacheOptions = {}) => {
const app = fastify();
Expand Down
29 changes: 21 additions & 8 deletions __tests__/lcache.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '../lib/types/fastify';
import { FastifyInstance } from 'fastify';
import '@/types/fastify';
import type { FastifyInstance } from 'fastify';
import { getApp } from './helpers';

describe('Caching with default options', () => {
Expand All @@ -18,7 +18,8 @@ describe('Caching with default options', () => {
});

test('Cache is working', async () => {
const spy = jest.spyOn(app.lcache, 'get');
const spyGet = jest.spyOn(app.lcache, 'get');
const spySet = jest.spyOn(app.lcache, 'set');
const getPing = async () =>
app.inject({
method: 'GET',
Expand All @@ -27,14 +28,17 @@ describe('Caching with default options', () => {

const res1 = await getPing();
expect(res1.body).toBe('pong');
expect(spySet).toHaveBeenCalledTimes(1);

const res2 = await getPing();
// `set` shouldn't be called again
expect(spySet).toHaveBeenCalledTimes(1);
expect(res2.body).toBe('pong');
expect(spy).toHaveBeenCalled();
expect(spyGet).toHaveBeenCalledTimes(1);
});

test('Cache should return same headers as the original request', async () => {
const spy = jest.spyOn(app.lcache, 'get');
const spyGet = jest.spyOn(app.lcache, 'get');
const getJson = async () =>
app.inject({
method: 'GET',
Expand All @@ -45,7 +49,7 @@ describe('Caching with default options', () => {
const res2 = await getJson();

expect(res2.headers['content-type']).toBe(res1.headers['content-type']);
expect(spy).toHaveBeenCalled();
expect(spyGet).toHaveBeenCalledTimes(1);
});
});

Expand All @@ -56,14 +60,17 @@ describe('Caching with custom options', () => {
app = await getApp({
excludeRoutes: ['/date'],
statusesToCache: [200, 201],
methodsToCache: ['GET', 'POST'],
});
});

afterEach(async () => {
await app.close();
});

test('POST method should not be cached', async () => {
test('Response should be cached separately for different payload', async () => {
const spyGet = jest.spyOn(app.lcache, 'get');
const spySet = jest.spyOn(app.lcache, 'set');
const res1 = await app.inject({
method: 'POST',
path: '/post',
Expand All @@ -81,9 +88,12 @@ describe('Caching with custom options', () => {
});

expect(res1.body).not.toBe(res2.body);
expect(spyGet).not.toHaveBeenCalled();
expect(spySet).toHaveBeenCalledTimes(2);
});

test('Excluded routes should not be cached', async () => {
const spySet = jest.spyOn(app.lcache, 'set');
const res1 = await app.inject({
method: 'GET',
path: '/date',
Expand All @@ -95,9 +105,11 @@ describe('Caching with custom options', () => {
});

expect(res1.body).not.toBe(res2.body);
expect(spySet).not.toHaveBeenCalled();
});

test('Method PUT should not be cached when only status code is 201', async () => {
test('PUT method should not be cached when only status code is 201', async () => {
const spySet = jest.spyOn(app.lcache, 'set');
const res1 = await app.inject({
method: 'PUT',
path: '/put',
Expand All @@ -115,5 +127,6 @@ describe('Caching with custom options', () => {
});

expect(res1.body).not.toBe(res2.body);
expect(spySet).not.toHaveBeenCalled();
});
});
11 changes: 10 additions & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export default {
import type { Config } from 'jest';

const jestConfig: Config = {
rootDir: '.',
testEnvironment: 'node',
transform: { '^.+\\.ts?$': 'ts-jest' },
globals: {
Expand All @@ -7,5 +10,11 @@ export default {
},
},
moduleFileExtensions: ['js', 'ts', 'd.ts'],
testRegex: '.*\\.test\\.ts$',
modulePathIgnorePatterns: ['<rootDir>/__tests__/helpers/'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/lib/$1',
},
};

export default jestConfig;
22 changes: 11 additions & 11 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { FastifyInstance, FastifyPluginCallback } from 'fastify';
import fp from 'fastify-plugin';
import type { ICacheOptions } from './types/lcache';
import { formatOptions, shouldBeCached } from './helpers';
import MapStorage from './storage/Map';
import type { ICacheOptions } from '@/types/lcache';
import { formatOptions, shouldBeCached } from '@/helpers';
import MapStorage from '@/storage/Map';
import { buildCacheKey } from '@/utils';

const defaultOpts: ICacheOptions = {
ttlInMinutes: 5,
Expand All @@ -25,26 +26,25 @@ const cache: FastifyPluginCallback<ICacheOptions> = (
const storage = new MapStorage(storageOpts);

instance.addHook('onSend', async (request, reply, payload) => {
const { url, method } = request;
const requestId = url + method;
const cacheKey = buildCacheKey(request);

if (
!storage.has(requestId) &&
!storage.has(cacheKey) &&
shouldBeCached(storageOpts, request, reply.statusCode)
) {
storage.set(requestId, {
storage.set(cacheKey, {
payload,
headers: reply.getHeaders(),
statusCode: reply.statusCode,
});
}
});

instance.addHook('onRequest', async ({ url, method }, reply) => {
const requestId = url + method;
instance.addHook('onRequest', async (request, reply) => {
const cacheKey = buildCacheKey(request);

if (storage.has(requestId)) {
const { headers, payload, statusCode } = storage.get(requestId);
if (storage.has(cacheKey)) {
const { headers, payload, statusCode } = storage.get(cacheKey);
reply.headers(headers);
reply.status(statusCode);
reply.send(payload);
Expand Down
2 changes: 1 addition & 1 deletion lib/storage/Map.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {
import type {
Src,
SrcMeta,
IStorage,
Expand Down
2 changes: 1 addition & 1 deletion lib/types/fastify.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import 'fastify';
import { IStorage } from './storage';
import type { IStorage } from './storage';

// extend fastify instance type on install package
declare module 'fastify' {
Expand Down
13 changes: 13 additions & 0 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable import/prefer-default-export */
import type { FastifyRequest } from 'fastify';
import { createHash } from 'node:crypto';

const hashValue = (text: unknown): string =>
createHash('sha256').update(JSON.stringify(text)).digest('hex');

export const buildCacheKey = (req: FastifyRequest) => {
const { url, method, body } = req;

// if there is a body - add it to the cache key
return body ? `${url}-${method}-${hashValue(body)}` : `${url}-${method}`;
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "fastify-lcache",
"version": "2.0.1",
"version": "2.0.2",
"description": "Light cache plugin for fastify",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "npx jest",
"lint": "npx eslint .",
"prebuild": "rm -fr dist",
"build": "npx tsc"
"build": "npx tsc -p tsconfig.build.json"
},
"keywords": [
"fastify",
Expand Down
11 changes: 11 additions & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": [
"./tsconfig.json"
],
"include": [
"lib/**/*.ts"
],
"exclude": [
"__tests__/**/*.ts"
]
}
11 changes: 7 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
"types": [
"node",
"jest"
]
],
"paths": {
"@/*": [
"./lib/*"
]
},
},
"include": [
"lib/**/*.ts"
],
"exclude": [
"lib/**/*.ts",
"__tests__/**/*.ts"
],
"ts-node": {
Expand Down

0 comments on commit 862dbdc

Please sign in to comment.