@blaze-cache/fetch 0.0.0-stage → 0.2.1-beta
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/README.md +193 -2
- package/dist/cache-control.d.ts +55 -0
- package/dist/cache-control.d.ts.map +1 -0
- package/dist/cache-control.js +110 -0
- package/dist/cache-control.js.map +1 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +398 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/key.d.ts +23 -0
- package/dist/key.d.ts.map +1 -0
- package/dist/key.js +41 -0
- package/dist/key.js.map +1 -0
- package/dist/request.d.ts +33 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +55 -0
- package/dist/request.js.map +1 -0
- package/dist/types.d.ts +192 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -3
package/dist/client.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { NO_DIRECTIVES, parseAgeMs, parseCacheControl, varyIsStorable } from './cache-control.js';
|
|
2
|
+
import { defaultCacheKey } from './key.js';
|
|
3
|
+
import { normalizeInit, resolveMethod, resolveUrl } from './request.js';
|
|
4
|
+
/** Freshness window when neither the option nor `Cache-Control` says otherwise. */
|
|
5
|
+
const DEFAULT_TTL_MS = 60_000;
|
|
6
|
+
/** Only `GET` is cached unless the caller says otherwise. */
|
|
7
|
+
const DEFAULT_METHODS = ['GET'];
|
|
8
|
+
/** A personalized response must never be replayed to a different user. */
|
|
9
|
+
const DEFAULT_VARY_HEADERS = ['authorization'];
|
|
10
|
+
const DEFAULT_MARKER_HEADER = 'x-blaze-cache';
|
|
11
|
+
/** Statuses that cannot carry a body — a replay must pass `null` for them. */
|
|
12
|
+
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
|
|
13
|
+
/**
|
|
14
|
+
* Wraps the platform `fetch` with a Blaze Cache layer.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createCache } from '@blaze-cache/core';
|
|
18
|
+
* import { createBlazeFetch } from '@blaze-cache/fetch';
|
|
19
|
+
*
|
|
20
|
+
* const cache = await createCache({ capacity: 1000 });
|
|
21
|
+
* const fetch = createBlazeFetch(cache, { ttlMs: 30_000 });
|
|
22
|
+
*
|
|
23
|
+
* const response = await fetch('/api/users');
|
|
24
|
+
* response.headers.get('x-blaze-cache'); // 'MISS', then 'HIT' on the next call
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The returned function keeps the platform signature — hand it to anything expecting
|
|
28
|
+
* `typeof fetch`. Requests outside {@link BlazeFetchOptions.methods} pass through untouched,
|
|
29
|
+
* and a failing network call rejects exactly as it would have without the wrapper.
|
|
30
|
+
*/
|
|
31
|
+
export function createBlazeFetch(cache, options = {}) {
|
|
32
|
+
// Called as a member of `globalThis`, so the receiver stays correct in browsers, where an
|
|
33
|
+
// unbound `fetch` throws "Illegal invocation".
|
|
34
|
+
const network = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
35
|
+
const markerHeader = options.markerHeader === undefined ? DEFAULT_MARKER_HEADER : options.markerHeader;
|
|
36
|
+
const methods = new Set((options.methods ?? DEFAULT_METHODS).map((method) => method.toUpperCase()));
|
|
37
|
+
const cacheControl = options.cacheControl ?? true;
|
|
38
|
+
const varyHeaders = options.varyHeaders ?? DEFAULT_VARY_HEADERS;
|
|
39
|
+
const shouldCache = options.shouldCache ?? defaultShouldCache;
|
|
40
|
+
const swr = options.staleWhileRevalidate ?? false;
|
|
41
|
+
/** Whether stale entries may be served at all, and for how long when the header is absent. */
|
|
42
|
+
const swrEnabled = swr !== false;
|
|
43
|
+
const swrFallbackMs = typeof swr === 'number' && swr > 0 ? swr : 0;
|
|
44
|
+
/** Keys with a background refresh already running — one network call per key at a time. */
|
|
45
|
+
const revalidating = new Set();
|
|
46
|
+
/**
|
|
47
|
+
* Identical requests currently on the wire, keyed by cache key — one promise per key that
|
|
48
|
+
* opens only when the leader's store attempt has fully *settled*, so a joiner can trust
|
|
49
|
+
* whatever it then finds in the cache. See {@link dispatch}.
|
|
50
|
+
*/
|
|
51
|
+
const inFlight = new Map();
|
|
52
|
+
/**
|
|
53
|
+
* Whether a cache-layer failure has been reported already.
|
|
54
|
+
*
|
|
55
|
+
* A throwing `cache.get`/`cache.set` or callback must never fail a request the network
|
|
56
|
+
* answered well — the first such failure is announced, then the wrapper keeps working
|
|
57
|
+
* (uncached) in silence rather than spamming the console on every call.
|
|
58
|
+
*/
|
|
59
|
+
let warned = false;
|
|
60
|
+
/** The one place a cache-layer exception is allowed to surface: a single console warning. */
|
|
61
|
+
function warnCacheFailure(error) {
|
|
62
|
+
if (warned)
|
|
63
|
+
return;
|
|
64
|
+
warned = true;
|
|
65
|
+
console.warn('[blaze-cache] cache layer failed; continuing without it', error);
|
|
66
|
+
}
|
|
67
|
+
const keyOf = (url, init) => options.key ? options.key(url, init) : defaultCacheKey(url, init, varyHeaders);
|
|
68
|
+
async function dispatch(input, init) {
|
|
69
|
+
const method = resolveMethod(input, init);
|
|
70
|
+
const url = resolveUrl(input);
|
|
71
|
+
const request = normalizeInit(input, init, method);
|
|
72
|
+
const key = keyOf(url, request);
|
|
73
|
+
// `init.cache` is the caller's instruction, `Cache-Control` is the server's; with
|
|
74
|
+
// `cacheControl: false` only the former is still obeyed.
|
|
75
|
+
const control = cacheControl ? parseCacheControl(request.headers.get('Cache-Control')) : NO_DIRECTIVES;
|
|
76
|
+
const bypassWrite = control.noStore || request.cache === 'no-store';
|
|
77
|
+
// A request `max-age=0` is, per RFC 9111 §5.2.2.1, a demand to revalidate before reuse.
|
|
78
|
+
// This cache has no conditional requests, so the closest honest reading is a full
|
|
79
|
+
// refetch — exactly what `bypassRead` does — rather than replaying the entry untouched.
|
|
80
|
+
const bypassRead = bypassWrite ||
|
|
81
|
+
control.noCache ||
|
|
82
|
+
control.maxAgeMs === 0 ||
|
|
83
|
+
init.forceRefresh === true ||
|
|
84
|
+
request.cache === 'no-cache' ||
|
|
85
|
+
request.cache === 'reload';
|
|
86
|
+
const cacheable = methods.has(method);
|
|
87
|
+
if (cacheable) {
|
|
88
|
+
if (!bypassRead) {
|
|
89
|
+
const record = read(key);
|
|
90
|
+
if (record) {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
// `force-cache` means "use it whatever its age"; otherwise freshness decides.
|
|
93
|
+
if (request.cache === 'force-cache' || now <= record.freshUntil) {
|
|
94
|
+
return replay(record, 'HIT');
|
|
95
|
+
}
|
|
96
|
+
if (swrEnabled && now <= record.staleUntil) {
|
|
97
|
+
// Refreshed in the background: the caller is served *now*, the next call a `HIT`.
|
|
98
|
+
revalidate(input, init, key);
|
|
99
|
+
return replay(record, 'STALE');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// An identical request is already on the wire. Its `Response` cannot be handed over —
|
|
104
|
+
// a body stream can be consumed exactly once, and the leader's own caller is reading
|
|
105
|
+
// it — but the *stored record* is the replayable form of the very same bytes, so the
|
|
106
|
+
// cache doubles as the rendezvous: wait for the leader's write to settle, then replay
|
|
107
|
+
// what it landed. A leader that stored nothing (an unstorable response, a failed
|
|
108
|
+
// write) opens its gate over an empty cache, and the joiner falls through to the
|
|
109
|
+
// network itself — never an unstored response, never a deadlock: gates only ever wait
|
|
110
|
+
// on leaders, and no leader waits on a joiner.
|
|
111
|
+
//
|
|
112
|
+
// Joined only when this call would itself have accepted a cached read. A caller that
|
|
113
|
+
// explicitly demanded the network (`forceRefresh`, `cache: 'reload'`, request
|
|
114
|
+
// `no-cache`/`max-age=0`) is joined *out*: answering it from the cache — however
|
|
115
|
+
// recently written — breaks the promise it made, and it leads below instead.
|
|
116
|
+
if (!bypassRead) {
|
|
117
|
+
const pending = inFlight.get(key);
|
|
118
|
+
if (pending) {
|
|
119
|
+
await pending;
|
|
120
|
+
const shared = read(key);
|
|
121
|
+
// Freshly written, but a pathologically short TTL may have aged it out already.
|
|
122
|
+
if (shared && Date.now() <= shared.freshUntil)
|
|
123
|
+
return replay(shared, 'HIT');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// From here on this call *leads* its key. The gate is registered before the network is
|
|
128
|
+
// touched — otherwise two callers could both miss and both fetch before either
|
|
129
|
+
// registers — and only cacheable, storable requests lead: a `POST` must never be
|
|
130
|
+
// silently coalesced with an identical one, side effects and all.
|
|
131
|
+
const leader = cacheable && !bypassWrite;
|
|
132
|
+
let settle = () => undefined;
|
|
133
|
+
const settled = new Promise((resolve) => {
|
|
134
|
+
settle = resolve;
|
|
135
|
+
});
|
|
136
|
+
if (leader)
|
|
137
|
+
inFlight.set(key, settled);
|
|
138
|
+
/**
|
|
139
|
+
* Opens the gate and retires the leadership — every exit passes through it.
|
|
140
|
+
*
|
|
141
|
+
* Identity-checked: a caller that demanded the network (`forceRefresh`, `reload`) leads
|
|
142
|
+
* too and may have replaced our gate with its own. Deleting whatever sits under `key`
|
|
143
|
+
* would retire *their* leadership while ours still stands, silently dropping the joiners
|
|
144
|
+
* waiting on them.
|
|
145
|
+
*/
|
|
146
|
+
const done = () => {
|
|
147
|
+
if (leader && inFlight.get(key) === settled)
|
|
148
|
+
inFlight.delete(key);
|
|
149
|
+
settle();
|
|
150
|
+
};
|
|
151
|
+
let response;
|
|
152
|
+
try {
|
|
153
|
+
response = await network(input, init);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// A failed leader still opens its gate — over an empty cache, so joiners wake and
|
|
157
|
+
// retry the network themselves instead of waiting on a promise that never settles.
|
|
158
|
+
done();
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
let keep = false;
|
|
162
|
+
if (leader) {
|
|
163
|
+
try {
|
|
164
|
+
keep = shouldCache(response, url, request);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
// A callback that throws must not fail the response the network just delivered.
|
|
168
|
+
warnCacheFailure(error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (keep) {
|
|
172
|
+
// Deliberately fire-and-forget. `store` tees the body with `clone()` — the caller's
|
|
173
|
+
// copy of the stream is untouched — and fully buffers *its* copy before `cache.set`,
|
|
174
|
+
// so nothing about this call's latency depends on the write, and a later read can
|
|
175
|
+
// never race a half-written entry. The gate above, however, opens only once that
|
|
176
|
+
// write has settled: that is what lets a joiner trust the cache it finds.
|
|
177
|
+
void store(response, key, url, request)
|
|
178
|
+
.catch(warnCacheFailure)
|
|
179
|
+
.finally(done);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Nothing will be stored for this call: wake any joiners so they can lead in turn.
|
|
183
|
+
done();
|
|
184
|
+
}
|
|
185
|
+
// Marked last, so `shouldCache`/`tags`/`ttlMs` see exactly what the network sent.
|
|
186
|
+
return mark(response, 'MISS');
|
|
187
|
+
}
|
|
188
|
+
/** Fetches a fresh copy of a stale entry so the next read is a `HIT` again. */
|
|
189
|
+
function revalidate(input, init, key) {
|
|
190
|
+
if (revalidating.has(key))
|
|
191
|
+
return;
|
|
192
|
+
revalidating.add(key);
|
|
193
|
+
// The caller's `signal` is deliberately stripped. It belongs to the request that merely
|
|
194
|
+
// *happened* to notice the staleness — and such callers usually abort right after being
|
|
195
|
+
// served (an unmount, a route change), which would abort the refresh and leave the
|
|
196
|
+
// entry stale forever. A background refresh is the wrapper's own work and runs on the
|
|
197
|
+
// wrapper's own terms; `forceRefresh` keeps it from re-serving the stale entry to itself.
|
|
198
|
+
void dispatch(input, { ...init, signal: undefined, forceRefresh: true })
|
|
199
|
+
// A background refresh must never reject into a caller: the stale entry simply stays
|
|
200
|
+
// until the next attempt.
|
|
201
|
+
.catch(() => undefined)
|
|
202
|
+
.finally(() => revalidating.delete(key));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Buffers the body and writes the record. Returns early when nothing is worth keeping.
|
|
206
|
+
*
|
|
207
|
+
* Deliberately fire-and-forget at the call site: the body it reads comes from a `clone()`
|
|
208
|
+
* of the caller's response, so every millisecond it spends buffering is invisible to the
|
|
209
|
+
* caller — and the record is complete (never half-written) by the time `cache.set` runs.
|
|
210
|
+
*/
|
|
211
|
+
async function store(response, key, url, request) {
|
|
212
|
+
// An opaque response exposes no status, headers or body: there is nothing to store.
|
|
213
|
+
if (response.status === 0)
|
|
214
|
+
return;
|
|
215
|
+
const control = cacheControl ? parseCacheControl(response.headers.get('Cache-Control')) : NO_DIRECTIVES;
|
|
216
|
+
// `no-store` forbids storing; `no-cache` demands revalidation on every use, and this
|
|
217
|
+
// cache has no conditional requests — so neither is worth an entry.
|
|
218
|
+
if (control.noStore || control.noCache)
|
|
219
|
+
return;
|
|
220
|
+
// `Vary` decides which *request* headers this response's identity hangs on. Only a
|
|
221
|
+
// response whose every named header is already folded into the cache key may be stored;
|
|
222
|
+
// anything else would be keyed wrongly, and a wrong key replays the wrong variant —
|
|
223
|
+
// another user's `Cookie`, another locale's `Accept-Language`. When we cannot key on a
|
|
224
|
+
// header, the safe answer is not to cache at all. (Checked unconditionally, not only
|
|
225
|
+
// under `cacheControl`: variant correctness is not a freshness preference.)
|
|
226
|
+
if (!varyIsStorable(response.headers.get('Vary'), varyHeaders))
|
|
227
|
+
return;
|
|
228
|
+
const configured = options.ttlMs;
|
|
229
|
+
const ttl = typeof configured === 'function' ? configured(response, url, request) : (configured ?? DEFAULT_TTL_MS);
|
|
230
|
+
const maxAge = control.maxAgeMs ?? ttl;
|
|
231
|
+
const age = cacheControl ? parseAgeMs(response.headers.get('Age')) : 0;
|
|
232
|
+
const freshMs = maxAge - age;
|
|
233
|
+
// Zero, negative or `NaN` means "keep nothing": already stale, or the caller opted out.
|
|
234
|
+
if (!(freshMs > 0))
|
|
235
|
+
return;
|
|
236
|
+
// The response's own directive wins; the numeric option is the fallback for servers that
|
|
237
|
+
// never send one. When the whole feature is off there is no stale window at all — and
|
|
238
|
+
// `must-revalidate` (or its shared-cache spelling `proxy-revalidate`) closes the window
|
|
239
|
+
// even when the feature is on: such a response must never be replayed past its
|
|
240
|
+
// freshness, so there is nothing it may serve "while revalidating".
|
|
241
|
+
const staleMs = !swrEnabled || control.mustRevalidate
|
|
242
|
+
? 0
|
|
243
|
+
: cacheControl && control.staleWhileRevalidateMs !== undefined
|
|
244
|
+
? control.staleWhileRevalidateMs
|
|
245
|
+
: swrFallbackMs;
|
|
246
|
+
let body;
|
|
247
|
+
try {
|
|
248
|
+
// `clone()` tees the stream: the caller's response still delivers every byte, while
|
|
249
|
+
// this copy is buffered in the background — and fully buffered *before* `cache.set`,
|
|
250
|
+
// so a follow-up read can never race a half-written entry.
|
|
251
|
+
body = await response.clone().arrayBuffer();
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return; // a body that fails mid-flight must not poison the cache
|
|
255
|
+
}
|
|
256
|
+
const storedAt = Date.now();
|
|
257
|
+
const freshUntil = storedAt + freshMs;
|
|
258
|
+
const record = {
|
|
259
|
+
status: response.status,
|
|
260
|
+
statusText: response.statusText,
|
|
261
|
+
headers: storedHeaders(response.headers, body),
|
|
262
|
+
body,
|
|
263
|
+
url: response.url,
|
|
264
|
+
redirected: response.redirected,
|
|
265
|
+
storedAt,
|
|
266
|
+
freshUntil,
|
|
267
|
+
staleUntil: freshUntil + staleMs,
|
|
268
|
+
};
|
|
269
|
+
const totalMs = freshMs + staleMs;
|
|
270
|
+
cache.set(key, record, {
|
|
271
|
+
// `0` is how the core spells "never expires" — reached only via `ttlMs: Infinity`.
|
|
272
|
+
ttlMs: Number.isFinite(totalMs) ? totalMs : 0,
|
|
273
|
+
tags: options.tags?.(response, url, request) ?? defaultTags(request.method, url),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* `cache.get`, guarded: a cache-layer failure reads as a plain miss — the caller falls
|
|
278
|
+
* through to the network instead of having its request fail over a broken cache.
|
|
279
|
+
*/
|
|
280
|
+
function read(key) {
|
|
281
|
+
try {
|
|
282
|
+
return cache.get(key);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
warnCacheFailure(error);
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** Rebuilds a `Response` from a stored record, without touching cache statistics. */
|
|
290
|
+
function replay(record, marker) {
|
|
291
|
+
const headers = headersFromRecord(record.headers);
|
|
292
|
+
if (markerHeader !== false)
|
|
293
|
+
headers.set(markerHeader, marker);
|
|
294
|
+
const response = new Response(NULL_BODY_STATUSES.has(record.status) ? null : record.body, { status: record.status, statusText: record.statusText, headers });
|
|
295
|
+
// `url`/`redirected` are read-only accessors on the prototype; an own property shadows
|
|
296
|
+
// them so a hit carries the metadata of the network response it stands in for.
|
|
297
|
+
defineOwn(response, 'url', record.url);
|
|
298
|
+
defineOwn(response, 'redirected', record.redirected);
|
|
299
|
+
return response;
|
|
300
|
+
}
|
|
301
|
+
/** Stamps the marker on a network response, rebuilding it only if its headers refuse. */
|
|
302
|
+
function mark(response, marker) {
|
|
303
|
+
if (markerHeader === false)
|
|
304
|
+
return response;
|
|
305
|
+
try {
|
|
306
|
+
response.headers.set(markerHeader, marker);
|
|
307
|
+
return response;
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// Immutable headers (network responses in undici, `Response.redirect()`): rebuild so
|
|
311
|
+
// the marker contract holds — the marker goes on the *rebuild*, since the original
|
|
312
|
+
// refused it. Status 0 cannot be rebuilt at all: pass it through unmarked.
|
|
313
|
+
if (response.status === 0)
|
|
314
|
+
return response;
|
|
315
|
+
const headers = new Headers(response.headers);
|
|
316
|
+
headers.set(markerHeader, marker);
|
|
317
|
+
const rebuilt = new Response(response.body, {
|
|
318
|
+
status: response.status,
|
|
319
|
+
statusText: response.statusText,
|
|
320
|
+
headers,
|
|
321
|
+
});
|
|
322
|
+
defineOwn(rebuilt, 'url', response.url);
|
|
323
|
+
defineOwn(rebuilt, 'redirected', response.redirected);
|
|
324
|
+
return rebuilt;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return (input, init = {}) => dispatch(input, init);
|
|
328
|
+
}
|
|
329
|
+
/** Successful responses only. The {@link BlazeFetchOptions.methods} gate has already applied. */
|
|
330
|
+
function defaultShouldCache(response) {
|
|
331
|
+
return response.status >= 200 && response.status < 300;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* `['fetch', '<method> <url>']` — one tag drops everything this wrapper stored
|
|
335
|
+
* (`cache.invalidateTags('fetch')`), the other drops a single resource.
|
|
336
|
+
*/
|
|
337
|
+
function defaultTags(method, url) {
|
|
338
|
+
return ['fetch', `${method.toLowerCase()} ${url}`];
|
|
339
|
+
}
|
|
340
|
+
/** Copies headers into a plain, structured-clone-friendly record. */
|
|
341
|
+
function headersToRecord(headers) {
|
|
342
|
+
const record = {};
|
|
343
|
+
// `Headers.forEach` reports names already lower-cased. Defined, never assigned: a plain
|
|
344
|
+
// `record[name] = value` hands a literal `__proto__` to the inherited setter, which
|
|
345
|
+
// ignores primitives — silently dropping a perfectly legal header instead of storing it.
|
|
346
|
+
headers.forEach((value, name) => {
|
|
347
|
+
Object.defineProperty(record, name, { value, enumerable: true, writable: true, configurable: true });
|
|
348
|
+
});
|
|
349
|
+
return record;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Response headers as a plain record, corrected to describe the bytes we actually hold.
|
|
353
|
+
*
|
|
354
|
+
* `fetch` decodes `content-encoding` for us, so a stored body is always the plain payload
|
|
355
|
+
* while the headers still describe the *wire* format — `gzip` plus the compressed
|
|
356
|
+
* `content-length` (on a real API: 1425 bytes claimed for 6504 stored). Replaying those
|
|
357
|
+
* would hand back a response that contradicts itself, breaking anything that reads
|
|
358
|
+
* `content-length` — a file downloader, `blob().size`, a progress bar — or that trusts the
|
|
359
|
+
* encoding and tries to inflate plain bytes. `transfer-encoding` is hop-by-hop and stops
|
|
360
|
+
* being true the moment the body is buffered.
|
|
361
|
+
*
|
|
362
|
+
* HEAD is the one exception: no body was ever sent, so its `content-length` and
|
|
363
|
+
* `content-encoding` describe what a GET *would* return and stay as metadata.
|
|
364
|
+
*/
|
|
365
|
+
function storedHeaders(headers, body) {
|
|
366
|
+
const record = headersToRecord(headers);
|
|
367
|
+
delete record['transfer-encoding'];
|
|
368
|
+
if (body.byteLength > 0) {
|
|
369
|
+
delete record['content-encoding'];
|
|
370
|
+
if ('content-length' in record)
|
|
371
|
+
record['content-length'] = String(body.byteLength);
|
|
372
|
+
}
|
|
373
|
+
return record;
|
|
374
|
+
}
|
|
375
|
+
/** Shadows a read-only prototype accessor with an own property; never throws. */
|
|
376
|
+
function defineOwn(target, key, value) {
|
|
377
|
+
try {
|
|
378
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: false, configurable: true });
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// A frozen response: the missing metadata is cosmetic, never worth failing a hit over.
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Rebuilds a `Headers` from a stored record, name by name.
|
|
386
|
+
*
|
|
387
|
+
* Never `new Headers(record)`: the platform's plain-object conversion drops a literal
|
|
388
|
+
* `__proto__` key on its way through the inherited setter, losing a legal header between
|
|
389
|
+
* the store and the replay. `append` accepts every name the store can hold.
|
|
390
|
+
*/
|
|
391
|
+
function headersFromRecord(record) {
|
|
392
|
+
const headers = new Headers();
|
|
393
|
+
for (const [name, value] of Object.entries(record)) {
|
|
394
|
+
headers.append(name, value);
|
|
395
|
+
}
|
|
396
|
+
return headers;
|
|
397
|
+
}
|
|
398
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A drop-in `fetch()` replacement backed by Blaze Cache.
|
|
3
|
+
*
|
|
4
|
+
* `createBlazeFetch` returns a function with the platform signature, so it slots into any
|
|
5
|
+
* code that expects `typeof fetch` — and gets caching underneath: per-entry TTLs,
|
|
6
|
+
* `Cache-Control` on both sides of the exchange, tag-based invalidation, SIEVE eviction and
|
|
7
|
+
* optional IndexedDB persistence, all inherited from {@link createCache | `@blaze-cache/core`}.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createCache } from '@blaze-cache/core';
|
|
11
|
+
* import { createBlazeFetch } from '@blaze-cache/fetch';
|
|
12
|
+
*
|
|
13
|
+
* const cache = await createCache({ capacity: 1000 });
|
|
14
|
+
* const fetch = createBlazeFetch(cache, { ttlMs: 30_000 });
|
|
15
|
+
*
|
|
16
|
+
* const response = await fetch('/api/users');
|
|
17
|
+
* response.headers.get('x-blaze-cache'); // 'MISS', then 'HIT'
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
export { createBlazeFetch } from './client.js';
|
|
23
|
+
export { parseCacheControl, parseAgeMs, varyIsStorable } from './cache-control.js';
|
|
24
|
+
export type { CacheControl } from './cache-control.js';
|
|
25
|
+
export { defaultCacheKey, fnv1a } from './key.js';
|
|
26
|
+
export type { BlazeFetch, BlazeFetchCacheLike, BlazeFetchEffectiveInit, BlazeFetchInit, BlazeFetchMarker, BlazeFetchOptions, FetchCacheRecord, } from './types.js';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACnF,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EACV,UAAU,EACV,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A drop-in `fetch()` replacement backed by Blaze Cache.
|
|
3
|
+
*
|
|
4
|
+
* `createBlazeFetch` returns a function with the platform signature, so it slots into any
|
|
5
|
+
* code that expects `typeof fetch` — and gets caching underneath: per-entry TTLs,
|
|
6
|
+
* `Cache-Control` on both sides of the exchange, tag-based invalidation, SIEVE eviction and
|
|
7
|
+
* optional IndexedDB persistence, all inherited from {@link createCache | `@blaze-cache/core`}.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createCache } from '@blaze-cache/core';
|
|
11
|
+
* import { createBlazeFetch } from '@blaze-cache/fetch';
|
|
12
|
+
*
|
|
13
|
+
* const cache = await createCache({ capacity: 1000 });
|
|
14
|
+
* const fetch = createBlazeFetch(cache, { ttlMs: 30_000 });
|
|
15
|
+
*
|
|
16
|
+
* const response = await fetch('/api/users');
|
|
17
|
+
* response.headers.get('x-blaze-cache'); // 'MISS', then 'HIT'
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
export { createBlazeFetch } from './client.js';
|
|
23
|
+
export { parseCacheControl, parseAgeMs, varyIsStorable } from './cache-control.js';
|
|
24
|
+
export { defaultCacheKey, fnv1a } from './key.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGnF,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/key.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BlazeFetchEffectiveInit } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* FNV-1a, 32-bit.
|
|
4
|
+
*
|
|
5
|
+
* Folds sensitive material (bearer tokens, `vary` headers) into a short, stable token. It is
|
|
6
|
+
* not a security primitive — the point is that a persisted cache key never contains a secret
|
|
7
|
+
* in readable form.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fnv1a(input: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Default cache key: method + URL, plus a hash of the `varyHeaders` values.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* defaultCacheKey('/api/users', init, ['authorization']); // "get /api/users?a3f9k"
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The hash is appended whenever `varyHeaders` is non-empty — including when every value is
|
|
18
|
+
* absent — so keys stay stable across calls. Pass `varyHeaders: []` to get the bare
|
|
19
|
+
* `"<method> <url>"` form. Call it from the {@link BlazeFetchOptions.key} callback with the
|
|
20
|
+
* `init` you were handed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function defaultCacheKey(url: string, init: BlazeFetchEffectiveInit, varyHeaders: readonly string[]): string;
|
|
23
|
+
//# sourceMappingURL=key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../src/key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO3C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,uBAAuB,EAC7B,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,MAAM,CAYR"}
|
package/dist/key.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FNV-1a, 32-bit.
|
|
3
|
+
*
|
|
4
|
+
* Folds sensitive material (bearer tokens, `vary` headers) into a short, stable token. It is
|
|
5
|
+
* not a security primitive — the point is that a persisted cache key never contains a secret
|
|
6
|
+
* in readable form.
|
|
7
|
+
*/
|
|
8
|
+
export function fnv1a(input) {
|
|
9
|
+
let hash = 0x811c9dc5;
|
|
10
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
11
|
+
hash ^= input.charCodeAt(index);
|
|
12
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
13
|
+
}
|
|
14
|
+
return hash.toString(36);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Default cache key: method + URL, plus a hash of the `varyHeaders` values.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* defaultCacheKey('/api/users', init, ['authorization']); // "get /api/users?a3f9k"
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The hash is appended whenever `varyHeaders` is non-empty — including when every value is
|
|
24
|
+
* absent — so keys stay stable across calls. Pass `varyHeaders: []` to get the bare
|
|
25
|
+
* `"<method> <url>"` form. Call it from the {@link BlazeFetchOptions.key} callback with the
|
|
26
|
+
* `init` you were handed.
|
|
27
|
+
*/
|
|
28
|
+
export function defaultCacheKey(url, init, varyHeaders) {
|
|
29
|
+
const base = `${init.method.toLowerCase()} ${url}`;
|
|
30
|
+
if (varyHeaders.length === 0)
|
|
31
|
+
return base;
|
|
32
|
+
const headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
|
|
33
|
+
// JSON, never a plain join: the encoding must stay *injective*, because a collision here
|
|
34
|
+
// would replay one identity's payload to another. Joining with a space lets a value
|
|
35
|
+
// containing the separator impersonate the boundary — `['x y', '']` and `['x', 'y']` are
|
|
36
|
+
// two different identities, and only a length-delimited encoding (JSON being the simplest)
|
|
37
|
+
// keeps them apart.
|
|
38
|
+
const vary = JSON.stringify(varyHeaders.map((name) => headers.get(name) ?? ''));
|
|
39
|
+
return `${base}?${fnv1a(vary)}`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=key.js.map
|
package/dist/key.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.js","sourceRoot":"","sources":["../src/key.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa;IACjC,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,IAA6B,EAC7B,WAA8B;IAE9B,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,GAAG,EAAE,CAAC;IACnD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,YAAY,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3F,yFAAyF;IACzF,oFAAoF;IACpF,yFAAyF;IACzF,2FAA2F;IAC3F,oBAAoB;IACpB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChF,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BlazeFetchEffectiveInit } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalising a `fetch` input.
|
|
4
|
+
*
|
|
5
|
+
* `fetch` accepts three shapes — a URL string, a {@link URL} or a whole {@link Request} —
|
|
6
|
+
* and the method/headers may live on the `Request`, on the `init` object, or on both (with
|
|
7
|
+
* `init` winning). Cache keys, `Cache-Control` and every user callback work from the
|
|
8
|
+
* *resolved* view instead: one absolute URL, one method, one merged header list.
|
|
9
|
+
*
|
|
10
|
+
* The network always receives the caller's original `input`/`init`, unchanged.
|
|
11
|
+
*/
|
|
12
|
+
/** `true` when `input` is a `Request`. Guarded, so exotic hosts without one still work. */
|
|
13
|
+
export declare function isRequest(input: RequestInfo | URL): input is Request;
|
|
14
|
+
/**
|
|
15
|
+
* Absolute URL of the request — the basis of every cache key.
|
|
16
|
+
*
|
|
17
|
+
* A `Request` and a `URL` are already absolute. A string is resolved against the page in a
|
|
18
|
+
* browser; in Node there is no document base, so it is kept verbatim, which is still a
|
|
19
|
+
* stable key as long as call sites are stable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveUrl(input: RequestInfo | URL): string;
|
|
22
|
+
/** HTTP method, upper-cased — `GET` when nothing says otherwise. */
|
|
23
|
+
export declare function resolveMethod(input: RequestInfo | URL, init: RequestInit): string;
|
|
24
|
+
/**
|
|
25
|
+
* The **effective** request: `init` as the caller passed it, with `method`, `headers` and
|
|
26
|
+
* `cache` filled in from `input` when it is a `Request`.
|
|
27
|
+
*
|
|
28
|
+
* This is what `key`, `shouldCache`, `tags` and `ttlMs` receive — so a callback can read
|
|
29
|
+
* `authorization` whether the caller set it on a `Request` or on the `init` object. It never
|
|
30
|
+
* reaches the network.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeInit(input: RequestInfo | URL, init: RequestInit, method: string): BlazeFetchEffectiveInit;
|
|
33
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;GASG;AAEH,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,GAAG,KAAK,IAAI,OAAO,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,GAAG,MAAM,CAS3D;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAGjF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,GACb,uBAAuB,CAOzB"}
|
package/dist/request.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalising a `fetch` input.
|
|
3
|
+
*
|
|
4
|
+
* `fetch` accepts three shapes — a URL string, a {@link URL} or a whole {@link Request} —
|
|
5
|
+
* and the method/headers may live on the `Request`, on the `init` object, or on both (with
|
|
6
|
+
* `init` winning). Cache keys, `Cache-Control` and every user callback work from the
|
|
7
|
+
* *resolved* view instead: one absolute URL, one method, one merged header list.
|
|
8
|
+
*
|
|
9
|
+
* The network always receives the caller's original `input`/`init`, unchanged.
|
|
10
|
+
*/
|
|
11
|
+
/** `true` when `input` is a `Request`. Guarded, so exotic hosts without one still work. */
|
|
12
|
+
export function isRequest(input) {
|
|
13
|
+
return typeof Request !== 'undefined' && input instanceof Request;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Absolute URL of the request — the basis of every cache key.
|
|
17
|
+
*
|
|
18
|
+
* A `Request` and a `URL` are already absolute. A string is resolved against the page in a
|
|
19
|
+
* browser; in Node there is no document base, so it is kept verbatim, which is still a
|
|
20
|
+
* stable key as long as call sites are stable.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveUrl(input) {
|
|
23
|
+
if (isRequest(input))
|
|
24
|
+
return input.url;
|
|
25
|
+
const raw = input instanceof URL ? input.href : String(input);
|
|
26
|
+
try {
|
|
27
|
+
return new URL(raw).href;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
const base = globalThis.location?.href;
|
|
31
|
+
return base === undefined ? raw : new URL(raw, base).href;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** HTTP method, upper-cased — `GET` when nothing says otherwise. */
|
|
35
|
+
export function resolveMethod(input, init) {
|
|
36
|
+
const explicit = init.method ?? (isRequest(input) ? input.method : undefined);
|
|
37
|
+
return (explicit ?? 'GET').toUpperCase();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The **effective** request: `init` as the caller passed it, with `method`, `headers` and
|
|
41
|
+
* `cache` filled in from `input` when it is a `Request`.
|
|
42
|
+
*
|
|
43
|
+
* This is what `key`, `shouldCache`, `tags` and `ttlMs` receive — so a callback can read
|
|
44
|
+
* `authorization` whether the caller set it on a `Request` or on the `init` object. It never
|
|
45
|
+
* reaches the network.
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeInit(input, init, method) {
|
|
48
|
+
return {
|
|
49
|
+
...init,
|
|
50
|
+
method,
|
|
51
|
+
headers: new Headers(init.headers ?? (isRequest(input) ? input.headers : undefined)),
|
|
52
|
+
cache: init.cache ?? (isRequest(input) ? input.cache : undefined),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=request.js.map
|