@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/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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"}
@@ -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
@@ -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"}
@@ -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