@swoff/cli 0.3.8 → 0.3.10

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 (228) hide show
  1. package/README.md +47 -33
  2. package/dist/__tests__/assemble-sw.test.js +14 -53
  3. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  4. package/dist/__tests__/cli-integration.test.js +119 -11
  5. package/dist/__tests__/cli-integration.test.js.map +1 -1
  6. package/dist/__tests__/config-types.test.js +44 -17
  7. package/dist/__tests__/config-types.test.js.map +1 -1
  8. package/dist/__tests__/file-generators.test.js +3 -3
  9. package/dist/__tests__/file-generators.test.js.map +1 -1
  10. package/dist/__tests__/glob-matcher.test.js +86 -0
  11. package/dist/__tests__/glob-matcher.test.js.map +1 -0
  12. package/dist/__tests__/invalidation-tags.test.js +207 -23
  13. package/dist/__tests__/invalidation-tags.test.js.map +1 -1
  14. package/dist/__tests__/loader.test.js +16 -12
  15. package/dist/__tests__/loader.test.js.map +1 -1
  16. package/dist/__tests__/validator.test.js +140 -100
  17. package/dist/__tests__/validator.test.js.map +1 -1
  18. package/dist/index.js +39 -34
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/cli/help.js +12 -12
  21. package/dist/lib/cli/help.js.map +1 -1
  22. package/dist/lib/commands/add.js +84 -36
  23. package/dist/lib/commands/add.js.map +1 -1
  24. package/dist/lib/commands/assets.js +37 -0
  25. package/dist/lib/commands/assets.js.map +1 -0
  26. package/dist/lib/commands/clean.js +32 -30
  27. package/dist/lib/commands/clean.js.map +1 -1
  28. package/dist/lib/commands/generate-assets.js +44 -0
  29. package/dist/lib/commands/generate-assets.js.map +1 -0
  30. package/dist/lib/commands/generate-guide.js +22 -29
  31. package/dist/lib/commands/generate-guide.js.map +1 -1
  32. package/dist/lib/commands/generate.js +17 -24
  33. package/dist/lib/commands/generate.js.map +1 -1
  34. package/dist/lib/commands/info.js +41 -48
  35. package/dist/lib/commands/info.js.map +1 -1
  36. package/dist/lib/commands/init.js +27 -13
  37. package/dist/lib/commands/init.js.map +1 -1
  38. package/dist/lib/commands/validate.js +4 -4
  39. package/dist/lib/commands/validate.js.map +1 -1
  40. package/dist/lib/config/loader.js +25 -23
  41. package/dist/lib/config/loader.js.map +1 -1
  42. package/dist/lib/config/validator.js +169 -91
  43. package/dist/lib/config/validator.js.map +1 -1
  44. package/dist/lib/generators/asset-generator/dependency-resolver.js +76 -0
  45. package/dist/lib/generators/asset-generator/dependency-resolver.js.map +1 -0
  46. package/dist/lib/generators/asset-generator/generate.js +111 -0
  47. package/dist/lib/generators/asset-generator/generate.js.map +1 -0
  48. package/dist/lib/generators/asset-generator/guide.js +43 -0
  49. package/dist/lib/generators/asset-generator/guide.js.map +1 -0
  50. package/dist/lib/generators/asset-generator/html-patcher.js +65 -0
  51. package/dist/lib/generators/asset-generator/html-patcher.js.map +1 -0
  52. package/dist/lib/generators/asset-generator/ico-encoder.js +29 -0
  53. package/dist/lib/generators/asset-generator/ico-encoder.js.map +1 -0
  54. package/dist/lib/generators/asset-generator/manifest-patcher.js +23 -0
  55. package/dist/lib/generators/asset-generator/manifest-patcher.js.map +1 -0
  56. package/dist/lib/generators/asset-generator/maskable.js +15 -0
  57. package/dist/lib/generators/asset-generator/maskable.js.map +1 -0
  58. package/dist/lib/generators/asset-generator/rasterize.js +32 -0
  59. package/dist/lib/generators/asset-generator/rasterize.js.map +1 -0
  60. package/dist/lib/generators/asset-generator/sizes.js +25 -0
  61. package/dist/lib/generators/asset-generator/sizes.js.map +1 -0
  62. package/dist/lib/generators/file-generators/api-config.js +6 -0
  63. package/dist/lib/generators/file-generators/api-config.js.map +1 -0
  64. package/dist/lib/generators/file-generators/auth-state.js +2 -34
  65. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  66. package/dist/lib/generators/file-generators/auth-store.js +2 -250
  67. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  68. package/dist/lib/generators/file-generators/auth-user.js +3 -81
  69. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  70. package/dist/lib/generators/file-generators/background-sync.js +2 -57
  71. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  72. package/dist/lib/generators/file-generators/cache.js +2 -38
  73. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  74. package/dist/lib/generators/file-generators/client-injector.js +2 -91
  75. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  76. package/dist/lib/generators/file-generators/fetch-state.js +6 -0
  77. package/dist/lib/generators/file-generators/fetch-state.js.map +1 -0
  78. package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -267
  79. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  80. package/dist/lib/generators/file-generators/generate-framework-adapters.js +56 -0
  81. package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -0
  82. package/dist/lib/generators/file-generators/generate-hooks.js +4 -3
  83. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  84. package/dist/lib/generators/file-generators/gql-wrapper.js +3 -145
  85. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
  86. package/dist/lib/generators/file-generators/guide-generator.js +24 -630
  87. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  88. package/dist/lib/generators/file-generators/invalidation-tags.js +18 -86
  89. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  90. package/dist/lib/generators/file-generators/manifest.js +15 -5
  91. package/dist/lib/generators/file-generators/manifest.js.map +1 -1
  92. package/dist/lib/generators/file-generators/mutation-queue.js +3 -285
  93. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  94. package/dist/lib/generators/file-generators/mutation-state.js +2 -117
  95. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
  96. package/dist/lib/generators/file-generators/push.js +6 -139
  97. package/dist/lib/generators/file-generators/push.js.map +1 -1
  98. package/dist/lib/generators/file-generators/pwa-install.js +11 -84
  99. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  100. package/dist/lib/generators/file-generators/quick-readme.js +8 -125
  101. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
  102. package/dist/lib/generators/file-generators/reset.js +6 -0
  103. package/dist/lib/generators/file-generators/reset.js.map +1 -0
  104. package/dist/lib/generators/file-generators/server-push.js +3 -124
  105. package/dist/lib/generators/file-generators/server-push.js.map +1 -1
  106. package/dist/lib/generators/file-generators/sw-generator-build.js +8 -9
  107. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  108. package/dist/lib/generators/file-generators/sw-injector.js +10 -230
  109. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  110. package/dist/lib/generators/file-generators/type-definitions.js +2 -114
  111. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  112. package/dist/lib/generators/sw-generator.js +10 -20
  113. package/dist/lib/generators/sw-generator.js.map +1 -1
  114. package/dist/lib/generators/sw-sections/activate-handler.js +25 -3
  115. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  116. package/dist/lib/generators/sw-sections/assemble-sw.js +20 -16
  117. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  118. package/dist/lib/generators/sw-sections/background-sync-handler.js +68 -19
  119. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  120. package/dist/lib/generators/sw-sections/config-header.js +2 -2
  121. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  122. package/dist/lib/generators/sw-sections/fetch-handler.js +506 -144
  123. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  124. package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +1 -0
  125. package/dist/lib/generators/sw-sections/message-handler.js +55 -15
  126. package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
  127. package/dist/lib/generators/sw-sections/server-push-handler.js +12 -0
  128. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
  129. package/dist/lib/generators/sw-sections/sw-push.js +67 -0
  130. package/dist/lib/generators/sw-sections/sw-push.js.map +1 -0
  131. package/dist/lib/generators/sw-sections/tag-management.js +65 -23
  132. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  133. package/dist/lib/generators/swoff-files-generator.js +66 -45
  134. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  135. package/dist/lib/shared/config-types.js +182 -71
  136. package/dist/lib/shared/config-types.js.map +1 -1
  137. package/dist/lib/shared/glob-matcher.js +77 -0
  138. package/dist/lib/shared/glob-matcher.js.map +1 -0
  139. package/dist/lib/utils/tty-status.js +21 -0
  140. package/dist/lib/utils/tty-status.js.map +1 -0
  141. package/dist/runtime/api-config.js +18 -0
  142. package/dist/runtime/api-config.js.map +1 -0
  143. package/dist/runtime/auth-state.js +35 -0
  144. package/dist/runtime/auth-state.js.map +1 -0
  145. package/dist/runtime/auth-store.js +273 -0
  146. package/dist/runtime/auth-store.js.map +1 -0
  147. package/dist/runtime/auth-user.js +168 -0
  148. package/dist/runtime/auth-user.js.map +1 -0
  149. package/dist/runtime/background-sync.js +59 -0
  150. package/dist/runtime/background-sync.js.map +1 -0
  151. package/dist/runtime/cache.js +30 -0
  152. package/dist/runtime/cache.js.map +1 -0
  153. package/dist/runtime/client-injector.js +136 -0
  154. package/dist/runtime/client-injector.js.map +1 -0
  155. package/dist/runtime/fetch-state.js +42 -0
  156. package/dist/runtime/fetch-state.js.map +1 -0
  157. package/dist/runtime/fetch-wrapper.js +355 -0
  158. package/dist/runtime/fetch-wrapper.js.map +1 -0
  159. package/dist/runtime/gql-wrapper.js +135 -0
  160. package/dist/runtime/gql-wrapper.js.map +1 -0
  161. package/dist/runtime/invalidation-tags.js +260 -0
  162. package/dist/runtime/invalidation-tags.js.map +1 -0
  163. package/dist/runtime/mutation-queue.js +304 -0
  164. package/dist/runtime/mutation-queue.js.map +1 -0
  165. package/dist/runtime/mutation-state.js +117 -0
  166. package/dist/runtime/mutation-state.js.map +1 -0
  167. package/dist/runtime/push.js +138 -0
  168. package/dist/runtime/push.js.map +1 -0
  169. package/dist/runtime/pwa-index.js +11 -0
  170. package/dist/runtime/pwa-index.js.map +1 -0
  171. package/dist/runtime/pwa-injector.js +44 -0
  172. package/dist/runtime/pwa-injector.js.map +1 -0
  173. package/dist/runtime/pwa-install.js +83 -0
  174. package/dist/runtime/pwa-install.js.map +1 -0
  175. package/dist/runtime/pwa-prompt.js +54 -0
  176. package/dist/runtime/pwa-prompt.js.map +1 -0
  177. package/dist/runtime/reset.js +148 -0
  178. package/dist/runtime/reset.js.map +1 -0
  179. package/dist/runtime/server-push.js +148 -0
  180. package/dist/runtime/server-push.js.map +1 -0
  181. package/dist/runtime/sw-injector.js +236 -0
  182. package/dist/runtime/sw-injector.js.map +1 -0
  183. package/dist/runtime/type-definitions.js +138 -0
  184. package/dist/runtime/type-definitions.js.map +1 -0
  185. package/dist/runtime/utils.js +27 -0
  186. package/dist/runtime/utils.js.map +1 -0
  187. package/package.json +3 -1
  188. package/templates/react/useAuth.jsx +106 -0
  189. package/templates/react/useAuth.tsx +118 -0
  190. package/templates/{hooks → react}/useBackgroundSync.jsx +6 -4
  191. package/templates/{hooks → react}/useBackgroundSync.tsx +6 -4
  192. package/templates/{hooks → react}/useCacheInvalidation.jsx +2 -2
  193. package/templates/react/useCacheInvalidation.tsx +52 -0
  194. package/templates/react/useCachedFetch.jsx +140 -0
  195. package/templates/react/useCachedFetch.tsx +160 -0
  196. package/templates/react/useIsFetching.jsx +32 -0
  197. package/templates/react/useIsFetching.tsx +32 -0
  198. package/templates/react/useMutation.jsx +141 -0
  199. package/templates/react/useMutation.tsx +180 -0
  200. package/templates/react/useMutationQueue.jsx +69 -0
  201. package/templates/react/useMutationQueue.tsx +83 -0
  202. package/templates/react/useMutationState.jsx +13 -0
  203. package/templates/react/useMutationState.tsx +25 -0
  204. package/templates/react/useNetworkStatus.jsx +61 -0
  205. package/templates/react/useNetworkStatus.tsx +76 -0
  206. package/templates/react/usePrefetch.jsx +32 -0
  207. package/templates/react/usePrefetch.tsx +42 -0
  208. package/templates/{hooks → react}/usePushSubscription.jsx +7 -7
  209. package/templates/{hooks → react}/usePushSubscription.tsx +7 -7
  210. package/templates/{hooks → react}/useSWUpdate.jsx +3 -3
  211. package/templates/{hooks → react}/useSWUpdate.tsx +4 -4
  212. package/templates/react/useSwoffReset.jsx +32 -0
  213. package/templates/react/useSwoffReset.tsx +39 -0
  214. package/templates/hooks/useAuth.jsx +0 -30
  215. package/templates/hooks/useAuth.tsx +0 -30
  216. package/templates/hooks/useCacheInvalidation.tsx +0 -19
  217. package/templates/hooks/useCachedFetch.jsx +0 -94
  218. package/templates/hooks/useCachedFetch.tsx +0 -112
  219. package/templates/hooks/useMutation.jsx +0 -76
  220. package/templates/hooks/useMutation.tsx +0 -97
  221. package/templates/hooks/useMutationQueue.jsx +0 -42
  222. package/templates/hooks/useMutationQueue.tsx +0 -49
  223. package/templates/hooks/useMutationState.jsx +0 -27
  224. package/templates/hooks/useMutationState.tsx +0 -41
  225. package/templates/hooks/useNetworkStatus.jsx +0 -19
  226. package/templates/hooks/useNetworkStatus.tsx +0 -19
  227. package/templates/hooks/usePrefetch.jsx +0 -10
  228. package/templates/hooks/usePrefetch.tsx +0 -25
@@ -1,654 +1,48 @@
1
1
  /**
2
- * Generates GUIDE.md — full integration guide for enabled features.
2
+ * Generates GUIDE.md — points user to online documentation and PWA asset info.
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateGuide(ctx) {
6
- const { config } = ctx;
7
- const ext = ctx.ext;
8
6
  const lines = [];
9
7
  const w = (s) => lines.push(s);
10
- const wb = (s) => lines.push("", s);
11
- w("# Swoff Integration Guide");
8
+ w("# Swoff Generated Files");
12
9
  w("");
13
- w("This guide explains every file and feature Swoff generated for your project.");
14
- w("Each section answers: **What is it?**, **What files were created?**, **How to use it?**, **Where to edit?**");
10
+ w("Your project was generated with Swoff. All generated files live in `swoff/`.");
15
11
  w("");
16
- // ── SW Registration ──
17
- wb("## 📦 Service Worker Registration");
18
- w("The service worker handles caching, offline support, background sync, and PWA installability.");
19
- w("");
20
- w("### `client-injector.ts` — Single entry point");
21
- w("This is the **only file you need to import** at app startup to enable all Swoff features.");
22
- w("```ts");
23
- w(`import { initServiceWorker } from "./swoff/client-injector.${ext}";`);
24
- w("initServiceWorker();");
25
- w("```");
26
- w("It wires together: SW registration, PWA install prompt, mutation queue online listener, and cross-tab sync.");
27
- w("");
28
- w("### `sw/injector.ts` — SW registration logic");
29
- w("Handles registering the service worker, checking for updates via version.json, and dispatching");
30
- w("update-available / ready / error events on the window.");
31
- w("");
32
- w("**Functions:**");
33
- w("- `initServiceWorker()` — registers the SW and checks for updates");
34
- w("- `handleUpdateApproved(version)` — accepts a pending update and reloads on activation");
35
- w("- `skipWaiting()` — activates a waiting SW without reloading");
36
- w("");
37
- // ── Stale Time & Auto-Refresh ──
38
- wb("## ⏱️ Stale Time — fresh vs stale data");
39
- w("`staleTime` controls how long cached data is considered **fresh** before it becomes **stale**.");
40
- w("When data is fresh, the SW serves it immediately from cache — no network request.");
41
- w("When data is stale, the SW serves the cached copy but triggers a **background refresh**,");
42
- w("so the next read returns fresh data.");
43
- w("");
44
- w("This is different from `maxCacheAge` (which **evicts** old entries entirely). StaleTime keeps the");
45
- w("entry usable while silently refreshing it — the user never sees a loading spinner.");
46
- w("");
47
- w("**3-tier staleTime resolution (like strategies):**");
48
- w("1. **Per-request** — `fetchWithCache(url, { staleTime: 30 })` overrides everything");
49
- w("2. **Route pattern** — `\"/api/*\": { staleTime: 60 }` in `swoff.config.json`");
50
- w("3. **Global default** — `features.serviceWorker.staleTime`");
51
- w("");
52
- w("**How staleTime changes each strategy:**");
53
- w("| Strategy | Fresh data (within staleTime) | Stale data (past staleTime) |");
54
- w("|----------|------------------------------|----------------------------|");
55
- w("| `cache-first` | Serve from cache, no network | Serve from cache + background refresh |");
56
- w("| `network-first` | Serve from cache, skip network | Try network first, fall back to cache |");
57
- w("| `stale-while-revalidate` | Serve from cache, no refresh | Serve + background refresh (was always-refresh) |");
58
- w("| `cache-only` | Serve from cache | Serve from cache + best-effort refresh |");
59
- w("| `network-only` | No effect | No effect |");
60
- w("");
61
- // ── Auto-refetch (Window Focus / Reconnect / Interval) ──
62
- wb("## 🔄 Auto-refetch — keep data fresh automatically");
63
- w("Three events can trigger an automatic refetch at the hook level:");
64
- w("");
65
- w("- **refetchOnWindowFocus**: When the user returns to the tab, re-fetch stale data");
66
- w("- **refetchOnReconnect**: When the browser comes back online, re-fetch stale data");
67
- w("- **refetchInterval**: Poll the server every N seconds for fresh data");
68
- w("");
69
- w("These are configured at 3 tiers too:");
70
- w("- Global: `features.serviceWorker.refetchOnWindowFocus: true`");
71
- w("- Per-route: `\"/api/*\": { refetchOnWindowFocus: false }`");
72
- w("- Per-request: `useCachedFetch(url, { refetchOnWindowFocus: true })`");
73
- w("");
74
- if (ctx.frameworkName === "react") {
75
- w("### React Hook: `useMutation`");
76
- w("Track mutation state (loading, error, success) per-operation.");
77
- w("```tsx");
78
- w(`import { useMutation } from "./swoff/hooks/useMutation.${ext}x";`);
79
- w("");
80
- w('const { mutate, isLoading, isError, isSuccess, data, error, reset } = useMutation({');
81
- w(" onSuccess: (data) => console.log('done', data),");
82
- w(" onError: (err) => console.error('failed', err),");
83
- w("});");
84
- w("");
85
- w('mutate("/api/todos", { method: "POST", body: JSON.stringify({ title: "New" }) });');
86
- w("```");
87
- w("");
88
- w("### React Hook: `usePrefetch`");
89
- w("Warm the cache proactively, e.g., on link hover.");
90
- w("```tsx");
91
- w(`import { usePrefetch } from "./swoff/hooks/usePrefetch.${ext}x";`);
92
- w("");
93
- w("const prefetch = usePrefetch();");
94
- w('return <a onMouseEnter={() => prefetch("/api/todos")} href="/todos">Todos</a>;');
95
- w("```");
96
- w("");
97
- }
98
- // ── Fetch Wrapper ──
99
- wb("## 🌐 fetchWithCache — API calls with caching");
100
- w("A drop-in replacement for `fetch()` that communicates with the service worker about caching strategy.");
101
- w("GET requests are cached by the SW for offline access; POST/PUT/DELETE pass through.");
102
- w("");
103
- w("**Important:** Use `fetchWithCache` for all API calls — it sets the `X-SW-Cache-Strategy` header that");
104
- w("the SW uses to determine whether to apply a caching strategy. Plain `fetch()` works for uncached requests,");
105
- w("but if `cacheStrategy` is set to `\"explicit-only\"`, the SW will skip plain `fetch()` calls entirely.");
106
- w("");
107
- w("### `fetch-wrapper.ts`");
108
- w("```ts");
109
- w(`import { fetchWithCache } from "./swoff/fetch-wrapper.${ext}";`);
110
- w("");
111
- w("// GET — cached for offline");
112
- w('const todos = await fetchWithCache("/api/todos").then(r => r.json());');
113
- w("");
114
- w("// POST — passes through to server");
115
- w("await fetchWithCache(\"/api/todos\", {");
116
- w(' method: "POST",');
117
- w(' body: JSON.stringify({ title: "New task" }),');
118
- w("});");
119
- w("```");
120
- w("");
121
- w("**Functions:**");
122
- w("- `fetchWithCache(input, options?)` — main fetch wrapper. Use for all API calls.");
123
- w("- `fetchWithCache(input, options?)` — unified fetch wrapper. Auto-queues writes when offline (disable with `queueOffline: false`).");
124
- w("");
125
- w("**Returns** `{ response: Response, fromCache: boolean }` — `fromCache` lets the UI show stale indicators when a stale-while-revalidate fallback is served.");
126
- w("");
127
- w("**Note:** For authenticated requests, pass `{ auth: true }` — there is no separate auth fetch wrapper.");
128
- w("");
129
- if (ctx.frameworkName === "react") {
130
- w("### React Hook: `useCachedFetch`");
131
- w("Re-fetches automatically when the SW invalidates related cache tags.");
132
- w("```tsx");
133
- w(`import { useCachedFetch } from "./swoff/hooks/useCachedFetch.${ext}x";`);
134
- w("");
135
- w('const { data, error, loading, refetch } = useCachedFetch<Todo[]>("/api/todos");');
136
- w("```");
12
+ if (ctx.config.features.pwa.enabled && ctx.config.features.pwa.assets.source) {
13
+ w("## PWA Assets");
137
14
  w("");
138
- w("**Returns** `{ data: T | null, error, loading, refetch }` `data` is the parsed JSON response.");
15
+ w("PWA assets (icons, favicon, OG image, splash screens) will be generated from your");
16
+ w(` source logo during \`swoff generate\`.`);
139
17
  w("");
140
- w("The hook listens for `cache-invalidated` events (when tag invalidation is enabled) and automatically");
141
- w("re-fetches if the event's tags match the URL. Call `refetch()` to manually refresh.");
142
- w("");
143
- w("### Dependent queries");
144
- w("Use `enabled: false` or pass a nullable URL to skip fetching until a condition is met.");
145
- w("When `enabled` becomes `true` or the URL becomes non-null, the query automatically starts fetching.");
146
- w("```tsx");
147
- w('const { data: user } = useCachedFetch<User>("/api/me");');
148
- w('const { data: posts } = useCachedFetch<Post[]>(user ? "/api/posts" : null);');
149
- w("// or");
150
- w('const { data: posts2 } = useCachedFetch<Post[]>("/api/posts", { enabled: !!user });');
151
- w("```");
152
- w("");
153
- w("### Query cancellation (AbortController)");
154
- w("`fetchWithCache` integrates with the dedup map so duplicate requests are automatically deduplicated.");
155
- w("Pass an AbortSignal to cancel an in-flight request:");
156
- w("```tsx");
157
- w("useEffect(() => {");
158
- w(" const ctrl = new AbortController();");
159
- w(' fetchWithCache("/api/search", { signal: ctrl.signal });');
160
- w(" return () => ctrl.abort();");
161
- w("}, [query]);");
162
- w("```");
163
- w("");
164
- w("### React Hook: `useNetworkStatus`");
165
- w("Tracks online/offline state reactively.");
166
- w("```tsx");
167
- w(`import { useNetworkStatus } from "./swoff/hooks/useNetworkStatus.${ext}x";`);
168
- w("");
169
- w("const online = useNetworkStatus();");
170
- w("```");
171
- w("");
172
- w("**Returns** `boolean` — `true` when online, `false` when offline.");
173
- w("");
174
- }
175
- // ── Cache Strategy Resolution ──
176
- wb("## 🎯 Cache Strategy Resolution");
177
- w("The SW uses a 3-tier priority system to determine which caching strategy applies to each request:");
178
- w("");
179
- w("1. **Per-request override (highest)** — set `strategy` or `staleWhileRevalidate` on `fetchWithCache()`.");
180
- w(" Sent as `X-SW-Strategy` header to the SW.");
181
- w("2. **URL pattern match** — configured in `swoff.config.json` under `features.serviceWorker.strategies`.");
182
- w(" e.g. `\"/api/*\": \"network-first\"` matches all paths starting with `/api/`.");
183
- w("3. **Default (lowest)** — `features.serviceWorker.defaultStrategy` (default: `\"cache-first\"`).");
184
- w("");
185
- w("### Cache strategy mode");
186
- w("The `features.serviceWorker.cacheStrategy` option controls when strategies are invoked:");
187
- w("");
188
- w("- `\"all\"` (default): every GET/HEAD request goes through strategy dispatch, including plain `fetch()` calls.");
189
- w("- `\"explicit-only\"`: only requests with an `X-SW-Cache-Strategy` header (set automatically by `fetchWithCache()`)");
190
- w(" are processed by the SW strategy system. Plain `fetch()` calls pass through unmodified.");
191
- w("");
192
- w("### Request dispatch flow");
193
- w("Each GET/HEAD request follows this path through the SW:");
194
- w("");
195
- w("```");
196
- w("navigation (SPA fallback) → precache check → strategy dispatch → network pass-through");
197
- w("```");
198
- w("");
199
- w("### Available strategies");
200
- w("");
201
- w("| Strategy | Behavior (without staleTime) | Behavior (with staleTime) | Best for |");
202
- w("|----------|------------------------------|---------------------------|----------|");
203
- w("| `cache-first` | Return cached if available, else fetch + cache. Default | Fresh: pure cache. Stale: cache + bg refresh | Static assets, images, fonts |");
204
- w("| `network-first` | Try network, cache on success, fall back to cache | Fresh: pure cache (skip network!). Stale: try network | API endpoints, dynamic content |");
205
- w("| `stale-while-revalidate` | Return cached immediately, refresh in background | Fresh: pure cache (no refresh). Stale: cache + bg refresh | Fast UI, non-critical data |");
206
- w("| `cache-only` | Serve from cache only (404 if missing) | Fresh: pure cache. Stale: cache + best-effort refresh | Offline-critical assets |");
207
- w("| `network-only` | Always fetch, never cache | No effect | Sensitive or real-time data |");
208
- w("");
209
- // ── GraphQL ──
210
- if (config.features.graphql.enabled) {
211
- wb("## ⚡ GraphQL");
212
- w("Swoff brings caching, offline queue, auth, and tag-based invalidation to GraphQL APIs via `fetchWithGql`. It hashes");
213
- w("the query + variables into a deterministic cache key (`X-SW-Cache-Key`) for SW-level caching, and auto-generates");
214
- w("tags from operation names for automatic cache invalidation after mutations.");
215
- w("");
216
- w("### `gql-wrapper.ts`");
217
- w("```ts");
218
- w(`import { queryGql, mutateGql } from "./swoff/gql-wrapper.${ext}";`);
219
- w("");
220
- w("// Query — cached with body-hash key");
221
- w('const { data } = await queryGql("{ todos { id title } }");');
222
- w("");
223
- w("// Query with variables");
224
- w('const { data: todo } = await queryGql(');
225
- w(' "query GetTodo($id: ID!) { todo(id: $id) { id title } }",');
226
- w(' { id: "42" },');
227
- w(");");
228
- w("");
229
- w("// Mutation — auto-invalidates 'todos' cache");
230
- w('const { data: created } = await mutateGql(');
231
- w(' "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",');
232
- w(' { title: "New task" },');
233
- w(");");
234
- w("");
235
- w("// With auth, stale-while-revalidate, and custom tags");
236
- w('const { data, fromCache } = await queryGql(');
237
- w(' "query Me { me { name } }",');
238
- w(" {},");
239
- w(' { auth: true, staleWhileRevalidate: true, tags: ["users"] },');
240
- w(");");
241
- w("```");
242
- w("");
243
- w("**How it works:**");
244
- w("- Queries are POSTed as `type: \"read\"` with `X-SW-Cache-Key: gql:<sha256-hash>`");
245
- w("- The SW caches responses under a virtual URL (`/__swc/gql:<hash>`) so different queries never collide");
246
- w("- Mutations are POSTed as `type: \"mutation\"` — auto-invalidate tags from operation name");
247
- w("- Offline mutations are queued via the mutation queue (if enabled)");
248
- w("- Auth, strategy override, and custom tags work the same as `fetchWithCache`");
249
- w("");
250
- w("**Functions:**");
251
- w("- `fetchWithGql<T>(query, options?)` — core GQL fetch with all Swoff features");
252
- w("- `queryGql<T>(query, variables?, options?)` — shorthand for queries");
253
- w("- `mutateGql<T>(mutation, variables?, options?)` — shorthand for mutations");
254
- w("");
255
- w("**Config:**");
256
- w("```json");
257
- w('"graphql": { "enabled": true, "endpoint": "/graphql" }');
258
- w("```");
259
- w("");
260
- }
261
- // ── Mutation Queue ──
262
- if (config.features.mutationQueue.enabled) {
263
- wb("## 📝 Mutation Queue — offline writes that sync when back online");
264
- w("When the user is offline and performs a write (POST/PUT/PATCH/DELETE), `queueMutation` stores it");
265
- w("in IndexedDB. When the connection returns, `processMutationQueue` replays them in order.");
18
+ w("To regenerate manually:");
266
19
  w("");
267
- w("**Configurable batching:** set `batchSize`, `batchDelayMs`, `maxRetries`, and `retryBackoffMs` in `swoff.config.json` under `features.mutationQueue`.");
268
- w("- `batchSize` (default 1) — mutations per progress event");
269
- w("- `batchDelayMs` (default 0) — delay between mutations (rate limiting)");
270
- w("- `maxRetries` (default 5) — max attempts before dropping");
271
- w("- `retryBackoffMs` (default 1000) — exponential backoff base (nextRetry = backoff × 2^retryCount)");
272
- w("");
273
- w("### `mutation-queue.ts`");
274
- w("```ts");
275
- w(`import { queueMutation, processMutationQueue, flushMutations, getPendingCount } from "./swoff/mutation-queue.${ext}";`);
276
- w("");
277
- w("// Queue an offline write");
278
- w("await queueMutation({");
279
- w(' method: "POST",');
280
- w(' url: "/api/todos",');
281
- w(' body: { title: "Grocery" },');
282
- w(' tags: ["todos"],');
283
- w("});");
284
- w("");
285
- w("// Flush after re-login (mutations queued while offline may fail with 401)");
286
- w("await flushMutations();");
287
- w("```");
288
- w("");
289
- w("**Functions:**");
290
- w("- `queueMutation(mutation)` — store a write for later sync");
291
- w("- `processMutationQueue()` — replay all queued writes. Respects batchDelayMs, maxRetries, retryBackoffMs.");
292
- w("- `flushMutations()` — same as processMutationQueue. Call after re-login.");
293
- w("- `getPendingCount()` — number of mutations waiting to sync.");
294
- w("");
295
- if (config.features.backgroundSync) {
296
- w("### `background-sync.ts` — Sync even after tab close");
297
- w("Uses the Background Sync API to register a sync event so mutations are processed even if the user");
298
- w("closes the tab. Falls back to the `online` event listener in unsupported browsers (Firefox, Safari).");
299
- w("```ts");
300
- w(`import { syncWhenPossible } from "./swoff/background-sync.${ext}";`);
301
- w("await syncWhenPossible({ method: \"POST\", url: \"/api/todos\", body: { ... } });");
302
- w("```");
303
- w("");
304
- w("**Functions:**");
305
- w("- `syncWhenPossible(mutation)` — queue and register background sync");
306
- w("- `retrySync()` — re-register sync if mutations are still pending (called automatically)");
307
- w("");
308
- w("> ⚠️ Background Sync is Chrome/Edge only. Not supported in Firefox or Safari.");
309
- w("");
310
- if (ctx.frameworkName === "react") {
311
- w("### React Hook: `useBackgroundSync`");
312
- w("Reactive background sync state and trigger.");
313
- w("```tsx");
314
- w(`import { useBackgroundSync } from "./swoff/hooks/useBackgroundSync.${ext}x";`);
315
- w("");
316
- w('const { supported, registered, lastSync, triggerSync } = useBackgroundSync();');
317
- w("```");
318
- w("");
319
- w("**Returns** `{ supported, registered, lastSync, triggerSync }` — `triggerSync()` calls `syncWhenPossible()`.");
320
- w("");
321
- }
322
- }
323
- }
324
- // ── Auth ──
325
- if (config.features.auth.enabled) {
326
- wb("## 🔐 Auth — token management and authenticated requests");
327
- w("Swoff's auth module manages authentication state with a **memory-only token** (never persisted to");
328
- w("IndexedDB) and optional offline user info caching.");
329
- w("");
330
- const authType = config.features.auth.type;
331
- w(`Auth type: **${authType}**`);
332
- w("");
333
- if (authType === "bearer") {
334
- w("> ⚠️ The Bearer token lives **in memory only** and is cleared on page refresh.");
335
- w("> Only `{ user, expiresAt }` is persisted to IndexedDB for offline user display.");
336
- w("> After a page refresh, re-login is required. Use the `refreshPath` for token refresh.");
337
- w("");
338
- }
339
- w("### `auth/store.ts` — Token and user persistence");
340
- w("```ts");
341
- w(`import { setAuth, getAuth, clearAuth, isAuthValid, createAuthFromResponse } from "./swoff/auth/store.${ext}";`);
342
- w("");
343
- w("// After successful login, store auth data");
344
- w("await setAuth({ token, user, expiresAt });");
345
- w("");
346
- w("// Check if still authenticated");
347
- w("const auth = await getAuth();");
348
- w("if (!isAuthValid(auth)) { /* redirect to login */ }");
349
- w("```");
350
- w("");
351
- w("**Where to edit:**");
352
- w("- `createAuthFromResponse(response)` — **edit this** to match your backend's login response shape.");
353
- w("");
354
- w("**Functions:**");
355
- w("- `setAuth(authData)` — store in memory + persist user to IndexedDB");
356
- w("- `getAuth()` — get from memory (or IndexedDB after refresh)");
357
- w("- `clearAuth()` — clear everything (call on logout/401)");
358
- w("- `isAuthValid(auth)` — check expiry");
359
- w("- `createAuthFromResponse(response)` — extract AuthData from login response. **Edit this.**");
360
- w("");
361
- w("### Authenticated API calls with fetchWithCache");
362
- w("Use `fetchWithCache` with `auth: true` for all authenticated requests — no separate auth fetch needed.");
363
- w("```ts");
364
- w(`import { fetchWithCache } from "./swoff/fetch-wrapper.${ext}";`);
365
- w(`import { ensureValidAuth } from "./swoff/auth/store.${ext}";`);
366
- w("");
367
- w("// Authenticated GET");
368
- w('const { response } = await fetchWithCache("/api/me", { auth: true });');
369
- w('const user = await response.json();');
370
- w("");
371
- w("// Authenticated POST (mutation)");
372
- w('await fetchWithCache("/api/todos", {');
373
- w(' method: "POST",');
374
- w(' body: JSON.stringify({ title: "New" }),');
375
- w(' auth: true,');
376
- w("});");
377
- w("```");
378
- w("");
379
- w("**Functions:**");
380
- w("- `fetchWithCache(input, options)` — pass `{ auth: true }` for auth headers, cache bypass for auth endpoints, and 401 handling.");
381
- w("- `ensureValidAuth()` — check expiry and refresh token if needed (uses refreshPath from config).");
382
- w("");
383
- w("**Where to edit:**");
384
- w("- The `isAuthUrl` function in `auth/store.ts` lists auth endpoints that bypass the SW cache. Edit this list if your backend uses different paths.");
385
- w("- If your auth type is `custom`, edit the `withAuthHeaders` function in `auth/store.ts`.");
386
- w("");
387
- w("### `auth/user.ts` — User data caching");
388
- w("```ts");
389
- w(`import { fetchCurrentUser, getCachedUser, cacheUser, clearCachedUser } from "./swoff/auth/user.${ext}";`);
390
- w("");
391
- w("// Fetch and cache the current user");
392
- w("const user = await fetchCurrentUser();");
393
- w("```");
394
- w("");
395
- w("**Functions:**");
396
- w("- `fetchCurrentUser()` — fetch from user endpoint and cache in IndexedDB");
397
- w("- `getCachedUser()` — load user from IndexedDB (available offline)");
398
- w("- `cacheUser(user)` — persist user object manually");
399
- w("- `clearCachedUser()` — remove user from cache (call on logout)");
400
- w("");
401
- w("### `auth/state.ts` — Auth state detection");
402
- w("Detects which of the 4 states the app is in: online+authenticated, online+unauthenticated, offline+authenticated, offline+unauthenticated.");
403
- w("```ts");
404
- w(`import { getAuthState } from "./swoff/auth/state.${ext}";`);
405
- w('const { authenticated, user, online } = await getAuthState();');
406
- w("```");
407
- w("");
408
- if (ctx.frameworkName === "react") {
409
- w("### React Hooks");
410
- w("- `useAuth()` — returns `{ authenticated, user, online }`, listens to online/offline/auth changes");
411
- w("- `useCachedFetch(url, options?)` — fetches with auto-refetch on tag invalidation, see Fetch Wrapper section");
412
- w("- `useNetworkStatus()` — returns `boolean`, standalone online/offline tracker, see Fetch Wrapper section");
413
- w("");
414
- }
415
- }
416
- // ── Tag Invalidation ──
417
- if (config.features.tagInvalidation) {
418
- wb("## 🏷️ Tag Invalidation — keep cached data fresh");
419
- w("When data changes on the server, cached responses in the SW become stale. Tag invalidation");
420
- w("lets you mark related cache entries as stale so they're re-fetched on next request.");
421
- w("");
422
- w("### How it works");
423
- w("1. When fetching, attach tags: `fetchWithCache(url, { tags: generateTags(url) })`");
424
- w("2. After a mutation, invalidate: `await invalidateUrl(url)`");
425
- w("3. The SW removes all cached responses that were tagged with the related tags");
426
- w("");
427
- w("### `invalidation-tags.ts` — Tag generation helpers");
428
- w("```ts");
429
- w(`import { generateTags, invalidateUrl } from "./swoff/invalidation-tags.${ext}";`);
430
- w("");
431
- w('// Tag reads');
432
- w('const data = await fetchWithCache("/api/todos", { tags: generateTags("/api/todos") });');
433
- w("");
434
- w('// Invalidate after writing');
435
- w('await invalidateUrl("/api/todos/42");');
436
- w("```");
437
- w("");
438
- w("**Functions:**");
439
- w("- `generateTags(url)` — extract tags from a URL path. e.g. `/api/todos/42` → `[\"todos\", \"todo:42\"]`");
440
- w("- `generateTagsFromMethod(method, url)` — method-prefixed tags. e.g. `post-todos`");
441
- w("- `invalidateUrl(url)` — extract tags and invalidate all matching cache entries");
442
- w("- `invalidateByMethod(method, url)` — invalidate using method-prefixed tags");
443
- w("");
444
- w("### `cache.ts` — Low-level invalidation");
445
- w("```ts");
446
- w(`import { invalidateByTag, invalidateByTags } from "./swoff/cache.${ext}";`);
447
- w("");
448
- w('await invalidateByTag("todos");');
449
- w('await invalidateByTags(["todos", "categories"]);');
450
- w("```");
451
- w("");
452
- w("**Functions:**");
453
- w("- `invalidateByTag(tag)` — invalidate a single tag. Dispatches `cache-invalidated` event.");
454
- w("- `invalidateByTags(tags)` — invalidate multiple tags.");
455
- w("");
456
- if (ctx.frameworkName === "react") {
457
- w("### React Hook: `useCacheInvalidation`");
458
- w("Reactive wrapper around cache invalidation functions.");
459
- w("```tsx");
460
- w(`import { useCacheInvalidation } from "./swoff/hooks/useCacheInvalidation.${ext}x";`);
461
- w("");
462
- w('const { invalidateByTag, invalidateByTags, invalidateUrl } = useCacheInvalidation();');
463
- w("```");
464
- w("");
465
- w("Returns stable `useCallback`-wrapped versions of each invalidation function.");
466
- w("");
467
- }
468
- }
469
- // ── Cross-tab Sync ──
470
- if (config.features.crossTabSync) {
471
- wb("## 🔄 Cross-tab Sync — keep tabs in sync");
472
- w("When the user opens your app in multiple browser tabs, changes in one tab (logout, mutation sync)");
473
- w("are broadcast to all other tabs via the service worker.");
474
- w("");
475
- w("No separate imports needed — this is handled automatically by `client-injector.ts`.");
476
- w("The service worker listens for invalidation events and forwards them to all clients.");
477
- w("");
478
- if (!config.features.tagInvalidation) {
479
- w("> ⚠️ Cross-tab sync requires tag invalidation to be enabled for full functionality.");
480
- w("");
481
- }
482
- }
483
- // ── Server Push ──
484
- if (config.features.serverPush.enabled) {
485
- wb("## 📡 Server Push Events — real-time cache invalidation");
486
- w("Instead of polling, the service worker maintains an SSE or WebSocket connection to your push endpoint.");
487
- w("When the server signals that data has changed (via an `invalidate` event), the SW automatically");
488
- w("calls `invalidateByTag()` so the next read gets fresh data.");
489
- w("");
490
- const pushType = config.features.serverPush.type;
491
- w(`### Transport: **${pushType.toUpperCase()}**`);
492
- if (pushType === "sse") {
493
- w("The SW establishes an `EventSource` connection. The server sends events with event name `invalidate`");
494
- w("and a JSON payload: `{ tags: string[] }`. On receiving it, the SW calls `invalidateByTags(tags)`.");
495
- w("");
496
- w("**Server format (SSE):**");
497
- w("```");
498
- w("event: invalidate");
499
- w("data: {\"tags\":[\"todos\",\"categories\"]}");
500
- w("");
501
- w("```");
502
- }
503
- else {
504
- w("The SW establishes a WebSocket connection. The server sends text frames with JSON payload:");
505
- w('`{ "event": "invalidate", "tags": string[] }`. On receiving it, the SW calls `invalidateByTags(tags)`.');
506
- w("");
507
- w("**Server format (WebSocket):**");
508
- w("```json");
509
- w('{ "event": "invalidate", "tags": ["todos", "categories"] }');
510
- w("```");
511
- }
512
- w("");
513
- w("### `server-push.ts` — Client-side connection manager");
514
- w("```ts");
515
- w(`import { startPushEvents, stopPushEvents, isPushConnected } from "./swoff/server-push.${ext}";`);
516
- w("");
517
- w("// Start listening for push events");
518
- w("startPushEvents();");
519
- w("");
520
- w("// Check connection");
521
- w('if (isPushConnected()) {');
522
- w(' console.log("Connected to push endpoint");');
523
- w("}");
524
- w("```");
525
- w("");
526
- w("**Functions:**");
527
- w("- `startPushEvents()` — connect to the push endpoint and begin listening for invalidation events");
528
- w("- `stopPushEvents()` — disconnect from the push endpoint");
529
- w("- `isPushConnected()` — check if the connection is active");
530
- w("");
531
- w("> ⚠️ The service worker directly manages the SSE/WS connection for reliability across page navigations.");
532
- w("> The client-side `server-push.ts` is a fallback that starts the connection when the SW is not yet active.");
533
- w("");
534
- if (ctx.frameworkName === "react") {
535
- w("### React Hook: `useServerPush()` (coming in a future release)");
536
- w("");
537
- }
538
- }
539
- // ── Push Notifications ──
540
- if (config.features.pushNotifications?.enabled) {
541
- wb("## 🔔 Push Notifications — subscription management");
542
- w("Swoff generates a push notification subscription client with IndexedDB persistence");
543
- w("and the service worker push event handlers.");
544
- w("");
545
- w("### `push.ts` — Client-side subscription management");
546
- w("```ts");
547
- w(`import { subscribeToPush, unsubscribeFromPush, isSubscribed } from "./swoff/push.${ext}";`);
548
- w("");
549
- w("// Subscribe (triggers permission prompt)");
550
- w('const sub = await subscribeToPush("YOUR_VAPID_PUBLIC_KEY");');
551
- w("if (sub) {");
552
- w(' await fetch("/api/push/subscribe", {');
553
- w(' method: "POST",');
554
- w(" body: JSON.stringify(sub.toJSON()),");
555
- w(" });");
556
- w("}");
557
- w("");
558
- w("// Unsubscribe");
559
- w("await unsubscribeFromPush();");
560
- w("```");
561
- w("");
562
- w("**Functions:**");
563
- w("- `subscribeToPush(vapidPublicKey)` — request permission and subscribe");
564
- w("- `unsubscribeFromPush()` — unsubscribe and clear stored subscription");
565
- w("- `isSubscribed()` — check if subscribed");
566
- w("- `getPushSubscription()` — get current PushSubscription object");
567
- w("- `requestNotificationPermission()` — request permission only (returns boolean)");
568
- w("");
569
- if (ctx.frameworkName === "react") {
570
- w("### React Hook: `usePushSubscription`");
571
- w("```tsx");
572
- w(`import { usePushSubscription } from "./swoff/hooks/usePushSubscription.${ext}x";`);
573
- w("");
574
- w('const { subscribed, subscription, permission, loading, subscribe, unsubscribe } =');
575
- w(' usePushSubscription("YOUR_VAPID_PUBLIC_KEY");');
576
- w("```");
577
- w("");
578
- w("**Returns** `{ subscribed, subscription, permission, loading, subscribe, unsubscribe }`");
579
- w("The hook listens for push-subscription-changed and push-permission-changed events.");
580
- w("Use `subscribe()` and `unsubscribe()` to toggle push notifications.");
581
- w("");
582
- }
583
- }
584
- // ── PWA ──
585
- if (config.features.pwa.enabled) {
586
- wb("## 📱 PWA — installable web app");
587
- w("Swoff adds a beforeinstallprompt handler and install flow so users can install your app");
588
- w("on their home screen.");
20
+ w(" swoff assets --source <path>");
589
21
  w("");
590
- w("### `pwa/install.ts`");
591
- w("```ts");
592
- w(`import { setupPwaInstall, isInstallable, promptInstall } from "./swoff/pwa/install.${ext}";`);
22
+ w("### What gets generated");
593
23
  w("");
594
- w("setupPwaInstall(); // called automatically by client-injector.ts");
24
+ w("- PWA icons: 64×64, 192×192, 512×512, maskable 512×512");
25
+ w("- Apple touch icon: 180×180");
26
+ w("- Apple splash screens: all device resolutions");
27
+ w("- Favicon: SVG + ICO (16×16, 32×32, 48×48)");
28
+ w("- OG image: 1200×630 (also used for Twitter card)");
595
29
  w("");
596
- w("// Show install button when available");
597
- w("if (isInstallable()) {");
598
- w(' const { outcome } = await promptInstall();');
599
- w("}");
600
- w("```");
30
+ w("After generation, reference these assets in your app manually. Run \`swoff assets\` for a copy-paste guide with the exact <link> and <meta> tags.");
601
31
  w("");
602
- w("**Functions:**");
603
- w("- `setupPwaInstall()` — listen for beforeinstallprompt/appinstalled events (called by client-injector)");
604
- w("- `isInstallable()` — check if install prompt is available");
605
- w("- `promptInstall()` — show the native install prompt");
32
+ w("### Source logo requirements");
606
33
  w("");
607
- w("### `manifest.json`");
608
- w("Generated in `swoff/manifest.json`. If you want it exposed at the root, copy it to your `public/` directory.");
34
+ w("- **SVG** recommended — produces the best quality at all sizes");
35
+ w("- PNG or JPG accepted will be upscaled if needed");
36
+ w("- Minimum 512×512 for crisp icons");
37
+ w("- Transparent backgrounds work well with theme colors");
609
38
  w("");
610
- if (ctx.frameworkName === "react") {
611
- w("### React Hooks");
612
- w("- `useSWUpdate()` — returns `{ updateStatus, currentVersion, availableVersion, forceUpdate, error, acceptUpdate, dismissUpdate }`");
613
- w("- `useSWProgress()` — returns `{ status, progress }` for download progress during SW update");
614
- w("- `useCachedFetch(url, options?)` — fetches with auto-refetch on tag invalidation, see Fetch Wrapper section");
615
- w("");
616
- }
617
39
  }
618
- // ── Build Script ──
619
- wb("## 🏗️ Build script");
620
- w("The SW generator must run after every build to produce the final service worker file.");
621
- w("Swoff has already added this to your `package.json` build script for you:");
622
- w("```");
623
- w('"build": "<your-build> && node swoff/sw/generator.js"');
624
- w("```");
625
- w("If you run `swoff clean`, this script suffix will be removed automatically.");
40
+ w("For the full documentation, visit:");
626
41
  w("");
627
- // ── Config file ──
628
- wb("## ⚙️ swoff.config.json");
629
- w("This is the configuration file that controls which features are enabled and how they behave.");
630
- w("Re-run `npx @swoff/cli generate` after changing it.");
42
+ w("- **Documentation** https://swoff.dev/docs");
631
43
  w("");
632
- w("### Features you can toggle:");
633
- w("- `mutationQueue.enabled` — offline write queue with IndexedDB. Object: `{ enabled, batchSize, batchDelayMs, maxRetries, retryBackoffMs }`");
634
- w("- `backgroundSync` — Background Sync API (Chrome/Edge only)");
635
- w("- `auth.enabled` — auth module (bearer/cookie/custom)");
636
- w("- `crossTabSync` — broadcast changes across tabs");
637
- w("- `tagInvalidation` — cache invalidation by tags");
638
- w("- `graphql.enabled` — GraphQL wrapper with body-hash caching. Object: `{ enabled, endpoint }`");
639
- w("- `pwa.enabled` — PWA install prompt and manifest");
640
- w("- `serverPush.enabled` — real-time cache invalidation via SSE/WebSocket. Object: `{ enabled, type, endpoint, reconnectDelayMs }`");
641
- w("- `serviceWorker.cacheStrategy` — caching strategy mode (`\"all\"` or `\"explicit-only\"`)");
642
- w("- `serviceWorker.defaultStrategy` — default caching strategy");
643
- w("- `serviceWorker.strategies` — per-route strategy overrides");
644
- w("- `serviceWorker.staleTime` — global stale time in seconds (data considered fresh for N seconds)");
645
- w("- `serviceWorker.refetchOnWindowFocus` — auto-refetch on tab focus (hook-level)");
646
- w("- `serviceWorker.refetchOnReconnect` — auto-refetch on reconnect");
647
- w("- `serviceWorker.refetchInterval` — auto-refetch every N seconds");
648
- w("- `serviceWorker.maxCacheEntries` — max entries in runtime cache (oldest evicted)");
649
- w("- `serviceWorker.maxCacheAge` — max age of cache entries in ms");
44
+ w("- **CLI Reference** `swoff generate`, `swoff validate`, etc. See [CLI.md](./CLI.md)");
650
45
  w("");
651
- w("---");
652
46
  writeFile(ctx, "GUIDE.md", lines.join("\n"));
653
47
  }
654
48
  //# sourceMappingURL=guide-generator.js.map