shared-http-cache 1.0.0
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/LICENSE +21 -0
- package/docs/images/cache.png +0 -0
- package/index.js +116 -0
- package/package.json +31 -0
- package/readme.md +348 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SorinGFS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// utility for fetching multiple URLs with HTTP caching management
|
|
3
|
+
const cacache = require('cacache');
|
|
4
|
+
// cacache wrapper
|
|
5
|
+
class SharedHttpCache {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
Object.assign(this, { cacheDir: '.cache', awaitStorage: false }, options);
|
|
8
|
+
this.store = cacache;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch multiple resources with HTTP cache support.
|
|
12
|
+
* @param {Array<{url:string,integrity?:string,options?:RequestInit,callback:(result:{buffer:Buffer,headers:Headers,fromCache:boolean})=>void}>} requests
|
|
13
|
+
* @returns {Promise<this|{url: string, headers?: Headers, error: Error }[]>}
|
|
14
|
+
* @see [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit), [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) MDN references
|
|
15
|
+
*/
|
|
16
|
+
async fetch(requests) {
|
|
17
|
+
const errors = [];
|
|
18
|
+
const parseHeader = (string) => {
|
|
19
|
+
if (!string || typeof string !== 'string') return {};
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const part of string.split(',').reverse()) {
|
|
22
|
+
const [key, value] = part.trim().split('=');
|
|
23
|
+
result[key] = value === undefined ? true : Number.isNaN(+value) ? value : +value;
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
const isFresh = (file, requestCacheControl = {}) => {
|
|
28
|
+
const storedCacheControl = parseHeader(file.metadata.headers['cache-control']);
|
|
29
|
+
const previousAge = Number(file.metadata.headers['age'] || 0);
|
|
30
|
+
const currentAge = previousAge + (Date.now() - file.time) / 1000;
|
|
31
|
+
let lifetime; // Response freshness lifetime (s-maxage > max-age > Expires)
|
|
32
|
+
if (storedCacheControl['s-maxage'] !== undefined) lifetime = storedCacheControl['s-maxage'];
|
|
33
|
+
else if (storedCacheControl['max-age'] !== undefined) lifetime = storedCacheControl['max-age'];
|
|
34
|
+
else {
|
|
35
|
+
const expires = Date.parse(file.metadata.headers['expires'] || '');
|
|
36
|
+
lifetime = !Number.isNaN(expires) ? Math.max(0, (expires - file.time) / 1000) : 0;
|
|
37
|
+
}
|
|
38
|
+
if (requestCacheControl['max-age'] !== undefined) lifetime = Math.min(lifetime, requestCacheControl['max-age']);
|
|
39
|
+
const remainingLifetime = lifetime - currentAge;
|
|
40
|
+
if (requestCacheControl['min-fresh'] !== undefined && remainingLifetime < requestCacheControl['min-fresh']) return false;
|
|
41
|
+
if (remainingLifetime >= 0) return true; // not stale
|
|
42
|
+
const maxStale = requestCacheControl['max-stale'];
|
|
43
|
+
if (maxStale !== undefined) {
|
|
44
|
+
if (maxStale === true) return true; // unspecified max-stale → accept any staleness
|
|
45
|
+
if (currentAge <= lifetime + maxStale) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
};
|
|
49
|
+
await Promise.all(
|
|
50
|
+
requests.map(async (request) => {
|
|
51
|
+
const { url, options = {}, integrity, callback } = request;
|
|
52
|
+
if (!options.method) options.method = 'GET';
|
|
53
|
+
if (!options.headers) options.headers = {};
|
|
54
|
+
Object.keys(options.headers).some((key) => key.toLowerCase() === 'cache-control' && (options.headers['cache-control'] = options.headers[key]));
|
|
55
|
+
Object.keys(options.headers).some((key) => key.toLowerCase() === 'authorization' && (options.headers['authorization'] = options.headers[key]));
|
|
56
|
+
// prettier-ignore
|
|
57
|
+
let response, buffer, headers, fromCache = true;
|
|
58
|
+
try {
|
|
59
|
+
const requestCacheControl = parseHeader(options.headers['cache-control']);
|
|
60
|
+
const file = await this.store.get.info(this.cacheDir, url);
|
|
61
|
+
const isFreshFile = file && isFresh(file, requestCacheControl);
|
|
62
|
+
if (file) {
|
|
63
|
+
const responseCacheControl = parseHeader(file.metadata.headers['cache-control']);
|
|
64
|
+
if (!isFreshFile && requestCacheControl['only-if-cached']) throw new Error('HTTP error! status: 504 Only-If-Cached');
|
|
65
|
+
if (!isFreshFile || requestCacheControl['no-cache'] || responseCacheControl['no-cache']) fromCache = false;
|
|
66
|
+
if (requestCacheControl['max-stale'] && (responseCacheControl['must-revalidate'] || responseCacheControl['proxy-revalidate'])) fromCache = false;
|
|
67
|
+
if (fromCache) {
|
|
68
|
+
buffer = (await this.store.get(this.cacheDir, url)).data;
|
|
69
|
+
headers = file.metadata.headers;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
fromCache = false;
|
|
73
|
+
if (requestCacheControl['only-if-cached']) throw new Error('HTTP error! status: 504 Only-If-Cached');
|
|
74
|
+
}
|
|
75
|
+
if (!fromCache) {
|
|
76
|
+
if (file && file.metadata.headers['etag']) options.headers['if-none-match'] = file.metadata.headers['etag'];
|
|
77
|
+
if (file && file.metadata.headers['last-modified']) options.headers['if-modified-since'] = file.metadata.headers['last-modified'];
|
|
78
|
+
response = await fetch(url, options);
|
|
79
|
+
if (response.status === 304) {
|
|
80
|
+
buffer = (await this.store.get(this.cacheDir, url)).data;
|
|
81
|
+
headers = { ...file.metadata.headers, ...Object.fromEntries(response.headers.entries()) };
|
|
82
|
+
fromCache = true;
|
|
83
|
+
} else if (response.ok) {
|
|
84
|
+
buffer = Buffer.from(await response.arrayBuffer());
|
|
85
|
+
headers = Object.fromEntries(response.headers.entries());
|
|
86
|
+
} else {
|
|
87
|
+
if (response.status === 410) {
|
|
88
|
+
this.store.rm.entry(this.cacheDir, url, { removeFully: true });
|
|
89
|
+
this.store.rm.content(this.cacheDir, file.integrity);
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// chance to preform content validation before saving it to disk
|
|
95
|
+
if (typeof callback === 'function') callback({ buffer, headers, fromCache });
|
|
96
|
+
if (!fromCache || response?.status === 304) {
|
|
97
|
+
const responseCacheControl = parseHeader(headers['cache-control']);
|
|
98
|
+
if (options.method !== 'GET') return;
|
|
99
|
+
if (responseCacheControl['no-store'] || responseCacheControl['private']) return;
|
|
100
|
+
if (requestCacheControl['no-store'] || requestCacheControl['authorization']) return;
|
|
101
|
+
const store = async () => {
|
|
102
|
+
await this.store.rm.entry(this.cacheDir, url, { removeFully: true });
|
|
103
|
+
await this.store.put(this.cacheDir, url, buffer, integrity ? { metadata: { headers }, integrity } : { metadata: { headers } });
|
|
104
|
+
};
|
|
105
|
+
this.awaitStorage ? await store() : store();
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
errors.push({ url, headers, error });
|
|
109
|
+
}
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
return errors.length ? Promise.reject(errors) : Promise.resolve(this);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// export
|
|
116
|
+
module.exports = SharedHttpCache;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shared-http-cache",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node.Js Utility for fetching multiple HTTP resources with browser-like cache management.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"http-cache",
|
|
7
|
+
"cache",
|
|
8
|
+
"cacache",
|
|
9
|
+
"rfc9111",
|
|
10
|
+
"cache-manager",
|
|
11
|
+
"http-cache-semantics"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/SorinGFS/shared-http-cache#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/SorinGFS/shared-http-cache/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/SorinGFS/shared-http-cache.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "SorinGFS",
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"main": "index.js",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"cacache": "^20.0.3"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
title: Shared HTTP Cache
|
|
4
|
+
|
|
5
|
+
description: Node.Js Utility for fetching multiple HTTP resources with browser-like cache management.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
### Shared HTTP Cache Semantics
|
|
12
|
+
|
|
13
|
+
This implementation models a shared HTTP cache that follows the semantics defined in [RFC 9111](https://datatracker.ietf.org/doc/html/rfc9111.html), with explicitly documented assumptions and controlled decisions. It uses a content-addressed cache through [cacache](https://github.com/npm/cacache) providing lockless, high-concurrency cache access, that along with the downloaded `content` is storing the associated `HTTP response headers` in the cache metadata. Overall, the design aims to provide `browser-like` caching behavior in a `Node.js` environment.
|
|
14
|
+
|
|
15
|
+
### Scope and assumptions
|
|
16
|
+
|
|
17
|
+
The cache is shared (not private) and applies shared-cache rules.
|
|
18
|
+
|
|
19
|
+
- Privacy factors are considered while storing responses (`safe method` constraints, request `authorization`, response `cache-control="private"`).
|
|
20
|
+
- No heuristic freshness is used.
|
|
21
|
+
- Time calculations rely exclusively on locally recorded timestamps, not on server-provided `Date`.
|
|
22
|
+
- Storage and eviction are deterministic; no background or implicit cleanup is assumed.
|
|
23
|
+
|
|
24
|
+
### Request initialization and cache lookup
|
|
25
|
+
|
|
26
|
+
Each request begins by determining whether a cached response exists.
|
|
27
|
+
|
|
28
|
+
If no cached entry exists:
|
|
29
|
+
|
|
30
|
+
- If the request requires `only-if-cached`, the cache returns a `504 HTTP status`.
|
|
31
|
+
- Otherwise, the request is sent to the origin server.
|
|
32
|
+
|
|
33
|
+
### Cache-control exclusions
|
|
34
|
+
|
|
35
|
+
Two cache-control directives may short-circuit normal cache usage:
|
|
36
|
+
|
|
37
|
+
- `no-cache` (request or response): cached data cannot be used without revalidation.
|
|
38
|
+
- `no-store` (request or response): the response must not be stored.
|
|
39
|
+
When `no-store` applies, the response is served directly and bypasses storage entirely.
|
|
40
|
+
|
|
41
|
+
### Freshness evaluation
|
|
42
|
+
|
|
43
|
+
If a cached response exists and is not excluded, strict freshness is evaluated first, without considering `max-stale`.
|
|
44
|
+
|
|
45
|
+
Freshness lifetime is computed from response headers:
|
|
46
|
+
|
|
47
|
+
- `s-maxage` first, then `max-age`, if present
|
|
48
|
+
- otherwise `Expires`, if present
|
|
49
|
+
|
|
50
|
+
Current age is derived from local metadata:
|
|
51
|
+
|
|
52
|
+
```excel-formula
|
|
53
|
+
currentAge = now − storedTime + incomingAge
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The `incomingAge` is taken from the stored response `age` header, if present.
|
|
57
|
+
|
|
58
|
+
Remaining freshness:
|
|
59
|
+
|
|
60
|
+
```excel-formula
|
|
61
|
+
remainingFreshness = freshnessLifetime − currentAge
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If `remainingFreshness ≥ 0`, the response is served as fresh.
|
|
65
|
+
|
|
66
|
+
### Stale handling
|
|
67
|
+
|
|
68
|
+
If the response is stale, `max-stale` on the request is evaluated.
|
|
69
|
+
|
|
70
|
+
If `max-stale` value is unspecified → accept any staleness; otherwise the response is acceptable if:
|
|
71
|
+
|
|
72
|
+
```excel-formula
|
|
73
|
+
currentAge − freshnessLifetime ≤ max-stale
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If staleness exceeds the acceptable `max-stale`, the cache proceeds toward revalidation or origin fetch.
|
|
77
|
+
|
|
78
|
+
### Revalidation constraints
|
|
79
|
+
|
|
80
|
+
Even if `max-stale` allows use of stale data:
|
|
81
|
+
|
|
82
|
+
- `must-revalidate` or `proxy-revalidate` on the response forbids serving stale.
|
|
83
|
+
- In that case, the cache must revalidate or fetch from the origin.
|
|
84
|
+
- If `only-if-cached` also applies, the cache returns a `504 HTTP status` instead of revalidating.
|
|
85
|
+
- If no revalidation constraint applies, stale content may be served.
|
|
86
|
+
|
|
87
|
+
On revalidation, if the cached content includes `ETag` or `Last-Modified` automatically `If-None-Match` and `If-Modified-Since` headers are added to the request. Revalidated entries are explicitly replaced during each successful fetch to avoid unbounded growth in the index.
|
|
88
|
+
|
|
89
|
+
### Origin request outcomes
|
|
90
|
+
|
|
91
|
+
When a request is sent to the origin:
|
|
92
|
+
|
|
93
|
+
- `2xx`: response is stored (unless restricted) and served.
|
|
94
|
+
- `304 Not Modified`: cached metadata is updated; response is served as fresh.
|
|
95
|
+
- `410 Gone`: cached entry is removed.
|
|
96
|
+
- Other responses: treated as errors and returned directly.
|
|
97
|
+
|
|
98
|
+
### Cleanup behavior
|
|
99
|
+
|
|
100
|
+
The cache does not:
|
|
101
|
+
|
|
102
|
+
- apply heuristic freshness
|
|
103
|
+
- perform automatic eviction based on staleness
|
|
104
|
+
|
|
105
|
+
However:
|
|
106
|
+
|
|
107
|
+
- a `410 Gone` response explicitly removes the cached entry.
|
|
108
|
+
- additional cleanup mechanisms are available to the user via the underlying storage system.
|
|
109
|
+
|
|
110
|
+
## State diagram
|
|
111
|
+
|
|
112
|
+
The accompanying state diagram represents the full decision flow:
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
Legend
|
|
117
|
+
|
|
118
|
+
1. `no-cache` may appear on request or response and always requires revalidation.
|
|
119
|
+
2. `no-store` may appear on request or response; [privacy factors](#scope-and-assumptions) are considered.
|
|
120
|
+
3. [Freshness evaluation](#freshness-evaluation) excludes `max-stale` that is evaluated only after strict freshness fails.
|
|
121
|
+
4. `410 Gone` [cleanup](#cleanup-behavior) is an explicit design choice to keep the cache coherent; no heuristic eviction is used.
|
|
122
|
+
|
|
123
|
+
## Install
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm i shared-http-cache
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Usage
|
|
130
|
+
|
|
131
|
+
### Init
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
new SharedHttpCache(options?) -> SharedHttpCache
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Create a shared HTTP cache instance.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const SharedHttpCache = require('shared-http-cache');
|
|
141
|
+
const sharedHttpCache = new SharedHttpCache();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Init options
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
new SharedHttpCache({ cacheDir?: string, awaitStorage?: boolean }) -> SharedHttpCache
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- `cacheDir`: cache storage directory (default `.cache`)
|
|
151
|
+
- `awaitStorage`: await cache writes before continuing (default `false`)
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
const sharedHttpCache = new SharedHttpCache({
|
|
155
|
+
cacheDir: '/tmp/http-cache',
|
|
156
|
+
awaitStorage: true,
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Fetch
|
|
161
|
+
|
|
162
|
+
`fetch` is the only method available. On success, `fetch` resolves to the same instance, enabling chained workflows.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
sharedHttpCache.fetch(requests) -> Promise<this | Error[]>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Syntax:**
|
|
169
|
+
```ts
|
|
170
|
+
fetch([{ url: string, integrity?: string, options?: RequestInit, callback?: function }]) -> Promise<this | Error[]>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Simple fetch call
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
await sharedHttpCache.fetch([
|
|
177
|
+
{
|
|
178
|
+
url: 'https://example.com/data.txt',
|
|
179
|
+
callback: ({ buffer }) => {
|
|
180
|
+
console.log(buffer.toString());
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Fetch with callback and error handling
|
|
187
|
+
|
|
188
|
+
Errors encountered during fetches are collected, and the returned `promise` either `resolves` with the instance itself for successful fetches or `rejects` with a list of `errors` for failed requests.
|
|
189
|
+
|
|
190
|
+
The response is converted into a [Buffer](https://nodejs.org/api/buffer.html) served to `callback`, then stored in the `cache` along with the `response headers`. For any other status code, unless `stale-if-error` is present, an `error` is thrown.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
callback({ buffer: Buffer, headers: Headers, fromCache: boolean }) -> void
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The `callback` provided for each request is executed before storing new content, allowing implementers to inspect, transform or validate the data before it's cached. The errors thrown by the `callback` are also catched and stored in the `errors` delivered by the `Promise.reject()`.
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
await sharedHttpCache
|
|
200
|
+
.fetch([
|
|
201
|
+
{
|
|
202
|
+
url: 'https://example.com/data.txt',
|
|
203
|
+
callback: ({ buffer, headers, fromCache }) => {
|
|
204
|
+
console.log(buffer.toString());
|
|
205
|
+
console.log(headers);
|
|
206
|
+
console.log(fromCache);
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
])
|
|
210
|
+
.catch((errors) => {
|
|
211
|
+
errors.forEach((entry) => {
|
|
212
|
+
console.error(entry.url, entry.error);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Fetch with integrity
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
await sharedHttpCache.fetch([
|
|
221
|
+
{
|
|
222
|
+
url: 'https://example.com/file.bin',
|
|
223
|
+
integrity: 'sha256-abcdef...',
|
|
224
|
+
callback: ({ buffer }) => {
|
|
225
|
+
console.log(buffer.length);
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
]);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Note:** `integrity `affects storage, not `callback` execution.
|
|
232
|
+
|
|
233
|
+
### Fetch options
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
fetch.options -> RequestInit
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`fetch.options` are passed directly to [node:fetch](https://nodejs.org/api/globals.html#fetch).
|
|
240
|
+
They follow standard [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) semantics ([method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods), [credentials](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#credentials), [headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [mode](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#mode), [cache-mode](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#cache), etc.).
|
|
241
|
+
|
|
242
|
+
### Fetch with Accept: application/json
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
await sharedHttpCache.fetch([
|
|
246
|
+
{
|
|
247
|
+
url: 'https://api.example.com/list',
|
|
248
|
+
options: {
|
|
249
|
+
headers: {
|
|
250
|
+
Accept: 'application/json',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
callback: ({ buffer }) => {
|
|
254
|
+
console.log(buffer.toString());
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
]);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Fetch with Cache-Control: no-cache
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
await sharedHttpCache.fetch([
|
|
264
|
+
{
|
|
265
|
+
url: 'https://example.com/data',
|
|
266
|
+
options: {
|
|
267
|
+
headers: {
|
|
268
|
+
'Cache-Control': 'no-cache',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
callback: ({ fromCache }) => {
|
|
272
|
+
console.log(fromCache);
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
]);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Fetch with Cache-Control: max-stale
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
await sharedHttpCache.fetch([
|
|
282
|
+
{
|
|
283
|
+
url: 'https://example.com/data',
|
|
284
|
+
options: {
|
|
285
|
+
headers: {
|
|
286
|
+
'Cache-Control': 'max-stale=3600',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
callback: ({ fromCache }) => {
|
|
290
|
+
console.log(fromCache);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
]);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Fetch with HEAD method
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
await sharedHttpCache.fetch([
|
|
300
|
+
{
|
|
301
|
+
url: 'https://example.com/resource',
|
|
302
|
+
options: {
|
|
303
|
+
method: 'HEAD',
|
|
304
|
+
},
|
|
305
|
+
callback: ({ headers }) => {
|
|
306
|
+
console.log(headers);
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
]);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Storage management
|
|
313
|
+
|
|
314
|
+
The underlying cache store (`cacache`) is exposed directly.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
sharedHttpCache.store -> cacache
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Compacting (example with await)**
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
sharedHttpCache.store.verify(cacheDir) -> Promise<Object>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
await sharedHttpCache.store.verify(sharedHttpCache.cacheDir);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Listing (example with promise)**
|
|
331
|
+
|
|
332
|
+
```js
|
|
333
|
+
sharedHttpCache
|
|
334
|
+
.fetch(requests)
|
|
335
|
+
.then((sharedHttpCache) => sharedHttpCache.store.ls(sharedHttpCache.cacheDir))
|
|
336
|
+
.then(console.log)
|
|
337
|
+
.catch((errors) => console.error('Errors:', errors));
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Other available operations
|
|
341
|
+
|
|
342
|
+
- sharedHttpCache.store.put(...)
|
|
343
|
+
- sharedHttpCache.store.get(...)
|
|
344
|
+
- sharedHttpCache.store.get.info(...)
|
|
345
|
+
- sharedHttpCache.store.rm.entry(...)
|
|
346
|
+
- sharedHttpCache.store.rm.content(...)
|
|
347
|
+
|
|
348
|
+
See full list of [cacache options](https://github.com/npm/cacache?tab=readme-ov-file#api).
|