@b9g/cache 0.1.4 → 0.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -136
- package/package.json +2 -5
- package/src/index.d.ts +20 -15
- package/src/index.js +33 -19
- package/src/memory.d.ts +6 -14
- package/src/memory.js +70 -46
- package/src/postmessage.d.ts +8 -5
- package/src/postmessage.js +84 -48
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **ServiceWorker Cache API**: Standard `
|
|
8
|
-
- **Multiple Backends**: Memory
|
|
7
|
+
- **ServiceWorker Cache API**: Standard `Cache` and `CacheStorage` interfaces from ServiceWorker spec
|
|
8
|
+
- **Multiple Backends**: Memory cache with LRU eviction, PostMessage coordination for workers
|
|
9
9
|
- **Universal**: Same API works in browsers, Node.js, Bun, and edge platforms
|
|
10
10
|
- **Request/Response Caching**: Full HTTP semantics with Request/Response objects
|
|
11
|
-
- **
|
|
11
|
+
- **Factory Pattern**: Flexible cache creation with factory functions
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -18,104 +18,208 @@ npm install @b9g/cache
|
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
### Using with Shovel (Recommended)
|
|
22
|
+
|
|
23
|
+
Configure cache providers in via the `shovel` key in package.json or `shovel.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"caches": {
|
|
28
|
+
"pages": {"provider": "memory"},
|
|
29
|
+
"api": {"provider": "memory", "maxEntries": 5000}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
const caches = new CacheStorage();
|
|
34
|
+
Shovel provides `self.caches` as a global following the ServiceWorker CacheStorage API. Access it directly in your handlers and middleware:
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
```typescript
|
|
37
|
+
import {Router} from '@b9g/router';
|
|
38
|
+
|
|
39
|
+
const router = new Router();
|
|
40
|
+
|
|
41
|
+
// Cache middleware using generator API
|
|
42
|
+
router.use(async function* (request, _context) {
|
|
43
|
+
if (request.method !== 'GET' || !self.caches) {
|
|
44
|
+
return yield request; // Skip caching
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Open cache
|
|
48
|
+
const cache = await self.caches.open('pages-v1');
|
|
49
|
+
|
|
50
|
+
// Check cache
|
|
51
|
+
const cached = await cache.match(request);
|
|
52
|
+
if (cached) {
|
|
53
|
+
return cached; // Cache hit
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Cache miss - get response from handler
|
|
57
|
+
const response = yield request;
|
|
58
|
+
|
|
59
|
+
// Store in cache
|
|
60
|
+
if (response.ok) {
|
|
61
|
+
await cache.put(request, response.clone());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return response;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
router.route('/posts/:id')
|
|
68
|
+
.get(async (request, context) => {
|
|
69
|
+
const post = await getPost(context.params.id);
|
|
70
|
+
return Response.json(post, {
|
|
71
|
+
headers: {'Cache-Control': 'max-age=300'},
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Standalone Usage
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
import {CustomCacheStorage} from '@b9g/cache';
|
|
80
|
+
import {MemoryCache} from '@b9g/cache/memory';
|
|
81
|
+
|
|
82
|
+
// Create cache storage with factory
|
|
83
|
+
const caches = new CustomCacheStorage((name) => {
|
|
84
|
+
return new MemoryCache(name, {maxEntries: 1000});
|
|
85
|
+
});
|
|
30
86
|
|
|
31
87
|
// Open and use caches
|
|
32
88
|
const apiCache = await caches.open('api');
|
|
33
89
|
|
|
34
90
|
// Store response
|
|
35
91
|
const request = new Request('https://api.example.com/posts/1');
|
|
36
|
-
const response = new Response(JSON.stringify({
|
|
92
|
+
const response = new Response(JSON.stringify({id: 1, title: 'Hello'}), {
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
'Cache-Control': 'max-age=300',
|
|
96
|
+
}
|
|
97
|
+
});
|
|
37
98
|
await apiCache.put(request, response);
|
|
38
99
|
|
|
39
100
|
// Retrieve response
|
|
40
101
|
const cached = await apiCache.match(request);
|
|
41
|
-
console.log(await cached.json()); // {
|
|
102
|
+
console.log(await cached.json()); // {id: 1, title: 'Hello'}
|
|
42
103
|
```
|
|
43
104
|
|
|
44
|
-
## Cache
|
|
105
|
+
## Cache Providers
|
|
45
106
|
|
|
46
|
-
|
|
107
|
+
Shovel supports multiple cache providers that can be configured in `shovel.json`:
|
|
47
108
|
|
|
48
|
-
|
|
109
|
+
### Built-in Providers
|
|
49
110
|
|
|
50
|
-
|
|
51
|
-
|
|
111
|
+
- **`memory`** - In-memory cache with LRU eviction (default)
|
|
112
|
+
- **`redis`** - Redis-backed cache (requires `@b9g/cache-redis`)
|
|
113
|
+
- **`cloudflare`** - Uses Cloudflare Workers native Cache API (only works with the Cloudflare platform)
|
|
52
114
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
115
|
+
You can also use custom providers by specifying a module path:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"caches": {
|
|
120
|
+
"pages": {"provider": "memory"},
|
|
121
|
+
"sessions": {"provider": "redis", "url": "REDIS_URL"},
|
|
122
|
+
"custom": {"provider": "./my-cache-provider.js"}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
58
125
|
```
|
|
59
126
|
|
|
60
|
-
|
|
127
|
+
Pattern matching is supported for cache names:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"caches": {
|
|
132
|
+
"api-*": {"provider": "memory", "maxEntries": 5000},
|
|
133
|
+
"page-*": {"provider": "memory", "maxEntries": 100}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
61
137
|
|
|
62
|
-
|
|
138
|
+
## Cache Implementations
|
|
139
|
+
|
|
140
|
+
### MemoryCache
|
|
141
|
+
|
|
142
|
+
In-memory cache with LRU eviction and HTTP Cache-Control header support:
|
|
63
143
|
|
|
64
144
|
```javascript
|
|
65
|
-
import {
|
|
145
|
+
import {MemoryCache} from '@b9g/cache/memory';
|
|
66
146
|
|
|
67
|
-
const cache = new
|
|
68
|
-
|
|
69
|
-
compression: true,
|
|
70
|
-
indexing: true
|
|
147
|
+
const cache = new MemoryCache(name, {
|
|
148
|
+
maxEntries: 1000, // Maximum number of entries (LRU eviction)
|
|
71
149
|
});
|
|
150
|
+
|
|
151
|
+
// Cache respects Cache-Control headers
|
|
152
|
+
await cache.put(request, new Response(data, {
|
|
153
|
+
headers: {'Cache-Control': 'max-age=300'},
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
// After 300 seconds, match() returns undefined
|
|
72
157
|
```
|
|
73
158
|
|
|
74
|
-
|
|
159
|
+
### PostMessageCache
|
|
160
|
+
|
|
161
|
+
Worker-side cache that coordinates with main thread via PostMessage:
|
|
75
162
|
|
|
76
163
|
```javascript
|
|
77
|
-
import {
|
|
164
|
+
import {PostMessageCache} from '@b9g/cache/postmessage';
|
|
78
165
|
|
|
79
|
-
|
|
166
|
+
// In worker thread - forwards operations to main thread
|
|
167
|
+
const cache = new PostMessageCache({
|
|
168
|
+
name: 'shared',
|
|
169
|
+
timeout: 30000, // Optional, defaults to 30000ms
|
|
170
|
+
});
|
|
80
171
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
172
|
+
// Operations are synchronized with main thread's MemoryCache
|
|
173
|
+
await cache.put(request, response);
|
|
174
|
+
```
|
|
85
175
|
|
|
86
|
-
|
|
87
|
-
new FilesystemCache('pages', { directory: './dist/pages' })
|
|
88
|
-
);
|
|
176
|
+
## CustomCacheStorage
|
|
89
177
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
178
|
+
Create cache storage with a factory function:
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
import {CustomCacheStorage} from '@b9g/cache';
|
|
182
|
+
import {MemoryCache} from '@b9g/cache/memory';
|
|
93
183
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
184
|
+
const caches = new CustomCacheStorage((name) => {
|
|
185
|
+
// Different caches can have different configurations
|
|
186
|
+
if (name === 'api') {
|
|
187
|
+
return new MemoryCache(name, {maxEntries: 5000});
|
|
188
|
+
}
|
|
189
|
+
if (name === 'pages') {
|
|
190
|
+
return new MemoryCache(name, {maxEntries: 100});
|
|
191
|
+
}
|
|
192
|
+
return new MemoryCache();
|
|
193
|
+
});
|
|
97
194
|
```
|
|
98
195
|
|
|
99
196
|
## Exports
|
|
100
197
|
|
|
101
|
-
###
|
|
198
|
+
### Main (`@b9g/cache`)
|
|
102
199
|
|
|
103
|
-
- `Cache` - Abstract base class
|
|
104
|
-
- `CustomCacheStorage` - CacheStorage implementation with factory
|
|
200
|
+
- `Cache` - Abstract base class implementing `globalThis.Cache`
|
|
201
|
+
- `CustomCacheStorage` - CacheStorage implementation with factory pattern
|
|
202
|
+
- `generateCacheKey(request, options?)` - Generate cache key from Request
|
|
203
|
+
- `toRequest(request)` - Convert RequestInfo or URL to Request
|
|
204
|
+
- `CacheQueryOptions` - Type for cache query options
|
|
105
205
|
|
|
106
|
-
###
|
|
206
|
+
### Memory (`@b9g/cache/memory`)
|
|
107
207
|
|
|
108
|
-
- `
|
|
208
|
+
- `MemoryCache` - In-memory cache with LRU and Cache-Control support
|
|
209
|
+
- `MemoryCacheOptions` - Configuration type
|
|
109
210
|
|
|
110
|
-
###
|
|
211
|
+
### PostMessage (`@b9g/cache/postmessage`)
|
|
111
212
|
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
213
|
+
- `PostMessageCache` - Worker-side cache with main thread coordination
|
|
214
|
+
- `PostMessageCacheOptions` - Configuration type
|
|
215
|
+
- `handleCacheResponse(message)` - Message handler for worker coordination
|
|
114
216
|
|
|
115
217
|
## API Reference
|
|
116
218
|
|
|
117
219
|
### Standard Cache Methods
|
|
118
220
|
|
|
221
|
+
All cache implementations provide the standard Cache API:
|
|
222
|
+
|
|
119
223
|
```javascript
|
|
120
224
|
// Check for cached response
|
|
121
225
|
const response = await cache.match(request, options?);
|
|
@@ -140,10 +244,7 @@ const requests = await cache.keys(request?, options?);
|
|
|
140
244
|
### CacheStorage Methods
|
|
141
245
|
|
|
142
246
|
```javascript
|
|
143
|
-
//
|
|
144
|
-
caches.register(name, factory);
|
|
145
|
-
|
|
146
|
-
// Open named cache
|
|
247
|
+
// Open named cache (creates if doesn't exist)
|
|
147
248
|
const cache = await caches.open(name);
|
|
148
249
|
|
|
149
250
|
// Check if cache exists
|
|
@@ -154,6 +255,12 @@ const deleted = await caches.delete(name);
|
|
|
154
255
|
|
|
155
256
|
// List cache names
|
|
156
257
|
const names = await caches.keys();
|
|
258
|
+
|
|
259
|
+
// Match across all caches
|
|
260
|
+
const response = await caches.match(request, options?);
|
|
261
|
+
|
|
262
|
+
// Cleanup (disposes all caches)
|
|
263
|
+
await caches.dispose();
|
|
157
264
|
```
|
|
158
265
|
|
|
159
266
|
## Cache Options
|
|
@@ -162,29 +269,52 @@ const names = await caches.keys();
|
|
|
162
269
|
|
|
163
270
|
```javascript
|
|
164
271
|
const response = await cache.match(request, {
|
|
165
|
-
ignoreSearch: true, // Ignore query parameters
|
|
272
|
+
ignoreSearch: true, // Ignore query parameters in URL
|
|
166
273
|
ignoreMethod: false, // Consider HTTP method
|
|
167
|
-
ignoreVary: false, // Honor Vary header
|
|
168
|
-
cacheName: 'specific' // Target specific cache
|
|
274
|
+
ignoreVary: false, // Honor Vary header (default behavior)
|
|
169
275
|
});
|
|
170
276
|
```
|
|
171
277
|
|
|
172
|
-
|
|
278
|
+
**Vary Header Support:**
|
|
279
|
+
|
|
280
|
+
The cache respects the HTTP `Vary` header by default:
|
|
173
281
|
|
|
174
282
|
```javascript
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
283
|
+
// Cache a response that varies on Accept-Encoding
|
|
284
|
+
await cache.put(
|
|
285
|
+
new Request('https://api.example.com/data', {
|
|
286
|
+
headers: {'Accept-Encoding': 'gzip'},
|
|
287
|
+
}),
|
|
288
|
+
new Response(gzippedData, {
|
|
289
|
+
headers: {'Vary': 'Accept-Encoding'},
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// Same URL with same Accept-Encoding: matches
|
|
294
|
+
await cache.match(new Request('https://api.example.com/data', {
|
|
295
|
+
headers: {'Accept-Encoding': 'gzip'},
|
|
296
|
+
})); // ✓ Returns cached response
|
|
297
|
+
|
|
298
|
+
// Same URL with different Accept-Encoding: no match
|
|
299
|
+
await cache.match(new Request('https://api.example.com/data', {
|
|
300
|
+
headers: {'Accept-Encoding': 'br'},
|
|
301
|
+
})); // ✗ Returns undefined
|
|
302
|
+
|
|
303
|
+
// Use ignoreVary to bypass Vary header checking
|
|
304
|
+
await cache.match(new Request('https://api.example.com/data', {
|
|
305
|
+
headers: {'Accept-Encoding': 'br'},
|
|
306
|
+
}), {ignoreVary: true}); // ✓ Returns cached response
|
|
307
|
+
```
|
|
181
308
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
309
|
+
Special cases:
|
|
310
|
+
- `Vary: *` means the response varies on everything and will never match (unless `ignoreVary: true`)
|
|
311
|
+
- Multiple headers: `Vary: Accept-Encoding, User-Agent` requires all specified headers to match
|
|
312
|
+
|
|
313
|
+
### MemoryCache Options
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
new MemoryCache(name, {
|
|
317
|
+
maxEntries: 1000 // Maximum entries (LRU eviction when exceeded)
|
|
188
318
|
});
|
|
189
319
|
```
|
|
190
320
|
|
|
@@ -193,99 +323,93 @@ new FilesystemCache('name', {
|
|
|
193
323
|
### With Router
|
|
194
324
|
|
|
195
325
|
```javascript
|
|
196
|
-
import {
|
|
197
|
-
import {
|
|
326
|
+
import {Router} from '@b9g/router';
|
|
327
|
+
import {CustomCacheStorage} from '@b9g/cache';
|
|
328
|
+
import {MemoryCache} from '@b9g/cache/memory';
|
|
198
329
|
|
|
199
|
-
const caches = new
|
|
200
|
-
|
|
330
|
+
const caches = new CustomCacheStorage((name) =>
|
|
331
|
+
new MemoryCache(name, {maxEntries: 1000})
|
|
332
|
+
);
|
|
201
333
|
|
|
202
|
-
const router = new Router(
|
|
334
|
+
const router = new Router();
|
|
203
335
|
|
|
204
336
|
// Cache-aware middleware
|
|
205
|
-
router.use(async function* (request,
|
|
206
|
-
if (request.method
|
|
207
|
-
|
|
208
|
-
if (cached) return cached;
|
|
337
|
+
router.use(async function* (request, _context) {
|
|
338
|
+
if (request.method !== 'GET') {
|
|
339
|
+
return yield request;
|
|
209
340
|
}
|
|
210
|
-
|
|
341
|
+
|
|
342
|
+
const cache = await caches.open('api');
|
|
343
|
+
const cached = await cache.match(request);
|
|
344
|
+
if (cached) return cached;
|
|
345
|
+
|
|
211
346
|
const response = yield request;
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
await
|
|
347
|
+
|
|
348
|
+
if (response.ok) {
|
|
349
|
+
await cache.put(request, response.clone());
|
|
215
350
|
}
|
|
216
|
-
|
|
351
|
+
|
|
217
352
|
return response;
|
|
218
353
|
});
|
|
219
354
|
|
|
220
|
-
router.route('/api/posts/:id'
|
|
355
|
+
router.route('/api/posts/:id')
|
|
221
356
|
.get(postHandler);
|
|
222
357
|
```
|
|
223
358
|
|
|
224
|
-
###
|
|
359
|
+
### Multi-Worker Setup
|
|
225
360
|
|
|
226
361
|
```javascript
|
|
227
|
-
|
|
362
|
+
// Main thread
|
|
363
|
+
import {CustomCacheStorage} from '@b9g/cache';
|
|
364
|
+
import {MemoryCache} from '@b9g/cache/memory';
|
|
228
365
|
|
|
229
|
-
const
|
|
230
|
-
|
|
366
|
+
const caches = new CustomCacheStorage((name) =>
|
|
367
|
+
new MemoryCache()
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
worker.on('message', (message) => {
|
|
371
|
+
if (message.type?.startsWith('cache:')) {
|
|
372
|
+
caches.handleMessage(worker, message);
|
|
373
|
+
}
|
|
231
374
|
});
|
|
232
375
|
|
|
233
|
-
//
|
|
234
|
-
|
|
376
|
+
// Worker thread
|
|
377
|
+
import {PostMessageCache} from '@b9g/cache/postmessage';
|
|
378
|
+
import {handleCacheResponse} from '@b9g/cache/postmessage';
|
|
235
379
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
380
|
+
const cache = new PostMessageCache('shared');
|
|
381
|
+
|
|
382
|
+
self.addEventListener('message', (event) => {
|
|
383
|
+
if (event.data.type === 'cache:response' || event.data.type === 'cache:error') {
|
|
384
|
+
handleCacheResponse(event.data);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
240
387
|
|
|
241
|
-
//
|
|
242
|
-
|
|
388
|
+
// Operations coordinate with main thread
|
|
389
|
+
await cache.put(request, response);
|
|
243
390
|
```
|
|
244
391
|
|
|
245
|
-
###
|
|
392
|
+
### HTTP Caching Semantics
|
|
246
393
|
|
|
247
394
|
```javascript
|
|
248
|
-
|
|
249
|
-
import { CacheStorage } from '@b9g/cache';
|
|
250
|
-
|
|
251
|
-
// Use native browser caches when available
|
|
252
|
-
const caches = new CacheStorage();
|
|
253
|
-
|
|
254
|
-
self.addEventListener('fetch', async event => {
|
|
255
|
-
const cache = await caches.open('runtime');
|
|
256
|
-
|
|
257
|
-
event.respondWith(
|
|
258
|
-
cache.match(event.request).then(response => {
|
|
259
|
-
if (response) return response;
|
|
260
|
-
|
|
261
|
-
return fetch(event.request).then(response => {
|
|
262
|
-
if (response.ok) {
|
|
263
|
-
cache.put(event.request, response.clone());
|
|
264
|
-
}
|
|
265
|
-
return response;
|
|
266
|
-
});
|
|
267
|
-
})
|
|
268
|
-
);
|
|
269
|
-
});
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
## Cache Coordination
|
|
395
|
+
import {MemoryCache} from '@b9g/cache/memory';
|
|
273
396
|
|
|
274
|
-
|
|
397
|
+
const cache = new MemoryCache();
|
|
275
398
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
channel: 'cache-coordination'
|
|
399
|
+
// Respect Cache-Control headers
|
|
400
|
+
const response = new Response(data, {
|
|
401
|
+
headers: {
|
|
402
|
+
'Cache-Control': 'max-age=3600', // Cache for 1 hour
|
|
403
|
+
'Vary': 'Accept-Encoding',
|
|
282
404
|
}
|
|
283
405
|
});
|
|
284
406
|
|
|
285
|
-
|
|
286
|
-
|
|
407
|
+
await cache.put(request, response);
|
|
408
|
+
|
|
409
|
+
// After 3600 seconds, entry expires automatically
|
|
410
|
+
const cached = await cache.match(request); // undefined after expiry
|
|
287
411
|
```
|
|
288
412
|
|
|
289
413
|
## License
|
|
290
414
|
|
|
291
|
-
MIT
|
|
415
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
4
|
"description": "Universal Cache API for ServiceWorker applications. Provides standard CacheStorage and Cache interfaces across all JavaScript runtimes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cache",
|
|
@@ -9,14 +9,11 @@
|
|
|
9
9
|
"cachestorage",
|
|
10
10
|
"web-standards",
|
|
11
11
|
"universal",
|
|
12
|
-
"memory",
|
|
13
|
-
"filesystem",
|
|
14
12
|
"shovel"
|
|
15
13
|
],
|
|
16
14
|
"dependencies": {},
|
|
17
15
|
"devDependencies": {
|
|
18
|
-
"@b9g/libuild": "^0.1.
|
|
19
|
-
"bun-types": "latest"
|
|
16
|
+
"@b9g/libuild": "^0.1.18"
|
|
20
17
|
},
|
|
21
18
|
"type": "module",
|
|
22
19
|
"types": "src/index.d.ts",
|
package/src/index.d.ts
CHANGED
|
@@ -17,28 +17,35 @@ export interface CacheQueryOptions {
|
|
|
17
17
|
/** Custom cache name for scoped operations */
|
|
18
18
|
cacheName?: string;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert RequestInfo or URL to Request
|
|
22
|
+
*/
|
|
23
|
+
export declare function toRequest(request: RequestInfo | URL): Request;
|
|
20
24
|
/**
|
|
21
25
|
* Abstract Cache class implementing the Cache API interface
|
|
22
26
|
* Provides shared implementations for add() and addAll() while requiring
|
|
23
27
|
* concrete implementations to handle the core storage operations
|
|
28
|
+
*
|
|
29
|
+
* All cache implementations must follow the constructor signature:
|
|
30
|
+
* constructor(name: string, options?: CacheOptions)
|
|
24
31
|
*/
|
|
25
|
-
export declare abstract class Cache {
|
|
32
|
+
export declare abstract class Cache implements globalThis.Cache {
|
|
26
33
|
/**
|
|
27
34
|
* Returns a Promise that resolves to the response associated with the first matching request
|
|
28
35
|
*/
|
|
29
|
-
abstract match(request:
|
|
36
|
+
abstract match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
30
37
|
/**
|
|
31
38
|
* Puts a request/response pair into the cache
|
|
32
39
|
*/
|
|
33
|
-
abstract put(request:
|
|
40
|
+
abstract put(request: RequestInfo | URL, response: Response): Promise<void>;
|
|
34
41
|
/**
|
|
35
42
|
* Finds the cache entry whose key is the request, and if found, deletes it and returns true
|
|
36
43
|
*/
|
|
37
|
-
abstract delete(request:
|
|
44
|
+
abstract delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;
|
|
38
45
|
/**
|
|
39
46
|
* Returns a Promise that resolves to an array of cache keys (Request objects)
|
|
40
47
|
*/
|
|
41
|
-
abstract keys(request?:
|
|
48
|
+
abstract keys(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<readonly Request[]>;
|
|
42
49
|
/**
|
|
43
50
|
* Takes a URL, retrieves it and adds the resulting response object to the cache
|
|
44
51
|
* Shared implementation using fetch() and put()
|
|
@@ -56,10 +63,15 @@ export declare abstract class Cache {
|
|
|
56
63
|
matchAll(request?: Request, options?: CacheQueryOptions): Promise<readonly Response[]>;
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
59
|
-
* Generate a cache key from a Request object
|
|
66
|
+
* Generate a cache key from a Request, string URL, or URL object
|
|
60
67
|
* Normalizes the request for consistent cache key generation
|
|
61
68
|
*/
|
|
62
|
-
export declare function generateCacheKey(request:
|
|
69
|
+
export declare function generateCacheKey(request: RequestInfo | URL, options?: CacheQueryOptions): string;
|
|
70
|
+
/**
|
|
71
|
+
* Constructor type for Cache implementations
|
|
72
|
+
* All cache classes must accept name as first parameter and optional options as second
|
|
73
|
+
*/
|
|
74
|
+
export type CacheConstructor<T extends Cache = Cache, O = any> = new (name: string, options?: O) => T;
|
|
63
75
|
/**
|
|
64
76
|
* Factory function for creating Cache instances based on cache name
|
|
65
77
|
*/
|
|
@@ -68,7 +80,7 @@ export type CacheFactory = (name: string) => Cache | Promise<Cache>;
|
|
|
68
80
|
* CustomCacheStorage implements CacheStorage interface with a configurable factory
|
|
69
81
|
* The factory function receives the cache name and can return different cache types
|
|
70
82
|
*/
|
|
71
|
-
export declare class CustomCacheStorage {
|
|
83
|
+
export declare class CustomCacheStorage implements CacheStorage {
|
|
72
84
|
#private;
|
|
73
85
|
constructor(factory: CacheFactory);
|
|
74
86
|
/**
|
|
@@ -92,13 +104,6 @@ export declare class CustomCacheStorage {
|
|
|
92
104
|
* Returns a list of all opened cache names
|
|
93
105
|
*/
|
|
94
106
|
keys(): Promise<string[]>;
|
|
95
|
-
/**
|
|
96
|
-
* Get statistics about the cache storage
|
|
97
|
-
*/
|
|
98
|
-
getStats(): {
|
|
99
|
-
openInstances: number;
|
|
100
|
-
cacheNames: string[];
|
|
101
|
-
};
|
|
102
107
|
/**
|
|
103
108
|
* Dispose of all cache instances
|
|
104
109
|
* Calls dispose() on each cache if it exists (e.g., RedisCache needs to close connections)
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
2
|
// src/index.ts
|
|
3
|
+
function toRequest(request) {
|
|
4
|
+
if (typeof request === "string") {
|
|
5
|
+
return new Request(request);
|
|
6
|
+
}
|
|
7
|
+
if (request instanceof URL) {
|
|
8
|
+
return new Request(request.href);
|
|
9
|
+
}
|
|
10
|
+
return request;
|
|
11
|
+
}
|
|
3
12
|
var Cache = class {
|
|
4
13
|
/**
|
|
5
14
|
* Takes a URL, retrieves it and adds the resulting response object to the cache
|
|
@@ -38,11 +47,12 @@ var Cache = class {
|
|
|
38
47
|
}
|
|
39
48
|
};
|
|
40
49
|
function generateCacheKey(request, options) {
|
|
41
|
-
const
|
|
50
|
+
const req = toRequest(request);
|
|
51
|
+
const url = new URL(req.url);
|
|
42
52
|
if (options?.ignoreSearch) {
|
|
43
53
|
url.search = "";
|
|
44
54
|
}
|
|
45
|
-
const method = options?.ignoreMethod ? "GET" :
|
|
55
|
+
const method = options?.ignoreMethod ? "GET" : req.method;
|
|
46
56
|
return `${method}:${url.href}`;
|
|
47
57
|
}
|
|
48
58
|
var CustomCacheStorage = class {
|
|
@@ -100,15 +110,6 @@ var CustomCacheStorage = class {
|
|
|
100
110
|
async keys() {
|
|
101
111
|
return Array.from(this.#instances.keys());
|
|
102
112
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Get statistics about the cache storage
|
|
105
|
-
*/
|
|
106
|
-
getStats() {
|
|
107
|
-
return {
|
|
108
|
-
openInstances: this.#instances.size,
|
|
109
|
-
cacheNames: Array.from(this.#instances.keys())
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
113
|
/**
|
|
113
114
|
* Dispose of all cache instances
|
|
114
115
|
* Calls dispose() on each cache if it exists (e.g., RedisCache needs to close connections)
|
|
@@ -131,16 +132,23 @@ var CustomCacheStorage = class {
|
|
|
131
132
|
try {
|
|
132
133
|
const cache = await this.open(cacheName);
|
|
133
134
|
let result;
|
|
135
|
+
const transfer = [];
|
|
134
136
|
switch (type) {
|
|
135
137
|
case "cache:match": {
|
|
136
138
|
const req = new Request(message.request.url, message.request);
|
|
137
139
|
const response = await cache.match(req, message.options);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
if (response) {
|
|
141
|
+
const body = await response.arrayBuffer();
|
|
142
|
+
transfer.push(body);
|
|
143
|
+
result = {
|
|
144
|
+
status: response.status,
|
|
145
|
+
statusText: response.statusText,
|
|
146
|
+
headers: Object.fromEntries(response.headers),
|
|
147
|
+
body
|
|
148
|
+
};
|
|
149
|
+
} else {
|
|
150
|
+
result = void 0;
|
|
151
|
+
}
|
|
144
152
|
break;
|
|
145
153
|
}
|
|
146
154
|
case "cache:put": {
|
|
@@ -170,7 +178,12 @@ var CustomCacheStorage = class {
|
|
|
170
178
|
result = true;
|
|
171
179
|
break;
|
|
172
180
|
}
|
|
173
|
-
|
|
181
|
+
const responseMessage = { type: "cache:response", requestID, result };
|
|
182
|
+
if (transfer.length > 0) {
|
|
183
|
+
worker.postMessage(responseMessage, transfer);
|
|
184
|
+
} else {
|
|
185
|
+
worker.postMessage(responseMessage);
|
|
186
|
+
}
|
|
174
187
|
} catch (error) {
|
|
175
188
|
worker.postMessage({
|
|
176
189
|
type: "cache:error",
|
|
@@ -183,5 +196,6 @@ var CustomCacheStorage = class {
|
|
|
183
196
|
export {
|
|
184
197
|
Cache,
|
|
185
198
|
CustomCacheStorage,
|
|
186
|
-
generateCacheKey
|
|
199
|
+
generateCacheKey,
|
|
200
|
+
toRequest
|
|
187
201
|
};
|
package/src/memory.d.ts
CHANGED
|
@@ -9,37 +9,29 @@ export interface MemoryCacheOptions {
|
|
|
9
9
|
/**
|
|
10
10
|
* In-memory cache implementation using Map for storage
|
|
11
11
|
* Supports LRU eviction and TTL expiration
|
|
12
|
+
* Uses Map's insertion order for LRU tracking
|
|
12
13
|
*/
|
|
13
14
|
export declare class MemoryCache extends Cache {
|
|
14
15
|
#private;
|
|
15
|
-
constructor(
|
|
16
|
+
constructor(_name: string, options?: MemoryCacheOptions);
|
|
16
17
|
/**
|
|
17
18
|
* Find a cached response for the given request
|
|
18
19
|
*/
|
|
19
|
-
match(request:
|
|
20
|
+
match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
20
21
|
/**
|
|
21
22
|
* Store a request/response pair in the cache
|
|
22
23
|
*/
|
|
23
|
-
put(request:
|
|
24
|
+
put(request: RequestInfo | URL, response: Response): Promise<void>;
|
|
24
25
|
/**
|
|
25
26
|
* Delete matching entries from the cache
|
|
26
27
|
*/
|
|
27
|
-
delete(request:
|
|
28
|
+
delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;
|
|
28
29
|
/**
|
|
29
30
|
* Get all stored requests, optionally filtered by a request pattern
|
|
30
31
|
*/
|
|
31
|
-
keys(request?:
|
|
32
|
+
keys(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<readonly Request[]>;
|
|
32
33
|
/**
|
|
33
34
|
* Clear all entries from the cache
|
|
34
35
|
*/
|
|
35
36
|
clear(): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* Get cache statistics
|
|
38
|
-
*/
|
|
39
|
-
getStats(): {
|
|
40
|
-
name: string;
|
|
41
|
-
size: number;
|
|
42
|
-
maxEntries: number;
|
|
43
|
-
hitRate: number;
|
|
44
|
-
};
|
|
45
37
|
}
|
package/src/memory.js
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
/// <reference types="./memory.d.ts" />
|
|
2
2
|
// src/memory.ts
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Cache,
|
|
5
|
+
generateCacheKey,
|
|
6
|
+
toRequest
|
|
7
|
+
} from "./index.js";
|
|
4
8
|
var MemoryCache = class extends Cache {
|
|
5
9
|
#storage;
|
|
6
|
-
#accessOrder;
|
|
7
|
-
#accessCounter;
|
|
8
|
-
#name;
|
|
9
10
|
#options;
|
|
10
|
-
constructor(
|
|
11
|
+
constructor(_name, options = {}) {
|
|
11
12
|
super();
|
|
12
13
|
this.#storage = /* @__PURE__ */ new Map();
|
|
13
|
-
this.#accessOrder = /* @__PURE__ */ new Map();
|
|
14
|
-
this.#accessCounter = 0;
|
|
15
|
-
this.#name = name;
|
|
16
14
|
this.#options = options;
|
|
17
15
|
}
|
|
18
16
|
/**
|
|
19
17
|
* Find a cached response for the given request
|
|
20
18
|
*/
|
|
21
19
|
async match(request, options) {
|
|
20
|
+
const req = toRequest(request);
|
|
21
|
+
if (options?.ignoreSearch) {
|
|
22
|
+
const filterKey = generateCacheKey(request, options);
|
|
23
|
+
for (const [key2, entry2] of this.#storage) {
|
|
24
|
+
if (this.#isExpired(entry2)) {
|
|
25
|
+
this.#storage.delete(key2);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const entryKey = generateCacheKey(entry2.request, options);
|
|
29
|
+
if (entryKey === filterKey) {
|
|
30
|
+
if (!options?.ignoreVary && !this.#matchesVary(req, entry2)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
this.#storage.delete(key2);
|
|
34
|
+
this.#storage.set(key2, entry2);
|
|
35
|
+
return entry2.response.clone();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
22
40
|
const key = generateCacheKey(request, options);
|
|
23
41
|
const entry = this.#storage.get(key);
|
|
24
42
|
if (!entry) {
|
|
@@ -26,41 +44,55 @@ var MemoryCache = class extends Cache {
|
|
|
26
44
|
}
|
|
27
45
|
if (this.#isExpired(entry)) {
|
|
28
46
|
this.#storage.delete(key);
|
|
29
|
-
this.#accessOrder.delete(key);
|
|
30
47
|
return void 0;
|
|
31
48
|
}
|
|
32
|
-
this.#
|
|
49
|
+
if (!options?.ignoreVary && !this.#matchesVary(req, entry)) {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
this.#storage.delete(key);
|
|
53
|
+
this.#storage.set(key, entry);
|
|
33
54
|
return entry.response.clone();
|
|
34
55
|
}
|
|
35
56
|
/**
|
|
36
57
|
* Store a request/response pair in the cache
|
|
37
58
|
*/
|
|
38
59
|
async put(request, response) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
60
|
+
const req = toRequest(request);
|
|
61
|
+
const key = generateCacheKey(req);
|
|
62
|
+
if (response.bodyUsed) {
|
|
63
|
+
throw new TypeError("Response body has already been used");
|
|
42
64
|
}
|
|
43
|
-
const clonedRequest =
|
|
65
|
+
const clonedRequest = req.clone();
|
|
44
66
|
const clonedResponse = response.clone();
|
|
45
67
|
const entry = {
|
|
46
68
|
request: clonedRequest,
|
|
47
69
|
response: clonedResponse,
|
|
48
70
|
timestamp: Date.now()
|
|
49
71
|
};
|
|
72
|
+
if (this.#storage.has(key)) {
|
|
73
|
+
this.#storage.delete(key);
|
|
74
|
+
}
|
|
50
75
|
this.#storage.set(key, entry);
|
|
51
|
-
this.#accessOrder.set(key, ++this.#accessCounter);
|
|
52
76
|
this.#enforceMaxEntries();
|
|
53
77
|
}
|
|
54
78
|
/**
|
|
55
79
|
* Delete matching entries from the cache
|
|
56
80
|
*/
|
|
57
81
|
async delete(request, options) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.#
|
|
82
|
+
if (options?.ignoreSearch) {
|
|
83
|
+
const filterKey = generateCacheKey(request, options);
|
|
84
|
+
let deleted = false;
|
|
85
|
+
for (const [key2, entry] of this.#storage) {
|
|
86
|
+
const entryKey = generateCacheKey(entry.request, options);
|
|
87
|
+
if (entryKey === filterKey) {
|
|
88
|
+
this.#storage.delete(key2);
|
|
89
|
+
deleted = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return deleted;
|
|
62
93
|
}
|
|
63
|
-
|
|
94
|
+
const key = generateCacheKey(request, options);
|
|
95
|
+
return this.#storage.delete(key);
|
|
64
96
|
}
|
|
65
97
|
/**
|
|
66
98
|
* Get all stored requests, optionally filtered by a request pattern
|
|
@@ -88,20 +120,6 @@ var MemoryCache = class extends Cache {
|
|
|
88
120
|
*/
|
|
89
121
|
async clear() {
|
|
90
122
|
this.#storage.clear();
|
|
91
|
-
this.#accessOrder.clear();
|
|
92
|
-
this.#accessCounter = 0;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Get cache statistics
|
|
96
|
-
*/
|
|
97
|
-
getStats() {
|
|
98
|
-
return {
|
|
99
|
-
name: this.#name,
|
|
100
|
-
size: this.#storage.size,
|
|
101
|
-
maxEntries: this.#options.maxEntries,
|
|
102
|
-
hitRate: 0
|
|
103
|
-
// Could be implemented with additional tracking
|
|
104
|
-
};
|
|
105
123
|
}
|
|
106
124
|
/**
|
|
107
125
|
* Check if a cache entry has expired based on Cache-Control header
|
|
@@ -119,15 +137,22 @@ var MemoryCache = class extends Cache {
|
|
|
119
137
|
return Date.now() - entry.timestamp > maxAge;
|
|
120
138
|
}
|
|
121
139
|
/**
|
|
122
|
-
* Check if a
|
|
140
|
+
* Check if a request matches the Vary header of a cached entry
|
|
141
|
+
* Returns true if the request matches or if there's no Vary header
|
|
123
142
|
*/
|
|
124
|
-
#
|
|
125
|
-
|
|
143
|
+
#matchesVary(request, entry) {
|
|
144
|
+
const varyHeader = entry.response.headers.get("vary");
|
|
145
|
+
if (!varyHeader) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (varyHeader === "*") {
|
|
126
149
|
return false;
|
|
127
150
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
151
|
+
const varyHeaders = varyHeader.split(",").map((h) => h.trim().toLowerCase());
|
|
152
|
+
for (const headerName of varyHeaders) {
|
|
153
|
+
const requestValue = request.headers.get(headerName);
|
|
154
|
+
const cachedValue = entry.request.headers.get(headerName);
|
|
155
|
+
if (requestValue !== cachedValue) {
|
|
131
156
|
return false;
|
|
132
157
|
}
|
|
133
158
|
}
|
|
@@ -135,19 +160,18 @@ var MemoryCache = class extends Cache {
|
|
|
135
160
|
}
|
|
136
161
|
/**
|
|
137
162
|
* Enforce maximum entry limits using LRU eviction
|
|
163
|
+
* Removes oldest entries (first in Map iteration order)
|
|
138
164
|
*/
|
|
139
165
|
#enforceMaxEntries() {
|
|
140
166
|
if (!this.#options.maxEntries || this.#storage.size <= this.#options.maxEntries) {
|
|
141
167
|
return;
|
|
142
168
|
}
|
|
143
|
-
const entries = Array.from(this.#accessOrder.entries()).sort(
|
|
144
|
-
(a, b) => a[1] - b[1]
|
|
145
|
-
);
|
|
146
169
|
const toRemove = this.#storage.size - this.#options.maxEntries;
|
|
147
|
-
|
|
148
|
-
|
|
170
|
+
let removed = 0;
|
|
171
|
+
for (const key of this.#storage.keys()) {
|
|
172
|
+
if (removed >= toRemove) break;
|
|
149
173
|
this.#storage.delete(key);
|
|
150
|
-
|
|
174
|
+
removed++;
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
};
|
package/src/postmessage.d.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { Cache, type CacheQueryOptions } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handle cache response/error messages from main thread.
|
|
4
|
+
* Called by worker.ts when receiving cache:response or cache:error messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleCacheResponse(message: any): void;
|
|
2
7
|
/**
|
|
3
8
|
* Configuration options for PostMessageCache
|
|
4
9
|
*/
|
|
5
10
|
export interface PostMessageCacheOptions {
|
|
6
|
-
/** Maximum number of entries to store */
|
|
7
|
-
maxEntries?: number;
|
|
8
11
|
/** Timeout for cache operations in milliseconds (default: 30000) */
|
|
9
12
|
timeout?: number;
|
|
10
13
|
}
|
|
11
14
|
/**
|
|
12
|
-
* Worker-side cache that forwards operations to main thread via postMessage
|
|
13
|
-
*
|
|
15
|
+
* Worker-side cache that forwards operations to main thread via postMessage.
|
|
16
|
+
* Used for MemoryCache in multi-worker environments so all workers share state.
|
|
14
17
|
*/
|
|
15
18
|
export declare class PostMessageCache extends Cache {
|
|
16
19
|
#private;
|
|
17
|
-
constructor(name: string,
|
|
20
|
+
constructor(name: string, options?: PostMessageCacheOptions);
|
|
18
21
|
match(request: Request, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
19
22
|
put(request: Request, response: Response): Promise<void>;
|
|
20
23
|
delete(request: Request, options?: CacheQueryOptions): Promise<boolean>;
|
package/src/postmessage.js
CHANGED
|
@@ -1,25 +1,7 @@
|
|
|
1
1
|
/// <reference types="./postmessage.d.ts" />
|
|
2
2
|
// src/postmessage.ts
|
|
3
3
|
import { Cache } from "./index.js";
|
|
4
|
-
function getParentPort() {
|
|
5
|
-
return typeof self !== "undefined" ? self : null;
|
|
6
|
-
}
|
|
7
|
-
var messageHandlerSetup = false;
|
|
8
4
|
var pendingRequestsRegistry = /* @__PURE__ */ new Map();
|
|
9
|
-
function setupMessageHandler() {
|
|
10
|
-
if (messageHandlerSetup)
|
|
11
|
-
return;
|
|
12
|
-
messageHandlerSetup = true;
|
|
13
|
-
const parentPort = getParentPort();
|
|
14
|
-
if (parentPort && parentPort.addEventListener) {
|
|
15
|
-
parentPort.addEventListener("message", (event) => {
|
|
16
|
-
const message = event.data;
|
|
17
|
-
if (message.type === "cache:response" || message.type === "cache:error") {
|
|
18
|
-
handleCacheResponse(message);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
5
|
function handleCacheResponse(message) {
|
|
24
6
|
const pending = pendingRequestsRegistry.get(message.requestID);
|
|
25
7
|
if (pending) {
|
|
@@ -34,44 +16,64 @@ function handleCacheResponse(message) {
|
|
|
34
16
|
var globalRequestID = 0;
|
|
35
17
|
var PostMessageCache = class extends Cache {
|
|
36
18
|
#name;
|
|
37
|
-
|
|
19
|
+
#timeout;
|
|
20
|
+
constructor(name, options = {}) {
|
|
38
21
|
super();
|
|
39
22
|
this.#name = name;
|
|
40
|
-
|
|
23
|
+
this.#timeout = options.timeout ?? 3e4;
|
|
41
24
|
}
|
|
42
|
-
async #sendRequest(type, data) {
|
|
43
|
-
|
|
44
|
-
if (!parentPort) {
|
|
25
|
+
async #sendRequest(type, data, transfer) {
|
|
26
|
+
if (typeof self === "undefined") {
|
|
45
27
|
throw new Error("PostMessageCache can only be used in worker threads");
|
|
46
28
|
}
|
|
29
|
+
if (globalRequestID >= Number.MAX_SAFE_INTEGER) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Congratulations! You've made 9 quadrillion cache requests. Please restart your server and tell us about your workload."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
47
34
|
const requestID = ++globalRequestID;
|
|
48
35
|
return new Promise((resolve, reject) => {
|
|
49
36
|
pendingRequestsRegistry.set(requestID, { resolve, reject });
|
|
50
|
-
|
|
37
|
+
const message = {
|
|
51
38
|
type,
|
|
52
39
|
requestID,
|
|
53
40
|
cacheName: this.#name,
|
|
54
41
|
...data
|
|
55
|
-
}
|
|
42
|
+
};
|
|
43
|
+
if (transfer && transfer.length > 0) {
|
|
44
|
+
self.postMessage(message, transfer);
|
|
45
|
+
} else {
|
|
46
|
+
self.postMessage(message);
|
|
47
|
+
}
|
|
56
48
|
setTimeout(() => {
|
|
57
49
|
if (pendingRequestsRegistry.has(requestID)) {
|
|
58
50
|
pendingRequestsRegistry.delete(requestID);
|
|
59
51
|
reject(new Error("Cache operation timeout"));
|
|
60
52
|
}
|
|
61
|
-
},
|
|
53
|
+
}, this.#timeout);
|
|
62
54
|
});
|
|
63
55
|
}
|
|
64
56
|
async match(request, options) {
|
|
57
|
+
let requestBody;
|
|
58
|
+
const transfer = [];
|
|
59
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
60
|
+
requestBody = await request.arrayBuffer();
|
|
61
|
+
transfer.push(requestBody);
|
|
62
|
+
}
|
|
65
63
|
const serializedRequest = {
|
|
66
64
|
url: request.url,
|
|
67
65
|
method: request.method,
|
|
68
66
|
headers: Object.fromEntries(request.headers),
|
|
69
|
-
body:
|
|
67
|
+
body: requestBody
|
|
70
68
|
};
|
|
71
|
-
const response = await this.#sendRequest(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
const response = await this.#sendRequest(
|
|
70
|
+
"cache:match",
|
|
71
|
+
{
|
|
72
|
+
request: serializedRequest,
|
|
73
|
+
options
|
|
74
|
+
},
|
|
75
|
+
transfer
|
|
76
|
+
);
|
|
75
77
|
if (!response) {
|
|
76
78
|
return void 0;
|
|
77
79
|
}
|
|
@@ -82,49 +84,82 @@ var PostMessageCache = class extends Cache {
|
|
|
82
84
|
});
|
|
83
85
|
}
|
|
84
86
|
async put(request, response) {
|
|
87
|
+
const transfer = [];
|
|
88
|
+
let requestBody;
|
|
89
|
+
let responseBody;
|
|
90
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
91
|
+
requestBody = await request.clone().arrayBuffer();
|
|
92
|
+
transfer.push(requestBody);
|
|
93
|
+
}
|
|
94
|
+
responseBody = await response.clone().arrayBuffer();
|
|
95
|
+
transfer.push(responseBody);
|
|
85
96
|
const serializedRequest = {
|
|
86
97
|
url: request.url,
|
|
87
98
|
method: request.method,
|
|
88
99
|
headers: Object.fromEntries(request.headers),
|
|
89
|
-
body:
|
|
100
|
+
body: requestBody
|
|
90
101
|
};
|
|
91
102
|
const serializedResponse = {
|
|
92
103
|
status: response.status,
|
|
93
104
|
statusText: response.statusText,
|
|
94
105
|
headers: Object.fromEntries(response.headers),
|
|
95
|
-
body:
|
|
106
|
+
body: responseBody
|
|
96
107
|
};
|
|
97
|
-
await this.#sendRequest(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
await this.#sendRequest(
|
|
109
|
+
"cache:put",
|
|
110
|
+
{
|
|
111
|
+
request: serializedRequest,
|
|
112
|
+
response: serializedResponse
|
|
113
|
+
},
|
|
114
|
+
transfer
|
|
115
|
+
);
|
|
101
116
|
}
|
|
102
117
|
async delete(request, options) {
|
|
118
|
+
let requestBody;
|
|
119
|
+
const transfer = [];
|
|
120
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
121
|
+
requestBody = await request.arrayBuffer();
|
|
122
|
+
transfer.push(requestBody);
|
|
123
|
+
}
|
|
103
124
|
const serializedRequest = {
|
|
104
125
|
url: request.url,
|
|
105
126
|
method: request.method,
|
|
106
127
|
headers: Object.fromEntries(request.headers),
|
|
107
|
-
body:
|
|
128
|
+
body: requestBody
|
|
108
129
|
};
|
|
109
|
-
return await this.#sendRequest(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
return await this.#sendRequest(
|
|
131
|
+
"cache:delete",
|
|
132
|
+
{
|
|
133
|
+
request: serializedRequest,
|
|
134
|
+
options
|
|
135
|
+
},
|
|
136
|
+
transfer
|
|
137
|
+
);
|
|
113
138
|
}
|
|
114
139
|
async keys(request, options) {
|
|
115
140
|
let serializedRequest;
|
|
141
|
+
const transfer = [];
|
|
116
142
|
if (request) {
|
|
143
|
+
let requestBody;
|
|
144
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
145
|
+
requestBody = await request.arrayBuffer();
|
|
146
|
+
transfer.push(requestBody);
|
|
147
|
+
}
|
|
117
148
|
serializedRequest = {
|
|
118
149
|
url: request.url,
|
|
119
150
|
method: request.method,
|
|
120
151
|
headers: Object.fromEntries(request.headers),
|
|
121
|
-
body:
|
|
152
|
+
body: requestBody
|
|
122
153
|
};
|
|
123
154
|
}
|
|
124
|
-
const keys = await this.#sendRequest(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
const keys = await this.#sendRequest(
|
|
156
|
+
"cache:keys",
|
|
157
|
+
{
|
|
158
|
+
request: serializedRequest,
|
|
159
|
+
options
|
|
160
|
+
},
|
|
161
|
+
transfer
|
|
162
|
+
);
|
|
128
163
|
return keys.map(
|
|
129
164
|
(req) => new Request(req.url, {
|
|
130
165
|
method: req.method,
|
|
@@ -138,5 +173,6 @@ var PostMessageCache = class extends Cache {
|
|
|
138
173
|
}
|
|
139
174
|
};
|
|
140
175
|
export {
|
|
141
|
-
PostMessageCache
|
|
176
|
+
PostMessageCache,
|
|
177
|
+
handleCacheResponse
|
|
142
178
|
};
|