51 Commits
3.0 ... 2.0

Author SHA1 Message Date
Randall Schmidt
185b47c8a6 update .npmignore 2021-06-12 19:25:21 -04:00
Randall Schmidt
b0b454de85 update README 2021-06-12 19:24:17 -04:00
Randall Schmidt
7b0835fcf1 make imports work properly 2021-06-12 15:47:06 -04:00
Randall Schmidt
34c8887e51 get rid of blob 2021-06-12 15:43:35 -04:00
Randall Schmidt
3d7b27ceb5 bump version 2021-06-11 17:52:29 -04:00
Randall Schmidt
49f1c0cdc1 add blob support 2021-06-11 16:40:35 -04:00
Randall Schmidt
2e7c35a34c add blob support 2021-06-11 16:38:23 -04:00
Randall Schmidt
3896ae2832 add blob support 2021-06-11 16:37:51 -04:00
Randall Schmidt
2f5409c25c update tests 2021-06-11 15:52:15 -04:00
Randall Schmidt
7f2dba84a3 add more header tests 2021-06-11 15:25:53 -04:00
Randall Schmidt
901e32fa09 add more files to npmignore 2021-06-11 14:17:11 -04:00
Randall Schmidt
ef5ddf69e4 npm ignore nyc_output 2021-06-11 14:12:34 -04:00
Randall Schmidt
6bc5de6581 npm ignore nyc_output 2021-06-11 14:12:34 -04:00
Randall Schmidt
f2a6b048bd add CI file for github actions 2021-06-11 14:12:34 -04:00
Randall Schmidt
2fb975d9a8 add coverage script 2021-06-11 14:12:34 -04:00
Randall Schmidt
41045996ae update readme 2021-06-11 14:12:33 -04:00
Randall Schmidt
ed25ae32ab update readme 2021-06-11 14:12:33 -04:00
Randall Schmidt
7779114055 update readme 2021-06-11 14:12:33 -04:00
Randall Schmidt
831440152a custom caching 2021-06-11 14:12:33 -04:00
Randall Schmidt
ad256800f8 npm audit fix 2021-06-11 14:12:32 -04:00
Randall Schmidt
421c68909e add fpersist as dependency 2021-06-11 14:12:32 -04:00
Randall Schmidt
1f9b533c66 update readme 2021-06-11 14:12:32 -04:00
Randall Schmidt
489da016fd update readme 2021-06-11 14:12:32 -04:00
Randall Schmidt
1e0030f5a3 update readme 2021-06-11 14:12:31 -04:00
Randall Schmidt
fe51a289e1 update readme 2021-06-11 14:12:31 -04:00
Randall Schmidt
1311282f5a update readme 2021-06-11 14:12:31 -04:00
Randall Schmidt
32a50f757a update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
664e2afc59 update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
ba9df959db update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
c46d08d76a update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
28348f511f update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
d676db7e88 update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
ac437a89ed update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
ff09dd8521 update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
9c9d2b88e1 support streaming response 2021-06-11 14:12:28 -04:00
Randall Schmidt
f92bd99968 only allow body to be consumed once, add some tests 2021-06-11 14:12:28 -04:00
Randall Schmidt
c3071e3059 give tests longer timeout 2021-06-11 14:12:28 -04:00
Randall Schmidt
bf4c295e48 add a CACHE_VERSION to invalid caches when versions become incompatible 2021-06-11 14:12:28 -04:00
Randall Schmidt
9241b74dde add support for FormData 2021-06-11 14:12:27 -04:00
Randall Schmidt
3b5adecd7f handle fs read streams 2021-06-11 14:12:27 -04:00
Randall Schmidt
8a338f5f5d refactor cache key calculation to have more space for dealing with different types of bodies 2021-06-11 14:12:27 -04:00
Randall Schmidt
a7fc94c3e9 add raw headers function 2021-06-11 14:12:26 -04:00
Randall Schmidt
facda80789 add tests for string body and URLSearchParams bodies 2021-06-11 14:12:26 -04:00
Randall Schmidt
8e1b57382a dont error if ejectFromCache is called when the response is already not cached 2021-06-11 14:12:26 -04:00
Randall Schmidt
558d63fd40 add husky to enforce lint rules and tests 2021-06-11 14:12:26 -04:00
Randall Schmidt
02653e04c4 fix lint warnings 2021-06-11 14:12:25 -04:00
Randall Schmidt
371d20e933 add test for cache ejection 2021-06-11 14:12:25 -04:00
Randall Schmidt
c452c8d860 add tests, fix flawed logic 2021-06-11 14:12:25 -04:00
Randall Schmidt
bc92aa6865 implement new expanded response caching logic 2021-06-11 14:12:24 -04:00
Randall Schmidt
01bc48594e add test boilerplate 2021-06-11 14:12:24 -04:00
Randall Schmidt
f854cccd23 add mocha for tests 2021-06-11 14:12:24 -04:00
19 changed files with 346 additions and 587 deletions

View File

@ -1,4 +1,4 @@
{
module.exports = {
"env": {
"commonjs": true,
"es6": true,
@ -15,7 +15,5 @@
"ecmaVersion": 2018
},
"rules": {
"import/extensions": "off",
"import/prefer-default-export": "off"
}
}
};

2
.gitignore vendored
View File

@ -105,5 +105,3 @@ dist
# Other
.cache
commonjs

View File

@ -1,4 +1,4 @@
.eslintrc.json
.eslintrc.js
test
.cache
.nyc_output

128
README.md
View File

@ -22,35 +22,86 @@ The next time you `fetch('http://google.com')`, the response will be returned fr
## API
This module's fetch function has almost the exact same API as node-fetch, and you should consult [the node-fetch documentation](https://www.npmjs.com/package/node-fetch) for how to use it.
This module aims to expose the same API as `node-fetch` does for the most common use cases, but may not support some of the less common functions, properties, and use cases.
This module just adds one extra function to the response object:
### const fetch = require('node-fetch-cache');
### res.ejectFromCache(): Promise\<void\>
Load the module.
This function can be used to eject the response from the cache, so that the next request will perform a true HTTP request rather than returning a cached response.
### await fetch(resource [, init])
This module caches ALL responses, even those with 4xx and 5xx response statuses. You can use this function to uncache such responses if desired. For example:
Same arguments as [node-fetch](https://www.npmjs.com/package/node-fetch).
```js
const fetch = require('node-fetch-cache');
Returns a **CachedResponse**.
fetch('http://google.com')
.then(async response => {
if (!response.ok) {
await response.ejectFromCache();
throw new Error('Non-okay response from google.com');
} else {
return response.text();
}
}).then(text => console.log(text));
```
### await CachedResponse.ejectFromCache()
Eject the response from the cache, so that the next request will perform a true HTTP request rather than returning a cached response.
Keep in mind that this module caches **all** responses, even if they return errors. You might want to use this function in certain cases like receiving a 5xx response status, so that you can retry requests.
### await CachedResponse.text()
Returns the body as a string, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### await CachedResponse.json()
Returns the body as a JavaScript object, parsed from JSON, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### await CachedResponse.buffer()
Returns the body as a Buffer, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### CachedResponse.status
Returns the HTTP status code of the response, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### CachedResponse.statusText
Returns a text represention of the response status, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### CachedResponse.ok
Returns true if the request returned a successful response status, false otherwise, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### CachedResponse.redirected
Returns true if the request was redirected, false otherwise, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### CachedResponse.headers
Returns a **ResponseHeaders** object representing the headers of the response, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### ResponseHeaders.entries()
Returns the raw headers as an array of `[key, value]` pairs, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### ResponseHeaders.keys()
Returns an array of all header keys, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### ResponseHeaders.values()
Returns an array of all header values, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### ResponseHeaders.get(key)
Returns the value of the header with the given key, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### ResponseHeaders.has(key)
Returns true if the headers has a value for the given key, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### ResponseHeaders.raw
Returns the headers as an object of `{ "key": "value" }` pairs, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
## Streaming
This module does not support Stream request bodies, except for fs.ReadStream. And when using fs.ReadStream, the cache key is generated based only on the path of the stream, not its content. That means if you stream `/my/desktop/image.png` twice, you will get a cached response the second time, **even if the content of image.png has changed**.
This module supports streams like [node-fetch](https://www.npmjs.com/package/node-fetch) does, but with a couple of caveats you should be aware of if you want to use streams.
Streams don't quite play nice with the concept of caching based on request characteristics, because we would have to read the stream to the end to find out what's in it and hash it into a proper cache key.
1. Response bodies are always read into memory even if you stream them to disk. That means if you need to stream large responses that don't fit into RAM, this module may be unsuitable.
2. When streaming a request body with fs.ReadStream, the cache key is generated based only on the path of the stream, not its content. That means if you stream `/my/desktop/image.png` twice, you will get a cached response the second time, **even if the content of image.png has changed**. This module may be unsuitable if you need to stream files in requests and the content of those files can change.
## Cache Customization
@ -88,8 +139,6 @@ const { fetchBuilder, FileSystemCache } = require('node-fetch-cache');
const fetch = fetchBuilder.withCache(new FileSystemCache(options));
```
Options:
```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.
@ -99,17 +148,42 @@ Options:
### Provide Your Own
You can implement a caching delegate yourself. The cache simply needs to be an object that has `set(key, bodyStream, bodyMeta)`, `get(key)`, and `remove(key)` functions.
You can implement a caching delegate yourself. The cache simply needs to be an object that has `set(key, value)`, `get(key)`, and `remove(key)` functions.
Check the built-in [MemoryCache](https://github.com/mistval/node-fetch-cache/blob/master/src/classes/caching/memory_cache.js) and [FileSystemCache](https://github.com/mistval/node-fetch-cache/blob/master/src/classes/caching/file_system_cache.js) for examples.
The set function must accept a key (which will be a string) and a value (which will be a JSON-serializable JS object) and store them.
The set function must accept a key (which will be a string), a body stream, and a metadata object (which will be a JSON-serializable JS object). It must store these, and then return an object with a `bodyStream` property, containing a fresh, unread stream of the body content, as well as a `metaData` property, containing the same metaData that was passed in.
The get function should accept a key and return whatever value was set for that key (or `undefined`/`null` if there is no value for that key).
The get function should accept a key and return undefined if no cached value is found, or else an object with a `bodyStream` property, containing a stream of the body content, as well as a `metaData` property, containing the metadata that was stored via the `set(key, bodyStream, bodyMeta)` function.
The remove function should accept a key and remove the cached value associated with that key, if any.
The remove function should accept a key and remove the cached value associated with that key, if any. It is also safe for your caching delegate to remove values from the cache arbitrarily if desired (for example if you want to implement a TTL in the caching delegate).
Both functions can be async.
All three functions may be async.
It is safe to remove values from the cache arbitrarily (for example if you implement a TTL in the caching delegate).
Example: you could make and use your own simple memory cache like this:
```js
class MyMemoryCache {
set(key, value) {
this[key] = value;
}
get(key) {
return this[key];
}
remove(key) {
delete this[key];
}
}
const fetchBuilder = require('node-fetch-cache');
fetch = fetchBuilder.withCache(new MyMemoryCache());
fetch('http://google.com')
.then(response => response.text())
.then(text => console.log(text));
```
## Bugs / Help / Feature Requests / Contributing

View 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));
}
}
};

View File

@ -1,4 +1,4 @@
export class KeyTimeout {
module.exports = class KeyTimeout {
constructor() {
this.timeoutHandleForKey = {};
}
@ -13,4 +13,4 @@ export class KeyTimeout {
callback();
}, durationMs);
}
}
};

View File

@ -0,0 +1,26 @@
const KeyTimeout = require('./key_timeout.js');
module.exports = class MemoryCache {
constructor(options = {}) {
this.ttl = options.ttl;
this.keyTimeout = new KeyTimeout();
this.cache = {};
}
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));
}
}
};

33
classes/headers.js Normal file
View File

@ -0,0 +1,33 @@
class Headers {
constructor(rawHeaders) {
this.rawHeaders = rawHeaders;
}
entries() {
return Object.entries(this.rawHeaders)
.sort((e1, e2) => e1[0].localeCompare(e2[0]))
.map(([key, val]) => [key, val[0]]);
}
keys() {
return this.entries().map((e) => e[0]);
}
values() {
return this.entries().map((e) => e[1]);
}
get(name) {
return (this.rawHeaders[name.toLowerCase()] || [])[0] || null;
}
has(name) {
return !!this.get(name);
}
raw() {
return this.rawHeaders;
}
}
module.exports = Headers;

47
classes/response.js Normal file
View File

@ -0,0 +1,47 @@
const stream = require('stream');
const Headers = require('./headers.js');
class Response {
constructor(raw, ejectSelfFromCache, fromCache) {
Object.assign(this, raw);
this.ejectSelfFromCache = ejectSelfFromCache;
this.headers = new Headers(raw.headers);
this.fromCache = fromCache;
this.bodyUsed = false;
if (this.bodyBuffer.type === 'Buffer') {
this.bodyBuffer = Buffer.from(this.bodyBuffer);
}
}
get body() {
return stream.Readable.from(this.bodyBuffer);
}
consumeBody() {
if (this.bodyUsed) {
throw new Error('Error: body used already');
}
this.bodyUsed = true;
return this.bodyBuffer;
}
async text() {
return this.consumeBody().toString();
}
async json() {
return JSON.parse(this.consumeBody().toString());
}
async buffer() {
return this.consumeBody();
}
ejectFromCache() {
return this.ejectSelfFromCache();
}
}
module.exports = Response;

View File

@ -1,4 +0,0 @@
const mod = require('./index.cjs');
module.exports = mod.default;
Object.assign(module.exports, mod);

View File

@ -1,12 +1,12 @@
import fetch, { Request } from 'node-fetch';
import fs from 'fs';
import { URLSearchParams } from 'url';
import crypto from 'crypto';
import locko from 'locko';
import { NFCResponse } from './classes/response.js';
import { MemoryCache } from './classes/caching/memory_cache.js';
const fetch = require('node-fetch');
const fs = require('fs');
const { URLSearchParams } = require('url');
const crypto = require('crypto');
const Response = require('./classes/response.js');
const MemoryCache = require('./classes/caching/memory_cache.js');
const FileSystemCache = require('./classes/caching/file_system_cache.js');
const CACHE_VERSION = 3;
const CACHE_VERSION = 2;
function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
@ -47,92 +47,57 @@ function getBodyCacheKeyJson(body) {
return body.path;
} if (body.toString && body.toString() === '[object FormData]') {
return getFormDataCacheKey(body);
} if (body instanceof Buffer) {
return body.toString();
}
throw new Error('Unsupported body type. Supported body types are: string, number, undefined, null, url.URLSearchParams, fs.ReadStream, FormData');
}
function getRequestCacheKey(req) {
return {
cache: req.cache,
credentials: req.credentials,
destination: req.destination,
headers: req.headers,
integrity: req.integrity,
method: req.method,
redirect: req.redirect,
referrer: req.referrer,
referrerPolicy: req.referrerPolicy,
url: req.url,
body: getBodyCacheKeyJson(req.body),
};
}
function getCacheKey(requestArguments) {
const resource = requestArguments[0];
const init = requestArguments[1] || {};
const resourceCacheKeyJson = resource instanceof Request
? getRequestCacheKey(resource)
: { url: resource };
if (typeof resource !== 'string') {
throw new Error('The first argument must be a string (fetch.Request is not supported).');
}
const resourceCacheKeyJson = { url: resource };
const initCacheKeyJson = { ...init };
resourceCacheKeyJson.body = getBodyCacheKeyJson(resourceCacheKeyJson.body);
initCacheKeyJson.body = getBodyCacheKeyJson(initCacheKeyJson.body);
delete initCacheKeyJson.agent;
return md5(JSON.stringify([resourceCacheKeyJson, initCacheKeyJson, CACHE_VERSION]));
}
async function createRawResponse(fetchRes) {
const buffer = await fetchRes.buffer();
return {
status: fetchRes.status,
statusText: fetchRes.statusText,
type: fetchRes.type,
url: fetchRes.url,
ok: fetchRes.ok,
headers: fetchRes.headers.raw(),
redirected: fetchRes.redirected,
bodyBuffer: buffer,
};
}
async function getResponse(cache, requestArguments) {
const cacheKey = getCacheKey(requestArguments);
let cachedValue = await cache.get(cacheKey);
const cachedValue = await cache.get(cacheKey);
const ejectSelfFromCache = () => cache.remove(cacheKey);
if (cachedValue) {
return new NFCResponse(
cachedValue.bodyStream,
cachedValue.metaData,
ejectSelfFromCache,
true,
);
return new Response(cachedValue, ejectSelfFromCache, true);
}
await locko.lock(cacheKey);
try {
cachedValue = await cache.get(cacheKey);
if (cachedValue) {
return new NFCResponse(
cachedValue.bodyStream,
cachedValue.metaData,
ejectSelfFromCache,
true,
);
}
const fetchResponse = await fetch(...requestArguments);
const serializedMeta = NFCResponse.serializeMetaFromNodeFetchResponse(fetchResponse);
const newlyCachedData = await cache.set(
cacheKey,
fetchResponse.body,
serializedMeta,
);
return new NFCResponse(
newlyCachedData.bodyStream,
newlyCachedData.metaData,
ejectSelfFromCache,
false,
);
} finally {
locko.unlock(cacheKey);
}
const fetchResponse = await fetch(...requestArguments);
const rawResponse = await createRawResponse(fetchResponse);
await cache.set(cacheKey, rawResponse);
return new Response(rawResponse, ejectSelfFromCache, false);
}
function createFetchWithCache(cache) {
@ -144,7 +109,7 @@ function createFetchWithCache(cache) {
const defaultFetch = createFetchWithCache(new MemoryCache());
export default defaultFetch;
export const fetchBuilder = defaultFetch;
export { MemoryCache } from './classes/caching/memory_cache.js';
export { FileSystemCache } from './classes/caching/file_system_cache.js';
module.exports = defaultFetch;
module.exports.fetchBuilder = defaultFetch;
module.exports.MemoryCache = MemoryCache;
module.exports.FileSystemCache = FileSystemCache;

240
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "node-fetch-cache",
"version": "3.0.0",
"version": "2.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -427,22 +427,6 @@
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true
},
"@npmcli/move-file": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
"requires": {
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
}
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -477,6 +461,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"dev": true,
"requires": {
"clean-stack": "^2.0.0",
"indent-string": "^4.0.0"
@ -602,7 +587,8 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"binary-extensions": {
"version": "2.1.0",
@ -614,6 +600,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -647,45 +634,6 @@
"node-releases": "^1.1.71"
}
},
"cacache": {
"version": "15.2.0",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz",
"integrity": "sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw==",
"requires": {
"@npmcli/move-file": "^1.0.1",
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"glob": "^7.1.4",
"infer-owner": "^1.0.4",
"lru-cache": "^6.0.0",
"minipass": "^3.1.1",
"minipass-collect": "^1.0.2",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.2",
"mkdirp": "^1.0.3",
"p-map": "^4.0.0",
"promise-inflight": "^1.0.1",
"rimraf": "^3.0.2",
"ssri": "^8.0.1",
"tar": "^6.0.2",
"unique-filename": "^1.1.1"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"requires": {
"aggregate-error": "^3.0.0"
}
}
}
},
"caching-transform": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
@ -749,11 +697,6 @@
"readdirp": "~3.5.0"
}
},
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
},
"ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
@ -763,7 +706,8 @@
"clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"dev": true
},
"cli-cursor": {
"version": "3.1.0",
@ -861,7 +805,8 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"confusing-browser-globals": {
"version": "1.0.9",
@ -1563,24 +1508,22 @@
"mime-types": "^2.1.12"
}
},
"fpersist": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/fpersist/-/fpersist-1.0.5.tgz",
"integrity": "sha512-WXY+zZXlOo1dU+wS8rqigz5PFu7WHBDd0vcaaWcnu319bPJi/IeWipOmi1PNaHAUqFVSzp1mLpNkgX6g2uLGbQ=="
},
"fromentries": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
"integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
"dev": true
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"requires": {
"minipass": "^3.0.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "2.1.3",
@ -1623,6 +1566,7 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -1866,22 +1810,20 @@
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
},
"infer-owner": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@ -1890,7 +1832,8 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"inquirer": {
"version": "7.1.0",
@ -2323,11 +2266,6 @@
"path-exists": "^3.0.0"
}
},
"locko": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/locko/-/locko-0.0.3.tgz",
"integrity": "sha512-ekhPWcejAum9WHN2ClkFA8RAUTDyYDlRRb4dSq1wCEPhIS6IMsdSKoWHl1qineCrlMEMbeD1/o2uautG4QEc7w=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -2400,14 +2338,6 @@
}
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@ -2442,6 +2372,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2452,47 +2383,6 @@
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"minipass": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
"requires": {
"yallist": "^4.0.0"
}
},
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"requires": {
"minipass": "^3.0.0"
}
},
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@ -2939,6 +2829,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@ -3050,7 +2941,8 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-key": {
"version": "2.0.1",
@ -3124,11 +3016,6 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -3230,26 +3117,9 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
},
"rollup": {
"version": "2.53.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.0.tgz",
"integrity": "sha512-spgrY78Toh+m0+zaOoeaayJKuzFuWy6o1PdFIBMVwRcuxT0xCOX9A5rChyKe+2ruL4lePKWUMImS4mMW1QAkmQ==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
},
"dependencies": {
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
}
"glob": "^7.1.3"
}
},
"run-async": {
@ -3430,14 +3300,6 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
"requires": {
"minipass": "^3.1.1"
}
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
@ -3577,26 +3439,6 @@
}
}
},
"tar": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
}
}
},
"test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@ -3674,22 +3516,6 @@
"is-typedarray": "^1.0.0"
}
},
"unique-filename": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
"integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
"requires": {
"unique-slug": "^2.0.0"
}
},
"unique-slug": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
"integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
"requires": {
"imurmurhash": "^0.1.4"
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
@ -3835,7 +3661,8 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write": {
"version": "1.0.3",
@ -3864,11 +3691,6 @@
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",

View File

@ -1,20 +1,13 @@
{
"name": "node-fetch-cache",
"version": "3.0.0",
"version": "2.0.0",
"description": "node-fetch with a persistent cache.",
"main": "src/index.js",
"type": "module",
"exports": {
"import": "./src/index.js",
"require": "./commonjs/wrapper.cjs"
},
"main": "index.js",
"scripts": {
"buildcjs": "rollup src/index.js --file commonjs/index.cjs --format cjs",
"test": "npm run buildcjs && mocha --timeout 10000 --exit",
"test": "mocha --timeout 10000 --exit",
"coverage": "nyc --reporter=lcov --reporter=text npm test",
"lint": "./node_modules/.bin/eslint .",
"lintfix": "./node_modules/.bin/eslint . --fix",
"prepublishOnly": "npm test"
"lintfix": "./node_modules/.bin/eslint . --fix"
},
"repository": {
"type": "git",
@ -41,13 +34,11 @@
"husky": "^4.3.0",
"mocha": "^8.2.1",
"nyc": "^15.1.0",
"rimraf": "^3.0.2",
"rollup": "^2.53.0"
"rimraf": "^3.0.2"
},
"dependencies": {
"cacache": "^15.2.0",
"locko": "0.0.3",
"node-fetch": "2.6.1"
"fpersist": "^1.0.5",
"node-fetch": "*"
},
"husky": {
"hooks": {

View File

@ -1,84 +0,0 @@
import cacache from 'cacache';
import { Readable } from 'stream';
import { KeyTimeout } from './key_timeout.js';
function getBodyAndMetaKeys(key) {
return [`${key}body`, `${key}meta`];
}
export class FileSystemCache {
constructor(options = {}) {
this.ttl = options.ttl;
this.keyTimeout = new KeyTimeout();
this.cacheDirectory = options.cacheDirectory || '.cache';
}
async get(key) {
const [, metaKey] = getBodyAndMetaKeys(key);
const metaInfo = await cacache.get.info(this.cacheDirectory, metaKey);
if (!metaInfo) {
return undefined;
}
const metaBuffer = await cacache.get.byDigest(this.cacheDirectory, metaInfo.integrity);
const metaData = JSON.parse(metaBuffer);
const { bodyStreamIntegrity, empty } = metaData;
delete metaData.bodyStreamIntegrity;
delete metaData.empty;
const bodyStream = empty
? Readable.from(Buffer.alloc(0))
: cacache.get.stream.byDigest(this.cacheDirectory, bodyStreamIntegrity);
return {
bodyStream,
metaData,
};
}
remove(key) {
const [bodyKey, metaKey] = getBodyAndMetaKeys(key);
this.keyTimeout.clearTimeout(key);
return Promise.all([
cacache.rm.entry(this.cacheDirectory, bodyKey),
cacache.rm.entry(this.cacheDirectory, metaKey),
]);
}
async set(key, bodyStream, metaData) {
const [bodyKey, metaKey] = getBodyAndMetaKeys(key);
const metaCopy = { ...metaData };
this.keyTimeout.clearTimeout(key);
try {
metaCopy.bodyStreamIntegrity = await new Promise((fulfill, reject) => {
bodyStream.pipe(cacache.put.stream(this.cacheDirectory, bodyKey))
.on('integrity', (i) => fulfill(i))
.on('error', (e) => {
reject(e);
});
});
} catch (err) {
if (err.code !== 'ENODATA') {
throw err;
}
metaCopy.empty = true;
}
const metaBuffer = Buffer.from(JSON.stringify(metaCopy));
await cacache.put(this.cacheDirectory, metaKey, metaBuffer);
const cachedData = await this.get(key);
if (typeof this.ttl === 'number') {
this.keyTimeout.updateTimeout(key, this.ttl, () => this.remove(key));
}
return cachedData;
}
}

View File

@ -1,47 +0,0 @@
import { Readable } from 'stream';
import { KeyTimeout } from './key_timeout.js';
function streamToBuffer(stream) {
const chunks = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
stream.on('error', (err) => reject(err));
stream.on('end', () => resolve(Buffer.concat(chunks)));
});
}
export class MemoryCache {
constructor(options = {}) {
this.ttl = options.ttl;
this.keyTimeout = new KeyTimeout();
this.cache = {};
}
get(key) {
const cachedValue = this.cache[key];
if (cachedValue) {
return {
bodyStream: Readable.from(cachedValue.bodyBuffer),
metaData: cachedValue.metaData,
};
}
return undefined;
}
remove(key) {
this.keyTimeout.clearTimeout(key);
delete this.cache[key];
}
async set(key, bodyStream, metaData) {
const bodyBuffer = await streamToBuffer(bodyStream);
this.cache[key] = { bodyBuffer, metaData };
if (typeof this.ttl === 'number') {
this.keyTimeout.updateTimeout(key, this.ttl, () => this.remove(key));
}
return this.get(key);
}
}

View File

@ -1,29 +0,0 @@
import { Response } from 'node-fetch';
const responseInternalSymbol = Object.getOwnPropertySymbols(new Response())[1];
export class NFCResponse extends Response {
constructor(bodyStream, metaData, ejectFromCache, fromCache) {
super(bodyStream, metaData);
this.ejectFromCache = ejectFromCache;
this.fromCache = fromCache;
}
static serializeMetaFromNodeFetchResponse(res) {
const metaData = {
url: res.url,
status: res.status,
statusText: res.statusText,
headers: res.headers.raw(),
size: res.size,
timeout: res.timeout,
counter: res[responseInternalSymbol].counter,
};
return metaData;
}
ejectFromCache() {
return this.ejectSelfFromCache();
}
}

View File

@ -1,18 +0,0 @@
const assert = require('assert');
const fetch = require('../commonjs/wrapper.cjs');
const TWO_HUNDRED_URL = 'https://httpbin.org/status/200';
describe('Commonjs module tests', function() {
it('Can make a request', async function() {
const res = await fetch(TWO_HUNDRED_URL);
assert.strictEqual(res.status, 200);
});
it('Has expected properties', function() {
assert(typeof fetch === 'function');
assert(fetch.MemoryCache);
assert(fetch.FileSystemCache);
assert(fetch.fetchBuilder);
});
});

View File

@ -1,16 +1,11 @@
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import FormData from 'form-data';
import assert from 'assert';
import rimraf from 'rimraf';
import path from 'path';
import { URLSearchParams } from 'url';
import standardFetch from 'node-fetch';
import FetchCache, { MemoryCache, FileSystemCache } from '../src/index.js';
import { Agent } from 'http';
const __dirname = dirname(fileURLToPath(import.meta.url));
const fs = require('fs');
const FormData = require('form-data');
const assert = require('assert');
const rimraf = require('rimraf');
const path = require('path');
const { URLSearchParams } = require('url');
const standardFetch = require('node-fetch');
const FetchCache = require('../index.js');
const CACHE_PATH = path.join(__dirname, '..', '.cache');
const expectedPngBuffer = fs.readFileSync(path.join(__dirname, 'expected_png.png'));
@ -64,11 +59,9 @@ async function dualFetch(...args) {
beforeEach(async function() {
rimraf.sync(CACHE_PATH);
cachedFetch = FetchCache.withCache(new MemoryCache());
cachedFetch = FetchCache.withCache(new FetchCache.MemoryCache());
});
let res;
describe('Basic property tests', function() {
it('Has a status property', async function() {
let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
@ -130,22 +123,22 @@ describe('Header tests', function() {
it('Gets correct header keys', async function() {
let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual([...cachedFetchResponse.headers.keys()], [...standardFetchResponse.headers.keys()]);
assert.deepStrictEqual(cachedFetchResponse.headers.keys(), [...standardFetchResponse.headers.keys()]);
cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual([...cachedFetchResponse.headers.keys()], [...standardFetchResponse.headers.keys()]);
assert.deepStrictEqual(cachedFetchResponse.headers.keys(), [...standardFetchResponse.headers.keys()]);
});
it('Gets correct header values', async function() {
let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates([...cachedFetchResponse.headers.values()]),
removeDates(cachedFetchResponse.headers.values()),
removeDates([...standardFetchResponse.headers.values()]),
);
cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates([...cachedFetchResponse.headers.values()]),
removeDates(cachedFetchResponse.headers.values()),
removeDates([...standardFetchResponse.headers.values()]),
);
});
@ -153,13 +146,13 @@ describe('Header tests', function() {
it('Gets correct header entries', async function() {
let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates([...cachedFetchResponse.headers.entries()]),
removeDates(cachedFetchResponse.headers.entries()),
removeDates([...standardFetchResponse.headers.entries()]),
);
cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates([...cachedFetchResponse.headers.entries()]),
removeDates(cachedFetchResponse.headers.entries()),
removeDates([...standardFetchResponse.headers.entries()]),
);
});
@ -259,8 +252,8 @@ describe('Cache tests', function() {
});
it('Gives different read streams different cache keys', async function() {
const s1 = fs.createReadStream(path.join(__dirname, 'expected_png.png'));
const s2 = fs.createReadStream(path.join(__dirname, '..', 'src', 'index.js'));
const s1 = fs.createReadStream(__filename);
const s2 = fs.createReadStream(path.join(__dirname, '..', 'index.js'));
res = await cachedFetch(TWO_HUNDRED_URL, post(s1));
assert.strictEqual(res.fromCache, false);
@ -270,7 +263,7 @@ describe('Cache tests', function() {
});
it('Gives the same read streams the same cache key', async function() {
const s1 = fs.createReadStream(path.join(__dirname, 'expected_png.png'));
const s1 = fs.createReadStream(__filename);
res = await cachedFetch(TWO_HUNDRED_URL, post(s1));
assert.strictEqual(res.fromCache, false);
@ -306,24 +299,17 @@ describe('Cache tests', function() {
res = await cachedFetch(TWO_HUNDRED_URL, post(data2));
assert.strictEqual(res.fromCache, true);
});
it('Does not error with custom agent with circular properties', async function() {
const agent = new Agent();
agent.agent = agent;
await cachedFetch('http://httpbin.org/status/200', { agent });
})
}).timeout(10000);
describe('Data tests', function() {
it('Supports request objects', async function() {
let request = new standardFetch.Request('https://google.com', { body: 'test', method: 'POST' });
res = await cachedFetch(request);
assert.strictEqual(res.fromCache, false);
request = new standardFetch.Request('https://google.com', { body: 'test', method: 'POST' });
res = await cachedFetch(request);
assert.strictEqual(res.fromCache, true);
it('Does not support Request objects', async function() {
try {
const request = new standardFetch.Request('https://google.com');
await cachedFetch(request);
throw new Error('The above line should have thrown.');
} catch (err) {
assert(err.message.includes('The first argument must be a string (fetch.Request is not supported).'));
}
});
it('Refuses to consume body twice', async function() {
@ -334,7 +320,7 @@ describe('Data tests', function() {
await res.text();
throw new Error('The above line should have thrown.');
} catch (err) {
assert(err.message.includes('body used already for:'));
assert(err.message.includes('Error: body used already'));
}
});
@ -404,21 +390,11 @@ describe('Data tests', function() {
assert(err.message.includes('Unsupported body type'));
}
});
it('Uses cache even if you make multiple requests at the same time', async function() {
const [res1, res2] = await Promise.all([
cachedFetch('http://httpbin.org/status/200'),
cachedFetch('http://httpbin.org/status/200'),
]);
// One should be false, the other should be true
assert(res1.fromCache !== res2.fromCache);
});
}).timeout(10000);
describe('Memory cache tests', function() {
it('Supports TTL', async function() {
cachedFetch = FetchCache.withCache(new MemoryCache({ ttl: 100 }));
cachedFetch = FetchCache.withCache(new FetchCache.MemoryCache({ ttl: 100 }));
let res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(TWO_HUNDRED_URL);
@ -433,7 +409,7 @@ describe('Memory cache tests', function() {
describe('File system cache tests', function() {
it('Supports TTL', async function() {
cachedFetch = FetchCache.withCache(new FileSystemCache({ ttl: 100 }));
cachedFetch = FetchCache.withCache(new FetchCache.FileSystemCache({ ttl: 100 }));
let res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(TWO_HUNDRED_URL);
@ -446,7 +422,7 @@ describe('File system cache tests', function() {
});
it('Can get PNG buffer body', async function() {
cachedFetch = FetchCache.withCache(new FileSystemCache());
cachedFetch = FetchCache.withCache(new FetchCache.FileSystemCache());
res = await cachedFetch(PNG_BODY_URL);
body = await res.buffer();
assert.strictEqual(expectedPngBuffer.equals(body), true);
@ -457,22 +433,4 @@ describe('File system cache tests', function() {
assert.strictEqual(expectedPngBuffer.equals(body), true);
assert.strictEqual(res.fromCache, true);
});
it('Can eject from cache', async function() {
cachedFetch = FetchCache.withCache(new FileSystemCache());
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true);
await res.ejectFromCache();
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true);
});
});