@b9g/cache 0.1.3 → 0.1.5
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 +23 -7
- package/package.json +9 -20
- package/src/index.d.ts +108 -10
- package/src/index.js +191 -14
- package/src/memory.d.ts +6 -83
- package/src/memory.js +83 -202
- package/src/postmessage.d.ts +5 -13
- package/src/postmessage.js +52 -50
- package/src/cache-storage.d.ts +0 -44
- package/src/cache-storage.js +0 -74
- package/src/cache.d.ts +0 -66
- package/src/cache.js +0 -55
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @b9g/cache
|
|
2
2
|
|
|
3
|
-
Universal Cache API
|
|
3
|
+
**Universal Cache API for ServiceWorker applications. Provides standard CacheStorage and Cache interfaces across all JavaScript runtimes.**
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **Multiple Backends**: Memory, filesystem, Redis, KV store implementations
|
|
7
|
+
- **ServiceWorker Cache API**: Standard `caches` global and Cache interface from ServiceWorker spec
|
|
8
|
+
- **Multiple Backends**: Memory, filesystem, Redis, KV store implementations
|
|
9
9
|
- **Universal**: Same API works in browsers, Node.js, Bun, and edge platforms
|
|
10
|
-
- **Request/Response**: HTTP
|
|
10
|
+
- **Request/Response Caching**: Full HTTP semantics with Request/Response objects
|
|
11
11
|
- **Registry Pattern**: Named cache management with factory registration
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
@@ -96,7 +96,23 @@ import { Router } from '@b9g/router';
|
|
|
96
96
|
const router = new Router({ caches });
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
##
|
|
99
|
+
## Exports
|
|
100
|
+
|
|
101
|
+
### Classes
|
|
102
|
+
|
|
103
|
+
- `Cache` - Abstract base class for cache implementations
|
|
104
|
+
- `CustomCacheStorage` - CacheStorage implementation with factory registration
|
|
105
|
+
|
|
106
|
+
### Functions
|
|
107
|
+
|
|
108
|
+
- `generateCacheKey(request, options?)` - Generate a cache key from a Request
|
|
109
|
+
|
|
110
|
+
### Types
|
|
111
|
+
|
|
112
|
+
- `CacheQueryOptions` - Options for cache query operations (ignoreSearch, ignoreMethod, ignoreVary)
|
|
113
|
+
- `CacheFactory` - Factory function type `(name: string) => Cache | Promise<Cache>`
|
|
114
|
+
|
|
115
|
+
## API Reference
|
|
100
116
|
|
|
101
117
|
### Standard Cache Methods
|
|
102
118
|
|
|
@@ -186,13 +202,13 @@ caches.register('api', () => new MemoryCache('api'));
|
|
|
186
202
|
const router = new Router({ caches });
|
|
187
203
|
|
|
188
204
|
// Cache-aware middleware
|
|
189
|
-
router.use(async (request, context
|
|
205
|
+
router.use(async function* (request, context) {
|
|
190
206
|
if (request.method === 'GET' && context.cache) {
|
|
191
207
|
const cached = await context.cache.match(request);
|
|
192
208
|
if (cached) return cached;
|
|
193
209
|
}
|
|
194
210
|
|
|
195
|
-
const response =
|
|
211
|
+
const response = yield request;
|
|
196
212
|
|
|
197
213
|
if (request.method === 'GET' && context.cache && response.ok) {
|
|
198
214
|
await context.cache.put(request, response.clone());
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/cache",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Universal Cache API
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Universal Cache API for ServiceWorker applications. Provides standard CacheStorage and Cache interfaces across all JavaScript runtimes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cache",
|
|
7
7
|
"storage",
|
|
8
|
+
"serviceworker",
|
|
9
|
+
"cachestorage",
|
|
8
10
|
"web-standards",
|
|
9
|
-
"universal"
|
|
11
|
+
"universal",
|
|
12
|
+
"memory",
|
|
13
|
+
"filesystem",
|
|
14
|
+
"shovel"
|
|
10
15
|
],
|
|
11
16
|
"dependencies": {},
|
|
12
17
|
"devDependencies": {
|
|
13
|
-
"@b9g/libuild": "^0.1.
|
|
18
|
+
"@b9g/libuild": "^0.1.18",
|
|
14
19
|
"bun-types": "latest"
|
|
15
20
|
},
|
|
16
21
|
"type": "module",
|
|
@@ -21,10 +26,6 @@
|
|
|
21
26
|
"types": "./src/index.d.ts",
|
|
22
27
|
"import": "./src/index.js"
|
|
23
28
|
},
|
|
24
|
-
"./cache": {
|
|
25
|
-
"types": "./src/cache.d.ts",
|
|
26
|
-
"import": "./src/cache.js"
|
|
27
|
-
},
|
|
28
29
|
"./memory": {
|
|
29
30
|
"types": "./src/memory.d.ts",
|
|
30
31
|
"import": "./src/memory.js"
|
|
@@ -34,18 +35,6 @@
|
|
|
34
35
|
"import": "./src/postmessage.js"
|
|
35
36
|
},
|
|
36
37
|
"./package.json": "./package.json",
|
|
37
|
-
"./cache.js": {
|
|
38
|
-
"types": "./src/cache.d.ts",
|
|
39
|
-
"import": "./src/cache.js"
|
|
40
|
-
},
|
|
41
|
-
"./cache-storage": {
|
|
42
|
-
"types": "./src/cache-storage.d.ts",
|
|
43
|
-
"import": "./src/cache-storage.js"
|
|
44
|
-
},
|
|
45
|
-
"./cache-storage.js": {
|
|
46
|
-
"types": "./src/cache-storage.d.ts",
|
|
47
|
-
"import": "./src/cache-storage.js"
|
|
48
|
-
},
|
|
49
38
|
"./index": {
|
|
50
39
|
"types": "./src/index.d.ts",
|
|
51
40
|
"import": "./src/index.js"
|
package/src/index.d.ts
CHANGED
|
@@ -3,15 +3,113 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides HTTP-aware caching with PostMessage coordination for worker environments
|
|
5
5
|
*/
|
|
6
|
-
export { Cache, type CacheQueryOptions, generateCacheKey, cloneResponse } from "./cache.js";
|
|
7
|
-
export { CustomCacheStorage, type CacheFactory } from "./cache-storage.js";
|
|
8
|
-
export { MemoryCache, MemoryCacheManager, type MemoryCacheOptions } from "./memory.js";
|
|
9
|
-
export { PostMessageCache, type PostMessageCacheOptions } from "./postmessage.js";
|
|
10
|
-
import { MemoryCache, type MemoryCacheOptions } from "./memory.js";
|
|
11
6
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* Cache query options for matching requests
|
|
8
|
+
* Based on the Cache API specification
|
|
14
9
|
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
export interface CacheQueryOptions {
|
|
11
|
+
/** Ignore the search portion of the request URL */
|
|
12
|
+
ignoreSearch?: boolean;
|
|
13
|
+
/** Ignore the request method */
|
|
14
|
+
ignoreMethod?: boolean;
|
|
15
|
+
/** Ignore the Vary header */
|
|
16
|
+
ignoreVary?: boolean;
|
|
17
|
+
/** Custom cache name for scoped operations */
|
|
18
|
+
cacheName?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert RequestInfo or URL to Request
|
|
22
|
+
*/
|
|
23
|
+
export declare function toRequest(request: RequestInfo | URL): Request;
|
|
24
|
+
/**
|
|
25
|
+
* Abstract Cache class implementing the Cache API interface
|
|
26
|
+
* Provides shared implementations for add() and addAll() while requiring
|
|
27
|
+
* concrete implementations to handle the core storage operations
|
|
28
|
+
*/
|
|
29
|
+
export declare abstract class Cache {
|
|
30
|
+
/**
|
|
31
|
+
* Returns a Promise that resolves to the response associated with the first matching request
|
|
32
|
+
*/
|
|
33
|
+
abstract match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Puts a request/response pair into the cache
|
|
36
|
+
*/
|
|
37
|
+
abstract put(request: RequestInfo | URL, response: Response): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Finds the cache entry whose key is the request, and if found, deletes it and returns true
|
|
40
|
+
*/
|
|
41
|
+
abstract delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Returns a Promise that resolves to an array of cache keys (Request objects)
|
|
44
|
+
*/
|
|
45
|
+
abstract keys(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<readonly Request[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Takes a URL, retrieves it and adds the resulting response object to the cache
|
|
48
|
+
* Shared implementation using fetch() and put()
|
|
49
|
+
*/
|
|
50
|
+
add(request: Request): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Takes an array of URLs, retrieves them, and adds the resulting response objects to the cache
|
|
53
|
+
* Shared implementation using add()
|
|
54
|
+
*/
|
|
55
|
+
addAll(requests: Request[]): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Returns a Promise that resolves to an array of all matching responses
|
|
58
|
+
* Default implementation using keys() and match() - can be overridden for efficiency
|
|
59
|
+
*/
|
|
60
|
+
matchAll(request?: Request, options?: CacheQueryOptions): Promise<readonly Response[]>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate a cache key from a Request, string URL, or URL object
|
|
64
|
+
* Normalizes the request for consistent cache key generation
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateCacheKey(request: RequestInfo | URL, options?: CacheQueryOptions): string;
|
|
67
|
+
/**
|
|
68
|
+
* Factory function for creating Cache instances based on cache name
|
|
69
|
+
*/
|
|
70
|
+
export type CacheFactory = (name: string) => Cache | Promise<Cache>;
|
|
71
|
+
/**
|
|
72
|
+
* CustomCacheStorage implements CacheStorage interface with a configurable factory
|
|
73
|
+
* The factory function receives the cache name and can return different cache types
|
|
74
|
+
*/
|
|
75
|
+
export declare class CustomCacheStorage {
|
|
76
|
+
#private;
|
|
77
|
+
constructor(factory: CacheFactory);
|
|
78
|
+
/**
|
|
79
|
+
* Matches a request across all caches
|
|
80
|
+
*/
|
|
81
|
+
match(request: Request, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
82
|
+
/**
|
|
83
|
+
* Opens a cache with the given name
|
|
84
|
+
* Returns existing instance if already opened, otherwise creates a new one
|
|
85
|
+
*/
|
|
86
|
+
open(name: string): Promise<Cache>;
|
|
87
|
+
/**
|
|
88
|
+
* Returns true if a cache with the given name exists (has been opened)
|
|
89
|
+
*/
|
|
90
|
+
has(name: string): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* Deletes a cache with the given name
|
|
93
|
+
*/
|
|
94
|
+
delete(name: string): Promise<boolean>;
|
|
95
|
+
/**
|
|
96
|
+
* Returns a list of all opened cache names
|
|
97
|
+
*/
|
|
98
|
+
keys(): Promise<string[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Get statistics about the cache storage
|
|
101
|
+
*/
|
|
102
|
+
getStats(): {
|
|
103
|
+
openInstances: number;
|
|
104
|
+
cacheNames: string[];
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Dispose of all cache instances
|
|
108
|
+
* Calls dispose() on each cache if it exists (e.g., RedisCache needs to close connections)
|
|
109
|
+
*/
|
|
110
|
+
dispose(): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Handle cache messages from worker threads (PostMessageCache coordination)
|
|
113
|
+
*/
|
|
114
|
+
handleMessage(worker: any, message: any): Promise<void>;
|
|
115
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,21 +1,198 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
2
|
// src/index.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return
|
|
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
11
|
}
|
|
12
|
+
var Cache = class {
|
|
13
|
+
/**
|
|
14
|
+
* Takes a URL, retrieves it and adds the resulting response object to the cache
|
|
15
|
+
* Shared implementation using fetch() and put()
|
|
16
|
+
*/
|
|
17
|
+
async add(request) {
|
|
18
|
+
const response = await fetch(request);
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new TypeError(
|
|
21
|
+
`Failed to fetch ${request.url}: ${response.status} ${response.statusText}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
await this.put(request, response);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Takes an array of URLs, retrieves them, and adds the resulting response objects to the cache
|
|
28
|
+
* Shared implementation using add()
|
|
29
|
+
*/
|
|
30
|
+
async addAll(requests) {
|
|
31
|
+
await Promise.all(requests.map((request) => this.add(request)));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns a Promise that resolves to an array of all matching responses
|
|
35
|
+
* Default implementation using keys() and match() - can be overridden for efficiency
|
|
36
|
+
*/
|
|
37
|
+
async matchAll(request, options) {
|
|
38
|
+
const keys = await this.keys(request, options);
|
|
39
|
+
const responses = [];
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
const response = await this.match(key, options);
|
|
42
|
+
if (response) {
|
|
43
|
+
responses.push(response);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return responses;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function generateCacheKey(request, options) {
|
|
50
|
+
const req = toRequest(request);
|
|
51
|
+
const url = new URL(req.url);
|
|
52
|
+
if (options?.ignoreSearch) {
|
|
53
|
+
url.search = "";
|
|
54
|
+
}
|
|
55
|
+
const method = options?.ignoreMethod ? "GET" : req.method;
|
|
56
|
+
return `${method}:${url.href}`;
|
|
57
|
+
}
|
|
58
|
+
var CustomCacheStorage = class {
|
|
59
|
+
#instances;
|
|
60
|
+
#factory;
|
|
61
|
+
constructor(factory) {
|
|
62
|
+
this.#instances = /* @__PURE__ */ new Map();
|
|
63
|
+
this.#factory = factory;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Matches a request across all caches
|
|
67
|
+
*/
|
|
68
|
+
async match(request, options) {
|
|
69
|
+
for (const cache of this.#instances.values()) {
|
|
70
|
+
const response = await cache.match(request, options);
|
|
71
|
+
if (response) {
|
|
72
|
+
return response;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Opens a cache with the given name
|
|
79
|
+
* Returns existing instance if already opened, otherwise creates a new one
|
|
80
|
+
*/
|
|
81
|
+
async open(name) {
|
|
82
|
+
const existingInstance = this.#instances.get(name);
|
|
83
|
+
if (existingInstance) {
|
|
84
|
+
return existingInstance;
|
|
85
|
+
}
|
|
86
|
+
const cache = await this.#factory(name);
|
|
87
|
+
this.#instances.set(name, cache);
|
|
88
|
+
return cache;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns true if a cache with the given name exists (has been opened)
|
|
92
|
+
*/
|
|
93
|
+
async has(name) {
|
|
94
|
+
return this.#instances.has(name);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Deletes a cache with the given name
|
|
98
|
+
*/
|
|
99
|
+
async delete(name) {
|
|
100
|
+
const instance = this.#instances.get(name);
|
|
101
|
+
if (instance) {
|
|
102
|
+
this.#instances.delete(name);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns a list of all opened cache names
|
|
109
|
+
*/
|
|
110
|
+
async keys() {
|
|
111
|
+
return Array.from(this.#instances.keys());
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get statistics about the cache storage
|
|
115
|
+
*/
|
|
116
|
+
getStats() {
|
|
117
|
+
return {
|
|
118
|
+
openInstances: this.#instances.size,
|
|
119
|
+
cacheNames: Array.from(this.#instances.keys())
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Dispose of all cache instances
|
|
124
|
+
* Calls dispose() on each cache if it exists (e.g., RedisCache needs to close connections)
|
|
125
|
+
*/
|
|
126
|
+
async dispose() {
|
|
127
|
+
const disposePromises = [];
|
|
128
|
+
for (const cache of this.#instances.values()) {
|
|
129
|
+
if (typeof cache.dispose === "function") {
|
|
130
|
+
disposePromises.push(cache.dispose());
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await Promise.allSettled(disposePromises);
|
|
134
|
+
this.#instances.clear();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Handle cache messages from worker threads (PostMessageCache coordination)
|
|
138
|
+
*/
|
|
139
|
+
async handleMessage(worker, message) {
|
|
140
|
+
const { type, requestID, cacheName } = message;
|
|
141
|
+
try {
|
|
142
|
+
const cache = await this.open(cacheName);
|
|
143
|
+
let result;
|
|
144
|
+
switch (type) {
|
|
145
|
+
case "cache:match": {
|
|
146
|
+
const req = new Request(message.request.url, message.request);
|
|
147
|
+
const response = await cache.match(req, message.options);
|
|
148
|
+
result = response ? {
|
|
149
|
+
status: response.status,
|
|
150
|
+
statusText: response.statusText,
|
|
151
|
+
headers: Object.fromEntries(response.headers),
|
|
152
|
+
body: await response.text()
|
|
153
|
+
} : void 0;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "cache:put": {
|
|
157
|
+
const req = new Request(message.request.url, message.request);
|
|
158
|
+
const res = new Response(message.response.body, message.response);
|
|
159
|
+
await cache.put(req, res);
|
|
160
|
+
result = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case "cache:delete": {
|
|
164
|
+
const req = new Request(message.request.url, message.request);
|
|
165
|
+
result = await cache.delete(req, message.options);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case "cache:keys": {
|
|
169
|
+
const req = message.request ? new Request(message.request.url, message.request) : void 0;
|
|
170
|
+
const keys = await cache.keys(req, message.options);
|
|
171
|
+
result = keys.map((r) => ({
|
|
172
|
+
url: r.url,
|
|
173
|
+
method: r.method,
|
|
174
|
+
headers: Object.fromEntries(r.headers.entries())
|
|
175
|
+
}));
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case "cache:clear":
|
|
179
|
+
await cache.clear?.();
|
|
180
|
+
result = true;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
worker.postMessage({ type: "cache:response", requestID, result });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
worker.postMessage({
|
|
186
|
+
type: "cache:error",
|
|
187
|
+
requestID,
|
|
188
|
+
error: error.message
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
12
193
|
export {
|
|
13
194
|
Cache,
|
|
14
195
|
CustomCacheStorage,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
PostMessageCache,
|
|
18
|
-
cloneResponse,
|
|
19
|
-
createCache,
|
|
20
|
-
generateCacheKey
|
|
196
|
+
generateCacheKey,
|
|
197
|
+
toRequest
|
|
21
198
|
};
|
package/src/memory.d.ts
CHANGED
|
@@ -1,40 +1,34 @@
|
|
|
1
|
-
import { Cache, type CacheQueryOptions } from "./
|
|
1
|
+
import { Cache, type CacheQueryOptions } from "./index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration options for MemoryCache
|
|
4
4
|
*/
|
|
5
5
|
export interface MemoryCacheOptions {
|
|
6
6
|
/** Maximum number of entries to store */
|
|
7
7
|
maxEntries?: number;
|
|
8
|
-
/** Maximum age of entries in milliseconds */
|
|
9
|
-
maxAge?: number;
|
|
10
8
|
}
|
|
11
9
|
/**
|
|
12
10
|
* In-memory cache implementation using Map for storage
|
|
13
11
|
* Supports LRU eviction and TTL expiration
|
|
14
12
|
*/
|
|
15
13
|
export declare class MemoryCache extends Cache {
|
|
16
|
-
private
|
|
17
|
-
private options;
|
|
18
|
-
private storage;
|
|
19
|
-
private accessOrder;
|
|
20
|
-
private accessCounter;
|
|
14
|
+
#private;
|
|
21
15
|
constructor(name: string, options?: MemoryCacheOptions);
|
|
22
16
|
/**
|
|
23
17
|
* Find a cached response for the given request
|
|
24
18
|
*/
|
|
25
|
-
match(request:
|
|
19
|
+
match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
26
20
|
/**
|
|
27
21
|
* Store a request/response pair in the cache
|
|
28
22
|
*/
|
|
29
|
-
put(request:
|
|
23
|
+
put(request: RequestInfo | URL, response: Response): Promise<void>;
|
|
30
24
|
/**
|
|
31
25
|
* Delete matching entries from the cache
|
|
32
26
|
*/
|
|
33
|
-
delete(request:
|
|
27
|
+
delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;
|
|
34
28
|
/**
|
|
35
29
|
* Get all stored requests, optionally filtered by a request pattern
|
|
36
30
|
*/
|
|
37
|
-
keys(request?:
|
|
31
|
+
keys(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<readonly Request[]>;
|
|
38
32
|
/**
|
|
39
33
|
* Clear all entries from the cache
|
|
40
34
|
*/
|
|
@@ -46,77 +40,6 @@ export declare class MemoryCache extends Cache {
|
|
|
46
40
|
name: string;
|
|
47
41
|
size: number;
|
|
48
42
|
maxEntries: number;
|
|
49
|
-
maxAge: number;
|
|
50
43
|
hitRate: number;
|
|
51
44
|
};
|
|
52
|
-
/**
|
|
53
|
-
* Dispose of the cache and clean up resources
|
|
54
|
-
*/
|
|
55
|
-
dispose(): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* Check if a cache entry has expired
|
|
58
|
-
*/
|
|
59
|
-
private isExpired;
|
|
60
|
-
/**
|
|
61
|
-
* Check if a response should be cached
|
|
62
|
-
*/
|
|
63
|
-
private isCacheable;
|
|
64
|
-
/**
|
|
65
|
-
* Enforce maximum entry limits using LRU eviction
|
|
66
|
-
*/
|
|
67
|
-
private enforceMaxEntries;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Memory Cache Manager for Main Thread
|
|
71
|
-
*
|
|
72
|
-
* Coordinates MemoryCache operations across Worker threads by managing
|
|
73
|
-
* shared MemoryCache instances and handling postMessage requests.
|
|
74
|
-
*
|
|
75
|
-
* Only MemoryCache needs coordination since it stores data in process memory.
|
|
76
|
-
* Other cache types can be used directly by workers without coordination.
|
|
77
|
-
*/
|
|
78
|
-
interface WorkerLike {
|
|
79
|
-
postMessage(value: any): void;
|
|
80
|
-
on(event: string, listener: (data: any) => void): void;
|
|
81
|
-
}
|
|
82
|
-
interface CacheMessage {
|
|
83
|
-
type: string;
|
|
84
|
-
requestId: string;
|
|
85
|
-
cacheName: string;
|
|
86
|
-
request?: SerializedRequest;
|
|
87
|
-
response?: SerializedResponse;
|
|
88
|
-
options?: any;
|
|
89
|
-
}
|
|
90
|
-
interface SerializedRequest {
|
|
91
|
-
url: string;
|
|
92
|
-
method: string;
|
|
93
|
-
headers: Record<string, string>;
|
|
94
|
-
body?: string;
|
|
95
|
-
}
|
|
96
|
-
interface SerializedResponse {
|
|
97
|
-
status: number;
|
|
98
|
-
statusText: string;
|
|
99
|
-
headers: Record<string, string>;
|
|
100
|
-
body: string;
|
|
101
|
-
}
|
|
102
|
-
export declare class MemoryCacheManager {
|
|
103
|
-
private memoryCaches;
|
|
104
|
-
/**
|
|
105
|
-
* Handle memory cache-related message from a Worker
|
|
106
|
-
*/
|
|
107
|
-
handleMessage(worker: WorkerLike, message: CacheMessage): Promise<void>;
|
|
108
|
-
/**
|
|
109
|
-
* Get or create a MemoryCache instance
|
|
110
|
-
*/
|
|
111
|
-
private getMemoryCache;
|
|
112
|
-
private handleMatch;
|
|
113
|
-
private handlePut;
|
|
114
|
-
private handleDelete;
|
|
115
|
-
private handleKeys;
|
|
116
|
-
private handleClear;
|
|
117
|
-
/**
|
|
118
|
-
* Dispose of all memory caches
|
|
119
|
-
*/
|
|
120
|
-
dispose(): Promise<void>;
|
|
121
45
|
}
|
|
122
|
-
export {};
|
package/src/memory.js
CHANGED
|
@@ -3,62 +3,96 @@
|
|
|
3
3
|
import {
|
|
4
4
|
Cache,
|
|
5
5
|
generateCacheKey,
|
|
6
|
-
|
|
7
|
-
} from "./
|
|
6
|
+
toRequest
|
|
7
|
+
} from "./index.js";
|
|
8
8
|
var MemoryCache = class extends Cache {
|
|
9
|
+
#storage;
|
|
10
|
+
#accessOrder;
|
|
11
|
+
#accessCounter;
|
|
12
|
+
#name;
|
|
13
|
+
#options;
|
|
9
14
|
constructor(name, options = {}) {
|
|
10
15
|
super();
|
|
11
|
-
this
|
|
12
|
-
this
|
|
16
|
+
this.#storage = /* @__PURE__ */ new Map();
|
|
17
|
+
this.#accessOrder = /* @__PURE__ */ new Map();
|
|
18
|
+
this.#accessCounter = 0;
|
|
19
|
+
this.#name = name;
|
|
20
|
+
this.#options = options;
|
|
13
21
|
}
|
|
14
|
-
storage = /* @__PURE__ */ new Map();
|
|
15
|
-
accessOrder = /* @__PURE__ */ new Map();
|
|
16
|
-
// For LRU tracking
|
|
17
|
-
accessCounter = 0;
|
|
18
22
|
/**
|
|
19
23
|
* Find a cached response for the given request
|
|
20
24
|
*/
|
|
21
25
|
async match(request, options) {
|
|
26
|
+
if (options?.ignoreSearch) {
|
|
27
|
+
const filterKey = generateCacheKey(request, options);
|
|
28
|
+
for (const [key2, entry2] of this.#storage) {
|
|
29
|
+
if (this.#isExpired(entry2)) {
|
|
30
|
+
this.#storage.delete(key2);
|
|
31
|
+
this.#accessOrder.delete(key2);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const entryKey = generateCacheKey(entry2.request, options);
|
|
35
|
+
if (entryKey === filterKey) {
|
|
36
|
+
this.#accessOrder.set(key2, ++this.#accessCounter);
|
|
37
|
+
return entry2.response.clone();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
22
42
|
const key = generateCacheKey(request, options);
|
|
23
|
-
const entry = this
|
|
43
|
+
const entry = this.#storage.get(key);
|
|
24
44
|
if (!entry) {
|
|
25
45
|
return void 0;
|
|
26
46
|
}
|
|
27
|
-
if (this
|
|
28
|
-
this
|
|
29
|
-
this
|
|
47
|
+
if (this.#isExpired(entry)) {
|
|
48
|
+
this.#storage.delete(key);
|
|
49
|
+
this.#accessOrder.delete(key);
|
|
30
50
|
return void 0;
|
|
31
51
|
}
|
|
32
|
-
this
|
|
33
|
-
return
|
|
52
|
+
this.#accessOrder.set(key, ++this.#accessCounter);
|
|
53
|
+
return entry.response.clone();
|
|
34
54
|
}
|
|
35
55
|
/**
|
|
36
56
|
* Store a request/response pair in the cache
|
|
37
57
|
*/
|
|
38
58
|
async put(request, response) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
const req = toRequest(request);
|
|
60
|
+
const key = generateCacheKey(req);
|
|
61
|
+
if (response.bodyUsed) {
|
|
62
|
+
throw new TypeError("Response body has already been used");
|
|
42
63
|
}
|
|
43
|
-
const clonedRequest =
|
|
44
|
-
const clonedResponse =
|
|
64
|
+
const clonedRequest = req.clone();
|
|
65
|
+
const clonedResponse = response.clone();
|
|
45
66
|
const entry = {
|
|
46
67
|
request: clonedRequest,
|
|
47
68
|
response: clonedResponse,
|
|
48
69
|
timestamp: Date.now()
|
|
49
70
|
};
|
|
50
|
-
this
|
|
51
|
-
this
|
|
52
|
-
this
|
|
71
|
+
this.#storage.set(key, entry);
|
|
72
|
+
this.#accessOrder.set(key, ++this.#accessCounter);
|
|
73
|
+
this.#enforceMaxEntries();
|
|
53
74
|
}
|
|
54
75
|
/**
|
|
55
76
|
* Delete matching entries from the cache
|
|
56
77
|
*/
|
|
57
78
|
async delete(request, options) {
|
|
79
|
+
if (options?.ignoreSearch) {
|
|
80
|
+
const filterKey = generateCacheKey(request, options);
|
|
81
|
+
let deleted2 = false;
|
|
82
|
+
for (const [key2, entry] of this.#storage) {
|
|
83
|
+
const entryKey = generateCacheKey(entry.request, options);
|
|
84
|
+
if (entryKey === filterKey) {
|
|
85
|
+
this.#storage.delete(key2);
|
|
86
|
+
this.#accessOrder.delete(key2);
|
|
87
|
+
deleted2 = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return deleted2;
|
|
91
|
+
}
|
|
58
92
|
const key = generateCacheKey(request, options);
|
|
59
|
-
const deleted = this
|
|
93
|
+
const deleted = this.#storage.delete(key);
|
|
60
94
|
if (deleted) {
|
|
61
|
-
this
|
|
95
|
+
this.#accessOrder.delete(key);
|
|
62
96
|
}
|
|
63
97
|
return deleted;
|
|
64
98
|
}
|
|
@@ -67,8 +101,8 @@ var MemoryCache = class extends Cache {
|
|
|
67
101
|
*/
|
|
68
102
|
async keys(request, options) {
|
|
69
103
|
const keys = [];
|
|
70
|
-
for (const [_, entry] of this
|
|
71
|
-
if (this
|
|
104
|
+
for (const [_, entry] of this.#storage) {
|
|
105
|
+
if (this.#isExpired(entry)) {
|
|
72
106
|
continue;
|
|
73
107
|
}
|
|
74
108
|
if (!request) {
|
|
@@ -87,208 +121,55 @@ var MemoryCache = class extends Cache {
|
|
|
87
121
|
* Clear all entries from the cache
|
|
88
122
|
*/
|
|
89
123
|
async clear() {
|
|
90
|
-
this
|
|
91
|
-
this
|
|
92
|
-
this
|
|
124
|
+
this.#storage.clear();
|
|
125
|
+
this.#accessOrder.clear();
|
|
126
|
+
this.#accessCounter = 0;
|
|
93
127
|
}
|
|
94
128
|
/**
|
|
95
129
|
* Get cache statistics
|
|
96
130
|
*/
|
|
97
131
|
getStats() {
|
|
98
132
|
return {
|
|
99
|
-
name: this
|
|
100
|
-
size: this
|
|
101
|
-
maxEntries: this
|
|
102
|
-
maxAge: this.options.maxAge,
|
|
133
|
+
name: this.#name,
|
|
134
|
+
size: this.#storage.size,
|
|
135
|
+
maxEntries: this.#options.maxEntries,
|
|
103
136
|
hitRate: 0
|
|
104
137
|
// Could be implemented with additional tracking
|
|
105
138
|
};
|
|
106
139
|
}
|
|
107
140
|
/**
|
|
108
|
-
*
|
|
109
|
-
*/
|
|
110
|
-
async dispose() {
|
|
111
|
-
await this.clear();
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Check if a cache entry has expired
|
|
141
|
+
* Check if a cache entry has expired based on Cache-Control header
|
|
115
142
|
*/
|
|
116
|
-
isExpired(entry) {
|
|
117
|
-
|
|
143
|
+
#isExpired(entry) {
|
|
144
|
+
const cacheControl = entry.response.headers.get("cache-control");
|
|
145
|
+
if (!cacheControl) {
|
|
118
146
|
return false;
|
|
119
147
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if a response should be cached
|
|
124
|
-
*/
|
|
125
|
-
isCacheable(response) {
|
|
126
|
-
if (!response.ok) {
|
|
148
|
+
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
|
|
149
|
+
if (!maxAgeMatch) {
|
|
127
150
|
return false;
|
|
128
151
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
if (cacheControl.includes("no-cache") || cacheControl.includes("no-store")) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
152
|
+
const maxAge = parseInt(maxAgeMatch[1], 10) * 1e3;
|
|
153
|
+
return Date.now() - entry.timestamp > maxAge;
|
|
136
154
|
}
|
|
137
155
|
/**
|
|
138
156
|
* Enforce maximum entry limits using LRU eviction
|
|
139
157
|
*/
|
|
140
|
-
enforceMaxEntries() {
|
|
141
|
-
if (!this
|
|
158
|
+
#enforceMaxEntries() {
|
|
159
|
+
if (!this.#options.maxEntries || this.#storage.size <= this.#options.maxEntries) {
|
|
142
160
|
return;
|
|
143
161
|
}
|
|
144
|
-
const entries = Array.from(this
|
|
145
|
-
|
|
162
|
+
const entries = Array.from(this.#accessOrder.entries()).sort(
|
|
163
|
+
(a, b) => a[1] - b[1]
|
|
164
|
+
);
|
|
165
|
+
const toRemove = this.#storage.size - this.#options.maxEntries;
|
|
146
166
|
for (let i = 0; i < toRemove; i++) {
|
|
147
167
|
const [key] = entries[i];
|
|
148
|
-
this
|
|
149
|
-
this
|
|
168
|
+
this.#storage.delete(key);
|
|
169
|
+
this.#accessOrder.delete(key);
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
172
|
};
|
|
153
|
-
var MemoryCacheManager = class {
|
|
154
|
-
memoryCaches = /* @__PURE__ */ new Map();
|
|
155
|
-
/**
|
|
156
|
-
* Handle memory cache-related message from a Worker
|
|
157
|
-
*/
|
|
158
|
-
async handleMessage(worker, message) {
|
|
159
|
-
const { type, requestId } = message;
|
|
160
|
-
try {
|
|
161
|
-
let result;
|
|
162
|
-
switch (type) {
|
|
163
|
-
case "cache:match":
|
|
164
|
-
result = await this.handleMatch(message);
|
|
165
|
-
break;
|
|
166
|
-
case "cache:put":
|
|
167
|
-
result = await this.handlePut(message);
|
|
168
|
-
break;
|
|
169
|
-
case "cache:delete":
|
|
170
|
-
result = await this.handleDelete(message);
|
|
171
|
-
break;
|
|
172
|
-
case "cache:keys":
|
|
173
|
-
result = await this.handleKeys(message);
|
|
174
|
-
break;
|
|
175
|
-
case "cache:clear":
|
|
176
|
-
result = await this.handleClear(message);
|
|
177
|
-
break;
|
|
178
|
-
default:
|
|
179
|
-
throw new Error(`Unknown cache operation: ${type}`);
|
|
180
|
-
}
|
|
181
|
-
worker.postMessage({
|
|
182
|
-
type: "cache:response",
|
|
183
|
-
requestId,
|
|
184
|
-
result
|
|
185
|
-
});
|
|
186
|
-
} catch (error) {
|
|
187
|
-
worker.postMessage({
|
|
188
|
-
type: "cache:error",
|
|
189
|
-
requestId,
|
|
190
|
-
error: error.message
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get or create a MemoryCache instance
|
|
196
|
-
*/
|
|
197
|
-
getMemoryCache(name, options) {
|
|
198
|
-
if (!this.memoryCaches.has(name)) {
|
|
199
|
-
this.memoryCaches.set(name, new MemoryCache(name, options));
|
|
200
|
-
}
|
|
201
|
-
return this.memoryCaches.get(name);
|
|
202
|
-
}
|
|
203
|
-
async handleMatch(message) {
|
|
204
|
-
const { cacheName, request, options } = message;
|
|
205
|
-
if (!request)
|
|
206
|
-
throw new Error("Request is required for match operation");
|
|
207
|
-
const cache = this.getMemoryCache(cacheName);
|
|
208
|
-
const req = new Request(request.url, {
|
|
209
|
-
method: request.method,
|
|
210
|
-
headers: request.headers,
|
|
211
|
-
body: request.body
|
|
212
|
-
});
|
|
213
|
-
const response = await cache.match(req, options);
|
|
214
|
-
if (!response) {
|
|
215
|
-
return void 0;
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
status: response.status,
|
|
219
|
-
statusText: response.statusText,
|
|
220
|
-
headers: Object.fromEntries(response.headers),
|
|
221
|
-
body: await response.text()
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
async handlePut(message) {
|
|
225
|
-
const { cacheName, request, response } = message;
|
|
226
|
-
if (!request || !response)
|
|
227
|
-
throw new Error("Request and response are required for put operation");
|
|
228
|
-
const cache = this.getMemoryCache(cacheName);
|
|
229
|
-
const req = new Request(request.url, {
|
|
230
|
-
method: request.method,
|
|
231
|
-
headers: request.headers,
|
|
232
|
-
body: request.body
|
|
233
|
-
});
|
|
234
|
-
const res = new Response(response.body, {
|
|
235
|
-
status: response.status,
|
|
236
|
-
statusText: response.statusText,
|
|
237
|
-
headers: response.headers
|
|
238
|
-
});
|
|
239
|
-
await cache.put(req, res);
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
async handleDelete(message) {
|
|
243
|
-
const { cacheName, request, options } = message;
|
|
244
|
-
if (!request)
|
|
245
|
-
throw new Error("Request is required for delete operation");
|
|
246
|
-
const cache = this.getMemoryCache(cacheName);
|
|
247
|
-
const req = new Request(request.url, {
|
|
248
|
-
method: request.method,
|
|
249
|
-
headers: request.headers,
|
|
250
|
-
body: request.body
|
|
251
|
-
});
|
|
252
|
-
return await cache.delete(req, options);
|
|
253
|
-
}
|
|
254
|
-
async handleKeys(message) {
|
|
255
|
-
const { cacheName, request, options } = message;
|
|
256
|
-
const cache = this.getMemoryCache(cacheName);
|
|
257
|
-
let req;
|
|
258
|
-
if (request) {
|
|
259
|
-
req = new Request(request.url, {
|
|
260
|
-
method: request.method,
|
|
261
|
-
headers: request.headers,
|
|
262
|
-
body: request.body
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
const keys = await cache.keys(req, options);
|
|
266
|
-
return keys.map((r) => ({
|
|
267
|
-
url: r.url,
|
|
268
|
-
method: r.method,
|
|
269
|
-
headers: Object.fromEntries(r.headers),
|
|
270
|
-
body: void 0
|
|
271
|
-
// Keys typically don't need body
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
async handleClear(message) {
|
|
275
|
-
const { cacheName } = message;
|
|
276
|
-
const cache = this.getMemoryCache(cacheName);
|
|
277
|
-
await cache.clear();
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Dispose of all memory caches
|
|
282
|
-
*/
|
|
283
|
-
async dispose() {
|
|
284
|
-
const disposePromises = Array.from(this.memoryCaches.values()).map(
|
|
285
|
-
(cache) => cache.dispose()
|
|
286
|
-
);
|
|
287
|
-
await Promise.all(disposePromises);
|
|
288
|
-
this.memoryCaches.clear();
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
173
|
export {
|
|
292
|
-
MemoryCache
|
|
293
|
-
MemoryCacheManager
|
|
174
|
+
MemoryCache
|
|
294
175
|
};
|
package/src/postmessage.d.ts
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
import { Cache, type CacheQueryOptions } from "./
|
|
1
|
+
import { Cache, type CacheQueryOptions } from "./index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Configuration options for PostMessageCache
|
|
4
4
|
*/
|
|
5
5
|
export interface PostMessageCacheOptions {
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/** Maximum age of entries in milliseconds */
|
|
9
|
-
maxAge?: number;
|
|
6
|
+
/** Timeout for cache operations in milliseconds (default: 30000) */
|
|
7
|
+
timeout?: number;
|
|
10
8
|
}
|
|
11
9
|
/**
|
|
12
10
|
* Worker-side cache that forwards operations to main thread via postMessage
|
|
13
11
|
* Only used for MemoryCache in multi-worker environments
|
|
14
12
|
*/
|
|
15
13
|
export declare class PostMessageCache extends Cache {
|
|
16
|
-
private
|
|
17
|
-
private options;
|
|
18
|
-
private requestId;
|
|
19
|
-
private pendingRequests;
|
|
14
|
+
#private;
|
|
20
15
|
constructor(name: string, options?: PostMessageCacheOptions);
|
|
21
|
-
private handleResponse;
|
|
22
|
-
private sendRequest;
|
|
23
16
|
match(request: Request, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
24
17
|
put(request: Request, response: Response): Promise<void>;
|
|
25
18
|
delete(request: Request, options?: CacheQueryOptions): Promise<boolean>;
|
|
26
|
-
keys(request?: Request, options?: CacheQueryOptions): Promise<Request[]>;
|
|
19
|
+
keys(request?: Request, options?: CacheQueryOptions): Promise<readonly Request[]>;
|
|
27
20
|
clear(): Promise<void>;
|
|
28
|
-
dispose(): Promise<void>;
|
|
29
21
|
}
|
package/src/postmessage.js
CHANGED
|
@@ -1,60 +1,66 @@
|
|
|
1
1
|
/// <reference types="./postmessage.d.ts" />
|
|
2
2
|
// src/postmessage.ts
|
|
3
|
-
import { Cache } from "./
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { Cache } from "./index.js";
|
|
4
|
+
function getParentPort() {
|
|
5
|
+
return typeof self !== "undefined" ? self : null;
|
|
6
|
+
}
|
|
7
|
+
var messageHandlerSetup = false;
|
|
8
|
+
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
|
+
function handleCacheResponse(message) {
|
|
24
|
+
const pending = pendingRequestsRegistry.get(message.requestID);
|
|
25
|
+
if (pending) {
|
|
26
|
+
pendingRequestsRegistry.delete(message.requestID);
|
|
27
|
+
if (message.type === "cache:error") {
|
|
28
|
+
pending.reject(new Error(message.error));
|
|
29
|
+
} else {
|
|
30
|
+
pending.resolve(message.result);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var globalRequestID = 0;
|
|
6
35
|
var PostMessageCache = class extends Cache {
|
|
36
|
+
#name;
|
|
37
|
+
#timeout;
|
|
7
38
|
constructor(name, options = {}) {
|
|
8
39
|
super();
|
|
9
|
-
this
|
|
10
|
-
this
|
|
11
|
-
|
|
12
|
-
throw new Error(
|
|
13
|
-
"PostMessageCache should only be used in worker threads"
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
if (parentPort) {
|
|
17
|
-
parentPort.on("message", (message) => {
|
|
18
|
-
if (message.type === "cache:response" || message.type === "cache:error") {
|
|
19
|
-
this.handleResponse(message);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
requestId = 0;
|
|
25
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
26
|
-
handleResponse(message) {
|
|
27
|
-
const pending = this.pendingRequests.get(message.requestId);
|
|
28
|
-
if (pending) {
|
|
29
|
-
this.pendingRequests.delete(message.requestId);
|
|
30
|
-
if (message.type === "cache:error") {
|
|
31
|
-
pending.reject(new Error(message.error));
|
|
32
|
-
} else {
|
|
33
|
-
pending.resolve(message.result);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
40
|
+
this.#name = name;
|
|
41
|
+
this.#timeout = options.timeout ?? 3e4;
|
|
42
|
+
setupMessageHandler();
|
|
36
43
|
}
|
|
37
|
-
async sendRequest(type, data) {
|
|
44
|
+
async #sendRequest(type, data) {
|
|
45
|
+
const parentPort = getParentPort();
|
|
38
46
|
if (!parentPort) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
"PostMessageCache can only be used in worker threads"
|
|
41
|
-
);
|
|
47
|
+
throw new Error("PostMessageCache can only be used in worker threads");
|
|
42
48
|
}
|
|
43
|
-
const
|
|
49
|
+
const requestID = ++globalRequestID;
|
|
44
50
|
return new Promise((resolve, reject) => {
|
|
45
|
-
|
|
51
|
+
pendingRequestsRegistry.set(requestID, { resolve, reject });
|
|
46
52
|
parentPort.postMessage({
|
|
47
53
|
type,
|
|
48
|
-
|
|
49
|
-
cacheName: this
|
|
54
|
+
requestID,
|
|
55
|
+
cacheName: this.#name,
|
|
50
56
|
...data
|
|
51
57
|
});
|
|
52
58
|
setTimeout(() => {
|
|
53
|
-
if (
|
|
54
|
-
|
|
59
|
+
if (pendingRequestsRegistry.has(requestID)) {
|
|
60
|
+
pendingRequestsRegistry.delete(requestID);
|
|
55
61
|
reject(new Error("Cache operation timeout"));
|
|
56
62
|
}
|
|
57
|
-
},
|
|
63
|
+
}, this.#timeout);
|
|
58
64
|
});
|
|
59
65
|
}
|
|
60
66
|
async match(request, options) {
|
|
@@ -64,7 +70,7 @@ var PostMessageCache = class extends Cache {
|
|
|
64
70
|
headers: Object.fromEntries(request.headers),
|
|
65
71
|
body: request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0
|
|
66
72
|
};
|
|
67
|
-
const response = await this
|
|
73
|
+
const response = await this.#sendRequest("cache:match", {
|
|
68
74
|
request: serializedRequest,
|
|
69
75
|
options
|
|
70
76
|
});
|
|
@@ -90,7 +96,7 @@ var PostMessageCache = class extends Cache {
|
|
|
90
96
|
headers: Object.fromEntries(response.headers),
|
|
91
97
|
body: await response.clone().text()
|
|
92
98
|
};
|
|
93
|
-
await this
|
|
99
|
+
await this.#sendRequest("cache:put", {
|
|
94
100
|
request: serializedRequest,
|
|
95
101
|
response: serializedResponse
|
|
96
102
|
});
|
|
@@ -102,7 +108,7 @@ var PostMessageCache = class extends Cache {
|
|
|
102
108
|
headers: Object.fromEntries(request.headers),
|
|
103
109
|
body: request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0
|
|
104
110
|
};
|
|
105
|
-
return await this
|
|
111
|
+
return await this.#sendRequest("cache:delete", {
|
|
106
112
|
request: serializedRequest,
|
|
107
113
|
options
|
|
108
114
|
});
|
|
@@ -117,7 +123,7 @@ var PostMessageCache = class extends Cache {
|
|
|
117
123
|
body: request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0
|
|
118
124
|
};
|
|
119
125
|
}
|
|
120
|
-
const keys = await this
|
|
126
|
+
const keys = await this.#sendRequest("cache:keys", {
|
|
121
127
|
request: serializedRequest,
|
|
122
128
|
options
|
|
123
129
|
});
|
|
@@ -130,11 +136,7 @@ var PostMessageCache = class extends Cache {
|
|
|
130
136
|
);
|
|
131
137
|
}
|
|
132
138
|
async clear() {
|
|
133
|
-
await this
|
|
134
|
-
}
|
|
135
|
-
async dispose() {
|
|
136
|
-
await this.clear();
|
|
137
|
-
this.pendingRequests.clear();
|
|
139
|
+
await this.#sendRequest("cache:clear", {});
|
|
138
140
|
}
|
|
139
141
|
};
|
|
140
142
|
export {
|
package/src/cache-storage.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { Cache } from "./cache.js";
|
|
2
|
-
/**
|
|
3
|
-
* Factory function for creating Cache instances based on cache name
|
|
4
|
-
*/
|
|
5
|
-
export type CacheFactory = (name: string) => Cache | Promise<Cache>;
|
|
6
|
-
/**
|
|
7
|
-
* CustomCacheStorage implements CacheStorage interface with a configurable factory
|
|
8
|
-
* The factory function receives the cache name and can return different cache types
|
|
9
|
-
*/
|
|
10
|
-
export declare class CustomCacheStorage {
|
|
11
|
-
private factory;
|
|
12
|
-
private instances;
|
|
13
|
-
constructor(factory: CacheFactory);
|
|
14
|
-
/**
|
|
15
|
-
* Opens a cache with the given name
|
|
16
|
-
* Returns existing instance if already opened, otherwise creates a new one
|
|
17
|
-
*/
|
|
18
|
-
open(name: string): Promise<Cache>;
|
|
19
|
-
/**
|
|
20
|
-
* Returns true if a cache with the given name exists (has been opened)
|
|
21
|
-
*/
|
|
22
|
-
has(name: string): Promise<boolean>;
|
|
23
|
-
/**
|
|
24
|
-
* Deletes a cache with the given name
|
|
25
|
-
* Disposes of the instance if it exists
|
|
26
|
-
*/
|
|
27
|
-
delete(name: string): Promise<boolean>;
|
|
28
|
-
/**
|
|
29
|
-
* Returns a list of all opened cache names
|
|
30
|
-
*/
|
|
31
|
-
keys(): Promise<string[]>;
|
|
32
|
-
/**
|
|
33
|
-
* Get statistics about the cache storage
|
|
34
|
-
*/
|
|
35
|
-
getStats(): {
|
|
36
|
-
openInstances: number;
|
|
37
|
-
cacheNames: string[];
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Dispose of all open cache instances
|
|
41
|
-
* Useful for cleanup during shutdown
|
|
42
|
-
*/
|
|
43
|
-
dispose(): Promise<void>;
|
|
44
|
-
}
|
package/src/cache-storage.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/// <reference types="./cache-storage.d.ts" />
|
|
2
|
-
// src/cache-storage.ts
|
|
3
|
-
var CustomCacheStorage = class {
|
|
4
|
-
constructor(factory) {
|
|
5
|
-
this.factory = factory;
|
|
6
|
-
}
|
|
7
|
-
instances = /* @__PURE__ */ new Map();
|
|
8
|
-
/**
|
|
9
|
-
* Opens a cache with the given name
|
|
10
|
-
* Returns existing instance if already opened, otherwise creates a new one
|
|
11
|
-
*/
|
|
12
|
-
async open(name) {
|
|
13
|
-
const existingInstance = this.instances.get(name);
|
|
14
|
-
if (existingInstance) {
|
|
15
|
-
return existingInstance;
|
|
16
|
-
}
|
|
17
|
-
const cache = await this.factory(name);
|
|
18
|
-
this.instances.set(name, cache);
|
|
19
|
-
return cache;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Returns true if a cache with the given name exists (has been opened)
|
|
23
|
-
*/
|
|
24
|
-
async has(name) {
|
|
25
|
-
return this.instances.has(name);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Deletes a cache with the given name
|
|
29
|
-
* Disposes of the instance if it exists
|
|
30
|
-
*/
|
|
31
|
-
async delete(name) {
|
|
32
|
-
const instance = this.instances.get(name);
|
|
33
|
-
if (instance) {
|
|
34
|
-
if (instance.dispose) {
|
|
35
|
-
await instance.dispose();
|
|
36
|
-
}
|
|
37
|
-
this.instances.delete(name);
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Returns a list of all opened cache names
|
|
44
|
-
*/
|
|
45
|
-
async keys() {
|
|
46
|
-
return Array.from(this.instances.keys());
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Get statistics about the cache storage
|
|
50
|
-
*/
|
|
51
|
-
getStats() {
|
|
52
|
-
return {
|
|
53
|
-
openInstances: this.instances.size,
|
|
54
|
-
cacheNames: Array.from(this.instances.keys())
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Dispose of all open cache instances
|
|
59
|
-
* Useful for cleanup during shutdown
|
|
60
|
-
*/
|
|
61
|
-
async dispose() {
|
|
62
|
-
const disposePromises = [];
|
|
63
|
-
for (const [_name, instance] of this.instances) {
|
|
64
|
-
if (instance.dispose) {
|
|
65
|
-
disposePromises.push(instance.dispose());
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
await Promise.all(disposePromises);
|
|
69
|
-
this.instances.clear();
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
export {
|
|
73
|
-
CustomCacheStorage
|
|
74
|
-
};
|
package/src/cache.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache query options for matching requests
|
|
3
|
-
* Based on the Cache API specification
|
|
4
|
-
*/
|
|
5
|
-
export interface CacheQueryOptions {
|
|
6
|
-
/** Ignore the search portion of the request URL */
|
|
7
|
-
ignoreSearch?: boolean;
|
|
8
|
-
/** Ignore the request method */
|
|
9
|
-
ignoreMethod?: boolean;
|
|
10
|
-
/** Ignore the Vary header */
|
|
11
|
-
ignoreVary?: boolean;
|
|
12
|
-
/** Custom cache name for scoped operations */
|
|
13
|
-
cacheName?: string;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Abstract Cache class implementing the Cache API interface
|
|
17
|
-
* Provides shared implementations for add() and addAll() while requiring
|
|
18
|
-
* concrete implementations to handle the core storage operations
|
|
19
|
-
*/
|
|
20
|
-
export declare abstract class Cache {
|
|
21
|
-
/**
|
|
22
|
-
* Returns a Promise that resolves to the response associated with the first matching request
|
|
23
|
-
*/
|
|
24
|
-
abstract match(request: Request, options?: CacheQueryOptions): Promise<Response | undefined>;
|
|
25
|
-
/**
|
|
26
|
-
* Puts a request/response pair into the cache
|
|
27
|
-
*/
|
|
28
|
-
abstract put(request: Request, response: Response): Promise<void>;
|
|
29
|
-
/**
|
|
30
|
-
* Finds the cache entry whose key is the request, and if found, deletes it and returns true
|
|
31
|
-
*/
|
|
32
|
-
abstract delete(request: Request, options?: CacheQueryOptions): Promise<boolean>;
|
|
33
|
-
/**
|
|
34
|
-
* Returns a Promise that resolves to an array of cache keys (Request objects)
|
|
35
|
-
*/
|
|
36
|
-
abstract keys(request?: Request, options?: CacheQueryOptions): Promise<Request[]>;
|
|
37
|
-
/**
|
|
38
|
-
* Takes a URL, retrieves it and adds the resulting response object to the cache
|
|
39
|
-
* Shared implementation using fetch() and put()
|
|
40
|
-
*/
|
|
41
|
-
add(request: Request): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* Takes an array of URLs, retrieves them, and adds the resulting response objects to the cache
|
|
44
|
-
* Shared implementation using add()
|
|
45
|
-
*/
|
|
46
|
-
addAll(requests: Request[]): Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Returns a Promise that resolves to an array of all matching responses
|
|
49
|
-
* Default implementation using keys() and match() - can be overridden for efficiency
|
|
50
|
-
*/
|
|
51
|
-
matchAll(request?: Request, options?: CacheQueryOptions): Promise<Response[]>;
|
|
52
|
-
/**
|
|
53
|
-
* Optional cleanup method for implementations that need resource disposal
|
|
54
|
-
*/
|
|
55
|
-
dispose?(): Promise<void>;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Generate a cache key from a Request object
|
|
59
|
-
* Normalizes the request for consistent cache key generation
|
|
60
|
-
*/
|
|
61
|
-
export declare function generateCacheKey(request: Request, options?: CacheQueryOptions): string;
|
|
62
|
-
/**
|
|
63
|
-
* Clone a Response object for storage
|
|
64
|
-
* Responses can only be consumed once, so we need to clone them for caching
|
|
65
|
-
*/
|
|
66
|
-
export declare function cloneResponse(response: Response): Promise<Response>;
|
package/src/cache.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/// <reference types="./cache.d.ts" />
|
|
2
|
-
// src/cache.ts
|
|
3
|
-
var Cache = class {
|
|
4
|
-
/**
|
|
5
|
-
* Takes a URL, retrieves it and adds the resulting response object to the cache
|
|
6
|
-
* Shared implementation using fetch() and put()
|
|
7
|
-
*/
|
|
8
|
-
async add(request) {
|
|
9
|
-
const response = await fetch(request);
|
|
10
|
-
if (!response.ok) {
|
|
11
|
-
throw new TypeError(
|
|
12
|
-
`Failed to fetch ${request.url}: ${response.status} ${response.statusText}`
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
await this.put(request, response);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Takes an array of URLs, retrieves them, and adds the resulting response objects to the cache
|
|
19
|
-
* Shared implementation using add()
|
|
20
|
-
*/
|
|
21
|
-
async addAll(requests) {
|
|
22
|
-
await Promise.all(requests.map((request) => this.add(request)));
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Returns a Promise that resolves to an array of all matching responses
|
|
26
|
-
* Default implementation using keys() and match() - can be overridden for efficiency
|
|
27
|
-
*/
|
|
28
|
-
async matchAll(request, options) {
|
|
29
|
-
const keys = await this.keys(request, options);
|
|
30
|
-
const responses = [];
|
|
31
|
-
for (const key of keys) {
|
|
32
|
-
const response = await this.match(key, options);
|
|
33
|
-
if (response) {
|
|
34
|
-
responses.push(response);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return responses;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
function generateCacheKey(request, options) {
|
|
41
|
-
const url = new URL(request.url);
|
|
42
|
-
if (options?.ignoreSearch) {
|
|
43
|
-
url.search = "";
|
|
44
|
-
}
|
|
45
|
-
const method = options?.ignoreMethod ? "GET" : request.method;
|
|
46
|
-
return `${method}:${url.href}`;
|
|
47
|
-
}
|
|
48
|
-
async function cloneResponse(response) {
|
|
49
|
-
return response.clone();
|
|
50
|
-
}
|
|
51
|
-
export {
|
|
52
|
-
Cache,
|
|
53
|
-
cloneResponse,
|
|
54
|
-
generateCacheKey
|
|
55
|
-
};
|