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 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. When enabled, the library logs cache hits, misses, header usage, invalidation, and cleanup events to the console.
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
- enableLogging: true
176
+ loggingLevel: "verbose" // or "minimal" for less verbose output
173
177
  });
174
178
  ```
175
179
 
176
- When logging is enabled, you'll see console output like:
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
- | `enableLogging` | `boolean` | No | `false` | Enable logging for cache hits, misses, header usage, invalidation, and cleanup. When enabled, logs are written to the console with the `[swimple]` prefix. Useful for debugging cache behavior. |
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
- let loggingEnabled = false;
12
+ /** @type {LoggingLevel} */
13
+ let loggingLevel = "none";
13
14
 
14
15
  /**
15
- * Set whether logging is enabled
16
- * @param {boolean} enabled - Whether to enable logging
16
+ * Set the logging level
17
+ * @param {LoggingLevel} level - Logging level: "none", "minimal", or "verbose"
17
18
  */
18
- export function setLoggingEnabled(enabled) {
19
- loggingEnabled = enabled;
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
- log(`X-SW-Cache-Strategy header set: ${strategyHeader} (${url})`);
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
- log(`X-SW-Cache-TTL-Seconds header set: ${ttlHeader} (${url})`);
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
- log(`X-SW-Cache-Stale-TTL-Seconds header set: ${staleTTLHeader} (${url})`);
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
- log(`Cache entry cleaned up (maxAge): ${url}`);
300
+ logVerbose(`Cache entry cleaned up (maxAge): ${url}`);
296
301
  });
297
- log(
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 a message if logging is enabled
309
+ * Log an informational message (minimal and verbose levels)
305
310
  * @param {string} message - Message to log
306
311
  */
307
- export function log(message) {
308
- if (loggingEnabled) {
309
- console.log(`[swimple] ${message}`);
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.enableLogging !== undefined &&
349
- typeof cfg.enableLogging !== "boolean"
363
+ cfg.loggingLevel !== undefined &&
364
+ !["none", "minimal", "verbose"].includes(String(cfg.loggingLevel))
350
365
  ) {
351
- throw new Error("config.enableLogging must be a boolean if provided");
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
- setLoggingEnabled,
28
- log
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 enableLogging = config.enableLogging ?? false;
55
+ const loggingLevel = config.loggingLevel ?? "none";
55
56
 
56
57
  // Set module-level logging state
57
- setLoggingEnabled(enableLogging);
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
- log(`X-SW-Cache-Clear header set: ${url}`);
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
- log(`X-SW-Cache-Invalidate header set: ${path} (${url})`);
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
- log(`Cache hit: ${url}`);
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
- log(`Cache entry cleaned up (maxAge): ${url}`);
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
- log(`Cache miss: ${url}`);
194
+ logVerbose(`Cache miss: ${url}`);
194
195
  } else if (!isStale(cachedResponse, ttl, staleTTL)) {
195
- log(`Cache miss (stale): ${url}`);
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
- log(`Cache hit (stale, offline): ${url}`);
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
- log(`Cache entry cleaned up (maxAge): ${url}`);
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
- log(`Cache hit (offline): ${url}`);
241
+ logInfo(`Cache hit (offline): ${url}`);
241
242
  return cachedResponse;
242
243
  }
243
244
  }
244
- log(`Cache miss (offline): ${url}`);
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
- log(`Cache entry cleaned up (maxAge): ${url}`);
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
- log(`Cache hit: ${url}`);
276
+ logInfo(`Cache hit: ${url}`);
276
277
  } else {
277
- log(`Cache hit (stale): ${url}`);
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
- log(`Cache miss: ${url}`);
305
+ logVerbose(`Cache miss: ${url}`);
305
306
  } else {
306
- log(`Cache miss (too stale): ${url}`);
307
+ logVerbose(`Cache miss (too stale): ${url}`);
307
308
  }
308
309
  const networkResponse = await customFetch(request);
309
310
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swimple",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A simple service worker library for request caching",
5
5
  "type": "module",
6
6
  "main": "index.js",
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
- /** Enable logging for cache hits, misses, header usage, invalidation, and cleanup. Defaults to false. */
30
- enableLogging?: boolean;
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
  }