@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.
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- 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
|
+
```
|