fetchache 0.0.4 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +26 -15
- package/index.js +108 -0
- package/index.mjs +102 -0
- package/package.json +23 -11
- package/index.cjs.js +0 -106
- package/index.cjs.js.map +0 -1
- package/index.esm.js +0 -93
- package/index.esm.js.map +0 -1
package/index.d.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import CachePolicy from 'http-cache-semantics';
|
|
2
|
+
export interface FetchacheCacheEntry {
|
|
3
|
+
policy: CachePolicy.CachePolicyObject;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
declare type FetchFn = WindowOrWorkerGlobalScope['fetch'];
|
|
7
|
+
export interface FetchacheOptions {
|
|
8
|
+
fetch: FetchFn;
|
|
9
|
+
Request: typeof Request;
|
|
10
|
+
Response: typeof Response;
|
|
11
|
+
cache: KeyValueCache<FetchacheCacheEntry>;
|
|
12
|
+
}
|
|
13
|
+
export declare function fetchFactory({ fetch, Request, Response, cache }: FetchacheOptions): FetchFn;
|
|
14
|
+
export interface KeyValueCacheSetOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Specified in **seconds**, the time-to-live (TTL) value limits the lifespan
|
|
17
|
+
* of the data being stored in the cache.
|
|
18
|
+
*/
|
|
19
|
+
ttl?: number | null;
|
|
20
|
+
}
|
|
21
|
+
export interface KeyValueCache<V = any> {
|
|
22
|
+
get(key: string): Promise<V | undefined>;
|
|
23
|
+
set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise<void>;
|
|
24
|
+
delete(key: string): Promise<boolean | void>;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
package/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
6
|
+
|
|
7
|
+
const CachePolicy = _interopDefault(require('http-cache-semantics'));
|
|
8
|
+
|
|
9
|
+
function fetchFactory({ fetch, Request, Response, cache }) {
|
|
10
|
+
return async (input, init) => {
|
|
11
|
+
let request;
|
|
12
|
+
if (input instanceof Request) {
|
|
13
|
+
request = input;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
request = new Request(input, init);
|
|
17
|
+
}
|
|
18
|
+
const cacheKey = request.url;
|
|
19
|
+
const entry = await cache.get(cacheKey);
|
|
20
|
+
if (!entry) {
|
|
21
|
+
const response = await fetch(request);
|
|
22
|
+
const policy = new CachePolicy(policyRequestFrom(request), policyResponseFrom(response));
|
|
23
|
+
return storeResponseAndReturnClone(cache, response, policy, cacheKey);
|
|
24
|
+
}
|
|
25
|
+
const { policy: policyRaw, bytes } = typeof entry === 'string' ? JSON.parse(entry) : entry;
|
|
26
|
+
const policy = CachePolicy.fromObject(policyRaw);
|
|
27
|
+
// Remove url from the policy, because otherwise it would never match a request with a custom cache key
|
|
28
|
+
policy._url = undefined;
|
|
29
|
+
const bodyInit = new Uint8Array(bytes);
|
|
30
|
+
if (policy.satisfiesWithoutRevalidation(policyRequestFrom(request))) {
|
|
31
|
+
const headers = policy.responseHeaders();
|
|
32
|
+
return new Response(bodyInit, {
|
|
33
|
+
url: policy._url,
|
|
34
|
+
status: policy._status,
|
|
35
|
+
headers,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const revalidationHeaders = policy.revalidationHeaders(policyRequestFrom(request));
|
|
40
|
+
const revalidationRequest = new Request(request, {
|
|
41
|
+
headers: revalidationHeaders,
|
|
42
|
+
});
|
|
43
|
+
const revalidationResponse = await fetch(revalidationRequest);
|
|
44
|
+
const { policy: revalidatedPolicy, modified } = policy.revalidatedPolicy(policyRequestFrom(revalidationRequest), policyResponseFrom(revalidationResponse));
|
|
45
|
+
const newArrayBuffer = await revalidationResponse.arrayBuffer();
|
|
46
|
+
const newBody = modified ? newArrayBuffer : bodyInit;
|
|
47
|
+
return storeResponseAndReturnClone(cache, new Response(newBody, {
|
|
48
|
+
url: revalidatedPolicy._url,
|
|
49
|
+
status: revalidatedPolicy._status,
|
|
50
|
+
headers: revalidatedPolicy.responseHeaders(),
|
|
51
|
+
}), revalidatedPolicy, cacheKey);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
async function storeResponseAndReturnClone(cache, response, policy, cacheKey) {
|
|
55
|
+
let ttl = Math.round(policy.timeToLive() / 1000);
|
|
56
|
+
if (ttl <= 0)
|
|
57
|
+
return response;
|
|
58
|
+
// If a response can be revalidated, we don't want to remove it from the cache right after it expires.
|
|
59
|
+
// We may be able to use better heuristics here, but for now we'll take the max-age times 2.
|
|
60
|
+
if (canBeRevalidated(response)) {
|
|
61
|
+
ttl *= 2;
|
|
62
|
+
}
|
|
63
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
64
|
+
const uint8array = new Uint8Array(arrayBuffer);
|
|
65
|
+
const entry = {
|
|
66
|
+
policy: policy.toObject(),
|
|
67
|
+
bytes: [...uint8array],
|
|
68
|
+
};
|
|
69
|
+
await cache.set(cacheKey, entry, {
|
|
70
|
+
ttl,
|
|
71
|
+
});
|
|
72
|
+
// We have to clone the response before returning it because the
|
|
73
|
+
// body can only be used once.
|
|
74
|
+
// To avoid https://github.com/bitinn/node-fetch/issues/151, we don't use
|
|
75
|
+
// response.clone() but create a new response from the consumed body
|
|
76
|
+
return new Response(uint8array, {
|
|
77
|
+
url: response.url,
|
|
78
|
+
status: response.status,
|
|
79
|
+
statusText: response.statusText,
|
|
80
|
+
headers: response.headers,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function canBeRevalidated(response) {
|
|
85
|
+
return response.headers.has('ETag');
|
|
86
|
+
}
|
|
87
|
+
function policyRequestFrom(request) {
|
|
88
|
+
return {
|
|
89
|
+
url: request.url,
|
|
90
|
+
method: request.method,
|
|
91
|
+
headers: headersToObject(request.headers),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function policyResponseFrom(response) {
|
|
95
|
+
return {
|
|
96
|
+
status: response.status,
|
|
97
|
+
headers: headersToObject(response.headers),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function headersToObject(headers) {
|
|
101
|
+
const object = Object.create(null);
|
|
102
|
+
headers === null || headers === void 0 ? void 0 : headers.forEach((val, key) => {
|
|
103
|
+
object[key] = val;
|
|
104
|
+
});
|
|
105
|
+
return object;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
exports.fetchFactory = fetchFactory;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import CachePolicy from 'http-cache-semantics';
|
|
2
|
+
|
|
3
|
+
function fetchFactory({ fetch, Request, Response, cache }) {
|
|
4
|
+
return async (input, init) => {
|
|
5
|
+
let request;
|
|
6
|
+
if (input instanceof Request) {
|
|
7
|
+
request = input;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
request = new Request(input, init);
|
|
11
|
+
}
|
|
12
|
+
const cacheKey = request.url;
|
|
13
|
+
const entry = await cache.get(cacheKey);
|
|
14
|
+
if (!entry) {
|
|
15
|
+
const response = await fetch(request);
|
|
16
|
+
const policy = new CachePolicy(policyRequestFrom(request), policyResponseFrom(response));
|
|
17
|
+
return storeResponseAndReturnClone(cache, response, policy, cacheKey);
|
|
18
|
+
}
|
|
19
|
+
const { policy: policyRaw, bytes } = typeof entry === 'string' ? JSON.parse(entry) : entry;
|
|
20
|
+
const policy = CachePolicy.fromObject(policyRaw);
|
|
21
|
+
// Remove url from the policy, because otherwise it would never match a request with a custom cache key
|
|
22
|
+
policy._url = undefined;
|
|
23
|
+
const bodyInit = new Uint8Array(bytes);
|
|
24
|
+
if (policy.satisfiesWithoutRevalidation(policyRequestFrom(request))) {
|
|
25
|
+
const headers = policy.responseHeaders();
|
|
26
|
+
return new Response(bodyInit, {
|
|
27
|
+
url: policy._url,
|
|
28
|
+
status: policy._status,
|
|
29
|
+
headers,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const revalidationHeaders = policy.revalidationHeaders(policyRequestFrom(request));
|
|
34
|
+
const revalidationRequest = new Request(request, {
|
|
35
|
+
headers: revalidationHeaders,
|
|
36
|
+
});
|
|
37
|
+
const revalidationResponse = await fetch(revalidationRequest);
|
|
38
|
+
const { policy: revalidatedPolicy, modified } = policy.revalidatedPolicy(policyRequestFrom(revalidationRequest), policyResponseFrom(revalidationResponse));
|
|
39
|
+
const newArrayBuffer = await revalidationResponse.arrayBuffer();
|
|
40
|
+
const newBody = modified ? newArrayBuffer : bodyInit;
|
|
41
|
+
return storeResponseAndReturnClone(cache, new Response(newBody, {
|
|
42
|
+
url: revalidatedPolicy._url,
|
|
43
|
+
status: revalidatedPolicy._status,
|
|
44
|
+
headers: revalidatedPolicy.responseHeaders(),
|
|
45
|
+
}), revalidatedPolicy, cacheKey);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
async function storeResponseAndReturnClone(cache, response, policy, cacheKey) {
|
|
49
|
+
let ttl = Math.round(policy.timeToLive() / 1000);
|
|
50
|
+
if (ttl <= 0)
|
|
51
|
+
return response;
|
|
52
|
+
// If a response can be revalidated, we don't want to remove it from the cache right after it expires.
|
|
53
|
+
// We may be able to use better heuristics here, but for now we'll take the max-age times 2.
|
|
54
|
+
if (canBeRevalidated(response)) {
|
|
55
|
+
ttl *= 2;
|
|
56
|
+
}
|
|
57
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
58
|
+
const uint8array = new Uint8Array(arrayBuffer);
|
|
59
|
+
const entry = {
|
|
60
|
+
policy: policy.toObject(),
|
|
61
|
+
bytes: [...uint8array],
|
|
62
|
+
};
|
|
63
|
+
await cache.set(cacheKey, entry, {
|
|
64
|
+
ttl,
|
|
65
|
+
});
|
|
66
|
+
// We have to clone the response before returning it because the
|
|
67
|
+
// body can only be used once.
|
|
68
|
+
// To avoid https://github.com/bitinn/node-fetch/issues/151, we don't use
|
|
69
|
+
// response.clone() but create a new response from the consumed body
|
|
70
|
+
return new Response(uint8array, {
|
|
71
|
+
url: response.url,
|
|
72
|
+
status: response.status,
|
|
73
|
+
statusText: response.statusText,
|
|
74
|
+
headers: response.headers,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function canBeRevalidated(response) {
|
|
79
|
+
return response.headers.has('ETag');
|
|
80
|
+
}
|
|
81
|
+
function policyRequestFrom(request) {
|
|
82
|
+
return {
|
|
83
|
+
url: request.url,
|
|
84
|
+
method: request.method,
|
|
85
|
+
headers: headersToObject(request.headers),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function policyResponseFrom(response) {
|
|
89
|
+
return {
|
|
90
|
+
status: response.status,
|
|
91
|
+
headers: headersToObject(response.headers),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function headersToObject(headers) {
|
|
95
|
+
const object = Object.create(null);
|
|
96
|
+
headers === null || headers === void 0 ? void 0 : headers.forEach((val, key) => {
|
|
97
|
+
object[key] = val;
|
|
98
|
+
});
|
|
99
|
+
return object;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { fetchFactory };
|
package/package.json
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fetchache",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Cross Platform Fetch with Key Value Cache support",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Cross Platform Fetch Wrapper with Key Value Cache support",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"http-cache-semantics": "4.1.0",
|
|
8
|
-
"
|
|
7
|
+
"http-cache-semantics": "^4.1.0",
|
|
8
|
+
"tslib": "^2.3.1"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "ardatan/whatwg-node",
|
|
13
|
+
"directory": "packages/fetchache"
|
|
9
14
|
},
|
|
10
|
-
"repository": "git@github.com:ardatan/fetch-with-cache.git",
|
|
11
15
|
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
|
|
12
16
|
"license": "MIT",
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
"main": "index.cjs.js",
|
|
17
|
-
"module": "index.esm.js",
|
|
17
|
+
"main": "index.js",
|
|
18
|
+
"module": "index.mjs",
|
|
18
19
|
"typings": "index.d.ts",
|
|
19
20
|
"typescript": {
|
|
20
21
|
"definition": "index.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"require": "./index.js",
|
|
26
|
+
"import": "./index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"./*": {
|
|
29
|
+
"require": "./*.js",
|
|
30
|
+
"import": "./*.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
21
33
|
}
|
|
22
|
-
}
|
|
34
|
+
}
|
package/index.cjs.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
6
|
-
|
|
7
|
-
const CachePolicy = _interopDefault(require('http-cache-semantics'));
|
|
8
|
-
const crossFetch = require('cross-fetch');
|
|
9
|
-
|
|
10
|
-
async function fetchache(request, cache) {
|
|
11
|
-
const cacheKey = request.url;
|
|
12
|
-
const entry = await cache.get(cacheKey);
|
|
13
|
-
if (!entry) {
|
|
14
|
-
const response = await crossFetch.fetch(request);
|
|
15
|
-
const policy = new CachePolicy(policyRequestFrom(request), policyResponseFrom(response));
|
|
16
|
-
return storeResponseAndReturnClone(cache, response, policy, cacheKey);
|
|
17
|
-
}
|
|
18
|
-
const { policy: policyRaw, body } = JSON.parse(entry);
|
|
19
|
-
const policy = CachePolicy.fromObject(policyRaw);
|
|
20
|
-
// Remove url from the policy, because otherwise it would never match a request with a custom cache key
|
|
21
|
-
policy._url = undefined;
|
|
22
|
-
if (policy.satisfiesWithoutRevalidation(policyRequestFrom(request))) {
|
|
23
|
-
const headers = policy.responseHeaders();
|
|
24
|
-
return new crossFetch.Response(body, {
|
|
25
|
-
url: policy._url,
|
|
26
|
-
status: policy._status,
|
|
27
|
-
headers,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
const revalidationHeaders = policy.revalidationHeaders(policyRequestFrom(request));
|
|
32
|
-
const revalidationRequest = new crossFetch.Request(request, {
|
|
33
|
-
headers: revalidationHeaders,
|
|
34
|
-
});
|
|
35
|
-
const revalidationResponse = await crossFetch.fetch(revalidationRequest);
|
|
36
|
-
const { policy: revalidatedPolicy, modified } = policy.revalidatedPolicy(policyRequestFrom(revalidationRequest), policyResponseFrom(revalidationResponse));
|
|
37
|
-
return storeResponseAndReturnClone(cache, new crossFetch.Response(modified ? await revalidationResponse.text() : body, {
|
|
38
|
-
url: revalidatedPolicy._url,
|
|
39
|
-
status: revalidatedPolicy._status,
|
|
40
|
-
headers: revalidatedPolicy.responseHeaders(),
|
|
41
|
-
}), revalidatedPolicy, cacheKey);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
async function storeResponseAndReturnClone(cache, response, policy, cacheKey) {
|
|
45
|
-
let ttl = Math.round(policy.timeToLive() / 1000);
|
|
46
|
-
if (ttl <= 0)
|
|
47
|
-
return response;
|
|
48
|
-
// If a response can be revalidated, we don't want to remove it from the cache right after it expires.
|
|
49
|
-
// We may be able to use better heuristics here, but for now we'll take the max-age times 2.
|
|
50
|
-
if (canBeRevalidated(response)) {
|
|
51
|
-
ttl *= 2;
|
|
52
|
-
}
|
|
53
|
-
const body = await response.text();
|
|
54
|
-
const entry = JSON.stringify({
|
|
55
|
-
policy: policy.toObject(),
|
|
56
|
-
body,
|
|
57
|
-
});
|
|
58
|
-
await cache.set(cacheKey, entry, {
|
|
59
|
-
ttl,
|
|
60
|
-
});
|
|
61
|
-
// We have to clone the response before returning it because the
|
|
62
|
-
// body can only be used once.
|
|
63
|
-
// To avoid https://github.com/bitinn/node-fetch/issues/151, we don't use
|
|
64
|
-
// response.clone() but create a new response from the consumed body
|
|
65
|
-
return new crossFetch.Response(body, {
|
|
66
|
-
url: response.url,
|
|
67
|
-
status: response.status,
|
|
68
|
-
statusText: response.statusText,
|
|
69
|
-
headers: response.headers,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
function canBeRevalidated(response) {
|
|
73
|
-
return response.headers.has('ETag');
|
|
74
|
-
}
|
|
75
|
-
function policyRequestFrom(request) {
|
|
76
|
-
return {
|
|
77
|
-
url: request.url,
|
|
78
|
-
method: request.method,
|
|
79
|
-
headers: headersToObject(request.headers),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function policyResponseFrom(response) {
|
|
83
|
-
return {
|
|
84
|
-
status: response.status,
|
|
85
|
-
headers: headersToObject(response.headers),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function headersToObject(headers) {
|
|
89
|
-
const object = Object.create(null);
|
|
90
|
-
headers.forEach((val, key) => {
|
|
91
|
-
object[key] = val;
|
|
92
|
-
});
|
|
93
|
-
return object;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
Object.keys(crossFetch).forEach(function (k) {
|
|
97
|
-
if (k !== 'default') Object.defineProperty(exports, k, {
|
|
98
|
-
enumerable: true,
|
|
99
|
-
get: function () {
|
|
100
|
-
return crossFetch[k];
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
exports.default = fetchache;
|
|
105
|
-
exports.fetchache = fetchache;
|
|
106
|
-
//# sourceMappingURL=index.cjs.js.map
|
package/index.cjs.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/index.ts"],"sourcesContent":["import CachePolicy from 'http-cache-semantics';\r\nimport { fetch, Request, Response } from 'cross-fetch';\r\n\r\nexport async function fetchache(request: Request, cache: KeyValueCache) {\r\n const cacheKey = request.url;\r\n const entry = await cache.get(cacheKey);\r\n if (!entry) {\r\n const response = await fetch(request);\r\n\r\n const policy = new CachePolicy(\r\n policyRequestFrom(request),\r\n policyResponseFrom(response),\r\n );\r\n\r\n return storeResponseAndReturnClone(\r\n cache,\r\n response,\r\n policy,\r\n cacheKey,\r\n );\r\n }\r\n\r\n const { policy: policyRaw, body } = JSON.parse(entry);\r\n\r\n const policy = CachePolicy.fromObject(policyRaw);\r\n // Remove url from the policy, because otherwise it would never match a request with a custom cache key\r\n (policy as any)._url = undefined;\r\n\r\n if (policy.satisfiesWithoutRevalidation(policyRequestFrom(request))) {\r\n const headers = policy.responseHeaders() as HeadersInit;\r\n return new Response(body, {\r\n url: (policy as any)._url,\r\n status: (policy as any)._status,\r\n headers,\r\n } as ResponseInit);\r\n } else {\r\n const revalidationHeaders = policy.revalidationHeaders(\r\n policyRequestFrom(request),\r\n );\r\n const revalidationRequest = new Request(request, {\r\n headers: revalidationHeaders as HeadersInit,\r\n });\r\n const revalidationResponse = await fetch(revalidationRequest);\r\n\r\n const { policy: revalidatedPolicy, modified } = policy.revalidatedPolicy(\r\n policyRequestFrom(revalidationRequest),\r\n policyResponseFrom(revalidationResponse),\r\n );\r\n\r\n return storeResponseAndReturnClone(\r\n cache,\r\n new Response(modified ? await revalidationResponse.text() : body, {\r\n url: (revalidatedPolicy as any)._url,\r\n status: (revalidatedPolicy as any)._status,\r\n headers: (revalidatedPolicy as any).responseHeaders(),\r\n } as ResponseInit),\r\n revalidatedPolicy,\r\n cacheKey,\r\n );\r\n }\r\n}\r\n\r\nexport * from 'cross-fetch';\r\n\r\nexport default fetchache;\r\n\r\nasync function storeResponseAndReturnClone(\r\n cache: KeyValueCache,\r\n response: Response,\r\n policy: CachePolicy,\r\n cacheKey: string,\r\n): Promise<Response> {\r\n\r\n let ttl = Math.round(policy.timeToLive() / 1000);\r\n if (ttl <= 0) return response;\r\n\r\n // If a response can be revalidated, we don't want to remove it from the cache right after it expires.\r\n // We may be able to use better heuristics here, but for now we'll take the max-age times 2.\r\n if (canBeRevalidated(response)) {\r\n ttl *= 2;\r\n }\r\n\r\n const body = await response.text();\r\n const entry = JSON.stringify({\r\n policy: policy.toObject(),\r\n body,\r\n });\r\n\r\n await cache.set(cacheKey, entry, {\r\n ttl,\r\n });\r\n\r\n // We have to clone the response before returning it because the\r\n // body can only be used once.\r\n // To avoid https://github.com/bitinn/node-fetch/issues/151, we don't use\r\n // response.clone() but create a new response from the consumed body\r\n return new Response(body, {\r\n url: response.url,\r\n status: response.status,\r\n statusText: response.statusText,\r\n headers: response.headers,\r\n } as ResponseInit);\r\n}\r\n\r\nfunction canBeRevalidated(response: Response): boolean {\r\n return response.headers.has('ETag');\r\n}\r\n\r\nfunction policyRequestFrom(request: Request) {\r\n return {\r\n url: request.url,\r\n method: request.method,\r\n headers: headersToObject(request.headers),\r\n };\r\n}\r\n\r\nfunction policyResponseFrom(response: Response) {\r\n return {\r\n status: response.status,\r\n headers: headersToObject(response.headers),\r\n };\r\n}\r\n\r\nfunction headersToObject(headers: Headers) {\r\n const object = Object.create(null);\r\n headers.forEach((val, key) => {\r\n object[key] = val;\r\n });\r\n return object;\r\n}\r\n\r\nexport interface KeyValueCacheSetOptions {\r\n /**\r\n * Specified in **seconds**, the time-to-live (TTL) value limits the lifespan\r\n * of the data being stored in the cache.\r\n */\r\n ttl?: number | null\r\n};\r\n\r\nexport interface KeyValueCache<V = string> {\r\n get(key: string): Promise<V | undefined>;\r\n set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise<void>;\r\n delete(key: string): Promise<boolean | void>;\r\n}\r\n"],"names":["fetch","Response","Request"],"mappings":"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"}
|
package/index.esm.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import CachePolicy from 'http-cache-semantics';
|
|
2
|
-
import { fetch, Response, Request } from 'cross-fetch';
|
|
3
|
-
export * from 'cross-fetch';
|
|
4
|
-
|
|
5
|
-
async function fetchache(request, cache) {
|
|
6
|
-
const cacheKey = request.url;
|
|
7
|
-
const entry = await cache.get(cacheKey);
|
|
8
|
-
if (!entry) {
|
|
9
|
-
const response = await fetch(request);
|
|
10
|
-
const policy = new CachePolicy(policyRequestFrom(request), policyResponseFrom(response));
|
|
11
|
-
return storeResponseAndReturnClone(cache, response, policy, cacheKey);
|
|
12
|
-
}
|
|
13
|
-
const { policy: policyRaw, body } = JSON.parse(entry);
|
|
14
|
-
const policy = CachePolicy.fromObject(policyRaw);
|
|
15
|
-
// Remove url from the policy, because otherwise it would never match a request with a custom cache key
|
|
16
|
-
policy._url = undefined;
|
|
17
|
-
if (policy.satisfiesWithoutRevalidation(policyRequestFrom(request))) {
|
|
18
|
-
const headers = policy.responseHeaders();
|
|
19
|
-
return new Response(body, {
|
|
20
|
-
url: policy._url,
|
|
21
|
-
status: policy._status,
|
|
22
|
-
headers,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
const revalidationHeaders = policy.revalidationHeaders(policyRequestFrom(request));
|
|
27
|
-
const revalidationRequest = new Request(request, {
|
|
28
|
-
headers: revalidationHeaders,
|
|
29
|
-
});
|
|
30
|
-
const revalidationResponse = await fetch(revalidationRequest);
|
|
31
|
-
const { policy: revalidatedPolicy, modified } = policy.revalidatedPolicy(policyRequestFrom(revalidationRequest), policyResponseFrom(revalidationResponse));
|
|
32
|
-
return storeResponseAndReturnClone(cache, new Response(modified ? await revalidationResponse.text() : body, {
|
|
33
|
-
url: revalidatedPolicy._url,
|
|
34
|
-
status: revalidatedPolicy._status,
|
|
35
|
-
headers: revalidatedPolicy.responseHeaders(),
|
|
36
|
-
}), revalidatedPolicy, cacheKey);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async function storeResponseAndReturnClone(cache, response, policy, cacheKey) {
|
|
40
|
-
let ttl = Math.round(policy.timeToLive() / 1000);
|
|
41
|
-
if (ttl <= 0)
|
|
42
|
-
return response;
|
|
43
|
-
// If a response can be revalidated, we don't want to remove it from the cache right after it expires.
|
|
44
|
-
// We may be able to use better heuristics here, but for now we'll take the max-age times 2.
|
|
45
|
-
if (canBeRevalidated(response)) {
|
|
46
|
-
ttl *= 2;
|
|
47
|
-
}
|
|
48
|
-
const body = await response.text();
|
|
49
|
-
const entry = JSON.stringify({
|
|
50
|
-
policy: policy.toObject(),
|
|
51
|
-
body,
|
|
52
|
-
});
|
|
53
|
-
await cache.set(cacheKey, entry, {
|
|
54
|
-
ttl,
|
|
55
|
-
});
|
|
56
|
-
// We have to clone the response before returning it because the
|
|
57
|
-
// body can only be used once.
|
|
58
|
-
// To avoid https://github.com/bitinn/node-fetch/issues/151, we don't use
|
|
59
|
-
// response.clone() but create a new response from the consumed body
|
|
60
|
-
return new Response(body, {
|
|
61
|
-
url: response.url,
|
|
62
|
-
status: response.status,
|
|
63
|
-
statusText: response.statusText,
|
|
64
|
-
headers: response.headers,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function canBeRevalidated(response) {
|
|
68
|
-
return response.headers.has('ETag');
|
|
69
|
-
}
|
|
70
|
-
function policyRequestFrom(request) {
|
|
71
|
-
return {
|
|
72
|
-
url: request.url,
|
|
73
|
-
method: request.method,
|
|
74
|
-
headers: headersToObject(request.headers),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
function policyResponseFrom(response) {
|
|
78
|
-
return {
|
|
79
|
-
status: response.status,
|
|
80
|
-
headers: headersToObject(response.headers),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function headersToObject(headers) {
|
|
84
|
-
const object = Object.create(null);
|
|
85
|
-
headers.forEach((val, key) => {
|
|
86
|
-
object[key] = val;
|
|
87
|
-
});
|
|
88
|
-
return object;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default fetchache;
|
|
92
|
-
export { fetchache };
|
|
93
|
-
//# sourceMappingURL=index.esm.js.map
|
package/index.esm.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/index.ts"],"sourcesContent":["import CachePolicy from 'http-cache-semantics';\r\nimport { fetch, Request, Response } from 'cross-fetch';\r\n\r\nexport async function fetchache(request: Request, cache: KeyValueCache) {\r\n const cacheKey = request.url;\r\n const entry = await cache.get(cacheKey);\r\n if (!entry) {\r\n const response = await fetch(request);\r\n\r\n const policy = new CachePolicy(\r\n policyRequestFrom(request),\r\n policyResponseFrom(response),\r\n );\r\n\r\n return storeResponseAndReturnClone(\r\n cache,\r\n response,\r\n policy,\r\n cacheKey,\r\n );\r\n }\r\n\r\n const { policy: policyRaw, body } = JSON.parse(entry);\r\n\r\n const policy = CachePolicy.fromObject(policyRaw);\r\n // Remove url from the policy, because otherwise it would never match a request with a custom cache key\r\n (policy as any)._url = undefined;\r\n\r\n if (policy.satisfiesWithoutRevalidation(policyRequestFrom(request))) {\r\n const headers = policy.responseHeaders() as HeadersInit;\r\n return new Response(body, {\r\n url: (policy as any)._url,\r\n status: (policy as any)._status,\r\n headers,\r\n } as ResponseInit);\r\n } else {\r\n const revalidationHeaders = policy.revalidationHeaders(\r\n policyRequestFrom(request),\r\n );\r\n const revalidationRequest = new Request(request, {\r\n headers: revalidationHeaders as HeadersInit,\r\n });\r\n const revalidationResponse = await fetch(revalidationRequest);\r\n\r\n const { policy: revalidatedPolicy, modified } = policy.revalidatedPolicy(\r\n policyRequestFrom(revalidationRequest),\r\n policyResponseFrom(revalidationResponse),\r\n );\r\n\r\n return storeResponseAndReturnClone(\r\n cache,\r\n new Response(modified ? await revalidationResponse.text() : body, {\r\n url: (revalidatedPolicy as any)._url,\r\n status: (revalidatedPolicy as any)._status,\r\n headers: (revalidatedPolicy as any).responseHeaders(),\r\n } as ResponseInit),\r\n revalidatedPolicy,\r\n cacheKey,\r\n );\r\n }\r\n}\r\n\r\nexport * from 'cross-fetch';\r\n\r\nexport default fetchache;\r\n\r\nasync function storeResponseAndReturnClone(\r\n cache: KeyValueCache,\r\n response: Response,\r\n policy: CachePolicy,\r\n cacheKey: string,\r\n): Promise<Response> {\r\n\r\n let ttl = Math.round(policy.timeToLive() / 1000);\r\n if (ttl <= 0) return response;\r\n\r\n // If a response can be revalidated, we don't want to remove it from the cache right after it expires.\r\n // We may be able to use better heuristics here, but for now we'll take the max-age times 2.\r\n if (canBeRevalidated(response)) {\r\n ttl *= 2;\r\n }\r\n\r\n const body = await response.text();\r\n const entry = JSON.stringify({\r\n policy: policy.toObject(),\r\n body,\r\n });\r\n\r\n await cache.set(cacheKey, entry, {\r\n ttl,\r\n });\r\n\r\n // We have to clone the response before returning it because the\r\n // body can only be used once.\r\n // To avoid https://github.com/bitinn/node-fetch/issues/151, we don't use\r\n // response.clone() but create a new response from the consumed body\r\n return new Response(body, {\r\n url: response.url,\r\n status: response.status,\r\n statusText: response.statusText,\r\n headers: response.headers,\r\n } as ResponseInit);\r\n}\r\n\r\nfunction canBeRevalidated(response: Response): boolean {\r\n return response.headers.has('ETag');\r\n}\r\n\r\nfunction policyRequestFrom(request: Request) {\r\n return {\r\n url: request.url,\r\n method: request.method,\r\n headers: headersToObject(request.headers),\r\n };\r\n}\r\n\r\nfunction policyResponseFrom(response: Response) {\r\n return {\r\n status: response.status,\r\n headers: headersToObject(response.headers),\r\n };\r\n}\r\n\r\nfunction headersToObject(headers: Headers) {\r\n const object = Object.create(null);\r\n headers.forEach((val, key) => {\r\n object[key] = val;\r\n });\r\n return object;\r\n}\r\n\r\nexport interface KeyValueCacheSetOptions {\r\n /**\r\n * Specified in **seconds**, the time-to-live (TTL) value limits the lifespan\r\n * of the data being stored in the cache.\r\n */\r\n ttl?: number | null\r\n};\r\n\r\nexport interface KeyValueCache<V = string> {\r\n get(key: string): Promise<V | undefined>;\r\n set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise<void>;\r\n delete(key: string): Promise<boolean | void>;\r\n}\r\n"],"names":[],"mappings":"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"}
|