@hkdigital/lib-sveltekit 0.1.68 → 0.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/classes/cache/CacheStorage.js__ +45 -0
- package/dist/classes/cache/MemoryResponseCache.d.ts +19 -0
- package/dist/classes/cache/MemoryResponseCache.js +53 -0
- package/dist/classes/cache/PersistentResponseCache.d.ts +46 -0
- package/dist/classes/cache/PersistentResponseCache.js +165 -0
- package/dist/classes/cache/index.d.ts +3 -0
- package/dist/classes/cache/index.js +5 -0
- package/dist/classes/cache/typedef.d.ts +50 -0
- package/dist/classes/cache/typedef.js +25 -0
- package/dist/util/http/caching.d.ts +28 -0
- package/dist/util/http/caching.js +251 -0
- package/dist/util/http/headers.d.ts +33 -4
- package/dist/util/http/headers.js +57 -27
- package/dist/util/http/http-request.d.ts +93 -115
- package/dist/util/http/http-request.js +329 -262
- package/dist/util/http/json-request.d.ts +65 -45
- package/dist/util/http/json-request.js +188 -138
- package/dist/util/http/response.d.ts +91 -22
- package/dist/util/http/response.js +229 -176
- package/dist/util/http/typedef.d.ts +184 -0
- package/dist/util/http/typedef.js +93 -0
- package/dist/widgets/game-box/GameBox.svelte +2 -2
- package/package.json +1 -1
@@ -0,0 +1,45 @@
|
|
1
|
+
/**
|
2
|
+
* @typedef {Object} CacheEntry
|
3
|
+
* @property {Response} response The cached response
|
4
|
+
* @property {Object} metadata Cache metadata including expiration
|
5
|
+
* @property {string} url The request URL
|
6
|
+
* @property {number} timestamp When the entry was cached
|
7
|
+
* @property {number|null} expires When the entry expires
|
8
|
+
* @property {string|null} etag ETag for conditional requests
|
9
|
+
* @property {string|null} lastModified Last-Modified for conditional requests
|
10
|
+
*/
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Cache Storage Interface
|
14
|
+
* @interface
|
15
|
+
*/
|
16
|
+
class CacheStorage {
|
17
|
+
/**
|
18
|
+
* Get a cached response for a key
|
19
|
+
* @param {string} key Cache key
|
20
|
+
* @returns {Promise<CacheEntry|null>} Cached response or null if not found
|
21
|
+
*/
|
22
|
+
async get(key) { throw new Error('Not implemented'); }
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Store a response in the cache
|
26
|
+
* @param {string} key Cache key
|
27
|
+
* @param {Response} response Response to cache
|
28
|
+
* @param {Object} metadata Cache metadata
|
29
|
+
* @returns {Promise<void>}
|
30
|
+
*/
|
31
|
+
async set(key, response, metadata) { throw new Error('Not implemented'); }
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Remove a cached response
|
35
|
+
* @param {string} key Cache key
|
36
|
+
* @returns {Promise<boolean>} True if entry was removed
|
37
|
+
*/
|
38
|
+
async delete(key) { throw new Error('Not implemented'); }
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Clear all cached responses
|
42
|
+
* @returns {Promise<void>}
|
43
|
+
*/
|
44
|
+
async clear() { throw new Error('Not implemented'); }
|
45
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
/**
|
2
|
+
* ReponseCache
|
3
|
+
* Using in-memory storage
|
4
|
+
*/
|
5
|
+
export default class MemoryResponseCache {
|
6
|
+
cache: Map<any, any>;
|
7
|
+
get(key: any): Promise<{
|
8
|
+
response: any;
|
9
|
+
metadata: any;
|
10
|
+
url: any;
|
11
|
+
timestamp: any;
|
12
|
+
expires: any;
|
13
|
+
etag: any;
|
14
|
+
lastModified: any;
|
15
|
+
}>;
|
16
|
+
set(key: any, response: any, metadata: any): Promise<void>;
|
17
|
+
delete(key: any): Promise<boolean>;
|
18
|
+
clear(): Promise<void>;
|
19
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/**
|
2
|
+
* ReponseCache
|
3
|
+
* Using in-memory storage
|
4
|
+
*/
|
5
|
+
export default class MemoryResponseCache {
|
6
|
+
constructor() {
|
7
|
+
this.cache = new Map();
|
8
|
+
}
|
9
|
+
|
10
|
+
async get(key) {
|
11
|
+
const entry = this.cache.get(key);
|
12
|
+
|
13
|
+
if (!entry) {
|
14
|
+
return null;
|
15
|
+
}
|
16
|
+
|
17
|
+
// Check if expired
|
18
|
+
if (entry.expires && Date.now() > entry.expires) {
|
19
|
+
this.delete(key);
|
20
|
+
return null;
|
21
|
+
}
|
22
|
+
|
23
|
+
return {
|
24
|
+
response: entry.response.clone(),
|
25
|
+
metadata: entry.metadata,
|
26
|
+
url: entry.url,
|
27
|
+
timestamp: entry.timestamp,
|
28
|
+
expires: entry.expires,
|
29
|
+
etag: entry.etag,
|
30
|
+
lastModified: entry.lastModified
|
31
|
+
};
|
32
|
+
}
|
33
|
+
|
34
|
+
async set(key, response, metadata) {
|
35
|
+
this.cache.set(key, {
|
36
|
+
response: response.clone(),
|
37
|
+
metadata,
|
38
|
+
url: response.url,
|
39
|
+
timestamp: Date.now(),
|
40
|
+
expires: metadata.expires || null,
|
41
|
+
etag: response.headers.get('ETag'),
|
42
|
+
lastModified: response.headers.get('Last-Modified')
|
43
|
+
});
|
44
|
+
}
|
45
|
+
|
46
|
+
async delete(key) {
|
47
|
+
return this.cache.delete(key);
|
48
|
+
}
|
49
|
+
|
50
|
+
async clear() {
|
51
|
+
this.cache.clear();
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
/**
|
2
|
+
* ReponseCache
|
3
|
+
* Using IndexedDB for persistent storage
|
4
|
+
*/
|
5
|
+
export default class ResponseCache {
|
6
|
+
/**
|
7
|
+
* Create a new IndexedDB cache storage
|
8
|
+
* @param {string} dbName Database name
|
9
|
+
* @param {string} storeName Store name
|
10
|
+
*/
|
11
|
+
constructor(dbName?: string, storeName?: string);
|
12
|
+
dbName: string;
|
13
|
+
storeName: string;
|
14
|
+
dbPromise: Promise<IDBDatabase>;
|
15
|
+
/**
|
16
|
+
* Open the IndexedDB database
|
17
|
+
* @private
|
18
|
+
* @returns {Promise<IDBDatabase>}
|
19
|
+
*/
|
20
|
+
private _openDatabase;
|
21
|
+
/**
|
22
|
+
* Get a cached response
|
23
|
+
* @param {string} key Cache key
|
24
|
+
* @returns {Promise<import('./typedef').CacheEntry|null>}
|
25
|
+
*/
|
26
|
+
get(key: string): Promise<import("./typedef").CacheEntry | null>;
|
27
|
+
/**
|
28
|
+
* Store a response in the cache
|
29
|
+
* @param {string} key Cache key
|
30
|
+
* @param {Response} response Response to cache
|
31
|
+
* @param {Object} metadata Cache metadata
|
32
|
+
* @returns {Promise<void>}
|
33
|
+
*/
|
34
|
+
set(key: string, response: Response, metadata: any): Promise<void>;
|
35
|
+
/**
|
36
|
+
* Delete a cached response
|
37
|
+
* @param {string} key Cache key
|
38
|
+
* @returns {Promise<boolean>}
|
39
|
+
*/
|
40
|
+
delete(key: string): Promise<boolean>;
|
41
|
+
/**
|
42
|
+
* Clear all cached responses
|
43
|
+
* @returns {Promise<void>}
|
44
|
+
*/
|
45
|
+
clear(): Promise<void>;
|
46
|
+
}
|
@@ -0,0 +1,165 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
/**
|
4
|
+
* ReponseCache
|
5
|
+
* Using IndexedDB for persistent storage
|
6
|
+
*/
|
7
|
+
export default class ResponseCache {
|
8
|
+
/**
|
9
|
+
* Create a new IndexedDB cache storage
|
10
|
+
* @param {string} dbName Database name
|
11
|
+
* @param {string} storeName Store name
|
12
|
+
*/
|
13
|
+
constructor(dbName = 'http-cache', storeName = 'responses') {
|
14
|
+
this.dbName = dbName;
|
15
|
+
this.storeName = storeName;
|
16
|
+
this.dbPromise = this._openDatabase();
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Open the IndexedDB database
|
21
|
+
* @private
|
22
|
+
* @returns {Promise<IDBDatabase>}
|
23
|
+
*/
|
24
|
+
async _openDatabase() {
|
25
|
+
return new Promise((resolve, reject) => {
|
26
|
+
const request = indexedDB.open(this.dbName, 1);
|
27
|
+
|
28
|
+
request.onerror = () => reject(request.error);
|
29
|
+
request.onsuccess = () => resolve(request.result);
|
30
|
+
|
31
|
+
request.onupgradeneeded = (event) => {
|
32
|
+
const db = event.target.result;
|
33
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
34
|
+
db.createObjectStore(this.storeName, { keyPath: 'key' });
|
35
|
+
}
|
36
|
+
};
|
37
|
+
});
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Get a cached response
|
42
|
+
* @param {string} key Cache key
|
43
|
+
* @returns {Promise<import('./typedef').CacheEntry|null>}
|
44
|
+
*/
|
45
|
+
async get(key) {
|
46
|
+
const db = await this.dbPromise;
|
47
|
+
|
48
|
+
return new Promise((resolve, reject) => {
|
49
|
+
const transaction = db.transaction(this.storeName, 'readonly');
|
50
|
+
const store = transaction.objectStore(this.storeName);
|
51
|
+
const request = store.get(key);
|
52
|
+
|
53
|
+
request.onerror = () => reject(request.error);
|
54
|
+
request.onsuccess = () => {
|
55
|
+
const entry = request.result;
|
56
|
+
|
57
|
+
if (!entry) {
|
58
|
+
resolve(null);
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
|
62
|
+
// Check if expired
|
63
|
+
if (entry.expires && Date.now() > entry.expires) {
|
64
|
+
// Delete expired entry
|
65
|
+
this.delete(key).catch(console.error);
|
66
|
+
resolve(null);
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
|
70
|
+
// Deserialize the response
|
71
|
+
const response = new Response(entry.body, {
|
72
|
+
status: entry.status,
|
73
|
+
statusText: entry.statusText,
|
74
|
+
headers: new Headers(entry.headers)
|
75
|
+
});
|
76
|
+
|
77
|
+
resolve({
|
78
|
+
response,
|
79
|
+
metadata: entry.metadata,
|
80
|
+
url: entry.url,
|
81
|
+
timestamp: entry.timestamp,
|
82
|
+
expires: entry.expires,
|
83
|
+
etag: entry.etag,
|
84
|
+
lastModified: entry.lastModified
|
85
|
+
});
|
86
|
+
};
|
87
|
+
});
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Store a response in the cache
|
92
|
+
* @param {string} key Cache key
|
93
|
+
* @param {Response} response Response to cache
|
94
|
+
* @param {Object} metadata Cache metadata
|
95
|
+
* @returns {Promise<void>}
|
96
|
+
*/
|
97
|
+
async set(key, response, metadata) {
|
98
|
+
const db = await this.dbPromise;
|
99
|
+
|
100
|
+
// Clone the response to avoid consuming it
|
101
|
+
const clonedResponse = response.clone();
|
102
|
+
|
103
|
+
// Extract response data
|
104
|
+
const body = await clonedResponse.blob();
|
105
|
+
const headers = Array.from(clonedResponse.headers.entries());
|
106
|
+
|
107
|
+
const entry = {
|
108
|
+
key,
|
109
|
+
url: clonedResponse.url,
|
110
|
+
status: clonedResponse.status,
|
111
|
+
statusText: clonedResponse.statusText,
|
112
|
+
headers,
|
113
|
+
body,
|
114
|
+
metadata,
|
115
|
+
timestamp: Date.now(),
|
116
|
+
expires: metadata.expires || null,
|
117
|
+
etag: clonedResponse.headers.get('ETag'),
|
118
|
+
lastModified: clonedResponse.headers.get('Last-Modified')
|
119
|
+
};
|
120
|
+
|
121
|
+
return new Promise((resolve, reject) => {
|
122
|
+
const transaction = db.transaction(this.storeName, 'readwrite');
|
123
|
+
const store = transaction.objectStore(this.storeName);
|
124
|
+
const request = store.put(entry);
|
125
|
+
|
126
|
+
request.onerror = () => reject(request.error);
|
127
|
+
request.onsuccess = () => resolve();
|
128
|
+
});
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Delete a cached response
|
133
|
+
* @param {string} key Cache key
|
134
|
+
* @returns {Promise<boolean>}
|
135
|
+
*/
|
136
|
+
async delete(key) {
|
137
|
+
const db = await this.dbPromise;
|
138
|
+
|
139
|
+
return new Promise((resolve, reject) => {
|
140
|
+
const transaction = db.transaction(this.storeName, 'readwrite');
|
141
|
+
const store = transaction.objectStore(this.storeName);
|
142
|
+
const request = store.delete(key);
|
143
|
+
|
144
|
+
request.onerror = () => reject(request.error);
|
145
|
+
request.onsuccess = () => resolve(true);
|
146
|
+
});
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Clear all cached responses
|
151
|
+
* @returns {Promise<void>}
|
152
|
+
*/
|
153
|
+
async clear() {
|
154
|
+
const db = await this.dbPromise;
|
155
|
+
|
156
|
+
return new Promise((resolve, reject) => {
|
157
|
+
const transaction = db.transaction(this.storeName, 'readwrite');
|
158
|
+
const store = transaction.objectStore(this.storeName);
|
159
|
+
const request = store.clear();
|
160
|
+
|
161
|
+
request.onerror = () => reject(request.error);
|
162
|
+
request.onsuccess = () => resolve();
|
163
|
+
});
|
164
|
+
}
|
165
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
declare const _default: {};
|
2
|
+
export default _default;
|
3
|
+
export type CacheEntry = {
|
4
|
+
/**
|
5
|
+
* The cached response
|
6
|
+
*/
|
7
|
+
response: Response;
|
8
|
+
/**
|
9
|
+
* Cache metadata including expiration
|
10
|
+
*/
|
11
|
+
metadata: any;
|
12
|
+
/**
|
13
|
+
* The request URL
|
14
|
+
*/
|
15
|
+
url: string;
|
16
|
+
/**
|
17
|
+
* When the entry was cached
|
18
|
+
*/
|
19
|
+
timestamp: number;
|
20
|
+
/**
|
21
|
+
* When the entry expires
|
22
|
+
*/
|
23
|
+
expires: number | null;
|
24
|
+
/**
|
25
|
+
* ETag for conditional requests
|
26
|
+
*/
|
27
|
+
etag: string | null;
|
28
|
+
/**
|
29
|
+
* Last-Modified for conditional requests
|
30
|
+
*/
|
31
|
+
lastModified: string | null;
|
32
|
+
};
|
33
|
+
export type CacheStorage = {
|
34
|
+
/**
|
35
|
+
* Get a cached response for a key
|
36
|
+
*/
|
37
|
+
get: (arg0: string) => Promise<CacheEntry | null>;
|
38
|
+
/**
|
39
|
+
* Store a response in the cache
|
40
|
+
*/
|
41
|
+
set: (arg0: string, arg1: Response, arg2: any) => Promise<void>;
|
42
|
+
/**
|
43
|
+
* Remove a cached response
|
44
|
+
*/
|
45
|
+
delete: (arg0: string) => Promise<boolean>;
|
46
|
+
/**
|
47
|
+
* Clear all cached responses
|
48
|
+
*/
|
49
|
+
clear: () => Promise<void>;
|
50
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
/**
|
3
|
+
* @typedef {Object} CacheEntry
|
4
|
+
* @property {Response} response The cached response
|
5
|
+
* @property {Object} metadata Cache metadata including expiration
|
6
|
+
* @property {string} url The request URL
|
7
|
+
* @property {number} timestamp When the entry was cached
|
8
|
+
* @property {number|null} expires When the entry expires
|
9
|
+
* @property {string|null} etag ETag for conditional requests
|
10
|
+
* @property {string|null} lastModified Last-Modified for conditional requests
|
11
|
+
*/
|
12
|
+
|
13
|
+
/**
|
14
|
+
* @typedef {Object} CacheStorage
|
15
|
+
* @property {function(string): Promise<CacheEntry|null>} get
|
16
|
+
* Get a cached response for a key
|
17
|
+
* @property {function(string, Response, Object): Promise<void>} set
|
18
|
+
* Store a response in the cache
|
19
|
+
* @property {function(string): Promise<boolean>} delete
|
20
|
+
* Remove a cached response
|
21
|
+
* @property {function(): Promise<void>} clear
|
22
|
+
* Clear all cached responses
|
23
|
+
*/
|
24
|
+
|
25
|
+
export default {};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
/**
|
2
|
+
* Store a response in the cache
|
3
|
+
*
|
4
|
+
* @param {object} cacheKeyParams - Parameters to use for cache key generation
|
5
|
+
* @param {string|URL} cacheKeyParams.url - URL string or URL object
|
6
|
+
* @param {object} [cacheKeyParams.headers] - Request headers that affect caching
|
7
|
+
* @param {Response} response - Response to cache
|
8
|
+
* @param {object} metadata - Cache metadata
|
9
|
+
* @returns {Promise<void>}
|
10
|
+
*/
|
11
|
+
export function storeResponseInCache(cacheKeyParams: {
|
12
|
+
url: string | URL;
|
13
|
+
headers?: object;
|
14
|
+
}, response: Response, metadata: object): Promise<void>;
|
15
|
+
/**
|
16
|
+
* Get a cached response if available and valid
|
17
|
+
*
|
18
|
+
* @param {object} cacheKeyParams - Parameters to use for cache key generation
|
19
|
+
* @param {string|URL} cacheKeyParams.url - URL string or URL object
|
20
|
+
* @param {object} [cacheKeyParams.headers] - Request headers that affect caching
|
21
|
+
*
|
22
|
+
* @returns {Promise<Response|import('./typedef').ResponseWithStale|null>}
|
23
|
+
* Cached response or null
|
24
|
+
*/
|
25
|
+
export function getCachedResponse(cacheKeyParams: {
|
26
|
+
url: string | URL;
|
27
|
+
headers?: object;
|
28
|
+
}): Promise<Response | import("./typedef").ResponseWithStale | null>;
|
@@ -0,0 +1,251 @@
|
|
1
|
+
import {
|
2
|
+
MemoryResponseCache,
|
3
|
+
PersistentResponseCache
|
4
|
+
} from '../../classes/cache';
|
5
|
+
|
6
|
+
let defaultCacheStorage = null;
|
7
|
+
|
8
|
+
function getCacheStorage()
|
9
|
+
{
|
10
|
+
if( !defaultCacheStorage )
|
11
|
+
{
|
12
|
+
defaultCacheStorage =
|
13
|
+
createCacheStorage(
|
14
|
+
process.env.NODE_ENV === 'test' ? 'memory' : 'persistent'
|
15
|
+
);
|
16
|
+
}
|
17
|
+
|
18
|
+
return defaultCacheStorage
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Store a response in the cache
|
23
|
+
*
|
24
|
+
* @param {object} cacheKeyParams - Parameters to use for cache key generation
|
25
|
+
* @param {string|URL} cacheKeyParams.url - URL string or URL object
|
26
|
+
* @param {object} [cacheKeyParams.headers] - Request headers that affect caching
|
27
|
+
* @param {Response} response - Response to cache
|
28
|
+
* @param {object} metadata - Cache metadata
|
29
|
+
* @returns {Promise<void>}
|
30
|
+
*/
|
31
|
+
export async function storeResponseInCache(cacheKeyParams, response, metadata) {
|
32
|
+
try {
|
33
|
+
const { url: rawUrl, ...headers } = cacheKeyParams;
|
34
|
+
const url = typeof rawUrl === 'string' ? rawUrl : rawUrl.toString();
|
35
|
+
|
36
|
+
// Generate cache key
|
37
|
+
const cacheKey = generateCacheKey(url, headers);
|
38
|
+
|
39
|
+
// Extract Vary header info
|
40
|
+
const varyHeader = response.headers.get('Vary');
|
41
|
+
let varyHeaders = null;
|
42
|
+
|
43
|
+
if (varyHeader) {
|
44
|
+
varyHeaders = {};
|
45
|
+
const varyFields = varyHeader.split(',').map(field => field.trim().toLowerCase());
|
46
|
+
|
47
|
+
// Store hashes of headers mentioned in Vary
|
48
|
+
for (const field of varyFields) {
|
49
|
+
if (field !== '*') {
|
50
|
+
const value = headers[field];
|
51
|
+
varyHeaders[field] = hashValue(value);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
// Add vary headers to metadata
|
57
|
+
const enhancedMetadata = {
|
58
|
+
...metadata,
|
59
|
+
varyHeaders
|
60
|
+
};
|
61
|
+
|
62
|
+
// Store in cache
|
63
|
+
await getCacheStorage().set(cacheKey, response, enhancedMetadata);
|
64
|
+
} catch (error) {
|
65
|
+
console.error('Cache storage error:', error);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Get a cached response if available and valid
|
71
|
+
*
|
72
|
+
* @param {object} cacheKeyParams - Parameters to use for cache key generation
|
73
|
+
* @param {string|URL} cacheKeyParams.url - URL string or URL object
|
74
|
+
* @param {object} [cacheKeyParams.headers] - Request headers that affect caching
|
75
|
+
*
|
76
|
+
* @returns {Promise<Response|import('./typedef').ResponseWithStale|null>}
|
77
|
+
* Cached response or null
|
78
|
+
*/
|
79
|
+
export async function getCachedResponse(cacheKeyParams) {
|
80
|
+
try {
|
81
|
+
const { url: rawUrl, ...headers } = cacheKeyParams;
|
82
|
+
const url = typeof rawUrl === 'string' ? rawUrl : rawUrl.toString();
|
83
|
+
|
84
|
+
// Generate cache key
|
85
|
+
const cacheKey = generateCacheKey(url, headers);
|
86
|
+
|
87
|
+
// Get cached entry
|
88
|
+
const cachedEntry = await getCacheStorage().get(cacheKey);
|
89
|
+
|
90
|
+
if (!cachedEntry) {
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
|
94
|
+
// Validate based on Vary headers
|
95
|
+
if (!isValidForVaryHeaders(cachedEntry, headers)) {
|
96
|
+
return null;
|
97
|
+
}
|
98
|
+
|
99
|
+
const { response, metadata } = cachedEntry;
|
100
|
+
|
101
|
+
// Check if expired
|
102
|
+
const isExpired = metadata.expires && Date.now() > metadata.expires;
|
103
|
+
|
104
|
+
if (!isExpired) {
|
105
|
+
// Not expired, return as-is
|
106
|
+
return response;
|
107
|
+
}
|
108
|
+
|
109
|
+
// Response is stale, add stale info
|
110
|
+
return enhanceResponseWithStale(response, {
|
111
|
+
isStale: true,
|
112
|
+
fresh: null, // No fresh data available yet
|
113
|
+
timestamp: metadata.timestamp,
|
114
|
+
expires: metadata.expires
|
115
|
+
});
|
116
|
+
} catch (error) {
|
117
|
+
console.error('Cache retrieval error:', error);
|
118
|
+
return null;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
// > Internal functions
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Create a cache storage adapter
|
126
|
+
* @param {string} type Type of storage ('persistent', 'memory')
|
127
|
+
* @param {Object} options Options for the storage adapter
|
128
|
+
*
|
129
|
+
* @returns {import('../../classes/cache').CacheStorage}
|
130
|
+
*/
|
131
|
+
function createCacheStorage(type = 'persistent', options = {}) {
|
132
|
+
switch (type) {
|
133
|
+
case 'persistent':
|
134
|
+
return new PersistentResponseCache(
|
135
|
+
options.dbName || 'http-cache',
|
136
|
+
options.storeName || 'responses'
|
137
|
+
);
|
138
|
+
|
139
|
+
case 'memory':
|
140
|
+
return new MemoryResponseCache();
|
141
|
+
|
142
|
+
default:
|
143
|
+
throw new Error(`Unsupported cache storage type: ${type}`);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Check if cached entry is valid based on Vary headers
|
149
|
+
*
|
150
|
+
* @param {object} cachedEntry - Cached entry with response and metadata
|
151
|
+
* @param {object} headers - Current request headers
|
152
|
+
* @returns {boolean} True if valid for these headers
|
153
|
+
*/
|
154
|
+
function isValidForVaryHeaders(cachedEntry, headers) {
|
155
|
+
const { metadata } = cachedEntry;
|
156
|
+
|
157
|
+
// If no vary headers stored, consider valid
|
158
|
+
if (!metadata.varyHeaders) {
|
159
|
+
return true;
|
160
|
+
}
|
161
|
+
|
162
|
+
// Check each stored vary header
|
163
|
+
for (const [name, storedHash] of Object.entries(metadata.varyHeaders)) {
|
164
|
+
const currentValue = headers[name];
|
165
|
+
const currentHash = hashValue(currentValue);
|
166
|
+
|
167
|
+
// Compare hashes
|
168
|
+
if (currentHash !== storedHash) {
|
169
|
+
return false;
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
return true;
|
174
|
+
}
|
175
|
+
|
176
|
+
/**
|
177
|
+
* Generate a cache key based on URL and relevant headers
|
178
|
+
*
|
179
|
+
* @param {string} url - URL string
|
180
|
+
* @param {object} headers - Request headers
|
181
|
+
* @returns {string} Cache key
|
182
|
+
*/
|
183
|
+
function generateCacheKey(url, headers) {
|
184
|
+
// Start with a hash of the URL
|
185
|
+
const keyParts = [hashValue(url)];
|
186
|
+
|
187
|
+
// If we have headers
|
188
|
+
if (headers && Object.keys(headers).length > 0) {
|
189
|
+
// Get all header names, sorted for consistency
|
190
|
+
const headerNames = Object.keys(headers).sort();
|
191
|
+
|
192
|
+
// Create a hash for each header value
|
193
|
+
const headerHashes = headerNames.map(name => {
|
194
|
+
const value = headers[name];
|
195
|
+
// Format: "name:hash_of_value"
|
196
|
+
return `${name}:${hashValue(value)}`;
|
197
|
+
});
|
198
|
+
|
199
|
+
if (headerHashes.length > 0) {
|
200
|
+
keyParts.push(headerHashes.join('|'));
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
return keyParts.join('::');
|
205
|
+
}
|
206
|
+
|
207
|
+
/**
|
208
|
+
* Create a hash of a value
|
209
|
+
*
|
210
|
+
* @param {string} value - Value to hash
|
211
|
+
* @returns {string} Hash representation
|
212
|
+
*/
|
213
|
+
function hashValue(value) {
|
214
|
+
if (!value) return '';
|
215
|
+
|
216
|
+
// Convert to string if not already
|
217
|
+
const stringValue = String(value);
|
218
|
+
|
219
|
+
// Simple non-cryptographic hash function
|
220
|
+
let hash = 0;
|
221
|
+
for (let i = 0; i < stringValue.length; i++) {
|
222
|
+
const char = stringValue.charCodeAt(i);
|
223
|
+
hash = ((hash << 5) - hash) + char;
|
224
|
+
hash = hash & hash; // Convert to 32bit integer
|
225
|
+
}
|
226
|
+
return hash.toString(16); // Convert to hex
|
227
|
+
}
|
228
|
+
|
229
|
+
|
230
|
+
/**
|
231
|
+
* Enhance a Response object with stale data properties
|
232
|
+
*
|
233
|
+
* @param {Response} response - The original Response object
|
234
|
+
* @param {import('./typedef').StaleInfo} staleInfo - Stale data information
|
235
|
+
*
|
236
|
+
* @returns {import('./typedef').ResponseWithStale}
|
237
|
+
* Enhanced response with stale data properties
|
238
|
+
*/
|
239
|
+
function enhanceResponseWithStale(response, staleInfo) {
|
240
|
+
// Create a new object that inherits from Response prototype
|
241
|
+
const enhanced = Object.create(
|
242
|
+
Object.getPrototypeOf(response),
|
243
|
+
Object.getOwnPropertyDescriptors(response)
|
244
|
+
);
|
245
|
+
|
246
|
+
// Add stale properties
|
247
|
+
enhanced.isStale = staleInfo.isStale || false;
|
248
|
+
enhanced.fresh = staleInfo.fresh || null;
|
249
|
+
|
250
|
+
return enhanced;
|
251
|
+
}
|