@jgamaraalv/ts-dev-kit 1.0.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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -0
  5. package/README.md +128 -0
  6. package/agents/accessibility-pro.md +139 -0
  7. package/agents/api-builder.md +110 -0
  8. package/agents/code-reviewer.md +190 -0
  9. package/agents/database-expert.md +138 -0
  10. package/agents/debugger.md +241 -0
  11. package/agents/docker-expert.md +51 -0
  12. package/agents/multi-agent-coordinator.md +378 -0
  13. package/agents/nextjs-expert.md +136 -0
  14. package/agents/performance-engineer.md +138 -0
  15. package/agents/playwright-expert.md +126 -0
  16. package/agents/react-specialist.md +97 -0
  17. package/agents/security-scanner.md +105 -0
  18. package/agents/test-generator.md +221 -0
  19. package/agents/typescript-pro.md +253 -0
  20. package/agents/ux-optimizer.md +93 -0
  21. package/docs/rules/orchestration.md.template +126 -0
  22. package/package.json +28 -0
  23. package/skills/bullmq/SKILL.md +225 -0
  24. package/skills/bullmq/references/flows-and-schedulers.md +186 -0
  25. package/skills/bullmq/references/job-types-and-options.md +163 -0
  26. package/skills/bullmq/references/patterns.md +273 -0
  27. package/skills/bullmq/references/production.md +308 -0
  28. package/skills/composition-patterns/SKILL.md +58 -0
  29. package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
  30. package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
  31. package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
  32. package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
  33. package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
  34. package/skills/composition-patterns/references/state-context-interface.md +194 -0
  35. package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
  36. package/skills/composition-patterns/references/state-lift-state.md +126 -0
  37. package/skills/conventional-commits/SKILL.md +148 -0
  38. package/skills/docker/SKILL.md +55 -0
  39. package/skills/docker/references/compose-configs.md +95 -0
  40. package/skills/docker/references/monorepo-dockerfile.md +111 -0
  41. package/skills/drizzle-pg/SKILL.md +202 -0
  42. package/skills/drizzle-pg/references/advanced.md +299 -0
  43. package/skills/drizzle-pg/references/migrations.md +214 -0
  44. package/skills/drizzle-pg/references/queries.md +321 -0
  45. package/skills/drizzle-pg/references/relations.md +272 -0
  46. package/skills/drizzle-pg/references/schema-pg.md +256 -0
  47. package/skills/drizzle-pg/references/sql-operator.md +215 -0
  48. package/skills/fastify-best-practices/SKILL.md +143 -0
  49. package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
  50. package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
  51. package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
  52. package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
  53. package/skills/fastify-best-practices/references/server-and-options.md +127 -0
  54. package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
  55. package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
  56. package/skills/ioredis/SKILL.md +51 -0
  57. package/skills/ioredis/references/advanced-patterns.md +312 -0
  58. package/skills/ioredis/references/cluster-sentinel.md +280 -0
  59. package/skills/ioredis/references/connection-options.md +187 -0
  60. package/skills/ioredis/references/core-api.md +179 -0
  61. package/skills/nextjs-best-practices/SKILL.md +194 -0
  62. package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
  63. package/skills/nextjs-best-practices/references/bundling.md +192 -0
  64. package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
  65. package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
  66. package/skills/nextjs-best-practices/references/directives.md +74 -0
  67. package/skills/nextjs-best-practices/references/error-handling.md +237 -0
  68. package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
  69. package/skills/nextjs-best-practices/references/font.md +175 -0
  70. package/skills/nextjs-best-practices/references/functions.md +116 -0
  71. package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
  72. package/skills/nextjs-best-practices/references/image.md +184 -0
  73. package/skills/nextjs-best-practices/references/metadata.md +305 -0
  74. package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
  75. package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
  76. package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
  77. package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
  78. package/skills/nextjs-best-practices/references/scripts.md +148 -0
  79. package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
  80. package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
  81. package/skills/owasp-security-review/SKILL.md +98 -0
  82. package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
  83. package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
  84. package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
  85. package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
  86. package/skills/owasp-security-review/references/a05-injection.md +106 -0
  87. package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
  88. package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
  89. package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
  90. package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
  91. package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
  92. package/skills/postgresql/SKILL.md +50 -0
  93. package/skills/postgresql/references/ddl-schema.md +300 -0
  94. package/skills/postgresql/references/indexes.md +257 -0
  95. package/skills/postgresql/references/jsonb.md +261 -0
  96. package/skills/postgresql/references/performance.md +291 -0
  97. package/skills/postgresql/references/psql-cli.md +153 -0
  98. package/skills/postgresql/references/queries.md +287 -0
  99. package/skills/postgresql/references/transactions.md +280 -0
  100. package/skills/react-best-practices/SKILL.md +110 -0
  101. package/skills/react-best-practices/references/advanced-patterns.md +91 -0
  102. package/skills/react-best-practices/references/async-patterns.md +233 -0
  103. package/skills/react-best-practices/references/bundle-optimization.md +201 -0
  104. package/skills/react-best-practices/references/client-patterns.md +178 -0
  105. package/skills/react-best-practices/references/js-performance.md +210 -0
  106. package/skills/react-best-practices/references/rendering-performance.md +209 -0
  107. package/skills/react-best-practices/references/rerender-optimization.md +316 -0
  108. package/skills/react-best-practices/references/server-performance.md +274 -0
  109. package/skills/service-worker/SKILL.md +195 -0
  110. package/skills/service-worker/references/api-reference.md +114 -0
  111. package/skills/service-worker/references/caching-strategies.md +202 -0
  112. package/skills/service-worker/references/push-and-sync.md +261 -0
  113. package/skills/typescript-conventions/SKILL.md +51 -0
  114. package/skills/ui-ux-guidelines/SKILL.md +105 -0
  115. package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
  116. package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
  117. package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: service-worker
3
+ description: "Service Worker API implementation guide — registration, lifecycle management, caching strategies, push notifications, and background sync. Use when: (1) creating or modifying service worker files (sw.js), (2) implementing offline-first caching (cache-first, network-first, stale-while-revalidate), (3) setting up push notifications or background sync, (4) debugging service worker registration, scope, or update issues, (5) implementing navigation preload, (6) user mentions 'service worker', 'sw.js', 'offline support', 'cache strategy', 'push notification', 'background sync', 'workbox alternative', or 'PWA caching'."
4
+ ---
5
+
6
+ # Service Worker
7
+
8
+ ## Table of Contents
9
+
10
+ - [Constraints](#constraints)
11
+ - [Lifecycle](#lifecycle)
12
+ - [Registration](#registration)
13
+ - [Install Event — Pre-cache Assets](#install-event--pre-cache-assets)
14
+ - [Activate Event — Clean Up Old Caches](#activate-event--clean-up-old-caches)
15
+ - [Fetch Event — Intercept Requests](#fetch-event--intercept-requests)
16
+ - [Navigation Preload](#navigation-preload)
17
+ - [Updating a Service Worker](#updating-a-service-worker)
18
+ - [Communicating with Pages](#communicating-with-pages)
19
+ - [Common Pitfalls](#common-pitfalls)
20
+ - [Push Notifications & Background Sync](#push-notifications--background-sync)
21
+ - [API Quick Reference](#api-quick-reference)
22
+ - [Next.js Integration](#nextjs-integration)
23
+ - [DevTools](#devtools)
24
+
25
+ ## Constraints
26
+
27
+ - HTTPS required (localhost exempt for dev)
28
+ - No DOM access — runs on separate thread
29
+ - Fully async — no synchronous XHR, no localStorage
30
+ - No dynamic `import()` — only static `import` statements
31
+ - Scope defaults to the directory containing the SW file
32
+ - `self` refers to `ServiceWorkerGlobalScope`
33
+
34
+ ## Lifecycle
35
+
36
+ ```
37
+ register() → Download → Install → [Wait] → Activate → Fetch control
38
+ ```
39
+
40
+ 1. **Register** from main thread via `navigator.serviceWorker.register()`
41
+ 2. **Install** event fires once — use to pre-cache static assets
42
+ 3. **Wait** — new SW waits until all tabs using old SW are closed (skip with `self.skipWaiting()`)
43
+ 4. **Activate** event fires — use to clean up old caches
44
+ 5. **Fetch** events start flowing — SW controls page network requests
45
+
46
+ A document must reload to be controlled (or call `clients.claim()` during activate).
47
+
48
+ ## Registration
49
+
50
+ ```js
51
+ // main.js — register from the page
52
+ if ("serviceWorker" in navigator) {
53
+ const reg = await navigator.serviceWorker.register("/sw.js", { scope: "/" });
54
+ // reg.installing | reg.waiting | reg.active
55
+ }
56
+ ```
57
+
58
+ **Scope rules:**
59
+
60
+ - SW at `/sw.js` can control `/` and all subpaths
61
+ - SW at `/app/sw.js` can only control `/app/` by default
62
+ - Broaden scope with `Service-Worker-Allowed` response header
63
+
64
+ ## Install Event — Pre-cache Assets
65
+
66
+ ```js
67
+ // sw.js
68
+ const CACHE_NAME = "v1";
69
+ const PRECACHE_URLS = ["/", "/index.html", "/style.css", "/app.js"];
70
+
71
+ self.addEventListener("install", (event) => {
72
+ event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)));
73
+ });
74
+ ```
75
+
76
+ `waitUntil(promise)` — keeps install phase alive until the promise settles. If rejected, installation fails and the SW won't activate.
77
+
78
+ ## Activate Event — Clean Up Old Caches
79
+
80
+ ```js
81
+ self.addEventListener("activate", (event) => {
82
+ event.waitUntil(
83
+ caches
84
+ .keys()
85
+ .then((keys) =>
86
+ Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
87
+ ),
88
+ );
89
+ });
90
+ ```
91
+
92
+ ## Fetch Event — Intercept Requests
93
+
94
+ ```js
95
+ self.addEventListener("fetch", (event) => {
96
+ event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
97
+ });
98
+ ```
99
+
100
+ `respondWith(promise)` — must be called synchronously (within the event handler, not in a microtask). The promise resolves to a `Response`.
101
+
102
+ For caching strategy patterns (cache-first, network-first, stale-while-revalidate), see [references/caching-strategies.md](references/caching-strategies.md).
103
+
104
+ ## Navigation Preload
105
+
106
+ Avoid the startup delay when a SW boots to handle a navigation:
107
+
108
+ ```js
109
+ self.addEventListener("activate", (event) => {
110
+ event.waitUntil(self.registration?.navigationPreload.enable());
111
+ });
112
+
113
+ self.addEventListener("fetch", (event) => {
114
+ event.respondWith(
115
+ (async () => {
116
+ const cached = await caches.match(event.request);
117
+ if (cached) return cached;
118
+
119
+ const preloaded = await event.preloadResponse;
120
+ if (preloaded) return preloaded;
121
+
122
+ return fetch(event.request);
123
+ })(),
124
+ );
125
+ });
126
+ ```
127
+
128
+ ## Updating a Service Worker
129
+
130
+ - Browser byte-compares the SW file on each navigation (or every 24h)
131
+ - New version installs in background while old version still serves
132
+ - Increment the cache name (e.g., `v1` → `v2`) in the new version
133
+ - Delete old caches in the `activate` handler
134
+ - Call `self.skipWaiting()` in `install` to activate immediately
135
+ - Call `self.clients.claim()` in `activate` to take control of open pages
136
+
137
+ ## Communicating with Pages
138
+
139
+ ```js
140
+ // Page → SW
141
+ navigator.serviceWorker.controller.postMessage({ type: "SKIP_WAITING" });
142
+
143
+ // SW → Page (via Clients API)
144
+ const clients = await self.clients.matchAll({ type: "window" });
145
+ clients.forEach((client) => client.postMessage({ type: "UPDATED" }));
146
+
147
+ // SW listens
148
+ self.addEventListener("message", (event) => {
149
+ if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
150
+ });
151
+ ```
152
+
153
+ ## Common Pitfalls
154
+
155
+ 1. **Response cloning** — `response.clone()` before both caching and returning, since body streams can only be read once
156
+ 2. **Opaque responses** — cross-origin fetches without CORS return opaque responses (status 0). `cache.add()` will refuse them. Use `cache.put()` but you can't inspect the response
157
+ 3. **waitUntil timing** — call `event.waitUntil()` synchronously within the event handler, not inside an async callback
158
+ 4. **Scope ceiling** — a SW cannot control URLs above its own directory unless `Service-Worker-Allowed` header is set
159
+ 5. **No state persistence** — the SW may terminate at any time when idle. Don't store state in global variables — use Cache API or IndexedDB
160
+
161
+ ## Push Notifications & Background Sync
162
+
163
+ For push subscription, handling push events, and background sync implementation, see [references/push-and-sync.md](references/push-and-sync.md).
164
+
165
+ ## API Quick Reference
166
+
167
+ For detailed interfaces (`Cache`, `CacheStorage`, `FetchEvent`, `Clients`, `ServiceWorkerRegistration`, `ServiceWorkerGlobalScope`), see [references/api-reference.md](references/api-reference.md).
168
+
169
+ ## Next.js Integration
170
+
171
+ In Next.js, place the service worker file in `public/sw.js`. `public/sw.js` is intentionally plain JS (not processed by Next.js build pipeline). Register it from a client component:
172
+
173
+ ```tsx
174
+ "use client";
175
+ import { useEffect } from "react";
176
+
177
+ export function ServiceWorkerRegistrar() {
178
+ useEffect(() => {
179
+ if ("serviceWorker" in navigator) {
180
+ navigator.serviceWorker.register("/sw.js");
181
+ }
182
+ }, []);
183
+ return null;
184
+ }
185
+ ```
186
+
187
+ Add to root layout. Next.js serves `public/` files at the root, so `/sw.js` scope covers `/`.
188
+
189
+ ## DevTools
190
+
191
+ - **Chrome**: `chrome://inspect/#service-workers` or Application > Service Workers
192
+ - **Firefox**: `about:debugging#/runtime/this-firefox` or Application > Service Workers
193
+ - **Edge**: `edge://inspect/#service-workers` or Application > Service Workers
194
+
195
+ Unregister, update, and inspect caches from the Application panel. Use "Update on reload" checkbox during development.
@@ -0,0 +1,114 @@
1
+ # Service Worker API Reference
2
+
3
+ ## Table of Contents
4
+
5
+ - [Cache](#cache)
6
+ - [CacheStorage (caches)](#cachestorage)
7
+ - [FetchEvent](#fetchevent)
8
+ - [Clients](#clients)
9
+ - [Client / WindowClient](#client--windowclient)
10
+
11
+ ## Cache
12
+
13
+ A single named cache. Stores `Request`/`Response` pairs.
14
+
15
+ ### Methods
16
+
17
+ | Method | Returns | Description |
18
+ | ------------------------------ | -------------------------------- | ---------------------------------- |
19
+ | `match(request, options?)` | `Promise<Response \| undefined>` | Find first matching response |
20
+ | `matchAll(request?, options?)` | `Promise<Response[]>` | Find all matching responses |
21
+ | `add(request)` | `Promise<void>` | Fetch URL and cache the response |
22
+ | `addAll(requests)` | `Promise<void>` | Fetch all URLs and cache responses |
23
+ | `put(request, response)` | `Promise<void>` | Store a request/response pair |
24
+ | `delete(request, options?)` | `Promise<boolean>` | Remove a cached entry |
25
+ | `keys(request?, options?)` | `Promise<Request[]>` | List cached requests |
26
+
27
+ **Match options:** `{ ignoreSearch, ignoreMethod, ignoreVary }`
28
+
29
+ **Notes:**
30
+
31
+ - `add()`/`addAll()` will reject for non-2xx responses
32
+ - `put()` accepts any response (including opaque)
33
+ - Always `response.clone()` before caching if you also need to return it
34
+ - Cache API ignores HTTP caching headers
35
+ - `Set-Cookie` headers are stripped from cached responses
36
+
37
+ ## CacheStorage
38
+
39
+ Accessed via `caches` (global in SW and window).
40
+
41
+ ### Methods
42
+
43
+ | Method | Returns | Description |
44
+ | -------------------------- | -------------------------------- | --------------------------- |
45
+ | `open(name)` | `Promise<Cache>` | Open/create a named cache |
46
+ | `match(request, options?)` | `Promise<Response \| undefined>` | Search across all caches |
47
+ | `has(name)` | `Promise<boolean>` | Check if named cache exists |
48
+ | `delete(name)` | `Promise<boolean>` | Delete a named cache |
49
+ | `keys()` | `Promise<string[]>` | List all cache names |
50
+
51
+ ## FetchEvent
52
+
53
+ Passed to `fetch` event handlers. Only available in SW context.
54
+
55
+ ### Properties
56
+
57
+ | Property | Type | Description |
58
+ | ------------------- | -------------------------------- | ----------------------------------------- |
59
+ | `request` | `Request` | The intercepted request |
60
+ | `clientId` | `string` | ID of the client that initiated the fetch |
61
+ | `resultingClientId` | `string` | ID of the client being navigated to |
62
+ | `replacesClientId` | `string` | ID of the client being replaced |
63
+ | `preloadResponse` | `Promise<Response \| undefined>` | Navigation preload response |
64
+ | `handled` | `Promise<void>` | Resolves when the event has been handled |
65
+
66
+ ### Methods
67
+
68
+ | Method | Description |
69
+ | ---------------------- | -------------------------------------------------------- |
70
+ | `respondWith(promise)` | Provide a custom response. Must be called synchronously. |
71
+ | `waitUntil(promise)` | Extend event lifetime (inherited from ExtendableEvent) |
72
+
73
+ ## Clients
74
+
75
+ Access controlled pages from the SW. Available via `self.clients`.
76
+
77
+ ### Methods
78
+
79
+ | Method | Returns | Description |
80
+ | -------------------- | ------------------------------- | ------------------------------------------------------------------ |
81
+ | `get(id)` | `Promise<Client \| undefined>` | Get a client by ID |
82
+ | `matchAll(options?)` | `Promise<Client[]>` | Get all matching clients. Options: `{ type, includeUncontrolled }` |
83
+ | `openWindow(url)` | `Promise<WindowClient \| null>` | Open a new window/tab |
84
+ | `claim()` | `Promise<void>` | Take control of all in-scope clients without reload |
85
+
86
+ ## Client / WindowClient
87
+
88
+ Represents a controlled page/worker.
89
+
90
+ ### Client Properties
91
+
92
+ | Property | Type | Description |
93
+ | -------- | -------- | ---------------------------------------- |
94
+ | `id` | `string` | Unique identifier |
95
+ | `type` | `string` | `"window" \| "worker" \| "sharedworker"` |
96
+ | `url` | `string` | URL of the client |
97
+
98
+ ### Client Methods
99
+
100
+ | Method | Description |
101
+ | ------------------------------ | -------------------------- |
102
+ | `postMessage(data, transfer?)` | Send message to the client |
103
+
104
+ ### WindowClient (extends Client)
105
+
106
+ | Property | Type | Description |
107
+ | ----------------- | --------- | ----------------------------- |
108
+ | `focused` | `boolean` | Whether the window is focused |
109
+ | `visibilityState` | `string` | `"visible" \| "hidden"` |
110
+
111
+ | Method | Returns | Description |
112
+ | --------------- | ------------------------------- | ---------------- |
113
+ | `focus()` | `Promise<WindowClient>` | Focus the window |
114
+ | `navigate(url)` | `Promise<WindowClient \| null>` | Navigate to URL |
@@ -0,0 +1,202 @@
1
+ # Caching Strategies
2
+
3
+ ## Table of Contents
4
+
5
+ - [Cache-First (Cache Falling Back to Network)](#cache-first)
6
+ - [Network-First (Network Falling Back to Cache)](#network-first)
7
+ - [Stale-While-Revalidate](#stale-while-revalidate)
8
+ - [Cache-Only](#cache-only)
9
+ - [Network-Only](#network-only)
10
+ - [Dynamic Caching with Fallback](#dynamic-caching-with-fallback)
11
+ - [Cache Versioning and Cleanup](#cache-versioning-and-cleanup)
12
+ - [Strategy Selection Guide](#strategy-selection-guide)
13
+
14
+ ## Cache-First
15
+
16
+ Best for: static assets (CSS, JS, images, fonts) that change infrequently.
17
+
18
+ ```js
19
+ self.addEventListener("fetch", (event) => {
20
+ event.respondWith(
21
+ caches.match(event.request).then((cached) => {
22
+ if (cached) return cached;
23
+ return fetch(event.request).then((response) => {
24
+ // Cache new responses for future use
25
+ const clone = response.clone();
26
+ caches.open("static-v1").then((cache) => cache.put(event.request, clone));
27
+ return response;
28
+ });
29
+ }),
30
+ );
31
+ });
32
+ ```
33
+
34
+ ## Network-First
35
+
36
+ Best for: API calls, frequently updated content, HTML pages.
37
+
38
+ ```js
39
+ const networkFirst = async (request, cacheName = "dynamic-v1") => {
40
+ try {
41
+ const response = await fetch(request);
42
+ const cache = await caches.open(cacheName);
43
+ cache.put(request, response.clone()); // update cache in background
44
+ return response;
45
+ } catch {
46
+ const cached = await caches.match(request);
47
+ if (cached) return cached;
48
+ return new Response("Offline", { status: 503, headers: { "Content-Type": "text/plain" } });
49
+ }
50
+ };
51
+
52
+ self.addEventListener("fetch", (event) => {
53
+ if (event.request.url.includes("/api/")) {
54
+ event.respondWith(networkFirst(event.request));
55
+ }
56
+ });
57
+ ```
58
+
59
+ ## Stale-While-Revalidate
60
+
61
+ Best for: resources where freshness matters but stale content is acceptable briefly (avatars, non-critical API data).
62
+
63
+ ```js
64
+ self.addEventListener("fetch", (event) => {
65
+ const cache = caches.open("swr-v1");
66
+ const cached = cache.then((c) => c.match(event.request));
67
+ const fetched = fetch(event.request);
68
+ const fetchedCopy = fetched.then((r) => r.clone());
69
+
70
+ // Return cached immediately if available, race with network otherwise
71
+ event.respondWith(
72
+ Promise.race([fetched.catch(() => cached), cached])
73
+ .then((r) => r || fetched)
74
+ .catch(() => new Response("Offline", { status: 503 })),
75
+ );
76
+
77
+ // Keep SW alive until the cache is updated with the fresh response
78
+ event.waitUntil(Promise.all([cache, fetchedCopy]).then(([c, r]) => c.put(event.request, r)));
79
+ });
80
+ ```
81
+
82
+ **Important:** Always use `event.waitUntil()` around the cache update promise. Without it, the SW may terminate before the background fetch completes, and the cache will never be refreshed.
83
+
84
+ ## Cache-Only
85
+
86
+ Best for: pre-cached assets during install that never change within a version.
87
+
88
+ ```js
89
+ self.addEventListener("fetch", (event) => {
90
+ event.respondWith(caches.match(event.request));
91
+ });
92
+ ```
93
+
94
+ ## Network-Only
95
+
96
+ Best for: non-GET requests, analytics pings, real-time data. Typically just don't call `respondWith()`:
97
+
98
+ ```js
99
+ self.addEventListener("fetch", (event) => {
100
+ // Non-GET → let browser handle normally
101
+ if (event.request.method !== "GET") return;
102
+ // Otherwise apply a cache strategy
103
+ event.respondWith(/* ... */);
104
+ });
105
+ ```
106
+
107
+ ## Dynamic Caching with Fallback
108
+
109
+ Complete pattern combining pre-cache, dynamic cache, and offline fallback:
110
+
111
+ ```js
112
+ const CACHE_NAME = "app-v1";
113
+ const PRECACHE = ["/", "/offline.html", "/style.css", "/app.js"];
114
+
115
+ self.addEventListener("install", (event) => {
116
+ event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE)));
117
+ });
118
+
119
+ self.addEventListener("fetch", (event) => {
120
+ if (event.request.method !== "GET") return;
121
+
122
+ event.respondWith(
123
+ (async () => {
124
+ // 1. Check cache
125
+ const cached = await caches.match(event.request);
126
+ if (cached) return cached;
127
+
128
+ // 2. Try network
129
+ try {
130
+ const response = await fetch(event.request);
131
+ // Cache successful responses
132
+ if (response.ok) {
133
+ const cache = await caches.open(CACHE_NAME);
134
+ event.waitUntil(cache.put(event.request, response.clone()));
135
+ }
136
+ return response;
137
+ } catch {
138
+ // 3. Offline fallback for navigations
139
+ if (event.request.mode === "navigate") {
140
+ return caches.match("/offline.html");
141
+ }
142
+ return new Response("Offline", { status: 503 });
143
+ }
144
+ })(),
145
+ );
146
+ });
147
+ ```
148
+
149
+ ## Cache Versioning and Cleanup
150
+
151
+ Increment the cache name on each deploy. Delete stale caches during activate:
152
+
153
+ ```js
154
+ const CACHE_VERSION = 2;
155
+ const CACHES = {
156
+ static: `static-v${CACHE_VERSION}`,
157
+ dynamic: `dynamic-v${CACHE_VERSION}`,
158
+ };
159
+
160
+ self.addEventListener("activate", (event) => {
161
+ const keep = new Set(Object.values(CACHES));
162
+ event.waitUntil(
163
+ caches
164
+ .keys()
165
+ .then((keys) => Promise.all(keys.filter((k) => !keep.has(k)).map((k) => caches.delete(k)))),
166
+ );
167
+ });
168
+ ```
169
+
170
+ ## Strategy Selection Guide
171
+
172
+ | Content type | Strategy | Why |
173
+ | ------------------------- | ----------------------- | ---------------------------------- |
174
+ | App shell (HTML, CSS, JS) | Cache-first | Instant load, update on next visit |
175
+ | API responses | Network-first | Fresh data, offline fallback |
176
+ | User avatars, thumbnails | Stale-while-revalidate | Fast display, background update |
177
+ | Fonts, icons | Cache-first | Rarely change |
178
+ | Analytics, real-time data | Network-only | Must be fresh, no offline value |
179
+ | Critical offline page | Cache-only (pre-cached) | Always available |
180
+
181
+ ### Per-route strategy pattern
182
+
183
+ ```js
184
+ self.addEventListener("fetch", (event) => {
185
+ const { request } = event;
186
+ const url = new URL(request.url);
187
+
188
+ if (request.method !== "GET") return;
189
+
190
+ if (url.origin === location.origin) {
191
+ // Same-origin: cache-first for assets, network-first for HTML
192
+ if (request.destination === "document") {
193
+ event.respondWith(networkFirst(request));
194
+ } else {
195
+ event.respondWith(cacheFirst(request));
196
+ }
197
+ } else {
198
+ // Cross-origin: stale-while-revalidate
199
+ event.respondWith(staleWhileRevalidate(request));
200
+ }
201
+ });
202
+ ```