@blaze-cache/fetch 0.0.0-stage → 0.2.0-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 +170 -2
- package/dist/cache-control.d.ts +35 -0
- package/dist/cache-control.d.ts.map +1 -0
- package/dist/cache-control.js +65 -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 +243 -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 +36 -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 +180 -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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Blaze Cache
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,171 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/blaze-cache/blaze-cache/main/docs/blaze-cache-logo.png" alt="Blaze Cache logo" width="150">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">@blaze-cache/fetch</h1>
|
|
6
|
+
|
|
7
|
+
A drop-in `fetch()` replacement backed by a WebAssembly cache — TTLs, `Cache-Control`,
|
|
8
|
+
stale-while-revalidate and an `x-blaze-cache: HIT | MISS` marker.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @blaze-cache/fetch @blaze-cache/core
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createCache } from '@blaze-cache/core';
|
|
20
|
+
import { createBlazeFetch } from '@blaze-cache/fetch';
|
|
21
|
+
|
|
22
|
+
const cache = await createCache({ capacity: 1000 });
|
|
23
|
+
const fetch = createBlazeFetch(cache, { ttlMs: 30_000 });
|
|
24
|
+
|
|
25
|
+
const response = await fetch('/api/users');
|
|
26
|
+
response.headers.get('x-blaze-cache'); // 'MISS' …
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const again = await fetch('/api/users');
|
|
31
|
+
again.headers.get('x-blaze-cache'); // … then 'HIT'
|
|
32
|
+
await again.json(); // the body replayed from the cache
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The wrapper **is** a `fetch`: same signature, same `Response` back. Hand it to anything that
|
|
36
|
+
expects the platform function.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const asFetch: typeof fetch = fetch;
|
|
40
|
+
window.fetch = fetch; // now every request in the app is cached
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Behaviour
|
|
44
|
+
|
|
45
|
+
* **Only `GET` is cached** — pass `methods: ['GET', 'HEAD']` to include `HEAD`. Everything
|
|
46
|
+
else (including errors from uncached methods) always goes to the network.
|
|
47
|
+
* **`Cache-Control` is honoured on both sides** when `cacheControl` is on (the default):
|
|
48
|
+
`max-age` overrides `ttlMs`, `Age` is deducted, `no-store`/`no-cache` on the request skip
|
|
49
|
+
the read, and the same directives on the response stop it being stored.
|
|
50
|
+
* **Only successful responses are stored** — the default `shouldCache` keeps `2xx`; replace
|
|
51
|
+
it to widen or narrow that.
|
|
52
|
+
* **`Authorization` participates in the default key** (hashed, never stored verbatim), so a
|
|
53
|
+
personalized response is never replayed to a different user. Tune with `varyHeaders`.
|
|
54
|
+
* **A hit is a real `Response`** — status, status text, headers, body, `url` after a
|
|
55
|
+
redirect and `redirected` are replayed exactly, so your code cannot tell the difference
|
|
56
|
+
apart from the marker header.
|
|
57
|
+
* **Binary, `Blob` and chunked bodies are first-class** — the payload is buffered as raw
|
|
58
|
+
bytes whether it arrived as a file, a `ReadableStream` or plain text, so an image
|
|
59
|
+
replays byte-for-byte with its `content-type` intact and `blob().type`/`blob().size`
|
|
60
|
+
still correct. A streamed (chunked) response is assembled *before* your `Response` is
|
|
61
|
+
handed back, so a hit can never half-load.
|
|
62
|
+
* **Transport headers are corrected to the bytes stored** — `fetch` decodes
|
|
63
|
+
`content-encoding` for you, so the record drops `content-encoding` and
|
|
64
|
+
`transfer-encoding` and rewrites `content-length` to the payload's real size. Without
|
|
65
|
+
that, a hit would claim `content-length: 1425` (the compressed size) for 6504 stored
|
|
66
|
+
bytes — which breaks anything that measures a download. `HEAD` keeps its headers: no
|
|
67
|
+
body was sent, so its `content-length` is what a GET *would* return.
|
|
68
|
+
* **Failures behave like plain `fetch`** — a rejected network call still rejects; a cache
|
|
69
|
+
that cannot store a response never breaks the request that produced it.
|
|
70
|
+
|
|
71
|
+
## Options
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const fetch = createBlazeFetch(cache, {
|
|
75
|
+
ttlMs: 30_000, // number | (response, url, init) => number
|
|
76
|
+
methods: ['GET'], // which methods are cacheable
|
|
77
|
+
cacheControl: true, // honour Cache-Control on request + response
|
|
78
|
+
staleWhileRevalidate: false, // true | milliseconds — see below
|
|
79
|
+
key: (url, init) => `${init.method} ${url}`, // custom cache key
|
|
80
|
+
shouldCache: (response) => response.ok, // which responses are stored
|
|
81
|
+
tags: (response, url) => ['fetch', url], // tags on stored entries
|
|
82
|
+
varyHeaders: ['authorization'], // headers folded into the default key
|
|
83
|
+
markerHeader: 'x-blaze-cache', // or false to disable the marker
|
|
84
|
+
fetch: globalThis.fetch, // the real network function (for tests)
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Option | Default | What it does |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `ttlMs` | `60_000` | Freshness window. Overridden by a response `max-age`. |
|
|
91
|
+
| `methods` | `['GET']` | Methods eligible for caching. |
|
|
92
|
+
| `cacheControl` | `true` | Honour `Cache-Control`/`Age` on both sides. |
|
|
93
|
+
| `staleWhileRevalidate` | `false` | Serve stale entries while refreshing. A number is the fallback window when the response omits the directive. |
|
|
94
|
+
| `key` | method + URL + vary hash | Custom cache key. |
|
|
95
|
+
| `shouldCache` | `2xx` | Which responses are stored. |
|
|
96
|
+
| `tags` | `['fetch', '<method> <url>']` | Tags for `cache.invalidateTags(...)`. |
|
|
97
|
+
| `varyHeaders` | `['authorization']` | Request headers folded into the default key. |
|
|
98
|
+
| `markerHeader` | `'x-blaze-cache'` | Header carrying `HIT`/`MISS`/`STALE`; `false` disables it. |
|
|
99
|
+
| `fetch` | `globalThis.fetch` | The real network call — pass a stub in tests. |
|
|
100
|
+
|
|
101
|
+
Requests can force the behaviour per call:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await fetch('/api/users', { forceRefresh: true }); // bypass the cache read
|
|
105
|
+
await fetch('/api/users', { cache: 'no-cache' }); // the standard spelling of the same
|
|
106
|
+
await fetch('/api/users', { cache: 'no-store' }); // neither read nor write
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Stale-while-revalidate
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const fetch = createBlazeFetch(cache, { ttlMs: 10_000, staleWhileRevalidate: 30_000 });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Within the stale window a request is answered **immediately** from the cache with
|
|
116
|
+
`x-blaze-cache: STALE`, while a background refresh rewrites the entry — the next request is
|
|
117
|
+
a fresh `HIT`. One refresh runs per key at a time, and its failure is swallowed: a stale
|
|
118
|
+
entry is better than a failed request.
|
|
119
|
+
|
|
120
|
+
The window comes from the response's `Cache-Control: stale-while-revalidate=<seconds>`;
|
|
121
|
+
the number you pass is only the fallback for servers that do not send it.
|
|
122
|
+
|
|
123
|
+
## Invalidating
|
|
124
|
+
|
|
125
|
+
Stored responses carry two tags, so they plug straight into the cache's tag API:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
cache.invalidateTags('fetch'); // everything this wrapper stored
|
|
129
|
+
cache.invalidateTags('get /api/users'); // one resource
|
|
130
|
+
cache.set('get /api/users', /* … */); // … or just overwrite it
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Custom keys
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { createBlazeFetch, defaultCacheKey } from '@blaze-cache/fetch';
|
|
137
|
+
|
|
138
|
+
const fetch = createBlazeFetch(cache, {
|
|
139
|
+
// Keep the default (method + URL + vary hash) and add a version prefix:
|
|
140
|
+
key: (url, init) => `v2:${defaultCacheKey(url, init, ['authorization'])}`,
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Testing
|
|
145
|
+
|
|
146
|
+
Two suites, both run against the built package:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
pnpm --filter @blaze-cache/fetch test # stubbed network — deterministic, offline
|
|
150
|
+
pnpm --filter @blaze-cache/fetch test:live # real public API — opt-in, needs the network
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The live suite calls `api.github.com` (keyless, well under ten requests per run) to prove
|
|
154
|
+
that *real* `Cache-Control` headers are read correctly — a stub can only simulate that.
|
|
155
|
+
Every test in it is skipped unless `BLAZE_CACHE_LIVE=1` is set, so the default suite always
|
|
156
|
+
passes with no network at all.
|
|
157
|
+
|
|
158
|
+
## Documentation
|
|
159
|
+
|
|
160
|
+
Setup, core concepts and the complete API reference live in the
|
|
161
|
+
[repository README](https://github.com/blaze-cache/blaze-cache#readme).
|
|
162
|
+
|
|
163
|
+
## Part of Blaze Cache
|
|
164
|
+
|
|
165
|
+
* [`@blaze-cache/core`](https://www.npmjs.com/package/@blaze-cache/core) — the cache itself
|
|
166
|
+
* [`@blaze-cache/axios`](https://www.npmjs.com/package/@blaze-cache/axios) — the axios adapter
|
|
167
|
+
* [`@blaze-cache/react-query`](https://www.npmjs.com/package/@blaze-cache/react-query) — TanStack Query v5 mirror
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
[MIT](https://github.com/blaze-cache/blaze-cache/blob/main/LICENSE) © Blaze Cache
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `Cache-Control` (and the related `Age`) say about a request or a response.
|
|
3
|
+
*
|
|
4
|
+
* Package-local on purpose: this wrapper needs the `stale-while-revalidate` directive, which
|
|
5
|
+
* `@blaze-cache/axios` does not, and each package is free to grow its own directives without
|
|
6
|
+
* dragging the others along.
|
|
7
|
+
*/
|
|
8
|
+
export interface CacheControl {
|
|
9
|
+
/** `no-store` — neither read nor write the cache. */
|
|
10
|
+
noStore: boolean;
|
|
11
|
+
/** `no-cache` — must not be served without revalidating. */
|
|
12
|
+
noCache: boolean;
|
|
13
|
+
/** `max-age=<seconds>`, converted to milliseconds. */
|
|
14
|
+
maxAgeMs: number | undefined;
|
|
15
|
+
/** `stale-while-revalidate=<seconds>`, converted to milliseconds. */
|
|
16
|
+
staleWhileRevalidateMs: number | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The value used when parsing is switched off (`cacheControl: false`) or a header is absent. */
|
|
19
|
+
export declare const NO_DIRECTIVES: CacheControl;
|
|
20
|
+
/**
|
|
21
|
+
* Minimal `Cache-Control` parser — only the directives a client-side cache acts on.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* parseCacheControl('max-age=30, stale-while-revalidate=120').maxAgeMs; // 30_000
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseCacheControl(value: string | undefined | null): CacheControl;
|
|
28
|
+
/**
|
|
29
|
+
* `Age: <seconds>` — how long an intermediate cache has been holding the response.
|
|
30
|
+
*
|
|
31
|
+
* The freshness window shrinks by this much: a response that is already halfway to its
|
|
32
|
+
* `max-age` deadline must not look brand new to us. Anything unparsable counts as zero.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseAgeMs(value: string | undefined | null): number;
|
|
35
|
+
//# sourceMappingURL=cache-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-control.d.ts","sourceRoot":"","sources":["../src/cache-control.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,qEAAqE;IACrE,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,iGAAiG;AACjG,eAAO,MAAM,aAAa,EAAE,YAK1B,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,YAAY,CAsBhF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAInE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `Cache-Control` (and the related `Age`) say about a request or a response.
|
|
3
|
+
*
|
|
4
|
+
* Package-local on purpose: this wrapper needs the `stale-while-revalidate` directive, which
|
|
5
|
+
* `@blaze-cache/axios` does not, and each package is free to grow its own directives without
|
|
6
|
+
* dragging the others along.
|
|
7
|
+
*/
|
|
8
|
+
/** The value used when parsing is switched off (`cacheControl: false`) or a header is absent. */
|
|
9
|
+
export const NO_DIRECTIVES = Object.freeze({
|
|
10
|
+
noStore: false,
|
|
11
|
+
noCache: false,
|
|
12
|
+
maxAgeMs: undefined,
|
|
13
|
+
staleWhileRevalidateMs: undefined,
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Minimal `Cache-Control` parser — only the directives a client-side cache acts on.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* parseCacheControl('max-age=30, stale-while-revalidate=120').maxAgeMs; // 30_000
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function parseCacheControl(value) {
|
|
23
|
+
const result = {
|
|
24
|
+
noStore: false,
|
|
25
|
+
noCache: false,
|
|
26
|
+
maxAgeMs: undefined,
|
|
27
|
+
staleWhileRevalidateMs: undefined,
|
|
28
|
+
};
|
|
29
|
+
if (!value)
|
|
30
|
+
return result;
|
|
31
|
+
for (const rawDirective of value.split(',')) {
|
|
32
|
+
const directive = rawDirective.trim().toLowerCase();
|
|
33
|
+
if (directive === 'no-store') {
|
|
34
|
+
result.noStore = true;
|
|
35
|
+
}
|
|
36
|
+
else if (directive === 'no-cache') {
|
|
37
|
+
result.noCache = true;
|
|
38
|
+
}
|
|
39
|
+
else if (directive.startsWith('max-age=')) {
|
|
40
|
+
result.maxAgeMs = seconds(directive.slice('max-age='.length));
|
|
41
|
+
}
|
|
42
|
+
else if (directive.startsWith('stale-while-revalidate=')) {
|
|
43
|
+
result.staleWhileRevalidateMs = seconds(directive.slice('stale-while-revalidate='.length));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `Age: <seconds>` — how long an intermediate cache has been holding the response.
|
|
50
|
+
*
|
|
51
|
+
* The freshness window shrinks by this much: a response that is already halfway to its
|
|
52
|
+
* `max-age` deadline must not look brand new to us. Anything unparsable counts as zero.
|
|
53
|
+
*/
|
|
54
|
+
export function parseAgeMs(value) {
|
|
55
|
+
if (!value)
|
|
56
|
+
return 0;
|
|
57
|
+
const secondsValue = Number(value.trim());
|
|
58
|
+
return Number.isFinite(secondsValue) && secondsValue >= 0 ? secondsValue * 1000 : 0;
|
|
59
|
+
}
|
|
60
|
+
/** Seconds → milliseconds, or `undefined` when the value is not a usable non-negative number. */
|
|
61
|
+
function seconds(raw) {
|
|
62
|
+
const value = Number(raw.trim());
|
|
63
|
+
return Number.isFinite(value) && value >= 0 ? value * 1000 : undefined;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=cache-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-control.js","sourceRoot":"","sources":["../src/cache-control.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,iGAAiG;AACjG,MAAM,CAAC,MAAM,aAAa,GAAiB,MAAM,CAAC,MAAM,CAAC;IACvD,OAAO,EAAE,KAAK;IACd,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,SAAS;IACnB,sBAAsB,EAAE,SAAS;CAClC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAgC;IAChE,MAAM,MAAM,GAAiB;QAC3B,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,SAAS;QACnB,sBAAsB,EAAE,SAAS;KAClC,CAAC;IACF,IAAI,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC;IAE1B,KAAK,MAAM,YAAY,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpD,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC7B,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,CAAC;aAAM,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,CAAC;aAAM,IAAI,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5C,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAChE,CAAC;aAAM,IAAI,SAAS,CAAC,UAAU,CAAC,yBAAyB,CAAC,EAAE,CAAC;YAC3D,MAAM,CAAC,sBAAsB,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAgC;IACzD,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IACrB,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,iGAAiG;AACjG,SAAS,OAAO,CAAC,GAAW;IAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BlazeFetch, BlazeFetchCacheLike, BlazeFetchOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps the platform `fetch` with a Blaze Cache layer.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { createCache } from '@blaze-cache/core';
|
|
7
|
+
* import { createBlazeFetch } from '@blaze-cache/fetch';
|
|
8
|
+
*
|
|
9
|
+
* const cache = await createCache({ capacity: 1000 });
|
|
10
|
+
* const fetch = createBlazeFetch(cache, { ttlMs: 30_000 });
|
|
11
|
+
*
|
|
12
|
+
* const response = await fetch('/api/users');
|
|
13
|
+
* response.headers.get('x-blaze-cache'); // 'MISS', then 'HIT' on the next call
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The returned function keeps the platform signature — hand it to anything expecting
|
|
17
|
+
* `typeof fetch`. Requests outside {@link BlazeFetchOptions.methods} pass through untouched,
|
|
18
|
+
* and a failing network call rejects exactly as it would have without the wrapper.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createBlazeFetch(cache: BlazeFetchCacheLike, options?: BlazeFetchOptions): BlazeFetch;
|
|
21
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,mBAAmB,EAInB,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAgBpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,UAAU,CAqLZ"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { NO_DIRECTIVES, parseAgeMs, parseCacheControl } 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
|
+
const keyOf = (url, init) => options.key ? options.key(url, init) : defaultCacheKey(url, init, varyHeaders);
|
|
47
|
+
async function dispatch(input, init) {
|
|
48
|
+
const method = resolveMethod(input, init);
|
|
49
|
+
const url = resolveUrl(input);
|
|
50
|
+
const request = normalizeInit(input, init, method);
|
|
51
|
+
const key = keyOf(url, request);
|
|
52
|
+
// `init.cache` is the caller's instruction, `Cache-Control` is the server's; with
|
|
53
|
+
// `cacheControl: false` only the former is still obeyed.
|
|
54
|
+
const control = cacheControl ? parseCacheControl(request.headers.get('Cache-Control')) : NO_DIRECTIVES;
|
|
55
|
+
const bypassWrite = control.noStore || request.cache === 'no-store';
|
|
56
|
+
const bypassRead = bypassWrite ||
|
|
57
|
+
control.noCache ||
|
|
58
|
+
init.forceRefresh === true ||
|
|
59
|
+
request.cache === 'no-cache' ||
|
|
60
|
+
request.cache === 'reload';
|
|
61
|
+
const cacheable = methods.has(method);
|
|
62
|
+
if (cacheable && !bypassRead) {
|
|
63
|
+
const record = cache.get(key);
|
|
64
|
+
if (record) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
// `force-cache` means "use it whatever its age"; otherwise freshness decides.
|
|
67
|
+
if (request.cache === 'force-cache' || now <= record.freshUntil) {
|
|
68
|
+
return replay(record, 'HIT');
|
|
69
|
+
}
|
|
70
|
+
if (swrEnabled && now <= record.staleUntil) {
|
|
71
|
+
// Refreshed in the background: the caller is served *now*, the next call a `HIT`.
|
|
72
|
+
revalidate(input, init, key);
|
|
73
|
+
return replay(record, 'STALE');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const response = await network(input, init);
|
|
78
|
+
if (cacheable && !bypassWrite && shouldCache(response, url, request)) {
|
|
79
|
+
await store(response, key, url, request);
|
|
80
|
+
}
|
|
81
|
+
// Marked last, so `shouldCache`/`tags`/`ttlMs` see exactly what the network sent.
|
|
82
|
+
return mark(response, 'MISS');
|
|
83
|
+
}
|
|
84
|
+
/** Fetches a fresh copy of a stale entry so the next read is a `HIT` again. */
|
|
85
|
+
function revalidate(input, init, key) {
|
|
86
|
+
if (revalidating.has(key))
|
|
87
|
+
return;
|
|
88
|
+
revalidating.add(key);
|
|
89
|
+
void dispatch(input, { ...init, forceRefresh: true })
|
|
90
|
+
// A background refresh must never reject into a caller: the stale entry simply stays
|
|
91
|
+
// until the next attempt.
|
|
92
|
+
.catch(() => undefined)
|
|
93
|
+
.finally(() => revalidating.delete(key));
|
|
94
|
+
}
|
|
95
|
+
/** Buffers the body and writes the record. Returns early when nothing is worth keeping. */
|
|
96
|
+
async function store(response, key, url, request) {
|
|
97
|
+
// An opaque response exposes no status, headers or body: there is nothing to store.
|
|
98
|
+
if (response.status === 0)
|
|
99
|
+
return;
|
|
100
|
+
const control = cacheControl ? parseCacheControl(response.headers.get('Cache-Control')) : NO_DIRECTIVES;
|
|
101
|
+
// `no-store` forbids storing; `no-cache` demands revalidation on every use, and this
|
|
102
|
+
// cache has no conditional requests — so neither is worth an entry.
|
|
103
|
+
if (control.noStore || control.noCache)
|
|
104
|
+
return;
|
|
105
|
+
const configured = options.ttlMs;
|
|
106
|
+
const ttl = typeof configured === 'function' ? configured(response, url, request) : (configured ?? DEFAULT_TTL_MS);
|
|
107
|
+
const maxAge = control.maxAgeMs ?? ttl;
|
|
108
|
+
const age = cacheControl ? parseAgeMs(response.headers.get('Age')) : 0;
|
|
109
|
+
const freshMs = maxAge - age;
|
|
110
|
+
// Zero, negative or `NaN` means "keep nothing": already stale, or the caller opted out.
|
|
111
|
+
if (!(freshMs > 0))
|
|
112
|
+
return;
|
|
113
|
+
// The response's own directive wins; the numeric option is the fallback for servers that
|
|
114
|
+
// never send one. When the whole feature is off there is no stale window at all.
|
|
115
|
+
const staleMs = !swrEnabled
|
|
116
|
+
? 0
|
|
117
|
+
: cacheControl && control.staleWhileRevalidateMs !== undefined
|
|
118
|
+
? control.staleWhileRevalidateMs
|
|
119
|
+
: swrFallbackMs;
|
|
120
|
+
let body;
|
|
121
|
+
try {
|
|
122
|
+
// `clone()` tees the stream: the caller's response still delivers every byte, while
|
|
123
|
+
// this copy is buffered *before* `dispatch` resolves — so a follow-up read can never
|
|
124
|
+
// race an in-flight write.
|
|
125
|
+
body = await response.clone().arrayBuffer();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return; // a body that fails mid-flight must not poison the cache
|
|
129
|
+
}
|
|
130
|
+
const storedAt = Date.now();
|
|
131
|
+
const freshUntil = storedAt + freshMs;
|
|
132
|
+
const record = {
|
|
133
|
+
status: response.status,
|
|
134
|
+
statusText: response.statusText,
|
|
135
|
+
headers: storedHeaders(response.headers, body),
|
|
136
|
+
body,
|
|
137
|
+
url: response.url,
|
|
138
|
+
redirected: response.redirected,
|
|
139
|
+
storedAt,
|
|
140
|
+
freshUntil,
|
|
141
|
+
staleUntil: freshUntil + staleMs,
|
|
142
|
+
};
|
|
143
|
+
const totalMs = freshMs + staleMs;
|
|
144
|
+
cache.set(key, record, {
|
|
145
|
+
// `0` is how the core spells "never expires" — reached only via `ttlMs: Infinity`.
|
|
146
|
+
ttlMs: Number.isFinite(totalMs) ? totalMs : 0,
|
|
147
|
+
tags: options.tags?.(response, url, request) ?? defaultTags(request.method, url),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Rebuilds a `Response` from a stored record, without touching cache statistics. */
|
|
151
|
+
function replay(record, marker) {
|
|
152
|
+
const headers = new Headers(record.headers);
|
|
153
|
+
if (markerHeader !== false)
|
|
154
|
+
headers.set(markerHeader, marker);
|
|
155
|
+
const response = new Response(NULL_BODY_STATUSES.has(record.status) ? null : record.body, { status: record.status, statusText: record.statusText, headers });
|
|
156
|
+
// `url`/`redirected` are read-only accessors on the prototype; an own property shadows
|
|
157
|
+
// them so a hit carries the metadata of the network response it stands in for.
|
|
158
|
+
defineOwn(response, 'url', record.url);
|
|
159
|
+
defineOwn(response, 'redirected', record.redirected);
|
|
160
|
+
return response;
|
|
161
|
+
}
|
|
162
|
+
/** Stamps the marker on a network response, rebuilding it only if its headers refuse. */
|
|
163
|
+
function mark(response, marker) {
|
|
164
|
+
if (markerHeader === false)
|
|
165
|
+
return response;
|
|
166
|
+
try {
|
|
167
|
+
response.headers.set(markerHeader, marker);
|
|
168
|
+
return response;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Immutable headers (network responses in undici, `Response.redirect()`): rebuild so
|
|
172
|
+
// the marker contract holds — the marker goes on the *rebuild*, since the original
|
|
173
|
+
// refused it. Status 0 cannot be rebuilt at all: pass it through unmarked.
|
|
174
|
+
if (response.status === 0)
|
|
175
|
+
return response;
|
|
176
|
+
const headers = new Headers(response.headers);
|
|
177
|
+
headers.set(markerHeader, marker);
|
|
178
|
+
const rebuilt = new Response(response.body, {
|
|
179
|
+
status: response.status,
|
|
180
|
+
statusText: response.statusText,
|
|
181
|
+
headers,
|
|
182
|
+
});
|
|
183
|
+
defineOwn(rebuilt, 'url', response.url);
|
|
184
|
+
defineOwn(rebuilt, 'redirected', response.redirected);
|
|
185
|
+
return rebuilt;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return (input, init = {}) => dispatch(input, init);
|
|
189
|
+
}
|
|
190
|
+
/** Successful responses only. The {@link BlazeFetchOptions.methods} gate has already applied. */
|
|
191
|
+
function defaultShouldCache(response) {
|
|
192
|
+
return response.status >= 200 && response.status < 300;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* `['fetch', '<method> <url>']` — one tag drops everything this wrapper stored
|
|
196
|
+
* (`cache.invalidateTags('fetch')`), the other drops a single resource.
|
|
197
|
+
*/
|
|
198
|
+
function defaultTags(method, url) {
|
|
199
|
+
return ['fetch', `${method.toLowerCase()} ${url}`];
|
|
200
|
+
}
|
|
201
|
+
/** Copies headers into a plain, structured-clone-friendly record. */
|
|
202
|
+
function headersToRecord(headers) {
|
|
203
|
+
const record = {};
|
|
204
|
+
// `Headers.forEach` reports names already lower-cased.
|
|
205
|
+
headers.forEach((value, name) => {
|
|
206
|
+
record[name] = value;
|
|
207
|
+
});
|
|
208
|
+
return record;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Response headers as a plain record, corrected to describe the bytes we actually hold.
|
|
212
|
+
*
|
|
213
|
+
* `fetch` decodes `content-encoding` for us, so a stored body is always the plain payload
|
|
214
|
+
* while the headers still describe the *wire* format — `gzip` plus the compressed
|
|
215
|
+
* `content-length` (on a real API: 1425 bytes claimed for 6504 stored). Replaying those
|
|
216
|
+
* would hand back a response that contradicts itself, breaking anything that reads
|
|
217
|
+
* `content-length` — a file downloader, `blob().size`, a progress bar — or that trusts the
|
|
218
|
+
* encoding and tries to inflate plain bytes. `transfer-encoding` is hop-by-hop and stops
|
|
219
|
+
* being true the moment the body is buffered.
|
|
220
|
+
*
|
|
221
|
+
* HEAD is the one exception: no body was ever sent, so its `content-length` and
|
|
222
|
+
* `content-encoding` describe what a GET *would* return and stay as metadata.
|
|
223
|
+
*/
|
|
224
|
+
function storedHeaders(headers, body) {
|
|
225
|
+
const record = headersToRecord(headers);
|
|
226
|
+
delete record['transfer-encoding'];
|
|
227
|
+
if (body.byteLength > 0) {
|
|
228
|
+
delete record['content-encoding'];
|
|
229
|
+
if ('content-length' in record)
|
|
230
|
+
record['content-length'] = String(body.byteLength);
|
|
231
|
+
}
|
|
232
|
+
return record;
|
|
233
|
+
}
|
|
234
|
+
/** Shadows a read-only prototype accessor with an own property; never throws. */
|
|
235
|
+
function defineOwn(target, key, value) {
|
|
236
|
+
try {
|
|
237
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: false, configurable: true });
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// A frozen response: the missing metadata is cosmetic, never worth failing a hit over.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAWxE,mFAAmF;AACnF,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,6DAA6D;AAC7D,MAAM,eAAe,GAAsB,CAAC,KAAK,CAAC,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,oBAAoB,GAAsB,CAAC,eAAe,CAAC,CAAC;AAElE,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAE9C,8EAA8E;AAC9E,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAA0B,EAC1B,UAA6B,EAAE;IAE/B,0FAA0F;IAC1F,+CAA+C;IAC/C,MAAM,OAAO,GACX,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAEpE,MAAM,YAAY,GAChB,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IACpF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACpG,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC;IAClD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,IAAI,KAAK,CAAC;IAClD,8FAA8F;IAC9F,MAAM,UAAU,GAAG,GAAG,KAAK,KAAK,CAAC;IACjC,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,2FAA2F;IAC3F,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IAEvC,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,IAA6B,EAAU,EAAE,CACnE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAEjF,KAAK,UAAU,QAAQ,CAAC,KAAwB,EAAE,IAAoB;QACpE,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAEhC,kFAAkF;QAClF,yDAAyD;QACzD,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;QACvG,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC;QACpE,MAAM,UAAU,GACd,WAAW;YACX,OAAO,CAAC,OAAO;YACf,IAAI,CAAC,YAAY,KAAK,IAAI;YAC1B,OAAO,CAAC,KAAK,KAAK,UAAU;YAC5B,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC;QAC7B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEtC,IAAI,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,8EAA8E;gBAC9E,IAAI,OAAO,CAAC,KAAK,KAAK,aAAa,IAAI,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;oBAChE,OAAO,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBAC/B,CAAC;gBACD,IAAI,UAAU,IAAI,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;oBAC3C,kFAAkF;oBAClF,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;oBAC7B,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAE5C,IAAI,SAAS,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC;YACrE,MAAM,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QAED,kFAAkF;QAClF,OAAO,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,+EAA+E;IAC/E,SAAS,UAAU,CAAC,KAAwB,EAAE,IAAoB,EAAE,GAAW;QAC7E,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAClC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtB,KAAK,QAAQ,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YACnD,qFAAqF;YACrF,0BAA0B;aACzB,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;aACtB,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,2FAA2F;IAC3F,KAAK,UAAU,KAAK,CAClB,QAAkB,EAClB,GAAW,EACX,GAAW,EACX,OAAgC;QAEhC,oFAAoF;QACpF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAElC,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;QACxG,qFAAqF;QACrF,oEAAoE;QACpE,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO;YAAE,OAAO;QAE/C,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC;QACjC,MAAM,GAAG,GAAG,OAAO,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,cAAc,CAAC,CAAC;QACnH,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC;QACvC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC;QAC7B,wFAAwF;QACxF,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;YAAE,OAAO;QAE3B,yFAAyF;QACzF,iFAAiF;QACjF,MAAM,OAAO,GAAG,CAAC,UAAU;YACzB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,YAAY,IAAI,OAAO,CAAC,sBAAsB,KAAK,SAAS;gBAC5D,CAAC,CAAC,OAAO,CAAC,sBAAsB;gBAChC,CAAC,CAAC,aAAa,CAAC;QAEpB,IAAI,IAAiB,CAAC;QACtB,IAAI,CAAC;YACH,oFAAoF;YACpF,qFAAqF;YACrF,2BAA2B;YAC3B,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,yDAAyD;QACnE,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;QACtC,MAAM,MAAM,GAAqB;YAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;YAC9C,IAAI;YACJ,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,QAAQ;YACR,UAAU;YACV,UAAU,EAAE,UAAU,GAAG,OAAO;SACjC,CAAC;QAEF,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;QAClC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE;YACrB,mFAAmF;YACnF,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC7C,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACjF,CAAC,CAAC;IACL,CAAC;IAED,qFAAqF;IACrF,SAAS,MAAM,CAAC,MAAwB,EAAE,MAAwB;QAChE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,YAAY,KAAK,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAE9D,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAC3B,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAC1D,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,CAClE,CAAC;QACF,uFAAuF;QACvF,+EAA+E;QAC/E,SAAS,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QACvC,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QACrD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,yFAAyF;IACzF,SAAS,IAAI,CAAC,QAAkB,EAAE,MAAwB;QACxD,IAAI,YAAY,KAAK,KAAK;YAAE,OAAO,QAAQ,CAAC;QAC5C,IAAI,CAAC;YACH,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YAC3C,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,qFAAqF;YACrF,mFAAmF;YACnF,2EAA2E;YAC3E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,QAAQ,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;gBAC1C,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,OAAO;aACR,CAAC,CAAC;YACH,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;YACxC,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YACtD,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,iGAAiG;AACjG,SAAS,kBAAkB,CAAC,QAAkB;IAC5C,OAAO,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,MAAc,EAAE,GAAW;IAC9C,OAAO,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,qEAAqE;AACrE,SAAS,eAAe,CAAC,OAAgB;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,uDAAuD;IACvD,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,aAAa,CAAC,OAAgB,EAAE,IAAiB;IACxD,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAClC,IAAI,gBAAgB,IAAI,MAAM;YAAE,MAAM,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,SAAS,SAAS,CAAC,MAAc,EAAE,GAAW,EAAE,KAAc;IAC5D,IAAI,CAAC;QACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACvG,CAAC;IAAC,MAAM,CAAC;QACP,uFAAuF;IACzF,CAAC;AACH,CAAC"}
|
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 } 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,MAAM,oBAAoB,CAAC;AACnE,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 } 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,MAAM,oBAAoB,CAAC;AAGnE,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,CAOR"}
|
package/dist/key.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
const vary = varyHeaders.map((name) => headers.get(name) ?? '').join(' ');
|
|
34
|
+
return `${base}?${fnv1a(vary)}`;
|
|
35
|
+
}
|
|
36
|
+
//# 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,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1E,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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,2FAA2F;AAC3F,MAAM,UAAU,SAAS,CAAC,KAAwB;IAChD,OAAO,OAAO,OAAO,KAAK,WAAW,IAAI,KAAK,YAAY,OAAO,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAwB;IACjD,IAAI,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC;IACvC,MAAM,GAAG,GAAG,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC;QACvC,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,KAAwB,EAAE,IAAiB;IACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC9E,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAwB,EACxB,IAAiB,EACjB,MAAc;IAEd,OAAO;QACL,GAAG,IAAI;QACP,MAAM;QACN,OAAO,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACpF,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;KAClE,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value written on the `x-blaze-cache` response header by every response the wrapper returns.
|
|
3
|
+
*
|
|
4
|
+
* * `MISS` — the response came from the network.
|
|
5
|
+
* * `HIT` — served from the cache while still fresh.
|
|
6
|
+
* * `STALE` — served from the cache past its freshness window, only while
|
|
7
|
+
* {@link BlazeFetchOptions.staleWhileRevalidate} is enabled. A background revalidation is
|
|
8
|
+
* already under way, so the *next* request gets a `HIT`.
|
|
9
|
+
*/
|
|
10
|
+
export type BlazeFetchMarker = 'HIT' | 'MISS' | 'STALE';
|
|
11
|
+
/**
|
|
12
|
+
* What the wrapper stores in Blaze Cache for one response.
|
|
13
|
+
*
|
|
14
|
+
* The record is a plain, structured-clone-friendly object: an `ArrayBuffer` body plus
|
|
15
|
+
* primitive metadata, so it survives an IndexedDB round-trip when the cache is configured
|
|
16
|
+
* with persistence.
|
|
17
|
+
*/
|
|
18
|
+
export interface FetchCacheRecord {
|
|
19
|
+
status: number;
|
|
20
|
+
statusText: string;
|
|
21
|
+
/** Response headers as a plain record, names lower-cased (as `Headers` reports them). */
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
/**
|
|
24
|
+
* Raw response body.
|
|
25
|
+
*
|
|
26
|
+
* Empty for statuses that cannot carry one (`204`, `205`, `304`) — those are replayed with
|
|
27
|
+
* a `null` body, exactly like the network sent them.
|
|
28
|
+
*/
|
|
29
|
+
body: ArrayBuffer;
|
|
30
|
+
/** Final URL after redirects — replayed as [`Response.url`](https://developer.mozilla.org/en-US/docs/Web/API/Response/url). */
|
|
31
|
+
url: string;
|
|
32
|
+
/** Whether the stored response followed a redirect. */
|
|
33
|
+
redirected: boolean;
|
|
34
|
+
/** `Date.now()` when the response was received. */
|
|
35
|
+
storedAt: number;
|
|
36
|
+
/** `Date.now()` at which the entry stops being *fresh* and switches to revalidation. */
|
|
37
|
+
freshUntil: number;
|
|
38
|
+
/** `Date.now()` at which the entry stops being served at all (equal to `freshUntil` when stale-while-revalidate is off). */
|
|
39
|
+
staleUntil: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `RequestInit` as accepted by the wrapper, plus one non-standard escape hatch.
|
|
43
|
+
*
|
|
44
|
+
* Everything the platform defines keeps its meaning; {@link forceRefresh} is additive, and
|
|
45
|
+
* the standard `cache: 'no-cache' | 'reload'` spellings do the same job if you prefer them.
|
|
46
|
+
*/
|
|
47
|
+
export interface BlazeFetchInit extends RequestInit {
|
|
48
|
+
/**
|
|
49
|
+
* Skip the cache **read** and go straight to the network. The fresh response still
|
|
50
|
+
* replaces whatever was stored, so one forced refresh restores a `HIT` afterwards.
|
|
51
|
+
*
|
|
52
|
+
* Standard equivalent: `cache: 'no-cache'`.
|
|
53
|
+
*/
|
|
54
|
+
forceRefresh?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `BlazeFetchInit` as the callbacks receive it — the *effective* request.
|
|
58
|
+
*
|
|
59
|
+
* `headers` is a concrete {@link Headers} (the caller's `init.headers` merged over a
|
|
60
|
+
* `Request`'s own) and `method` is resolved and upper-cased, so a callback never has to
|
|
61
|
+
* reproduce the three-shape `fetch` input handling:
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* key: (url, init) => `${init.method} ${url}?${init.headers.get('authorization') ?? ''}`,
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export interface BlazeFetchEffectiveInit extends Omit<BlazeFetchInit, 'headers' | 'method'> {
|
|
68
|
+
/** Resolved HTTP method, upper-cased. */
|
|
69
|
+
method: string;
|
|
70
|
+
/** Merged request headers, with `init.headers` winning over a `Request`'s. */
|
|
71
|
+
headers: Headers;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The wrapper's call signature — a drop-in replacement for the global `fetch`.
|
|
75
|
+
*
|
|
76
|
+
* Assignable to `typeof fetch` (proved at compile time in this file), so it drops into
|
|
77
|
+
* anything that expects the platform function:
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* const blazeFetch = createBlazeFetch(cache);
|
|
81
|
+
* const asFetch: typeof fetch = blazeFetch;
|
|
82
|
+
* window.fetch = blazeFetch;
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export type BlazeFetch = (input: RequestInfo | URL, init?: BlazeFetchInit) => Promise<Response>;
|
|
86
|
+
/** Options accepted by {@link createBlazeFetch}. All of them are optional. */
|
|
87
|
+
export interface BlazeFetchOptions {
|
|
88
|
+
/**
|
|
89
|
+
* The function performing the real network call. Defaults to the global `fetch`.
|
|
90
|
+
*
|
|
91
|
+
* Pass a stub to test callers without a server — it receives exactly the `input`/`init`
|
|
92
|
+
* the wrapper was called with, never a rewritten request.
|
|
93
|
+
*/
|
|
94
|
+
fetch?: typeof fetch;
|
|
95
|
+
/**
|
|
96
|
+
* Freshness window for a stored response, in milliseconds.
|
|
97
|
+
*
|
|
98
|
+
* Ignored when the response carries `Cache-Control: max-age`, which wins. A response
|
|
99
|
+
* carrying `Age` has that age deducted. Default: `60_000`. A value `<= 0` means
|
|
100
|
+
* "do not store".
|
|
101
|
+
*/
|
|
102
|
+
ttlMs?: number | ((response: Response, url: string, init: BlazeFetchEffectiveInit) => number);
|
|
103
|
+
/**
|
|
104
|
+
* HTTP methods eligible for caching. Default: `['GET']`.
|
|
105
|
+
*
|
|
106
|
+
* Pass `['GET', 'HEAD']` to cache `HEAD` too. Any other method always goes to the network
|
|
107
|
+
* and is never stored.
|
|
108
|
+
*/
|
|
109
|
+
methods?: readonly string[];
|
|
110
|
+
/**
|
|
111
|
+
* Honour `Cache-Control` on both sides of the exchange. Default: `true`.
|
|
112
|
+
*
|
|
113
|
+
* * request `no-store` → neither read nor write the cache;
|
|
114
|
+
* * request `no-cache` → skip the read, still store the response;
|
|
115
|
+
* * response `no-store`/`no-cache` → never stored;
|
|
116
|
+
* * response `max-age` → overrides {@link ttlMs}, minus the response's `Age`;
|
|
117
|
+
* * response `stale-while-revalidate` → the stale window, when {@link staleWhileRevalidate} is on.
|
|
118
|
+
*
|
|
119
|
+
* Set to `false` to treat `ttlMs` as the only authority.
|
|
120
|
+
*/
|
|
121
|
+
cacheControl?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Serve responses past their freshness window while refreshing them in the background.
|
|
124
|
+
* Default: `false` (a stale entry simply falls through to the network).
|
|
125
|
+
*
|
|
126
|
+
* * `true` — honour the response's `Cache-Control: stale-while-revalidate=<seconds>`.
|
|
127
|
+
* * a number — that, **plus** the same many milliseconds as a fallback when the response
|
|
128
|
+
* omits the directive, so opting in works against servers that never send it.
|
|
129
|
+
*
|
|
130
|
+
* Such responses are marked `x-blaze-cache: STALE` and revalidated once per key: the
|
|
131
|
+
* refresh is a forced refresh, so it can never re-serve the stale entry to itself.
|
|
132
|
+
*/
|
|
133
|
+
staleWhileRevalidate?: boolean | number;
|
|
134
|
+
/**
|
|
135
|
+
* Builds the cache key. Receives the resolved absolute `url` and the effective `init`
|
|
136
|
+
* (see {@link BlazeFetchEffectiveInit}).
|
|
137
|
+
*
|
|
138
|
+
* The default is `"<method> <url>"` plus a hash of {@link varyHeaders} — {@link defaultCacheKey}
|
|
139
|
+
* is that default, so you can wrap it and only change the parts you care about. Replacing
|
|
140
|
+
* the key replaces **everything** the default included, so include `init.method` yourself
|
|
141
|
+
* when caching more than one method.
|
|
142
|
+
*/
|
|
143
|
+
key?: (url: string, init: BlazeFetchEffectiveInit) => string;
|
|
144
|
+
/**
|
|
145
|
+
* Decide whether a response is worth storing. Default: only `2xx` responses
|
|
146
|
+
* (the {@link methods} gate has already applied by then).
|
|
147
|
+
*
|
|
148
|
+
* A response this rejects is still returned — it is simply not cached, and still carries
|
|
149
|
+
* `x-blaze-cache: MISS`.
|
|
150
|
+
*/
|
|
151
|
+
shouldCache?: (response: Response, url: string, init: BlazeFetchEffectiveInit) => boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Tags attached to stored responses. Default: `['fetch', '<method> <url>']`.
|
|
154
|
+
*
|
|
155
|
+
* The first tag lets one call drop the whole wrapper's content
|
|
156
|
+
* (`cache.invalidateTags('fetch')`); the second drops a single resource.
|
|
157
|
+
*/
|
|
158
|
+
tags?: (response: Response, url: string, init: BlazeFetchEffectiveInit) => readonly string[];
|
|
159
|
+
/**
|
|
160
|
+
* Request headers whose values participate in the cache key. Default: `['authorization']`.
|
|
161
|
+
*
|
|
162
|
+
* A personalized response must never be served to a different user just because the URL
|
|
163
|
+
* matched. Values are hashed, never stored verbatim.
|
|
164
|
+
*/
|
|
165
|
+
varyHeaders?: readonly string[];
|
|
166
|
+
/**
|
|
167
|
+
* Response header set to `HIT` / `MISS` / `STALE`. Default: `'x-blaze-cache'`.
|
|
168
|
+
* Pass `false` to disable the marker entirely.
|
|
169
|
+
*/
|
|
170
|
+
markerHeader?: string | false;
|
|
171
|
+
}
|
|
172
|
+
/** The slice of a cache the wrapper needs — deliberately structural. */
|
|
173
|
+
export interface BlazeFetchCacheLike {
|
|
174
|
+
get(key: string): FetchCacheRecord | undefined;
|
|
175
|
+
set(key: string, value: FetchCacheRecord, options?: {
|
|
176
|
+
ttlMs?: number;
|
|
177
|
+
tags?: readonly string[];
|
|
178
|
+
}): void;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;;OAKG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB,+HAA+H;IAC/H,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC;IACnB,4HAA4H;IAC5H,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,QAAQ,CAAC;IACzF,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEhG,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAErB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,KAAK,MAAM,CAAC,CAAC;IAE9F;;;;;OAKG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE5B;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAExC;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,KAAK,MAAM,CAAC;IAE7D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,KAAK,OAAO,CAAC;IAE1F;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,KAAK,SAAS,MAAM,EAAE,CAAC;IAE7F;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC/B;AAED,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;CACzG"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blaze-cache/fetch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0-beta",
|
|
4
|
+
"description": "Drop-in fetch() replacement backed by a WebAssembly cache — TTLs, Cache-Control, stale-while-revalidate and an x-blaze-cache HIT/MISS marker",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fetch",
|
|
7
|
+
"http",
|
|
8
|
+
"request",
|
|
9
|
+
"cache",
|
|
10
|
+
"caching",
|
|
11
|
+
"stale-while-revalidate",
|
|
12
|
+
"cache-control",
|
|
13
|
+
"webassembly",
|
|
14
|
+
"wasm",
|
|
15
|
+
"ttl"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/blaze-cache/blaze-cache.git",
|
|
21
|
+
"directory": "packages/fetch"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://blaze-cache.github.io/docs/",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "dist/index.js",
|
|
30
|
+
"types": "dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@blaze-cache/core": "0.2.0-beta"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.json",
|
|
48
|
+
"test": "pnpm run build && node --test",
|
|
49
|
+
"test:live": "pnpm run build && BLAZE_CACHE_LIVE=1 node --test test/live.test.mjs",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
51
|
+
}
|
|
6
52
|
}
|