swimple 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/helpers.js +36 -19
- package/index.js +20 -19
- package/package.json +1 -1
- package/types.d.ts +10 -2
package/README.md
CHANGED
|
@@ -163,17 +163,28 @@ The `customFetch` function has the same signature as the native `fetch` function
|
|
|
163
163
|
|
|
164
164
|
### Example 5: Enable Logging
|
|
165
165
|
|
|
166
|
-
You can enable logging to debug cache behavior.
|
|
166
|
+
You can enable logging to debug cache behavior. The library supports three logging levels:
|
|
167
|
+
|
|
168
|
+
- `"none"` (default): No logging
|
|
169
|
+
- `"minimal"`: console.info logs cache hits and cache invalidation only
|
|
170
|
+
- `"verbose"`: console.debug logs all events including cache misses, header usage, and cleanup
|
|
167
171
|
|
|
168
172
|
```javascript
|
|
169
173
|
const handleRequest = createHandleRequest({
|
|
170
174
|
cacheName: "api-cache-v1",
|
|
171
175
|
scope: ["/api/"],
|
|
172
|
-
|
|
176
|
+
loggingLevel: "verbose" // or "minimal" for less verbose output
|
|
173
177
|
});
|
|
174
178
|
```
|
|
175
179
|
|
|
176
|
-
|
|
180
|
+
With `loggingLevel: "minimal"`, you'll see:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
[swimple] Cache hit: https://example.com/api/users
|
|
184
|
+
[swimple] Cache invalidated: https://example.com/api/users/123
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
With `loggingLevel: "verbose"`, you'll see all events:
|
|
177
188
|
|
|
178
189
|
```
|
|
179
190
|
[swimple] Cache hit: https://example.com/api/users
|
|
@@ -255,7 +266,7 @@ Creates a request handler function for your service worker fetch handler.
|
|
|
255
266
|
| `inferInvalidation` | `boolean` | No | `true` | Automatically invalidate cache on POST/PATCH/PUT/DELETE requests. |
|
|
256
267
|
| `customFetch` | `function` | No | `fetch` | Custom fetch function to use for network requests. Receives a `Request` object and must return a `Promise<Response>`. Useful for handling authentication errors (401/403) or adding custom headers to all requests. |
|
|
257
268
|
| `maxCacheAgeSeconds` | `number` | No | `7200` | Maximum age (in seconds) before cache entries are automatically cleaned up. Entries older than this age are deleted. Defaults to 7200 seconds (2 hours, which is 2x the default stale TTL). Cache entries are cleaned up reactively (when accessed) and periodically (every 100 fetches). |
|
|
258
|
-
| `
|
|
269
|
+
| `loggingLevel` | `string` | No | `"none"` | Logging level: `"none"` (no logging), `"minimal"` (cache hits and invalidation only), or `"verbose"` (all logging including misses, header usage, and cleanup). When enabled, logs are written to the console with the `[swimple]` prefix. Useful for debugging cache behavior. |
|
|
259
270
|
|
|
260
271
|
#### Returns
|
|
261
272
|
|
package/helpers.js
CHANGED
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
/// <reference lib="esnext" />
|
|
7
7
|
/// <reference lib="webworker" />
|
|
8
8
|
|
|
9
|
-
/** @import { CacheStrategy, HandleRequestConfig } from "./types" */
|
|
9
|
+
/** @import { CacheStrategy, HandleRequestConfig, LoggingLevel } from "./types" */
|
|
10
10
|
|
|
11
11
|
// Module-level logging state
|
|
12
|
-
|
|
12
|
+
/** @type {LoggingLevel} */
|
|
13
|
+
let loggingLevel = "none";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* Set
|
|
16
|
-
* @param {
|
|
16
|
+
* Set the logging level
|
|
17
|
+
* @param {LoggingLevel} level - Logging level: "none", "minimal", or "verbose"
|
|
17
18
|
*/
|
|
18
|
-
export function
|
|
19
|
-
|
|
19
|
+
export function setLoggingLevel(level) {
|
|
20
|
+
loggingLevel = level;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -165,7 +166,7 @@ export function getStrategy(headers, defaultStrategy, url = "") {
|
|
|
165
166
|
strategyHeader
|
|
166
167
|
)
|
|
167
168
|
) {
|
|
168
|
-
|
|
169
|
+
logVerbose(`X-SW-Cache-Strategy header set: ${strategyHeader} (${url})`);
|
|
169
170
|
return /** @type {CacheStrategy} */ (strategyHeader);
|
|
170
171
|
}
|
|
171
172
|
return defaultStrategy;
|
|
@@ -183,7 +184,7 @@ export function getTTL(headers, defaultTTL, url = "") {
|
|
|
183
184
|
if (ttlHeader === null) {
|
|
184
185
|
return defaultTTL > 0 ? defaultTTL : null;
|
|
185
186
|
}
|
|
186
|
-
|
|
187
|
+
logVerbose(`X-SW-Cache-TTL-Seconds header set: ${ttlHeader} (${url})`);
|
|
187
188
|
const ttl = parseInt(ttlHeader, 10);
|
|
188
189
|
if (isNaN(ttl) || ttl <= 0) {
|
|
189
190
|
return null;
|
|
@@ -203,7 +204,9 @@ export function getStaleTTL(headers, defaultStaleTTL, url = "") {
|
|
|
203
204
|
if (staleTTLHeader === null) {
|
|
204
205
|
return defaultStaleTTL > 0 ? defaultStaleTTL : null;
|
|
205
206
|
}
|
|
206
|
-
|
|
207
|
+
logVerbose(
|
|
208
|
+
`X-SW-Cache-Stale-TTL-Seconds header set: ${staleTTLHeader} (${url})`
|
|
209
|
+
);
|
|
207
210
|
const staleTTL = parseInt(staleTTLHeader, 10);
|
|
208
211
|
if (isNaN(staleTTL) || staleTTL <= 0) {
|
|
209
212
|
return null;
|
|
@@ -235,8 +238,10 @@ export function matchesScope(url, scope, defaultTTLSeconds) {
|
|
|
235
238
|
export async function invalidateCache(cacheName, urls) {
|
|
236
239
|
const cache = await caches.open(cacheName);
|
|
237
240
|
const deletePromises = urls.map(async (url) => {
|
|
238
|
-
log(`Cache invalidated: ${url}`);
|
|
239
241
|
const deleted = await cache.delete(url);
|
|
242
|
+
if (deleted) {
|
|
243
|
+
logInfo(`Cache invalidated: ${url}`);
|
|
244
|
+
}
|
|
240
245
|
return deleted;
|
|
241
246
|
});
|
|
242
247
|
await Promise.allSettled(deletePromises);
|
|
@@ -292,21 +297,31 @@ export async function cleanupOldCacheEntries(cacheName, maxAgeSeconds) {
|
|
|
292
297
|
await Promise.allSettled(cleanupPromises);
|
|
293
298
|
if (cleanedUrls.length > 0) {
|
|
294
299
|
cleanedUrls.forEach((url) => {
|
|
295
|
-
|
|
300
|
+
logVerbose(`Cache entry cleaned up (maxAge): ${url}`);
|
|
296
301
|
});
|
|
297
|
-
|
|
302
|
+
logVerbose(
|
|
298
303
|
`Cleaned up ${cleanedUrls.length} cache entr${cleanedUrls.length === 1 ? "y" : "ies"} due to maxAge`
|
|
299
304
|
);
|
|
300
305
|
}
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
/**
|
|
304
|
-
* Log
|
|
309
|
+
* Log an informational message (minimal and verbose levels)
|
|
305
310
|
* @param {string} message - Message to log
|
|
306
311
|
*/
|
|
307
|
-
export function
|
|
308
|
-
if (
|
|
309
|
-
console.
|
|
312
|
+
export function logInfo(message) {
|
|
313
|
+
if (loggingLevel === "minimal" || loggingLevel === "verbose") {
|
|
314
|
+
console.info(`[swimple] ${message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Log a verbose message (verbose level only)
|
|
320
|
+
* @param {string} message - Message to log
|
|
321
|
+
*/
|
|
322
|
+
export function logVerbose(message) {
|
|
323
|
+
if (loggingLevel === "verbose") {
|
|
324
|
+
console.debug(`[swimple] ${message}`);
|
|
310
325
|
}
|
|
311
326
|
}
|
|
312
327
|
|
|
@@ -345,9 +360,11 @@ export function validateConfig(config) {
|
|
|
345
360
|
);
|
|
346
361
|
}
|
|
347
362
|
if (
|
|
348
|
-
cfg.
|
|
349
|
-
|
|
363
|
+
cfg.loggingLevel !== undefined &&
|
|
364
|
+
!["none", "minimal", "verbose"].includes(String(cfg.loggingLevel))
|
|
350
365
|
) {
|
|
351
|
-
throw new Error(
|
|
366
|
+
throw new Error(
|
|
367
|
+
'config.loggingLevel must be one of: "none", "minimal", "verbose"'
|
|
368
|
+
);
|
|
352
369
|
}
|
|
353
370
|
}
|
package/index.js
CHANGED
|
@@ -24,8 +24,9 @@ import {
|
|
|
24
24
|
validateConfig,
|
|
25
25
|
isOlderThanMaxAge,
|
|
26
26
|
cleanupOldCacheEntries,
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
setLoggingLevel,
|
|
28
|
+
logInfo,
|
|
29
|
+
logVerbose
|
|
29
30
|
} from "./helpers.js";
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -51,10 +52,10 @@ export function createHandleRequest(config) {
|
|
|
51
52
|
const maxCacheAgeSeconds = config.maxCacheAgeSeconds ?? 7200;
|
|
52
53
|
const inferInvalidation = config.inferInvalidation ?? true;
|
|
53
54
|
const customFetch = config.customFetch || fetch;
|
|
54
|
-
const
|
|
55
|
+
const loggingLevel = config.loggingLevel ?? "none";
|
|
55
56
|
|
|
56
57
|
// Set module-level logging state
|
|
57
|
-
|
|
58
|
+
setLoggingLevel(loggingLevel);
|
|
58
59
|
|
|
59
60
|
// Track fetch counter for periodic cleanup
|
|
60
61
|
let fetchCounter = 0;
|
|
@@ -75,7 +76,7 @@ export function createHandleRequest(config) {
|
|
|
75
76
|
|
|
76
77
|
// Handle cache clearing - header presence (any value) triggers cache clear
|
|
77
78
|
if (getHeader(headers, "X-SW-Cache-Clear") !== null) {
|
|
78
|
-
|
|
79
|
+
logVerbose(`X-SW-Cache-Clear header set: ${url}`);
|
|
79
80
|
return (async () => {
|
|
80
81
|
await clearCache(cacheName);
|
|
81
82
|
return customFetch(request);
|
|
@@ -89,7 +90,7 @@ export function createHandleRequest(config) {
|
|
|
89
90
|
|
|
90
91
|
if (invalidateHeaders.length > 0) {
|
|
91
92
|
invalidateHeaders.forEach((path) => {
|
|
92
|
-
|
|
93
|
+
logVerbose(`X-SW-Cache-Invalidate header set: ${path} (${url})`);
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -177,22 +178,22 @@ export function createHandleRequest(config) {
|
|
|
177
178
|
|
|
178
179
|
if (cachedResponse) {
|
|
179
180
|
if (isFresh(cachedResponse, ttl)) {
|
|
180
|
-
|
|
181
|
+
logInfo(`Cache hit: ${url}`);
|
|
181
182
|
return cachedResponse;
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
// Reactive cleanup: delete if older than maxCacheAgeSeconds
|
|
185
186
|
if (isOlderThanMaxAge(cachedResponse, maxCacheAgeSeconds)) {
|
|
186
|
-
|
|
187
|
+
logVerbose(`Cache entry cleaned up (maxAge): ${url}`);
|
|
187
188
|
await cache.delete(request); // Fire-and-forget cleanup
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
// No fresh cache, fetch from network
|
|
192
193
|
if (!cachedResponse) {
|
|
193
|
-
|
|
194
|
+
logVerbose(`Cache miss: ${url}`);
|
|
194
195
|
} else if (!isStale(cachedResponse, ttl, staleTTL)) {
|
|
195
|
-
|
|
196
|
+
logVerbose(`Cache miss (stale): ${url}`);
|
|
196
197
|
}
|
|
197
198
|
let networkResponse;
|
|
198
199
|
try {
|
|
@@ -200,7 +201,7 @@ export function createHandleRequest(config) {
|
|
|
200
201
|
} catch (error) {
|
|
201
202
|
// Network failed, return stale cache if available
|
|
202
203
|
if (cachedResponse && isStale(cachedResponse, ttl, staleTTL)) {
|
|
203
|
-
|
|
204
|
+
logInfo(`Cache hit (stale, offline): ${url}`);
|
|
204
205
|
return cachedResponse;
|
|
205
206
|
}
|
|
206
207
|
throw error;
|
|
@@ -229,7 +230,7 @@ export function createHandleRequest(config) {
|
|
|
229
230
|
if (cachedResponse) {
|
|
230
231
|
// Reactive cleanup: delete if older than maxCacheAgeSeconds
|
|
231
232
|
if (isOlderThanMaxAge(cachedResponse, maxCacheAgeSeconds)) {
|
|
232
|
-
|
|
233
|
+
logVerbose(`Cache entry cleaned up (maxAge): ${url}`);
|
|
233
234
|
cache.delete(request); // Fire-and-forget cleanup
|
|
234
235
|
throw error;
|
|
235
236
|
}
|
|
@@ -237,11 +238,11 @@ export function createHandleRequest(config) {
|
|
|
237
238
|
isFresh(cachedResponse, ttl) ||
|
|
238
239
|
isStale(cachedResponse, ttl, staleTTL)
|
|
239
240
|
) {
|
|
240
|
-
|
|
241
|
+
logInfo(`Cache hit (offline): ${url}`);
|
|
241
242
|
return cachedResponse;
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
|
-
|
|
245
|
+
logVerbose(`Cache miss (offline): ${url}`);
|
|
245
246
|
throw error;
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -263,7 +264,7 @@ export function createHandleRequest(config) {
|
|
|
263
264
|
if (cachedResponse) {
|
|
264
265
|
// Reactive cleanup: delete if older than maxCacheAgeSeconds
|
|
265
266
|
if (isOlderThanMaxAge(cachedResponse, maxCacheAgeSeconds)) {
|
|
266
|
-
|
|
267
|
+
logVerbose(`Cache entry cleaned up (maxAge): ${url}`);
|
|
267
268
|
cache.delete(request); // Fire-and-forget cleanup
|
|
268
269
|
// Continue to fetch from network
|
|
269
270
|
} else {
|
|
@@ -272,9 +273,9 @@ export function createHandleRequest(config) {
|
|
|
272
273
|
|
|
273
274
|
if (fresh || stale) {
|
|
274
275
|
if (fresh) {
|
|
275
|
-
|
|
276
|
+
logInfo(`Cache hit: ${url}`);
|
|
276
277
|
} else {
|
|
277
|
-
|
|
278
|
+
logInfo(`Cache hit (stale): ${url}`);
|
|
278
279
|
}
|
|
279
280
|
// Return cached response immediately
|
|
280
281
|
// Update cache in background if stale
|
|
@@ -301,9 +302,9 @@ export function createHandleRequest(config) {
|
|
|
301
302
|
// because we already know if there was a cached response it won't be
|
|
302
303
|
// fresh or stale if we've reached this point
|
|
303
304
|
if (!cachedResponse) {
|
|
304
|
-
|
|
305
|
+
logVerbose(`Cache miss: ${url}`);
|
|
305
306
|
} else {
|
|
306
|
-
|
|
307
|
+
logVerbose(`Cache miss (too stale): ${url}`);
|
|
307
308
|
}
|
|
308
309
|
const networkResponse = await customFetch(request);
|
|
309
310
|
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export type CacheStrategy =
|
|
|
9
9
|
| "network-first"
|
|
10
10
|
| "stale-while-revalidate";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Logging level for cache operations.
|
|
14
|
+
* - `none`: No logging
|
|
15
|
+
* - `minimal`: Logs cache hits and cache invalidation only
|
|
16
|
+
* - `verbose`: Logs all events including cache misses, cleanup, and header usage
|
|
17
|
+
*/
|
|
18
|
+
export type LoggingLevel = "none" | "minimal" | "verbose";
|
|
19
|
+
|
|
12
20
|
export interface HandleRequestConfig {
|
|
13
21
|
/** Name of the cache, used when calling `Cache.open(cacheName)` internally. Changing this name effectively clears the previous cache entries. */
|
|
14
22
|
cacheName: string;
|
|
@@ -26,6 +34,6 @@ export interface HandleRequestConfig {
|
|
|
26
34
|
customFetch?: typeof fetch;
|
|
27
35
|
/** Maximum age (in seconds) before cache entries are automatically cleaned up. Entries older than this age are deleted. Defaults to 7200 seconds (2 hours, which is 2x the default stale TTL). Cache entries are cleaned up reactively (when accessed) and periodically (every 100 fetches). */
|
|
28
36
|
maxCacheAgeSeconds?: number;
|
|
29
|
-
/**
|
|
30
|
-
|
|
37
|
+
/** Logging level: "none" (no logging), "minimal" (cache hits and invalidation only), or "verbose" (all logging including misses, cleanup, and headers). Defaults to "none". */
|
|
38
|
+
loggingLevel?: LoggingLevel;
|
|
31
39
|
}
|