swimple 0.4.0 → 0.6.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
@@ -416,6 +416,65 @@ fetch("/api/logout", {
416
416
 
417
417
  **Note:** While this header is typically used on mutation requests (POST/PATCH/PUT/DELETE), if used on a GET request, it will still clear the cache and attempt to fetch from network. If the network fails, the error will propagate to the caller.
418
418
 
419
+ ## Header Constants
420
+
421
+ For convenience, swimple exports header name constants that you can use in your non-service-worker code (e.g., client-side JavaScript) to avoid hard-coding header names. This helps prevent typos and makes refactoring easier.
422
+
423
+ ### Importing Header Constants
424
+
425
+ #### Node.js / npm Import
426
+
427
+ ```javascript
428
+ import {
429
+ CACHE_STRATEGY_HEADER,
430
+ CACHE_TTL_HEADER,
431
+ CACHE_STALE_TTL_HEADER,
432
+ CACHE_INVALIDATE_HEADER,
433
+ CACHE_CLEAR_HEADER
434
+ } from "swimple/headers";
435
+
436
+ // Use in your fetch calls
437
+ fetch("/api/users", {
438
+ headers: {
439
+ [CACHE_STRATEGY_HEADER]: "network-first",
440
+ [CACHE_TTL_HEADER]: "600"
441
+ }
442
+ });
443
+ ```
444
+
445
+ #### CDN Import
446
+
447
+ ```javascript
448
+ import {
449
+ CACHE_STRATEGY_HEADER,
450
+ CACHE_TTL_HEADER,
451
+ CACHE_STALE_TTL_HEADER,
452
+ CACHE_INVALIDATE_HEADER,
453
+ CACHE_CLEAR_HEADER
454
+ } from "https://cdn.jsdelivr.net/npm/swimple@1.0.0/headers.js";
455
+
456
+ // Use in your fetch calls
457
+ const headers = new Headers();
458
+ headers.set(CACHE_TTL_HEADER, "300");
459
+ headers.append(CACHE_INVALIDATE_HEADER, "/api/users");
460
+ headers.append(CACHE_INVALIDATE_HEADER, "/api/posts");
461
+
462
+ fetch("/api/users/123", {
463
+ method: "PATCH",
464
+ headers
465
+ });
466
+ ```
467
+
468
+ ### Available Constants
469
+
470
+ - `CACHE_STRATEGY_HEADER` - `"X-SW-Cache-Strategy"`
471
+ - `CACHE_TTL_HEADER` - `"X-SW-Cache-TTL-Seconds"`
472
+ - `CACHE_STALE_TTL_HEADER` - `"X-SW-Cache-Stale-TTL-Seconds"`
473
+ - `CACHE_INVALIDATE_HEADER` - `"X-SW-Cache-Invalidate"`
474
+ - `CACHE_CLEAR_HEADER` - `"X-SW-Cache-Clear"`
475
+
476
+ **Note:** There is also an internal `CACHE_TIMESTAMP_HEADER` constant (`"x-sw-cache-timestamp"`), but this is used internally by the library and should not be set manually.
477
+
419
478
  ## Caching Behavior
420
479
 
421
480
  ### Automatic Caching (Default)
package/headers.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Header constants used by swimple for cache control.
3
+ * These constants can be imported in non-service-worker code to set headers on requests.
4
+ *
5
+ * @example
6
+ * // Node.js / npm import
7
+ * import { CACHE_STRATEGY_HEADER, CACHE_TTL_HEADER } from "swimple/headers";
8
+ *
9
+ * @example
10
+ * // CDN import
11
+ * import { CACHE_STRATEGY_HEADER, CACHE_TTL_HEADER } from "https://cdn.jsdelivr.net/npm/swimple@1.0.0/headers.js";
12
+ */
13
+ /**
14
+ * Header name for overriding the caching strategy for a specific request.
15
+ * Values: "cache-first", "network-first", or "stale-while-revalidate"
16
+ */
17
+ export const CACHE_STRATEGY_HEADER: "X-SW-Cache-Strategy";
18
+ /**
19
+ * Header name for setting the time-to-live (in seconds) for a cached response.
20
+ * Set to "0" to completely opt out of caching for a specific request.
21
+ */
22
+ export const CACHE_TTL_HEADER: "X-SW-Cache-TTL-Seconds";
23
+ /**
24
+ * Header name for setting the stale time-to-live (in seconds) for a cached response.
25
+ * Used by cache-first (when offline), network-first (when offline), and stale-while-revalidate strategies.
26
+ */
27
+ export const CACHE_STALE_TTL_HEADER: "X-SW-Cache-Stale-TTL-Seconds";
28
+ /**
29
+ * Header name for explicitly invalidating specific cache entries.
30
+ * Can be set multiple times to invalidate multiple paths.
31
+ */
32
+ export const CACHE_INVALIDATE_HEADER: "X-SW-Cache-Invalidate";
33
+ /**
34
+ * Header name for clearing the entire cache.
35
+ * Any value works - the header's presence triggers cache clearing.
36
+ */
37
+ export const CACHE_CLEAR_HEADER: "X-SW-Cache-Clear";
38
+ /**
39
+ * Internal header name used to store the cache timestamp in cached responses.
40
+ * This header is set automatically by the library and should not be set manually.
41
+ */
42
+ export const CACHE_TIMESTAMP_HEADER: "x-sw-cache-timestamp";
package/headers.js ADDED
@@ -0,0 +1,50 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Header constants used by swimple for cache control.
5
+ * These constants can be imported in non-service-worker code to set headers on requests.
6
+ *
7
+ * @example
8
+ * // Node.js / npm import
9
+ * import { CACHE_STRATEGY_HEADER, CACHE_TTL_HEADER } from "swimple/headers";
10
+ *
11
+ * @example
12
+ * // CDN import
13
+ * import { CACHE_STRATEGY_HEADER, CACHE_TTL_HEADER } from "https://cdn.jsdelivr.net/npm/swimple@1.0.0/headers.js";
14
+ */
15
+
16
+ /**
17
+ * Header name for overriding the caching strategy for a specific request.
18
+ * Values: "cache-first", "network-first", or "stale-while-revalidate"
19
+ */
20
+ export const CACHE_STRATEGY_HEADER = "X-SW-Cache-Strategy";
21
+
22
+ /**
23
+ * Header name for setting the time-to-live (in seconds) for a cached response.
24
+ * Set to "0" to completely opt out of caching for a specific request.
25
+ */
26
+ export const CACHE_TTL_HEADER = "X-SW-Cache-TTL-Seconds";
27
+
28
+ /**
29
+ * Header name for setting the stale time-to-live (in seconds) for a cached response.
30
+ * Used by cache-first (when offline), network-first (when offline), and stale-while-revalidate strategies.
31
+ */
32
+ export const CACHE_STALE_TTL_HEADER = "X-SW-Cache-Stale-TTL-Seconds";
33
+
34
+ /**
35
+ * Header name for explicitly invalidating specific cache entries.
36
+ * Can be set multiple times to invalidate multiple paths.
37
+ */
38
+ export const CACHE_INVALIDATE_HEADER = "X-SW-Cache-Invalidate";
39
+
40
+ /**
41
+ * Header name for clearing the entire cache.
42
+ * Any value works - the header's presence triggers cache clearing.
43
+ */
44
+ export const CACHE_CLEAR_HEADER = "X-SW-Cache-Clear";
45
+
46
+ /**
47
+ * Internal header name used to store the cache timestamp in cached responses.
48
+ * This header is set automatically by the library and should not be set manually.
49
+ */
50
+ export const CACHE_TIMESTAMP_HEADER = "x-sw-cache-timestamp";
package/helpers.d.ts ADDED
@@ -0,0 +1,134 @@
1
+ /// <reference no-default-lib="true"/>
2
+ /**
3
+ * Set the logging level
4
+ * @param {LoggingLevel} level - Logging level: "none", "minimal", or "verbose"
5
+ */
6
+ export function setLoggingLevel(level: LoggingLevel): void;
7
+ /**
8
+ * Get header value (case-insensitive)
9
+ * @param {Headers} headers
10
+ * @param {string} name
11
+ * @returns {string | null}
12
+ */
13
+ export function getHeader(headers: Headers, name: string): string | null;
14
+ /**
15
+ * Get all header values for a given header name (case-insensitive)
16
+ * Useful when a header has been set multiple times (e.g., multiple X-SW-Cache-Invalidate headers)
17
+ * When headers are set multiple times with append(), they are concatenated with ", " (comma+space)
18
+ * This function splits them back into individual values
19
+ * @param {Headers} headers
20
+ * @param {string} name - Header name to look up
21
+ * @returns {string[]} Array of all header values for the given name
22
+ */
23
+ export function getAllHeaders(headers: Headers, name: string): string[];
24
+ /**
25
+ * Get cache timestamp from response
26
+ * @param {Response} response
27
+ * @returns {number | null} Timestamp in milliseconds since epoch, or null if not found
28
+ */
29
+ export function getCacheTimestamp(response: Response): number | null;
30
+ /**
31
+ * Check if response is fresh
32
+ * @param {Response} response
33
+ * @param {number} ttl - Time-to-live in seconds
34
+ * @returns {boolean}
35
+ */
36
+ export function isFresh(response: Response, ttl: number): boolean;
37
+ /**
38
+ * Check if response is stale (but usable)
39
+ * @param {Response} response
40
+ * @param {number} ttl - Time-to-live in seconds
41
+ * @param {number | null} staleTTL - Stale time-to-live in seconds
42
+ * @returns {boolean}
43
+ */
44
+ export function isStale(response: Response, ttl: number, staleTTL: number | null): boolean;
45
+ /**
46
+ * Add timestamp to response
47
+ * @param {Response} response
48
+ * @returns {Response}
49
+ */
50
+ export function addTimestamp(response: Response): Response;
51
+ /**
52
+ * Get inferred invalidation paths
53
+ * @param {string} url
54
+ * @returns {string[]}
55
+ */
56
+ export function getInferredInvalidationPaths(url: string): string[];
57
+ /**
58
+ * Get strategy from request headers or use default
59
+ * @param {Headers} headers
60
+ * @param {CacheStrategy} defaultStrategy
61
+ * @param {string} url - Request URL for logging
62
+ * @returns {CacheStrategy}
63
+ */
64
+ export function getStrategy(headers: Headers, defaultStrategy: CacheStrategy, url?: string): CacheStrategy;
65
+ /**
66
+ * Get TTL from request headers or use default
67
+ * @param {Headers} headers
68
+ * @param {number} defaultTTL - Default TTL in seconds
69
+ * @param {string} url - Request URL for logging
70
+ * @returns {number | null} TTL in seconds, or null if caching is disabled
71
+ */
72
+ export function getTTL(headers: Headers, defaultTTL: number, url?: string): number | null;
73
+ /**
74
+ * Get stale TTL from request headers or use default
75
+ * @param {Headers} headers
76
+ * @param {number} defaultStaleTTL - Default stale TTL in seconds
77
+ * @param {string} url - Request URL for logging
78
+ * @returns {number | null} Stale TTL in seconds, or null if stale caching is disabled
79
+ */
80
+ export function getStaleTTL(headers: Headers, defaultStaleTTL: number, url?: string): number | null;
81
+ /**
82
+ * Check if URL matches scope. Returns true if scope array is empty or if the URL pathname starts with any of the scope prefixes.
83
+ * @param {string} url
84
+ * @param {string[]} scope
85
+ * @param {number} defaultTTLSeconds
86
+ * @returns {boolean}
87
+ */
88
+ export function matchesScope(url: string, scope: string[], defaultTTLSeconds: number): boolean;
89
+ /**
90
+ * Invalidate cache entries
91
+ * @param {string} cacheName
92
+ * @param {string[]} urls
93
+ * @returns {Promise<void>}
94
+ */
95
+ export function invalidateCache(cacheName: string, urls: string[]): Promise<void>;
96
+ /**
97
+ * Clear entire cache
98
+ * @param {string} cacheName
99
+ * @returns {Promise<void>}
100
+ */
101
+ export function clearCache(cacheName: string): Promise<void>;
102
+ /**
103
+ * Check if a cached response is older than the maximum age
104
+ * @param {Response} response
105
+ * @param {number} maxAgeSeconds - Maximum age in seconds
106
+ * @returns {boolean}
107
+ */
108
+ export function isOlderThanMaxAge(response: Response, maxAgeSeconds: number): boolean;
109
+ /**
110
+ * Clean up cache entries older than maxAgeSeconds
111
+ * @param {string} cacheName
112
+ * @param {number} maxAgeSeconds - Maximum age in seconds
113
+ * @returns {Promise<void>}
114
+ */
115
+ export function cleanupOldCacheEntries(cacheName: string, maxAgeSeconds: number): Promise<void>;
116
+ /**
117
+ * Log an informational message (minimal and verbose levels)
118
+ * @param {string} message - Message to log
119
+ */
120
+ export function logInfo(message: string): void;
121
+ /**
122
+ * Log a verbose message (verbose level only)
123
+ * @param {string} message - Message to log
124
+ */
125
+ export function logVerbose(message: string): void;
126
+ /**
127
+ * Validate configuration object
128
+ * @param {HandleRequestConfig} config - Configuration object to validate
129
+ * @throws {Error} If config is invalid
130
+ */
131
+ export function validateConfig(config: HandleRequestConfig): void;
132
+ import type { LoggingLevel } from "./types";
133
+ import type { CacheStrategy } from "./types";
134
+ import type { HandleRequestConfig } from "./types";
package/helpers.js CHANGED
@@ -8,6 +8,13 @@
8
8
 
9
9
  /** @import { CacheStrategy, HandleRequestConfig, LoggingLevel } from "./types" */
10
10
 
11
+ import {
12
+ CACHE_TIMESTAMP_HEADER,
13
+ CACHE_STRATEGY_HEADER,
14
+ CACHE_TTL_HEADER,
15
+ CACHE_STALE_TTL_HEADER
16
+ } from "./headers.js";
17
+
11
18
  // Module-level logging state
12
19
  /** @type {LoggingLevel} */
13
20
  let loggingLevel = "none";
@@ -71,7 +78,7 @@ export function getAllHeaders(headers, name) {
71
78
  * @returns {number | null} Timestamp in milliseconds since epoch, or null if not found
72
79
  */
73
80
  export function getCacheTimestamp(response) {
74
- const timestampHeader = getHeader(response.headers, "x-sw-cache-timestamp");
81
+ const timestampHeader = getHeader(response.headers, CACHE_TIMESTAMP_HEADER);
75
82
  if (!timestampHeader) {
76
83
  return null;
77
84
  }
@@ -120,7 +127,7 @@ export function isStale(response, ttl, staleTTL) {
120
127
  */
121
128
  export function addTimestamp(response) {
122
129
  const headers = new Headers(response.headers);
123
- headers.set("x-sw-cache-timestamp", Date.now().toString());
130
+ headers.set(CACHE_TIMESTAMP_HEADER, Date.now().toString());
124
131
  return new Response(response.body, {
125
132
  status: response.status,
126
133
  statusText: response.statusText,
@@ -159,14 +166,16 @@ export function getInferredInvalidationPaths(url) {
159
166
  * @returns {CacheStrategy}
160
167
  */
161
168
  export function getStrategy(headers, defaultStrategy, url = "") {
162
- const strategyHeader = getHeader(headers, "X-SW-Cache-Strategy");
169
+ const strategyHeader = getHeader(headers, CACHE_STRATEGY_HEADER);
163
170
  if (
164
171
  strategyHeader &&
165
172
  ["cache-first", "network-first", "stale-while-revalidate"].includes(
166
173
  strategyHeader
167
174
  )
168
175
  ) {
169
- logVerbose(`X-SW-Cache-Strategy header set: ${strategyHeader} (${url})`);
176
+ logVerbose(
177
+ `${CACHE_STRATEGY_HEADER} header set: ${strategyHeader} (${url})`
178
+ );
170
179
  return /** @type {CacheStrategy} */ (strategyHeader);
171
180
  }
172
181
  return defaultStrategy;
@@ -180,11 +189,11 @@ export function getStrategy(headers, defaultStrategy, url = "") {
180
189
  * @returns {number | null} TTL in seconds, or null if caching is disabled
181
190
  */
182
191
  export function getTTL(headers, defaultTTL, url = "") {
183
- const ttlHeader = getHeader(headers, "X-SW-Cache-TTL-Seconds");
192
+ const ttlHeader = getHeader(headers, CACHE_TTL_HEADER);
184
193
  if (ttlHeader === null) {
185
194
  return defaultTTL > 0 ? defaultTTL : null;
186
195
  }
187
- logVerbose(`X-SW-Cache-TTL-Seconds header set: ${ttlHeader} (${url})`);
196
+ logVerbose(`${CACHE_TTL_HEADER} header set: ${ttlHeader} (${url})`);
188
197
  const ttl = parseInt(ttlHeader, 10);
189
198
  if (isNaN(ttl) || ttl <= 0) {
190
199
  return null;
@@ -200,12 +209,12 @@ export function getTTL(headers, defaultTTL, url = "") {
200
209
  * @returns {number | null} Stale TTL in seconds, or null if stale caching is disabled
201
210
  */
202
211
  export function getStaleTTL(headers, defaultStaleTTL, url = "") {
203
- const staleTTLHeader = getHeader(headers, "X-SW-Cache-Stale-TTL-Seconds");
212
+ const staleTTLHeader = getHeader(headers, CACHE_STALE_TTL_HEADER);
204
213
  if (staleTTLHeader === null) {
205
214
  return defaultStaleTTL > 0 ? defaultStaleTTL : null;
206
215
  }
207
216
  logVerbose(
208
- `X-SW-Cache-Stale-TTL-Seconds header set: ${staleTTLHeader} (${url})`
217
+ `${CACHE_STALE_TTL_HEADER} header set: ${staleTTLHeader} (${url})`
209
218
  );
210
219
  const staleTTL = parseInt(staleTTLHeader, 10);
211
220
  if (isNaN(staleTTL) || staleTTL <= 0) {
package/index.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /// <reference no-default-lib="true"/>
2
+ /**
3
+ * Creates a request handler function for service worker fetch events.
4
+ * The handler implements HTTP caching with configurable strategies (cache-first, network-first, stale-while-revalidate),
5
+ * automatic cache invalidation for mutations, and periodic cache cleanup. Only handles same-origin GET requests
6
+ * that match the configured scope.
7
+ *
8
+ * @param {HandleRequestConfig} config - Configuration options including cache name, scope, default strategy, and TTL settings
9
+ * @returns {(event: FetchEvent) => Promise<Response> | null} Request handler function that can be used in service worker fetch event listeners
10
+ */
11
+ export function createHandleRequest(config: HandleRequestConfig): (event: FetchEvent) => Promise<Response> | null;
12
+ export { cleanupOldCacheEntries } from "./helpers.js";
13
+ import type { HandleRequestConfig } from "./types";
package/index.js CHANGED
@@ -28,6 +28,11 @@ import {
28
28
  logInfo,
29
29
  logVerbose
30
30
  } from "./helpers.js";
31
+ import {
32
+ CACHE_CLEAR_HEADER,
33
+ CACHE_INVALIDATE_HEADER,
34
+ CACHE_TTL_HEADER
35
+ } from "./headers.js";
31
36
 
32
37
  /**
33
38
  * Creates a request handler function for service worker fetch events.
@@ -75,8 +80,8 @@ export function createHandleRequest(config) {
75
80
  const headers = request.headers;
76
81
 
77
82
  // Handle cache clearing - header presence (any value) triggers cache clear
78
- if (getHeader(headers, "X-SW-Cache-Clear") !== null) {
79
- logVerbose(`X-SW-Cache-Clear header set: ${url}`);
83
+ if (getHeader(headers, CACHE_CLEAR_HEADER) !== null) {
84
+ logVerbose(`${CACHE_CLEAR_HEADER} header set: ${url}`);
80
85
  return (async () => {
81
86
  await clearCache(cacheName);
82
87
  return customFetch(request);
@@ -85,12 +90,12 @@ export function createHandleRequest(config) {
85
90
 
86
91
  // Handle invalidation for mutations
87
92
  // Check for explicit invalidation headers first (works even if inferInvalidation is false)
88
- const invalidateHeaders = getAllHeaders(headers, "X-SW-Cache-Invalidate");
93
+ const invalidateHeaders = getAllHeaders(headers, CACHE_INVALIDATE_HEADER);
89
94
  const isMutation = ["POST", "PATCH", "PUT", "DELETE"].includes(method);
90
95
 
91
96
  if (invalidateHeaders.length > 0) {
92
97
  invalidateHeaders.forEach((path) => {
93
- logVerbose(`X-SW-Cache-Invalidate header set: ${path} (${url})`);
98
+ logVerbose(`${CACHE_INVALIDATE_HEADER} header set: ${path} (${url})`);
94
99
  });
95
100
  }
96
101
 
@@ -153,8 +158,7 @@ export function createHandleRequest(config) {
153
158
  }
154
159
 
155
160
  // Check if request matches scope and should be cached
156
- const hasExplicitTTLHeader =
157
- getHeader(headers, "X-SW-Cache-TTL-Seconds") !== null;
161
+ const hasExplicitTTLHeader = getHeader(headers, CACHE_TTL_HEADER) !== null;
158
162
  const ttl = getTTL(headers, defaultTTLSeconds, url);
159
163
 
160
164
  // If scope doesn't match and there's no explicit TTL header, don't handle the request
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "swimple",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "A simple service worker library for request caching",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {
8
- ".": "./index.js"
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "default": "./index.js"
11
+ },
12
+ "./headers": {
13
+ "types": "./headers.d.ts",
14
+ "default": "./headers.js"
15
+ }
9
16
  },
10
17
  "scripts": {
11
18
  "test": "npm run test:unit && npm run test:e2e && npm run test:ui",
@@ -13,7 +20,8 @@
13
20
  "test:e2e": "node --test tests/**/*.e2e.test.js",
14
21
  "test:ui": "playwright test",
15
22
  "format:check": "prettier --check .",
16
- "format:fix": "prettier --write ."
23
+ "format:fix": "prettier --write .",
24
+ "prepublishOnly": "tsc --allowJs --declaration --emitDeclarationOnly --outDir ./ --skipLibCheck --target es2022 --module es2022 --moduleResolution node index.js headers.js helpers.js"
17
25
  },
18
26
  "keywords": [
19
27
  "service-worker",
@@ -27,7 +35,8 @@
27
35
  "license": "MIT",
28
36
  "devDependencies": {
29
37
  "@playwright/test": "^1.57.0",
30
- "prettier": "^3.7.4"
38
+ "prettier": "^3.7.4",
39
+ "typescript": "^5.9.3"
31
40
  },
32
41
  "engines": {
33
42
  "node": ">=24"
package/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ // NOTE: this is the only .d.ts file that is checked in, other declaration files are generated as part of the prepublishOnly script.
2
+
1
3
  /**
2
4
  * Caching strategy for handling requests.
3
5
  * - `cache-first`: Return from cache if fresh (within TTL), otherwise fetch from network immediately. Stale cache is only used when offline (network request fails). No background updates.