implement new expanded response caching logic

This commit is contained in:
Randall Schmidt 2020-11-27 21:46:49 -05:00
parent 01bc48594e
commit bc92aa6865
6 changed files with 125 additions and 42 deletions

3
.gitignore vendored

@ -102,3 +102,6 @@ dist
# TernJS port file
.tern-port
# Other
.cache

@ -1,2 +1,3 @@
.eslintrc.js
test
.cache

22
.vscode/launch.json vendored Normal file

@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"args": [
"--colors",
"${workspaceFolder}/test"
],
"internalConsoleOptions": "openOnSessionStart",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
}
]
}

@ -7,9 +7,48 @@ function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
async function getResponse(cacheDirPath, requestArguments, bodyFunctionName) {
class Response {
constructor(raw, cacheFilePath) {
Object.assign(this, raw);
this.cacheFilePath = cacheFilePath;
}
text() {
return this.bodyBuffer.toString();
}
json() {
return JSON.parse(this.bodyBuffer.toString());
}
buffer() {
return this.bodyBuffer;
}
ejectFromCache() {
return fs.promises.unlink(this.cacheFilePath);
}
}
async function createRawResponse(fetchRes) {
const buffer = await fetchRes.buffer();
return {
status: fetchRes.status,
statusText: fetchRes.statusText,
type: fetchRes.type,
url: fetchRes.url,
useFinalURL: fetchRes.useFinalURL,
ok: fetchRes.ok,
headers: fetchRes.headers,
redirected: fetchRes.redirected,
bodyBuffer: buffer,
};
}
async function getResponse(cacheDirPath, requestArguments) {
const [url, requestInit, ...rest] = requestArguments;
const requestParams = requestInit && requestInit.body
const requestParams = requestInit.body
? ({ ...requestInit, body: typeof requestInit.body === 'object' ? requestInit.body.toString() : requestInit.body })
: requestInit;
@ -17,40 +56,13 @@ async function getResponse(cacheDirPath, requestArguments, bodyFunctionName) {
const cachedFilePath = path.join(cacheDirPath, `${cacheHash}.json`);
try {
const body = JSON.parse(await fs.promises.readFile(cachedFilePath));
if (bodyFunctionName === 'buffer') {
return Buffer.from(body);
}
return body;
const rawResponse = JSON.parse(await fs.promises.readFile(cachedFilePath));
return new Response(rawResponse);
} catch (err) {
const fetchResponse = await fetch(...requestArguments);
const bodyResponse = await fetchResponse[bodyFunctionName]();
await fs.promises.writeFile(cachedFilePath, JSON.stringify(bodyResponse));
return bodyResponse;
}
}
class ResponseWrapper {
constructor(cacheDirPath, requestArguments) {
this.cacheDirPath = cacheDirPath;
this.requestArguments = requestArguments;
}
text() {
return getResponse(this.cacheDirPath, this.requestArguments, this.text.name);
}
json() {
return getResponse(this.cacheDirPath, this.requestArguments, this.json.name);
}
buffer() {
return getResponse(this.cacheDirPath, this.requestArguments, this.buffer.name);
}
textConverted() {
return getResponse(this.cacheDirPath, this.requestArguments, this.textConverted.name);
const rawResponse = createRawResponse(fetchResponse);
await fs.promises.writeFile(cachedFilePath, JSON.stringify(rawResponse));
return new Response(rawResponse);
}
}
@ -59,16 +71,11 @@ function createFetch(cacheDirPath) {
return async (...args) => {
if (!madeDir) {
try {
await fs.promises.mkdir(cacheDirPath, { recursive: true });
} catch (err) {
// Ignore.
}
await fs.promises.mkdir(cacheDirPath, { recursive: true });
madeDir = true;
}
return new ResponseWrapper(cacheDirPath, args);
return getResponse(cacheDirPath, args);
};
}

@ -4,7 +4,7 @@
"description": "node-fetch with a persistent cache.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "mocha",
"lint": "./node_modules/.bin/eslint .",
"lintfix": "./node_modules/.bin/eslint . --fix"
},

@ -0,0 +1,50 @@
const assert = require('assert');
const path = require('path');
const fetch = require('../index.js')(path.join(__dirname, '..', '.cache'));
const TWO_HUNDRED_URL = 'https://httpbin.org/status/200';
const FOUR_HUNDRED_URL = 'https://httpbin.org/status/400';
const THREE_HUNDRED_TWO_URL = 'https://httpbin.org/status/302';
describe('Basic property tests', function() {
it('Has a status property', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.strictEqual(res.status, 200);
});
it('Has a statusText property', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.strictEqual(res.statusText, 'OK');
});
it('Has a type property', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.strictEqual(res.type, 'basic');
});
it('Has a url property', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.strictEqual(res.url, TWO_HUNDRED_URL);
});
it('Has a useFinalURL property', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.strictEqual(res.useFinalURL, true);
});
it('Has an ok property', async function() {
const res = await fetch(FOUR_HUNDRED_URL);
assert.strictEqual(res.ok, false);
assert.strictEqual(res.status, 400);
});
it('Has a headers property', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.notStrictEqual(res.headers, undefined);
});
it('Has a redirected property', async function() {
const res = await fetch(THREE_HUNDRED_TWO_URL);
assert.strictEqual(res.redirected, true);
});
});