custom caching
This commit is contained in:
parent
1ae909985e
commit
4cae72dce2
@ -118,7 +118,7 @@ const fetchBuilder, { MemoryCache } = require('node-fetch-cache');
|
|||||||
const fetch = fetchBuilder.withCache(new MemoryCache(options));
|
const fetch = fetchBuilder.withCache(new MemoryCache(options));
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported options:
|
Options:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
@ -140,6 +140,7 @@ const fetch = fetchBuilder.withCache(new FileSystemCache(options));
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
|
cacheDirectory: '/my/cache/directory/path', // Specify where to keep the cache. If undefined, '.cache' is used by default. If this directory does not exist, it will be created.
|
||||||
ttl: 1000, // Time to live. How long (in ms) responses remain cached before being automatically ejected. If undefined, responses are never automatically ejected from the cache.
|
ttl: 1000, // Time to live. How long (in ms) responses remain cached before being automatically ejected. If undefined, responses are never automatically ejected from the cache.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
29
classes/caching/file_system_cache.js
Normal file
29
classes/caching/file_system_cache.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const FPersist = require('fpersist');
|
||||||
|
const KeyTimeout = require('./key_timeout.js');
|
||||||
|
|
||||||
|
module.exports = class FileSystemCache {
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.ttl = options.ttl;
|
||||||
|
this.keyTimeout = new KeyTimeout();
|
||||||
|
|
||||||
|
const cacheDirectory = options.cacheDirectory || '.cache';
|
||||||
|
this.cache = new FPersist(cacheDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this.cache.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(key) {
|
||||||
|
this.keyTimeout.clearTimeout(key);
|
||||||
|
return this.cache.deleteItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key, value) {
|
||||||
|
await this.cache.setItem(key, value);
|
||||||
|
|
||||||
|
if (typeof this.ttl === 'number') {
|
||||||
|
this.keyTimeout.updateTimeout(key, this.ttl, () => this.remove(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
classes/caching/key_timeout.js
Normal file
16
classes/caching/key_timeout.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = class KeyTimeout {
|
||||||
|
constructor() {
|
||||||
|
this.timeoutHandleForKey = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(key) {
|
||||||
|
clearTimeout(this.timeoutHandleForKey[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeout(key, durationMs, callback) {
|
||||||
|
this.clearTimeout(key);
|
||||||
|
this.timeoutHandleForKey[key] = setTimeout(() => {
|
||||||
|
callback();
|
||||||
|
}, durationMs);
|
||||||
|
}
|
||||||
|
}
|
33
classes/caching/memory_cache.js
Normal file
33
classes/caching/memory_cache.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const KeyTimeout = require('./key_timeout.js');
|
||||||
|
|
||||||
|
module.exports = class MemoryCache {
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.ttl = options.ttl;
|
||||||
|
this.keyTimeout = new KeyTimeout();
|
||||||
|
|
||||||
|
if (options.global && !globalThis.nodeFetchCache) {
|
||||||
|
globalThis.nodeFetchCache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cache = options.global
|
||||||
|
? globalThis.nodeFetchCache
|
||||||
|
: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this.cache[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(key) {
|
||||||
|
this.keyTimeout.clearTimeout(key);
|
||||||
|
delete this.cache[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
this.cache[key] = value;
|
||||||
|
|
||||||
|
if (typeof this.ttl === 'number') {
|
||||||
|
this.keyTimeout.updateTimeout(key, this.ttl, () => this.remove(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,9 @@ const stream = require('stream');
|
|||||||
const Headers = require('./headers.js');
|
const Headers = require('./headers.js');
|
||||||
|
|
||||||
class Response {
|
class Response {
|
||||||
constructor(raw, cacheFilePath, fromCache) {
|
constructor(raw, ejectSelfFromCache, fromCache) {
|
||||||
Object.assign(this, raw);
|
Object.assign(this, raw);
|
||||||
this.cacheFilePath = cacheFilePath;
|
this.ejectSelfFromCache = ejectSelfFromCache;
|
||||||
this.headers = new Headers(raw.headers);
|
this.headers = new Headers(raw.headers);
|
||||||
this.fromCache = fromCache;
|
this.fromCache = fromCache;
|
||||||
this.bodyUsed = false;
|
this.bodyUsed = false;
|
||||||
@ -40,14 +40,8 @@ class Response {
|
|||||||
return this.consumeBody();
|
return this.consumeBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
async ejectFromCache() {
|
ejectFromCache() {
|
||||||
try {
|
return this.ejectSelfFromCache();
|
||||||
await fs.promises.unlink(this.cacheFilePath);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code !== 'ENOENT') {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
index.js
34
index.js
@ -4,6 +4,7 @@ const { URLSearchParams } = require('url');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Response = require('./classes/response.js');
|
const Response = require('./classes/response.js');
|
||||||
|
const MemoryCache = require('./classes/caching/memory_cache.js');
|
||||||
|
|
||||||
const CACHE_VERSION = 2;
|
const CACHE_VERSION = 2;
|
||||||
|
|
||||||
@ -88,32 +89,27 @@ async function createRawResponse(fetchRes) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getResponse(cacheDirPath, requestArguments) {
|
async function getResponse(cache, requestArguments) {
|
||||||
const cacheKey = getCacheKey(requestArguments);
|
const cacheKey = getCacheKey(requestArguments);
|
||||||
const cachedFilePath = path.join(cacheDirPath, `${cacheKey}.json`);
|
const cachedValue = await cache.get(cacheKey);
|
||||||
|
|
||||||
try {
|
const ejectSelfFromCache = () => cache.remove(cacheKey);
|
||||||
const rawResponse = JSON.parse(await fs.promises.readFile(cachedFilePath));
|
|
||||||
return new Response(rawResponse, cachedFilePath, true);
|
if (cachedValue) {
|
||||||
} catch (err) {
|
return new Response(cachedValue, ejectSelfFromCache, true);
|
||||||
|
} else {
|
||||||
const fetchResponse = await fetch(...requestArguments);
|
const fetchResponse = await fetch(...requestArguments);
|
||||||
const rawResponse = await createRawResponse(fetchResponse);
|
const rawResponse = await createRawResponse(fetchResponse);
|
||||||
await fs.promises.writeFile(cachedFilePath, JSON.stringify(rawResponse));
|
await cache.set(cacheKey, rawResponse);
|
||||||
return new Response(rawResponse, cachedFilePath, false);
|
return new Response(rawResponse, ejectSelfFromCache, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFetch(cacheDirPath) {
|
function createFetchWithCache(cache) {
|
||||||
let madeDir = false;
|
const fetch = (...args) => getResponse(cache, args);
|
||||||
|
fetch.withCache = createFetchWithCache;
|
||||||
|
|
||||||
return async (...args) => {
|
return fetch;
|
||||||
if (!madeDir) {
|
|
||||||
await fs.promises.mkdir(cacheDirPath, { recursive: true });
|
|
||||||
madeDir = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getResponse(cacheDirPath, args);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = createFetch;
|
module.exports = createFetchWithCache(new MemoryCache());
|
||||||
|
BIN
test/expected_png.png
Normal file
BIN
test/expected_png.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
@ -5,6 +5,7 @@ const rimraf = require('rimraf');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const FetchCache = require('../index.js');
|
const FetchCache = require('../index.js');
|
||||||
const { URLSearchParams } = require('url');
|
const { URLSearchParams } = require('url');
|
||||||
|
const MemoryCache = require('../classes/caching/memory_cache.js');
|
||||||
|
|
||||||
const CACHE_PATH = path.join(__dirname, '..', '.cache');
|
const CACHE_PATH = path.join(__dirname, '..', '.cache');
|
||||||
const expectedPngBuffer = fs.readFileSync(path.join(__dirname, 'expected_png.png'));
|
const expectedPngBuffer = fs.readFileSync(path.join(__dirname, 'expected_png.png'));
|
||||||
@ -28,7 +29,7 @@ function post(body) {
|
|||||||
|
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
rimraf.sync(CACHE_PATH);
|
rimraf.sync(CACHE_PATH);
|
||||||
fetch = FetchCache(CACHE_PATH);
|
fetch = FetchCache.withCache(new MemoryCache());
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Basic property tests', function() {
|
describe('Basic property tests', function() {
|
||||||
|
Reference in New Issue
Block a user