@swoff/cli 0.3.8 → 0.3.9
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 +47 -33
- package/dist/__tests__/assemble-sw.test.js +8 -38
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +119 -11
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +32 -15
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +2 -2
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/glob-matcher.test.js +86 -0
- package/dist/__tests__/glob-matcher.test.js.map +1 -0
- package/dist/__tests__/invalidation-tags.test.js +207 -23
- package/dist/__tests__/invalidation-tags.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +12 -12
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +144 -97
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +0 -5
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +50 -32
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/clean.js +1 -15
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +2 -2
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +7 -21
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +8 -6
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +2 -10
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +3 -3
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/loader.js +3 -6
- package/dist/lib/config/loader.js.map +1 -1
- package/dist/lib/config/validator.js +157 -88
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +2 -10
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +50 -11
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +92 -64
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +1 -14
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +6 -640
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/invalidation-tags.js +206 -19
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/manifest.js +2 -2
- package/dist/lib/generators/file-generators/manifest.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +29 -9
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/push.js +6 -7
- package/dist/lib/generators/file-generators/push.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +8 -125
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
- package/dist/lib/generators/file-generators/server-push.js +11 -1
- package/dist/lib/generators/file-generators/server-push.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-generator-build.js +8 -9
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +2 -1
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +0 -3
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +8 -18
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +14 -11
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-sync-handler.js +59 -17
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +2 -2
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +437 -136
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/message-handler.js +55 -15
- package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/sw-push.js +67 -0
- package/dist/lib/generators/sw-sections/sw-push.js.map +1 -0
- package/dist/lib/generators/sw-sections/tag-management.js +60 -20
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +5 -15
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +121 -71
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/shared/glob-matcher.js +77 -0
- package/dist/lib/shared/glob-matcher.js.map +1 -0
- package/dist/lib/utils/tty-status.js +21 -0
- package/dist/lib/utils/tty-status.js.map +1 -0
- package/docs/API.md +565 -0
- package/docs/ARCHITECTURE.md +374 -0
- package/docs/CLI.md +148 -0
- package/docs/COMPARISON.md +192 -0
- package/docs/CONFIG.md +277 -0
- package/docs/ECOSYSTEM.md +33 -0
- package/package.json +2 -1
- package/templates/hooks/useAuth.jsx +2 -2
- package/templates/hooks/useAuth.tsx +2 -2
- package/templates/hooks/useCachedFetch.jsx +3 -28
- package/templates/hooks/useCachedFetch.tsx +2 -33
- package/templates/hooks/useMutation.tsx +13 -1
- package/templates/hooks/useMutationQueue.jsx +2 -1
- package/templates/hooks/useMutationQueue.tsx +2 -1
- package/templates/hooks/useMutationState.jsx +8 -22
- package/templates/hooks/useMutationState.tsx +8 -24
- package/templates/hooks/usePushSubscription.jsx +3 -3
- package/templates/hooks/usePushSubscription.tsx +3 -3
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Offline-First Infrastructure: Library Comparison
|
|
2
|
+
|
|
3
|
+
## Libraries Compared
|
|
4
|
+
|
|
5
|
+
| Library | Category | Approach | Runtime size |
|
|
6
|
+
| ------------------- | ------------------------- | ---------------------------------- | ---------------------------------------- |
|
|
7
|
+
| **Swoff** | All-in-one generator | Config-driven code gen | 0 kB |
|
|
8
|
+
| **Workbox** | SW toolkit | Build-time + runtime modules | ~30 kB |
|
|
9
|
+
| **vite-plugin-pwa** | SW (Vite) | Vite plugin wrapping Workbox | ~30 kB |
|
|
10
|
+
| **TanStack Query** | Server state | Runtime JS | 3.8 kB gzip |
|
|
11
|
+
| **SWR** | Server state | Runtime JS | 3.3 kB gzip |
|
|
12
|
+
| **RTK Query** | Server state | Runtime JS + Redux | 2.9 kB + Redux |
|
|
13
|
+
| **Apollo Client** | GraphQL client | Runtime JS | ~32 kB gzip |
|
|
14
|
+
| **RxDB** | Offline-first DB | Runtime JS | ~40 kB gzip |
|
|
15
|
+
| **TanStack DB** | Client DB + offline-first | Runtime JS (differential dataflow) | ~6 kB gzip core (+ SQLite WASM optional) |
|
|
16
|
+
|
|
17
|
+
## Feature Matrix
|
|
18
|
+
|
|
19
|
+
### Caching & Strategies
|
|
20
|
+
|
|
21
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
22
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
23
|
+
| SW code generation | ✅ Full source | 🟡 Partial runtime | ❌ | ❌ | ❌ | ❌ |
|
|
24
|
+
| Caching strategies | ✅ 6 | ✅ 5 | ❌ | ❌ | ❌ | 🟡 via Query |
|
|
25
|
+
| Stale-while-revalidate | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ via Query |
|
|
26
|
+
| Cache-first / Network-first | ✅ both | ✅ both | ❌ | ❌ | ❌ | ❌ |
|
|
27
|
+
| Reactive strategy (staleTime) | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ via Query |
|
|
28
|
+
| Configurable mode (all/explicit) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
29
|
+
| Cache key normalization | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
30
|
+
| Ignore query params in cache key | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
31
|
+
| Precaching at install time | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
32
|
+
|
|
33
|
+
### Invalidation
|
|
34
|
+
|
|
35
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
36
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
37
|
+
| Tag-based invalidation | ✅ URL/op-name | ❌ | ✅ query-key | 🟡 custom | ❌ | ❌ query-key only |
|
|
38
|
+
| Cascading tag dependencies | ✅ client-expanded | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
39
|
+
| Glob-pattern invalidation | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
40
|
+
| Tag introspection (urls→tags, tags→urls) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
41
|
+
| Server push invalidation (SSE/WS) | ✅ built-in | ❌ | ❌ | ✅ subscriptions | ✅ replication | 🟡 via sync engine |
|
|
42
|
+
| SW confirmation before event | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
43
|
+
| De-duplicated refresh queue | ✅ batch + Map | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
44
|
+
|
|
45
|
+
### Offline & Background Sync
|
|
46
|
+
|
|
47
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
48
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
49
|
+
| Offline write queue | ✅ IndexedDB | 🟡 basic | ❌ | ❌ | ✅ | ✅ optimistic + offline |
|
|
50
|
+
| Configurable retry+backoff | ✅ | 🟡 | ❌ | ❌ | ❌ | 🟡 via Query |
|
|
51
|
+
| Background sync (post-tab-close) | ✅ SW via sync API | ✅ plugin | ❌ | ❌ | ❌ | ✅ offline tx |
|
|
52
|
+
| Per-mutation online check | ✅ before each | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
53
|
+
| Manual flush (after re-login) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
54
|
+
| Mutation progress tracking | ✅ per-item + batch | ❌ | ❌ | ❌ | ❌ | 🟡 per-item |
|
|
55
|
+
|
|
56
|
+
### Service Worker
|
|
57
|
+
|
|
58
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
59
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
60
|
+
| Generated auditable SW code | ✅ full source | 🟡 loader module | ❌ | ❌ | ❌ | ❌ |
|
|
61
|
+
| 3-tier config resolution | ✅ per-request → pattern → global | 🟡 2-tier | ❌ | ❌ | ❌ | ❌ |
|
|
62
|
+
| SW update versioning | ✅ 3 modes | ✅ runtime | ❌ | ❌ | ❌ | ❌ |
|
|
63
|
+
| Navigation preload | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
64
|
+
| SPA fallback for navigation | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
65
|
+
| SW lifecycle coordination | ✅ client-injector | ✅ built-in | ❌ | ❌ | ❌ | ❌ |
|
|
66
|
+
|
|
67
|
+
### Auth
|
|
68
|
+
|
|
69
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
70
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
71
|
+
| Built-in auth header injection | ✅ 3 types | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
72
|
+
| Auth-aware offline queue | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
73
|
+
| Token refresh before expiry | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
74
|
+
| 401 detection → clear auth | ✅ SW + client | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
75
|
+
| Memory-only token storage | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
76
|
+
| Offline user data cache | ✅ IndexedDB | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
77
|
+
|
|
78
|
+
### GraphQL
|
|
79
|
+
|
|
80
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
81
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
82
|
+
| Body-hash GQL caching | ✅ SHA-256 | ❌ | ❌ | ✅ in-memory | ❌ | 🟡 normalized cols |
|
|
83
|
+
| GQL operation-name auto-tags | ✅ | ❌ | ❌ | 🟡 custom | ❌ | ❌ |
|
|
84
|
+
| GQL offline mutation queue | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
85
|
+
|
|
86
|
+
### Real-time & PWA
|
|
87
|
+
|
|
88
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
89
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
90
|
+
| SSE/WS connection management | ✅ built-in | ❌ | ❌ | 🟡 link | ✅ replication | 🟡 via sync engine |
|
|
91
|
+
| PWA install prompt | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
92
|
+
| Push notifications | ✅ + React hook | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
93
|
+
| Manifest generation | ✅ | 🟡 | ❌ | ❌ | ❌ | ❌ |
|
|
94
|
+
|
|
95
|
+
### Cross-tab & State
|
|
96
|
+
|
|
97
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
98
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
99
|
+
| Cross-tab invalidation sync | ✅ SW-broadcast | ❌ | 🟡 limited | ❌ | ❌ | 🟡 LocalStorage |
|
|
100
|
+
| Tag registry (IndexedDB) | ✅ persistent | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
101
|
+
| Cache persistence model | Cache API + IDB | Cache API | In-memory | In-memory + IDB | IDB | IDB + SQLite |
|
|
102
|
+
| State survives page refresh | ✅ Cache API + IDB | ✅ Cache API | ❌ | 🟡 persisted | ✅ | ✅ |
|
|
103
|
+
|
|
104
|
+
### Developer Experience
|
|
105
|
+
|
|
106
|
+
| Feature | Swoff | Workbox | TanStack Query | Apollo | RxDB | TanStack DB |
|
|
107
|
+
| --------------------------------- | ------------------- | ------------------ | -------------- | ---------------- | -------------- | ----------------------- |
|
|
108
|
+
| Zero runtime dependencies | ✅ generated code | ❌ Workbox | ❌ 3.8 kB | ❌ 32 kB | ❌ 40 kB | ❌ + Query + DB |
|
|
109
|
+
| Config-driven setup | ✅ single file | ✅ workbox-config | ❌ code-only | ❌ code-only | ❌ code-only | ❌ code-only |
|
|
110
|
+
| Build-tool agnostic | ✅ any | ✅ any | ✅ any | ✅ any | ✅ any | ✅ any |
|
|
111
|
+
| React hooks | ✅ 11 | 🟡 minimal | ✅ extensive | ✅ | ✅ | ✅ useLiveQuery |
|
|
112
|
+
| TypeScript declarations generated | ✅ | ❌ | ✅ built-in | ✅ built-in | ✅ built-in | ✅ built-in |
|
|
113
|
+
| Auditable generated code | ✅ every file | 🟡 only SW | ❌ | ❌ | ❌ | ❌ |
|
|
114
|
+
|
|
115
|
+
## Edge Case Handling
|
|
116
|
+
|
|
117
|
+
| Edge Case | Swoff | TanStack Query / SWR | Workbox |
|
|
118
|
+
| --------------------------------- | ---------------------------------------- | -------------------- | --------------------------- |
|
|
119
|
+
| **Concurrent invalidation + SWR refresh** | Tagless refresh never clobbers invalidation entry in queue | N/A (no SW) | No tag system |
|
|
120
|
+
| **SW update during offline write** | Cache API + Tag IDB survive SW restart; refresh queue recreated on next fetch | N/A | Cached assets survive; runtime cache cleared if configured |
|
|
121
|
+
| **Tab crash mid-mutation replay** | SW detects no clients → replays via Background Sync | N/A (no SW) | No mutation system |
|
|
122
|
+
| **Rapid repeated invalidations** | Debounced INVALIDATE_TAG coalescing (configurable) | N/A | No tag system |
|
|
123
|
+
| **Offline mid-batch mutation replay** | Per-mutation online check stops processing; remaining mutations stay queued | N/A | No mutation system |
|
|
124
|
+
| **Query param cache-busting** | Configurable ignore params + sorted normalization | N/A | No SW-level caching |
|
|
125
|
+
| **Network flicker on reconnect** | Batch delay + retry backoff prevent stampede | ❌ no SW batching | ❌ per-request only |
|
|
126
|
+
| **staleVersions memory limit** | Max 100 entries, 30 min TTL; lost entries just skip stale fallback | N/A | N/A |
|
|
127
|
+
| **Tag IDB deleted mid-refresh** | Refresh re-populates tag IDB from queue entry's stored tags | N/A | N/A |
|
|
128
|
+
| **Dual-replay client + SW race** | SW checks clients.open() → skips entirely if any client page is open | N/A | No mutation system |
|
|
129
|
+
| **Background sync after all tabs close** | SW wakes via sync event, processes queue, IndexedDB persists | N/A | N/A |
|
|
130
|
+
| **Fetch error during stale refetch** | Exponential backoff retry via setTimeout keeps SW alive | Retry via hook | ❌ |
|
|
131
|
+
| **No service worker support** | SW-all features degrade gracefully; mutation queue falls back to client-only IDB | N/A (normal) | N/A (no SW = nothing) |
|
|
132
|
+
|
|
133
|
+
### How each library handles persistence model failure
|
|
134
|
+
|
|
135
|
+
| Failure Scenario | Swoff | TanStack Query | RxDB | TanStack DB |
|
|
136
|
+
| --------------------------------- | ---------------------------------------- | -------------------- | --------------------- | --------------------- |
|
|
137
|
+
| Memory cleared (tab close/refresh) | ✅ Tag IDB + Cache API survive | ❌ Full loss | ✅ IDB survives | ✅ IDB survives |
|
|
138
|
+
| SW terminated (idle) | ✅ IDB + Cache API survive; Maps rebuilt on next wake | N/A | N/A | N/A |
|
|
139
|
+
| SW killed during fetch handler | ✅ Cache API atomic; partial writes discarded | N/A | N/A | N/A |
|
|
140
|
+
| IndexedDB quota exceeded | ✅ Cache API still functional; only tag introspection lost | N/A | ❌ Full loss | ❌ Full loss |
|
|
141
|
+
| Browser storage cleared | ✅ All caches lost, SW stays installed; next generation re-caches | ❌ | ❌ | ❌ |
|
|
142
|
+
|
|
143
|
+
### How Swoff handles race conditions
|
|
144
|
+
|
|
145
|
+
| Race Condition | Mitigation |
|
|
146
|
+
| --------------------------------- | ---------------------------------------- |
|
|
147
|
+
| SWR queues refresh without tags → invalidation queues with tags | `queueRefresh` guard: tagless call doesn't override if entry with tags exists |
|
|
148
|
+
| Refresh completes after invalidation deletes tag IDB | `_processRefreshQueue` re-populates tags from queue entry |
|
|
149
|
+
| Client triggers `invalidateByTag` before SW caches response | SW processes `INVALIDATE_TAG` at message time; if cache entry doesn't exist yet, it's a no-op for that URL |
|
|
150
|
+
| Multiple tabs call `invalidateByTag` simultaneously | SW debounce (configurable) + Map dedup on tag; single IDB scan |
|
|
151
|
+
| SW and client both try to replay mutation queue | SW skips entirely if any client page is open |
|
|
152
|
+
| Refresh fails → staleVersions entry → next SWR serves stale | staleVersions TTL 30 min, max 100 entries; serves old cache as fallback until retry succeeds |
|
|
153
|
+
|
|
154
|
+
## Key Differentiators
|
|
155
|
+
|
|
156
|
+
**Where Swoff leads:**
|
|
157
|
+
|
|
158
|
+
- **Offline-first integration** — combines SW caching, data fetching, auth, mutation queue, GraphQL, PWA, push notifications, cross-tab sync, and real-time invalidation in a single config-driven system. Others require 4-6 separate tools.
|
|
159
|
+
- **Zero runtime dependencies** — generated code uses only browser APIs. No bundle impact, no version mismatches.
|
|
160
|
+
- **Auth across the stack** — token injection, 401 handling, refresh, offline user caching, and state detection built into fetch wrapper, mutation queue, and SW.
|
|
161
|
+
- **Generated auditable code** — every line visible in `swoff/`. Read, edit, and commit it.
|
|
162
|
+
- **Config-driven resolution** — 3-tier priority for strategy (per-request → route pattern → global default).
|
|
163
|
+
- **SW confirmation-driven events** — `cache-invalidated` fires only after SW confirms via `TAG_INVALIDATED`, not before SW clears cache. Eliminates the "event before action" race.
|
|
164
|
+
- **Tag registry repopulation on refresh** — after invalidation deletes tag→URL mappings from IDB, the background refresh re-populates them. Subsequent invalidations still find the URL.
|
|
165
|
+
- **SW-broadcast cross-tab invalidation** — `TAG_INVALIDATED` is posted to every connected client via `self.clients.matchAll()`. No BroadcastChannel needed.
|
|
166
|
+
|
|
167
|
+
**Where TanStack DB leads (and Swoff still needs to catch up):**
|
|
168
|
+
|
|
169
|
+
- **Optimistic updates** — instant local writes with automatic rollback on failure.
|
|
170
|
+
- **Normalized collections** — query across related data with joins, filters, and aggregates.
|
|
171
|
+
- **Differential dataflow** — sub-millisecond incremental query re-computation even with 100k+ items.
|
|
172
|
+
- **SSR support** — works with server rendering (TanStack DB is actively developing this).
|
|
173
|
+
|
|
174
|
+
**Where Swoff still leads vs TanStack DB:**
|
|
175
|
+
|
|
176
|
+
- **SW-level caching** — Swoff intercepts at the `fetch` event, caching even third-party and plain `fetch()` calls. TanStack DB operates at the application layer only.
|
|
177
|
+
- **Auth across the stack** — token injection, 401 handling, refresh in SW + client.
|
|
178
|
+
- **Zero runtime deps** — generated code vs TanStack DB's ~6 kB core + Query + optional SQLite WASM.
|
|
179
|
+
- **Config-driven setup** — one config file vs code-only setup.
|
|
180
|
+
- **PWA + push notifications** — built into the generated output.
|
|
181
|
+
|
|
182
|
+
**Where Swoff still needs to catch up:**
|
|
183
|
+
|
|
184
|
+
- **Framework hooks** — React only. Vue, Svelte, Solid adapters planned.
|
|
185
|
+
- **Infinite queries / pagination** — not yet built.
|
|
186
|
+
- **Optimistic updates** — not yet built.
|
|
187
|
+
- **Normalized GraphQL cache** — body-hash caching is simple but cannot merge overlapping query results like Apollo/Relay.
|
|
188
|
+
- **Devtools** — no browser extension yet.
|
|
189
|
+
|
|
190
|
+
## Architecture fit
|
|
191
|
+
|
|
192
|
+
Swoff operates at the **browser infrastructure layer** (Service Worker + `fetch` event), not the application layer. This means it works with any backend (PHP, Laravel, Django, Rails, Go, Java), any frontend (React, Vue, Svelte, HTMX, vanilla), and any rendering strategy (SSR, SSG, SPA, islands, HTML-over-wire). See [ECOSYSTEM.md](./ECOSYSTEM.md).
|
package/docs/CONFIG.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
Full schema for `swoff.config.json` — every field, its type, default, and description.
|
|
4
|
+
|
|
5
|
+
## Quick reference
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"$schema": "https://swoff.netlify.app/schema/v1.json",
|
|
10
|
+
"enabled": true,
|
|
11
|
+
"framework": "react",
|
|
12
|
+
"build": {
|
|
13
|
+
"outputDir": "dist",
|
|
14
|
+
"swFilename": "sw"
|
|
15
|
+
},
|
|
16
|
+
"features": {
|
|
17
|
+
"pwa": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"preventDefaultInstall": false
|
|
20
|
+
},
|
|
21
|
+
"serviceWorker": {
|
|
22
|
+
"version": "package",
|
|
23
|
+
"minSupportedVersion": "1.0.0",
|
|
24
|
+
"autoUpdate": true,
|
|
25
|
+
"autoActivate": false,
|
|
26
|
+
"strategy": {
|
|
27
|
+
"default": "cache-first",
|
|
28
|
+
"patterns": {
|
|
29
|
+
"/api/*": { "strategy": "reactive", "staleTime": 30, "refetchOnFocus": true },
|
|
30
|
+
"/static/*": "cache-first"
|
|
31
|
+
},
|
|
32
|
+
"reactive": {
|
|
33
|
+
"defaults": {
|
|
34
|
+
"staleTime": 0,
|
|
35
|
+
"refetchInterval": 0,
|
|
36
|
+
"refetchOnReconnect": false,
|
|
37
|
+
"refetchOnFocus": false
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"mode": "all",
|
|
41
|
+
"clearRuntimeOnUpdate": false,
|
|
42
|
+
"normalizeKey": false,
|
|
43
|
+
"ignoreQueryParams": []
|
|
44
|
+
},
|
|
45
|
+
"navigation": {
|
|
46
|
+
"mode": "spa",
|
|
47
|
+
"preload": true,
|
|
48
|
+
"fallback": "/index.html"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"refetchQueue": {
|
|
52
|
+
"batchSize": 5,
|
|
53
|
+
"batchDelayMs": 1000,
|
|
54
|
+
"maxRetries": 3,
|
|
55
|
+
"retryDelayMs": 1000
|
|
56
|
+
},
|
|
57
|
+
"mutationQueue": {
|
|
58
|
+
"enabled": false,
|
|
59
|
+
"batchSize": 1,
|
|
60
|
+
"batchDelayMs": 0,
|
|
61
|
+
"maxRetries": 5,
|
|
62
|
+
"retryBackoffMs": 1000
|
|
63
|
+
},
|
|
64
|
+
"backgroundSync": false,
|
|
65
|
+
"auth": {
|
|
66
|
+
"enabled": false,
|
|
67
|
+
"type": "bearer",
|
|
68
|
+
"refreshPath": "/api/refresh",
|
|
69
|
+
"userEndpoint": "/api/me"
|
|
70
|
+
},
|
|
71
|
+
"graphql": {
|
|
72
|
+
"enabled": false,
|
|
73
|
+
"endpoint": "/graphql"
|
|
74
|
+
},
|
|
75
|
+
"crossTabSync": true,
|
|
76
|
+
"tagInvalidation": {
|
|
77
|
+
"enabled": true
|
|
78
|
+
},
|
|
79
|
+
"pushNotifications": {
|
|
80
|
+
"enabled": false,
|
|
81
|
+
"vapidPublicKey": ""
|
|
82
|
+
},
|
|
83
|
+
"serverPush": {
|
|
84
|
+
"enabled": false,
|
|
85
|
+
"type": "sse",
|
|
86
|
+
"endpoint": "/api/events",
|
|
87
|
+
"reconnectDelayMs": 5000
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Top-level fields
|
|
96
|
+
|
|
97
|
+
| Field | Type | Default | Description |
|
|
98
|
+
|-------|------|---------|-------------|
|
|
99
|
+
| `$schema` | `string` | — | JSON Schema URL (for IDE autocomplete) |
|
|
100
|
+
| `enabled` | `boolean` | `true` | Master switch — disables all Swoff features |
|
|
101
|
+
| `framework` | `"react"` \| `"vue"` \| `"svelte"` \| `"vanilla"` | auto-detected | Your UI framework. Controls whether React hooks are generated. |
|
|
102
|
+
| `build.outputDir` | `string` | `"dist"` | Build tool output directory |
|
|
103
|
+
| `build.swFilename` | `string` | `"sw"` | Service worker filename prefix (e.g. `sw-v1.2.3.js`) |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## `features.serviceWorker`
|
|
108
|
+
|
|
109
|
+
| Field | Type | Default | Description |
|
|
110
|
+
|-------|------|---------|-------------|
|
|
111
|
+
| `version` | `"package"` \| `"hash"` \| `string` \| `false` | `"package"` | SW version mode. `"package"`: reads from `package.json`. `"hash"`: SHA-256 hash of generated SW content (deterministic, fixed SW URL). Semver string (e.g. `"1.2.3"`): explicit version. `false`: no versioning (simple mode, hash-based cache name). |
|
|
112
|
+
| `minSupportedVersion` | `string` | `"0.0.0"` | Minimum supported SW version — clients below this are force-updated on page load |
|
|
113
|
+
| `autoUpdate` | `boolean` | `true` | Automatically register new service worker versions when detected. When false, dispatches `sw-update-available` event for manual registration via `handleUpdateApproved()`. |
|
|
114
|
+
| `autoActivate` | `boolean` | `false` | Automatically activate newly registered service workers (`skipWaiting`). Only applies when a new version is registered — use with `autoUpdate` for fully silent updates, or wait for user consent then call `handleUpdateApproved()`. |
|
|
115
|
+
|
|
116
|
+
### `features.serviceWorker.strategy`
|
|
117
|
+
|
|
118
|
+
| Field | Type | Default | Description |
|
|
119
|
+
|-------|------|---------|-------------|
|
|
120
|
+
| `default` | `string` | `"cache-first"` | Default caching strategy (lowest priority in 3-tier resolution). One of: `cache-first`, `network-first`, `stale-while-revalidate`, `cache-only`, `network-only`, `reactive` |
|
|
121
|
+
| `patterns` | `object` | `{}` | Per-route strategy overrides. Keys are URL patterns (e.g. `/api/*`). Values can be a strategy name string or a `StrategyEntry` object |
|
|
122
|
+
| `reactive.defaults.staleTime` | `number` | `0` | Global stale time in seconds for reactive strategy. Data is considered fresh for this long; after expiry, SW serves cached but triggers a background refresh. 0 = always stale. |
|
|
123
|
+
| `reactive.defaults.refetchInterval` | `number` | `0` | Global auto-refetch interval in seconds for reactive strategy. SW periodically re-fetches matching routes in the background. 0 disables. |
|
|
124
|
+
| `reactive.defaults.refetchOnReconnect` | `boolean` | `false` | Global default — refetch reactive entries when the browser comes back online |
|
|
125
|
+
| `reactive.defaults.refetchOnFocus` | `boolean` | `false` | Global default — refetch reactive entries when the tab gains focus |
|
|
126
|
+
| `mode` | `"all"` \| `"explicit-only"` | `"all"` | When to apply caching strategies. `"all"`: every GET/HEAD request; `"explicit-only"`: only requests with `X-SW-Cache-Strategy` header |
|
|
127
|
+
| `clearRuntimeOnUpdate` | `boolean` | `false` | Clear runtime cache when a new SW version activates |
|
|
128
|
+
| `normalizeKey` | `boolean` | `false` | When `true`, query params are sorted alphabetically in cache keys so `?b=1&a=2` and `?a=2&b=1` resolve to the same entry. |
|
|
129
|
+
| `ignoreQueryParams` | `string[]` | `[]` | Query params to strip from cache keys (e.g. `["_t", "utm_source"]`). Prevents cache-busting params from creating duplicate cache entries. |
|
|
130
|
+
|
|
131
|
+
### `features.serviceWorker.navigation`
|
|
132
|
+
|
|
133
|
+
| Field | Type | Default | Description |
|
|
134
|
+
|-------|------|---------|-------------|
|
|
135
|
+
| `mode` | `"spa"` \| `"default"` | `"spa"` | SPA mode sends unmatched navigation requests to `fallback` |
|
|
136
|
+
| `preload` | `boolean` | `true` | Enable Navigation Preload API — reduces SW startup latency |
|
|
137
|
+
| `fallback` | `string` | `"/index.html"` | Fallback HTML for SPA navigation requests |
|
|
138
|
+
|
|
139
|
+
### `StrategyEntry` object
|
|
140
|
+
|
|
141
|
+
When a strategy value is an object instead of a string:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
"/api/*": {
|
|
145
|
+
"strategy": "reactive",
|
|
146
|
+
"staleTime": 30,
|
|
147
|
+
"refetchOnFocus": true
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Field | Type | Default | Description |
|
|
152
|
+
|-------|------|---------|-------------|
|
|
153
|
+
| `strategy` | `string` | (required) | Caching strategy name. One of: `cache-first`, `network-first`, `stale-while-revalidate`, `cache-only`, `network-only`, `reactive` |
|
|
154
|
+
| `staleTime` | `number` | — | Stale time in seconds. **Reactive-only** — only valid when `strategy` is `"reactive"`. Data is considered fresh for this long; after expiry, SW serves cached but triggers a background refresh. 0 = always stale. |
|
|
155
|
+
| `refetchInterval` | `number` | — | Auto-refetch interval in seconds. **Reactive-only**. The SW periodically re-fetches this route in the background. 0 disables. |
|
|
156
|
+
| `refetchOnReconnect` | `boolean` | `false` | Refetch when the browser comes back online. **Reactive-only**. |
|
|
157
|
+
| `refetchOnFocus` | `boolean` | `false` | Refetch when the browser tab gains focus. **Reactive-only**. |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## `features.refetchQueue`
|
|
162
|
+
|
|
163
|
+
| Field | Type | Default | Description |
|
|
164
|
+
|-------|------|---------|-------------|
|
|
165
|
+
| `batchSize` | `number` | `5` | Max stale cache entries to refetch per batch |
|
|
166
|
+
| `batchDelayMs` | `number` | `1000` | Delay in ms between batch cycles (rate limiting) |
|
|
167
|
+
| `maxRetries` | `number` | `3` | Max retries for background refetches (exponential backoff) |
|
|
168
|
+
| `retryDelayMs` | `number` | `1000` | Base delay in ms for retry backoff (delay × 2^retryCount) |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### Resolution tiers
|
|
173
|
+
|
|
174
|
+
`strategy` and `staleTime` resolve in 3 tiers (highest to lowest priority):
|
|
175
|
+
|
|
176
|
+
1. **Per-request** — passed as options to `fetchWithCache({ strategy })` or `useCachedFetch()`, or via `X-SW-Strategy` / `X-SW-Stale-Time` headers
|
|
177
|
+
2. **Route pattern** — configured in `features.serviceWorker.strategy.patterns`
|
|
178
|
+
3. **Global default** — configured at `features.serviceWorker.strategy.reactive.defaults.*` (for `staleTime`) or `features.serviceWorker.strategy.default` (for `strategy`)
|
|
179
|
+
|
|
180
|
+
`refetchInterval`, `refetchOnReconnect`, and `refetchOnFocus` are SW-initiated (interval timers, online/focus event handlers) and resolve in 2 tiers only:
|
|
181
|
+
|
|
182
|
+
1. **Route pattern** — configured in the pattern entry
|
|
183
|
+
2. **Global default** — configured at `features.serviceWorker.strategy.reactive.defaults.*`
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## `features.mutationQueue`
|
|
188
|
+
|
|
189
|
+
Object-only feature (boolean shorthand not supported).
|
|
190
|
+
|
|
191
|
+
| Field | Type | Default | Description |
|
|
192
|
+
|-------|------|---------|-------------|
|
|
193
|
+
| `enabled` | `boolean` | `false` | Enable offline write queue |
|
|
194
|
+
| `batchSize` | `number` | `1` | Mutations per progress event |
|
|
195
|
+
| `batchDelayMs` | `number` | `0` | Delay between mutations (rate limiting) |
|
|
196
|
+
| `maxRetries` | `number` | `5` | Max retries before dropping a mutation |
|
|
197
|
+
| `retryBackoffMs` | `number` | `1000` | Exponential backoff base (nextRetry = backoff × 2^retryCount) |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## `features.auth`
|
|
202
|
+
|
|
203
|
+
| Field | Type | Default | Description |
|
|
204
|
+
|-------|------|---------|-------------|
|
|
205
|
+
| `enabled` | `boolean` | `false` | Enable auth module |
|
|
206
|
+
| `type` | `"bearer"` \| `"cookie"` \| `"custom"` | `"bearer"` | Auth strategy |
|
|
207
|
+
| `refreshPath` | `string` | `"/api/refresh"` | Token refresh endpoint |
|
|
208
|
+
| `userEndpoint` | `string` | `"/api/me"` | Current user fetch endpoint |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## `features.graphql`
|
|
213
|
+
|
|
214
|
+
Object-only feature (boolean shorthand not supported).
|
|
215
|
+
|
|
216
|
+
| Field | Type | Default | Description |
|
|
217
|
+
|-------|------|---------|-------------|
|
|
218
|
+
| `enabled` | `boolean` | `false` | Generate GraphQL wrapper (`queryGql` / `mutateGql`) |
|
|
219
|
+
| `endpoint` | `string` | `"/graphql"` | GraphQL endpoint URL |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## `features.pushNotifications`
|
|
224
|
+
|
|
225
|
+
| Field | Type | Default | Description |
|
|
226
|
+
|-------|------|---------|-------------|
|
|
227
|
+
| `enabled` | `boolean` | `false` | Generate push notification subscription management |
|
|
228
|
+
| `vapidPublicKey` | `string` | `""` | VAPID public key. Baked into the generated push handler at build time — not needed at runtime. |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## `features.serverPush`
|
|
233
|
+
|
|
234
|
+
| Field | Type | Default | Description |
|
|
235
|
+
|-------|------|---------|-------------|
|
|
236
|
+
| `enabled` | `boolean` | `false` | Enable real-time cache invalidation via SSE or WebSocket |
|
|
237
|
+
| `type` | `"sse"` \| `"websocket"` | `"sse"` | Transport protocol |
|
|
238
|
+
| `endpoint` | `string` | `"/api/events"` | Push endpoint URL |
|
|
239
|
+
| `reconnectDelayMs` | `number` | `5000` | Initial reconnect delay on connection loss (exponential backoff, capped at 30s) |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## `features.tagInvalidation`
|
|
244
|
+
|
|
245
|
+
Object-only feature (boolean shorthand not supported).
|
|
246
|
+
|
|
247
|
+
| Field | Type | Default | Description |
|
|
248
|
+
|-------|------|---------|-------------|
|
|
249
|
+
| `enabled` | `boolean` | `true` | Enable tag-based cache invalidation |
|
|
250
|
+
| `debounceMs` | `number` | `0` | Debounce window (ms) for coalescing rapid invalidations. When > 0, repeated `INVALIDATE_TAG` messages within the window are batched and processed once, reducing redundant cache scans. |
|
|
251
|
+
| `prefixes` | `string[]` | `["api","v1","v2","v3","rest","graphql","gql"]` | URL path prefixes to skip during tag generation |
|
|
252
|
+
| `patterns` | `object` | `{}` | Custom glob patterns for tag generation. Keys are URL patterns (`/api/:id`), values are tag template arrays (`["{id}"]`) |
|
|
253
|
+
| `singularization` | `object` | `{}` | Custom plural→singular mapping (e.g. `{"categories": "category"}`). Default: strips trailing `s`. |
|
|
254
|
+
| `cascading` | `object` | `{}` | Cascading tag dependencies. `{"todos": ["categories"]}` — invalidating `todos` also invalidates `categories` |
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Boolean features
|
|
259
|
+
|
|
260
|
+
| Feature | Config path | Default |
|
|
261
|
+
|---------|-------------|---------|
|
|
262
|
+
| Background Sync | `features.backgroundSync` | `false` |
|
|
263
|
+
| Cross-tab Sync | `features.crossTabSync` | `true` |
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Feature dependencies
|
|
268
|
+
|
|
269
|
+
Some features work best together:
|
|
270
|
+
|
|
271
|
+
| Feature | Recommended combo | Why |
|
|
272
|
+
|---------|------------------|-----|
|
|
273
|
+
| `mutationQueue` | + `backgroundSync` | Background Sync processes mutations even after tab close |
|
|
274
|
+
| `tagInvalidation` | + `crossTabSync` | Invalidation events broadcast to all open tabs |
|
|
275
|
+
| `serverPush` | + `tagInvalidation` | Server push triggers `invalidateByTag()` — requires tag invalidation to function |
|
|
276
|
+
| `auth` + `mutationQueue` | — | `flushMutations()` after re-login replays mutations that failed with 401 |
|
|
277
|
+
| `graphql` | + `mutationQueue` + `tagInvalidation` | Offline GQL mutations queue in IndexedDB; mutations auto-invalidate operation-name tags |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Ecosystem Compatibility
|
|
2
|
+
|
|
3
|
+
Swoff operates at the **`fetch` event** + **Service Worker** layer — below the application and data-fetching layers. This makes it compatible with any web stack regardless of backend language, frontend framework, or rendering strategy.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Application Layer (React, Vue, Svelte, HTMX, vanilla)
|
|
7
|
+
Data Fetching Layer (TanStack Query, SWR, Apollo, fetch())
|
|
8
|
+
SWOFF — SW + Client (fetch interception, cache, offline)
|
|
9
|
+
Browser APIs (Cache Storage, IndexedDB, fetch, push)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The only framework-specific surface is **view adapters** (React hooks, future Vue/Svelte). These are optional thin wrappers over `postMessage` to the SW. The core caching, offline, invalidation, and auth work without them.
|
|
13
|
+
|
|
14
|
+
## Compatible with everything
|
|
15
|
+
|
|
16
|
+
| Category | Examples | Notes |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| **Backend** | PHP, Laravel, Django, Rails, ASP.NET, Go, Java, Node | Serve `client-injector.js` as a static asset; zero backend integration needed |
|
|
19
|
+
| **HTML-over-wire** | HTMX, Turbo Hotwire, Unpoly, Livewire | SW caches HTML fragments by URL — body format is irrelevant |
|
|
20
|
+
| **SSG** | Astro, Hugo, 11ty, Jekyll | Precaches all built assets at install time; SW works even if no hydration occurs |
|
|
21
|
+
| **SPA frameworks** | React, Vue, Svelte, Solid, Angular, Alpine | Core SW + client utilities are plain JS; React gets generated hooks |
|
|
22
|
+
| **SSR meta-frameworks** | Next.js, Remix, Nuxt, SvelteKit, Analog | Intercepts `fetch()` transparently at the SW level; disable the framework's own SW |
|
|
23
|
+
| **Edge / Serverless** | Cloudflare Workers, Deno Deploy, Vercel Edge | Edge runs on CDN, Swoff runs in browser — independent layers, no conflict |
|
|
24
|
+
| **Islands / Resumability** | Astro islands, Qwik, Marko | SW runs in its own thread regardless of component hydration |
|
|
25
|
+
|
|
26
|
+
## Why it matters
|
|
27
|
+
|
|
28
|
+
- **TanStack Query / SWR** — in-memory state only; no SW integration, no offline writes, no push
|
|
29
|
+
- **Workbox** — SW only; no data fetching, auth, or framework hooks
|
|
30
|
+
- **Apollo Client** — GraphQL only; no SW caching
|
|
31
|
+
- **Next.js / Remix** — full meta-frameworks dictating your entire architecture
|
|
32
|
+
|
|
33
|
+
Swoff is the only tool that combines SW caching, data fetching, offline writes, auth, real-time sync, and push into a single config-driven system — all framework-agnostic at the core, with optional reactive hooks per framework.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swoff/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for Swoff - Offline-first web apps made easy",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"bin/",
|
|
21
21
|
"dist/",
|
|
22
|
+
"docs/",
|
|
22
23
|
"templates/"
|
|
23
24
|
],
|
|
24
25
|
"keywords": [
|
|
@@ -9,11 +9,11 @@ export function useAuth() {
|
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
getAuthState().then(setState);
|
|
12
|
+
getAuthState().then(setState).catch(() => {});
|
|
13
13
|
|
|
14
14
|
const onOnline = () => setState((s) => ({ ...s, online: true }));
|
|
15
15
|
const onOffline = () => setState((s) => ({ ...s, online: false }));
|
|
16
|
-
const onAuthChange = () => getAuthState().then(setState);
|
|
16
|
+
const onAuthChange = () => getAuthState().then(setState).catch(() => {});
|
|
17
17
|
|
|
18
18
|
window.addEventListener("online", onOnline);
|
|
19
19
|
window.addEventListener("offline", onOffline);
|
|
@@ -9,11 +9,11 @@ export function useAuth() {
|
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
getAuthState().then(setState);
|
|
12
|
+
getAuthState().then(setState).catch(() => {});
|
|
13
13
|
|
|
14
14
|
const onOnline = () => setState((s) => ({ ...s, online: true }));
|
|
15
15
|
const onOffline = () => setState((s) => ({ ...s, online: false }));
|
|
16
|
-
const onAuthChange = () => getAuthState().then(setState);
|
|
16
|
+
const onAuthChange = () => getAuthState().then(setState).catch(() => {});
|
|
17
17
|
|
|
18
18
|
window.addEventListener("online", onOnline);
|
|
19
19
|
window.addEventListener("offline", onOffline);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback,
|
|
1
|
+
import { useState, useEffect, useCallback, startTransition } from "react";
|
|
2
2
|
import { fetchWithCache } from "../fetch-wrapper.js";
|
|
3
3
|
import { generateTags } from "../invalidation-tags.js";
|
|
4
4
|
|
|
@@ -7,8 +7,6 @@ export function useCachedFetch(url, options = {}) {
|
|
|
7
7
|
const [error, setError] = useState(null);
|
|
8
8
|
const [loading, setLoading] = useState(true);
|
|
9
9
|
const [refetchCount, setRefetchCount] = useState(0);
|
|
10
|
-
const optionsRef = useRef(options);
|
|
11
|
-
useEffect(() => { optionsRef.current = options; }, [options]);
|
|
12
10
|
|
|
13
11
|
const refetch = useCallback(() => setRefetchCount((c) => c + 1), []);
|
|
14
12
|
|
|
@@ -16,7 +14,7 @@ export function useCachedFetch(url, options = {}) {
|
|
|
16
14
|
|
|
17
15
|
useEffect(() => {
|
|
18
16
|
if (!isEnabled) {
|
|
19
|
-
setLoading(false);
|
|
17
|
+
startTransition(() => setLoading(false));
|
|
20
18
|
return;
|
|
21
19
|
}
|
|
22
20
|
let cancelled = false;
|
|
@@ -26,7 +24,7 @@ export function useCachedFetch(url, options = {}) {
|
|
|
26
24
|
const doFetch = async () => {
|
|
27
25
|
try {
|
|
28
26
|
const { response } = await fetchWithCache(url, {
|
|
29
|
-
...
|
|
27
|
+
...options,
|
|
30
28
|
signal: controller.signal,
|
|
31
29
|
});
|
|
32
30
|
if (cancelled) return;
|
|
@@ -67,28 +65,5 @@ export function useCachedFetch(url, options = {}) {
|
|
|
67
65
|
return () => window.removeEventListener("cache-invalidated", onInvalidated);
|
|
68
66
|
}, [url, isEnabled]);
|
|
69
67
|
|
|
70
|
-
// Auto-refetch on window focus
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (!options.refetchOnWindowFocus || !isEnabled) return;
|
|
73
|
-
const onFocus = () => refetch();
|
|
74
|
-
window.addEventListener("focus", onFocus);
|
|
75
|
-
return () => window.removeEventListener("focus", onFocus);
|
|
76
|
-
}, [options.refetchOnWindowFocus, refetch, isEnabled]);
|
|
77
|
-
|
|
78
|
-
// Auto-refetch on reconnect
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
if (!options.refetchOnReconnect || !isEnabled) return;
|
|
81
|
-
const onOnline = () => refetch();
|
|
82
|
-
window.addEventListener("online", onOnline);
|
|
83
|
-
return () => window.removeEventListener("online", onOnline);
|
|
84
|
-
}, [options.refetchOnReconnect, refetch, isEnabled]);
|
|
85
|
-
|
|
86
|
-
// Auto-refetch on interval
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (!options.refetchInterval || options.refetchInterval <= 0 || !isEnabled) return;
|
|
89
|
-
const id = setInterval(refetch, options.refetchInterval * 1000);
|
|
90
|
-
return () => clearInterval(id);
|
|
91
|
-
}, [options.refetchInterval, refetch, isEnabled]);
|
|
92
|
-
|
|
93
68
|
return { data, error, loading, refetch };
|
|
94
69
|
}
|