Skip to content

Commit 7fa91e8

Browse files
authored
feat(plugin): add withCaching plugin (#49)
* feat(plugin): add `withCaching` plugin * Create many-mails-tie.md * fix(with-caching): export drivers
1 parent 8f8e392 commit 7fa91e8

22 files changed

+656
-7
lines changed

.changeset/breezy-coins-buy.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@composite-fetcher/with-caching': minor
3+
'@composite-fetcher/with-logging': minor
4+
---
5+
6+
feat(plugin): add `withCaching` plugin

.changeset/many-mails-tie.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@composite-fetcher/with-caching": patch
3+
"@composite-fetcher/with-logging": patch
4+
---
5+
6+
feat(plugin): add `withCaching` plugin

composite-fetcher.code-workspace

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
"name": "Fetcher Core",
55
"path": "packages/core"
66
},
7-
{
7+
{
88
"name": "withLogging Plugin",
99
"path": "packages/with-logging"
1010
},
11+
{
12+
"name": "withCaching Plugin",
13+
"path": "packages/with-caching"
14+
},
1115
{
1216
"name": "Docs",
1317
"path": "apps/docs"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.eslintrc.cjs
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
root: true,
3+
extends: ['@composite-fetcher/eslint-config/base'],
4+
parserOptions: {
5+
project: ['./tsconfig.json'],
6+
},
7+
};

packages/with-caching/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# `@composite-fetcher/with-caching`
2+
3+
## Description
4+
5+
withCaching is a simple fetcher core plugin to manage request caching with different drivers
6+
7+
- Teofanis Papadopulos
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
moduleNameMapper: {
4+
'^@/(.*)$': '<rootDir>/src/$1',
5+
},
6+
transform: {
7+
'^.+\\.(j|t)s?$': [
8+
'ts-jest',
9+
{
10+
tsconfig: 'tsconfig.jest.json',
11+
},
12+
],
13+
},
14+
extensionsToTreatAsEsm: ['.ts', '.tsx'],
15+
resetModules: false,
16+
clearMocks: true,
17+
collectCoverage: true,
18+
collectCoverageFrom: [
19+
'<rootDir>/src/**/*.{js,jsx,ts,tsx}',
20+
'!<rootDir>/src/**/_*.{js,jsx,ts,tsx}',
21+
'!<rootDir>/src/**/index.{js,jsx,ts,tsx}',
22+
'!**/node_modules/**',
23+
'!**/*.d.ts',
24+
],
25+
coverageThreshold: {
26+
global: {
27+
branches: 75,
28+
functions: 75,
29+
lines: 75,
30+
statements: 75,
31+
},
32+
},
33+
transformIgnorePatterns: [],
34+
coverageReporters: ['json-summary', 'text', 'lcov'],
35+
coverageDirectory: '<rootDir>/coverage',
36+
testEnvironment: 'jest-environment-node',
37+
};

packages/with-caching/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@composite-fetcher/with-caching",
3+
"description": "withCaching is a simple fetcher core plugin to manage request caching with different drivers",
4+
"version": "0.0.0",
5+
"author": "Teofanis Papadopulos",
6+
"type": "module",
7+
"main": "./dist/index.js",
8+
"module": "./dist/index.mjs",
9+
"types": "./dist/index.d.ts",
10+
"sideEffects": false,
11+
"files": [
12+
"dist/**"
13+
],
14+
"exports": {
15+
".": {
16+
"import": "./dist/index.js",
17+
"require": "./dist/index.cjs",
18+
"types": "./dist/index.d.ts"
19+
}
20+
},
21+
"scripts": {
22+
"build": "tsup src/index.ts --format cjs,esm --dts",
23+
"dev": "pnpm run build -- --watch",
24+
"lint": "eslint \"src/**/*.ts*\"",
25+
"test": "jest",
26+
"format": "pnpm run lint -- --fix && prettier --write \"src/**/*.ts*\" --ignore-path .gitignore",
27+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
28+
},
29+
"devDependencies": {
30+
"@composite-fetcher/eslint-config": "workspace:*",
31+
"@composite-fetcher/tsconfig": "workspace:*",
32+
"@types/isomorphic-fetch": "^0.0.36",
33+
"@types/jest": "^29.5.5",
34+
"@types/node": "^20.7.0",
35+
"fetch-mock": "^9.11.0",
36+
"isomorphic-fetch": "^3.0.0",
37+
"jest": "^29.7.0",
38+
"ts-jest": "^29.1.1"
39+
},
40+
"dependencies": {
41+
"@composite-fetcher/core": "workspace:^"
42+
}
43+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-var-requires
2+
const fetchMock = require('fetch-mock');
3+
4+
const fetch = fetchMock.sandbox();
5+
6+
module.exports = fetch;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import InMemoryCacheDriver from '@/drivers/InMemoryCacheDriver';
3+
import _fetchMock from 'isomorphic-fetch';
4+
5+
type FetchMock = typeof import('fetch-mock');
6+
const fetchMock = _fetchMock as unknown as FetchMock;
7+
8+
describe('InMemoryCacheDriver', () => {
9+
let cacheDriver: InMemoryCacheDriver;
10+
11+
let mockResponse: (bodyContent: string) => Response;
12+
13+
beforeEach(() => {
14+
cacheDriver = new InMemoryCacheDriver();
15+
global.Response = fetchMock.config.Response as unknown as typeof Response;
16+
mockResponse = (bodyContent: string) =>
17+
new Response(bodyContent, {
18+
headers: {
19+
'Content-Type': 'text/plain',
20+
},
21+
});
22+
});
23+
24+
it('should add an item to the cache', async () => {
25+
const response = mockResponse('value1');
26+
await cacheDriver.set('key1', response);
27+
expect(await cacheDriver.has('key1')).toBeTruthy();
28+
});
29+
30+
it('should retrieve an item from the cache', async () => {
31+
const response = mockResponse('value1');
32+
await cacheDriver.set('key1', response);
33+
const cachedResponse = await cacheDriver.get('key1');
34+
expect(cachedResponse).toBeDefined();
35+
expect(await cachedResponse!.text()).toBe('value1');
36+
});
37+
38+
it('should return null for a non-existent item', async () => {
39+
const cachedResponse = await cacheDriver.get('nonexistent');
40+
expect(cachedResponse).toBeNull();
41+
});
42+
43+
it('should delete an item from the cache', async () => {
44+
const response = mockResponse('value1');
45+
await cacheDriver.set('key1', response);
46+
expect(await cacheDriver.has('key1')).toBeTruthy();
47+
48+
await cacheDriver.delete('key1');
49+
expect(await cacheDriver.has('key1')).toBeFalsy();
50+
});
51+
52+
it('should clear all items from the cache', async () => {
53+
await cacheDriver.set('key1', mockResponse('value1'));
54+
await cacheDriver.set('key2', mockResponse('value2'));
55+
56+
await cacheDriver.clear();
57+
58+
expect(await cacheDriver.has('key1')).toBeFalsy();
59+
expect(await cacheDriver.has('key2')).toBeFalsy();
60+
});
61+
62+
it('should not retrieve expired items', async () => {
63+
const oneSecondAgo = new Date(Date.now() - 1000);
64+
await cacheDriver.set('key1', mockResponse('value1'), oneSecondAgo);
65+
66+
const cachedResponse = await cacheDriver.get('key1');
67+
expect(cachedResponse).toBeNull();
68+
});
69+
70+
it('should automatically remove expired items when checking its existence', async () => {
71+
const oneSecondAgo = new Date(Date.now() - 1000);
72+
await cacheDriver.set('key1', mockResponse('value1'), oneSecondAgo);
73+
74+
// Checking its existence will internally remove it because it's expired
75+
const exists = await cacheDriver.has('key1');
76+
expect(exists).toBeFalsy();
77+
});
78+
79+
it('should retrieve items that are not yet expired', async () => {
80+
const tenSecondsFromNow = new Date(Date.now() + 10000);
81+
await cacheDriver.set('key1', mockResponse('value1'), tenSecondsFromNow);
82+
83+
const cachedResponse = await cacheDriver.get('key1');
84+
expect(cachedResponse).toBeDefined();
85+
expect(await cachedResponse!.text()).toBe('value1');
86+
});
87+
});

0 commit comments

Comments
 (0)