@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.
@@ -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,3 @@
1
+ export { default as MemoryResponseCache } from "./MemoryResponseCache.js";
2
+ export { default as PersistentResponseCache } from "./PersistentResponseCache.js";
3
+ export * from "./typedef.js";
@@ -0,0 +1,5 @@
1
+
2
+ export { default as MemoryResponseCache } from "./MemoryResponseCache.js";
3
+ export { default as PersistentResponseCache } from "./PersistentResponseCache.js";
4
+
5
+ export * from './typedef.js';
@@ -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
+ }