@swoff/cli 0.3.9 → 0.3.11

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 (216) hide show
  1. package/dist/__tests__/assemble-sw.test.js +8 -17
  2. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  3. package/dist/__tests__/cli-integration.test.js +2 -2
  4. package/dist/__tests__/cli-integration.test.js.map +1 -1
  5. package/dist/__tests__/config-types.test.js +18 -8
  6. package/dist/__tests__/config-types.test.js.map +1 -1
  7. package/dist/__tests__/file-generators.test.js +3 -3
  8. package/dist/__tests__/file-generators.test.js.map +1 -1
  9. package/dist/__tests__/loader.test.js +5 -1
  10. package/dist/__tests__/loader.test.js.map +1 -1
  11. package/dist/__tests__/validator.test.js +6 -13
  12. package/dist/__tests__/validator.test.js.map +1 -1
  13. package/dist/index.js +41 -35
  14. package/dist/index.js.map +1 -1
  15. package/dist/lib/cli/help.js +13 -8
  16. package/dist/lib/cli/help.js.map +1 -1
  17. package/dist/lib/commands/add.js +38 -8
  18. package/dist/lib/commands/add.js.map +1 -1
  19. package/dist/lib/commands/assets.js +38 -0
  20. package/dist/lib/commands/assets.js.map +1 -0
  21. package/dist/lib/commands/clean.js +33 -17
  22. package/dist/lib/commands/clean.js.map +1 -1
  23. package/dist/lib/commands/generate-assets.js +44 -0
  24. package/dist/lib/commands/generate-assets.js.map +1 -0
  25. package/dist/lib/commands/generate-guide.js +22 -29
  26. package/dist/lib/commands/generate-guide.js.map +1 -1
  27. package/dist/lib/commands/generate.js +16 -9
  28. package/dist/lib/commands/generate.js.map +1 -1
  29. package/dist/lib/commands/info.js +36 -45
  30. package/dist/lib/commands/info.js.map +1 -1
  31. package/dist/lib/commands/init.js +25 -3
  32. package/dist/lib/commands/init.js.map +1 -1
  33. package/dist/lib/commands/validate.js +1 -1
  34. package/dist/lib/commands/validate.js.map +1 -1
  35. package/dist/lib/config/loader.js +25 -20
  36. package/dist/lib/config/loader.js.map +1 -1
  37. package/dist/lib/config/validator.js +20 -11
  38. package/dist/lib/config/validator.js.map +1 -1
  39. package/dist/lib/generators/asset-generator/dependency-resolver.js +76 -0
  40. package/dist/lib/generators/asset-generator/dependency-resolver.js.map +1 -0
  41. package/dist/lib/generators/asset-generator/generate.js +111 -0
  42. package/dist/lib/generators/asset-generator/generate.js.map +1 -0
  43. package/dist/lib/generators/asset-generator/guide.js +43 -0
  44. package/dist/lib/generators/asset-generator/guide.js.map +1 -0
  45. package/dist/lib/generators/asset-generator/html-patcher.js +65 -0
  46. package/dist/lib/generators/asset-generator/html-patcher.js.map +1 -0
  47. package/dist/lib/generators/asset-generator/ico-encoder.js +29 -0
  48. package/dist/lib/generators/asset-generator/ico-encoder.js.map +1 -0
  49. package/dist/lib/generators/asset-generator/manifest-patcher.js +23 -0
  50. package/dist/lib/generators/asset-generator/manifest-patcher.js.map +1 -0
  51. package/dist/lib/generators/asset-generator/maskable.js +15 -0
  52. package/dist/lib/generators/asset-generator/maskable.js.map +1 -0
  53. package/dist/lib/generators/asset-generator/rasterize.js +32 -0
  54. package/dist/lib/generators/asset-generator/rasterize.js.map +1 -0
  55. package/dist/lib/generators/asset-generator/sizes.js +25 -0
  56. package/dist/lib/generators/asset-generator/sizes.js.map +1 -0
  57. package/dist/lib/generators/file-generators/api-config.js +6 -0
  58. package/dist/lib/generators/file-generators/api-config.js.map +1 -0
  59. package/dist/lib/generators/file-generators/auth-state.js +2 -34
  60. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  61. package/dist/lib/generators/file-generators/auth-store.js +2 -250
  62. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  63. package/dist/lib/generators/file-generators/auth-user.js +3 -81
  64. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  65. package/dist/lib/generators/file-generators/background-sync.js +2 -57
  66. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  67. package/dist/lib/generators/file-generators/cache.js +2 -30
  68. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  69. package/dist/lib/generators/file-generators/client-injector.js +2 -130
  70. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  71. package/dist/lib/generators/file-generators/fetch-state.js +6 -0
  72. package/dist/lib/generators/file-generators/fetch-state.js.map +1 -0
  73. package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -295
  74. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  75. package/dist/lib/generators/file-generators/generate-framework-adapters.js +56 -0
  76. package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -0
  77. package/dist/lib/generators/file-generators/generate-hooks.js +4 -3
  78. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  79. package/dist/lib/generators/file-generators/gql-wrapper.js +3 -132
  80. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
  81. package/dist/lib/generators/file-generators/guide-generator.js +29 -1
  82. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  83. package/dist/lib/generators/file-generators/invalidation-tags.js +3 -258
  84. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  85. package/dist/lib/generators/file-generators/manifest.js +13 -3
  86. package/dist/lib/generators/file-generators/manifest.js.map +1 -1
  87. package/dist/lib/generators/file-generators/mutation-queue.js +3 -305
  88. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  89. package/dist/lib/generators/file-generators/mutation-state.js +2 -117
  90. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
  91. package/dist/lib/generators/file-generators/push.js +6 -138
  92. package/dist/lib/generators/file-generators/push.js.map +1 -1
  93. package/dist/lib/generators/file-generators/pwa-install.js +11 -84
  94. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  95. package/dist/lib/generators/file-generators/reset.js +6 -0
  96. package/dist/lib/generators/file-generators/reset.js.map +1 -0
  97. package/dist/lib/generators/file-generators/server-push.js +3 -134
  98. package/dist/lib/generators/file-generators/server-push.js.map +1 -1
  99. package/dist/lib/generators/file-generators/sw-generator-build.js +4 -4
  100. package/dist/lib/generators/file-generators/sw-injector.js +9 -230
  101. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  102. package/dist/lib/generators/file-generators/type-definitions.js +2 -111
  103. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  104. package/dist/lib/generators/sw-generator.js +5 -5
  105. package/dist/lib/generators/sw-generator.js.map +1 -1
  106. package/dist/lib/generators/sw-sections/activate-handler.js +25 -3
  107. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  108. package/dist/lib/generators/sw-sections/assemble-sw.js +13 -12
  109. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  110. package/dist/lib/generators/sw-sections/background-sync-handler.js +14 -7
  111. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  112. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  113. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  114. package/dist/lib/generators/sw-sections/fetch-handler.js +165 -104
  115. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  116. package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +1 -0
  117. package/dist/lib/generators/sw-sections/server-push-handler.js +12 -0
  118. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
  119. package/dist/lib/generators/sw-sections/tag-management.js +6 -4
  120. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  121. package/dist/lib/generators/swoff-files-generator.js +66 -35
  122. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  123. package/dist/lib/shared/config-types.js +89 -28
  124. package/dist/lib/shared/config-types.js.map +1 -1
  125. package/dist/runtime/api-config.js +18 -0
  126. package/dist/runtime/api-config.js.map +1 -0
  127. package/dist/runtime/auth-state.js +35 -0
  128. package/dist/runtime/auth-state.js.map +1 -0
  129. package/dist/runtime/auth-store.js +273 -0
  130. package/dist/runtime/auth-store.js.map +1 -0
  131. package/dist/runtime/auth-user.js +168 -0
  132. package/dist/runtime/auth-user.js.map +1 -0
  133. package/dist/runtime/background-sync.js +59 -0
  134. package/dist/runtime/background-sync.js.map +1 -0
  135. package/dist/runtime/cache.js +30 -0
  136. package/dist/runtime/cache.js.map +1 -0
  137. package/dist/runtime/client-injector.js +136 -0
  138. package/dist/runtime/client-injector.js.map +1 -0
  139. package/dist/runtime/fetch-state.js +42 -0
  140. package/dist/runtime/fetch-state.js.map +1 -0
  141. package/dist/runtime/fetch-wrapper.js +355 -0
  142. package/dist/runtime/fetch-wrapper.js.map +1 -0
  143. package/dist/runtime/gql-wrapper.js +135 -0
  144. package/dist/runtime/gql-wrapper.js.map +1 -0
  145. package/dist/runtime/invalidation-tags.js +260 -0
  146. package/dist/runtime/invalidation-tags.js.map +1 -0
  147. package/dist/runtime/mutation-queue.js +304 -0
  148. package/dist/runtime/mutation-queue.js.map +1 -0
  149. package/dist/runtime/mutation-state.js +117 -0
  150. package/dist/runtime/mutation-state.js.map +1 -0
  151. package/dist/runtime/push.js +138 -0
  152. package/dist/runtime/push.js.map +1 -0
  153. package/dist/runtime/pwa-index.js +11 -0
  154. package/dist/runtime/pwa-index.js.map +1 -0
  155. package/dist/runtime/pwa-injector.js +44 -0
  156. package/dist/runtime/pwa-injector.js.map +1 -0
  157. package/dist/runtime/pwa-install.js +83 -0
  158. package/dist/runtime/pwa-install.js.map +1 -0
  159. package/dist/runtime/pwa-prompt.js +54 -0
  160. package/dist/runtime/pwa-prompt.js.map +1 -0
  161. package/dist/runtime/reset.js +148 -0
  162. package/dist/runtime/reset.js.map +1 -0
  163. package/dist/runtime/server-push.js +148 -0
  164. package/dist/runtime/server-push.js.map +1 -0
  165. package/dist/runtime/sw-injector.js +236 -0
  166. package/dist/runtime/sw-injector.js.map +1 -0
  167. package/dist/runtime/type-definitions.js +138 -0
  168. package/dist/runtime/type-definitions.js.map +1 -0
  169. package/dist/runtime/utils.js +27 -0
  170. package/dist/runtime/utils.js.map +1 -0
  171. package/package.json +3 -2
  172. package/templates/react/useAuth.jsx +106 -0
  173. package/templates/react/useAuth.tsx +118 -0
  174. package/templates/{hooks → react}/useBackgroundSync.jsx +6 -4
  175. package/templates/{hooks → react}/useBackgroundSync.tsx +6 -4
  176. package/templates/{hooks → react}/useCacheInvalidation.jsx +2 -2
  177. package/templates/react/useCacheInvalidation.tsx +52 -0
  178. package/templates/react/useCachedFetch.jsx +140 -0
  179. package/templates/react/useCachedFetch.tsx +160 -0
  180. package/templates/react/useIsFetching.jsx +32 -0
  181. package/templates/react/useIsFetching.tsx +32 -0
  182. package/templates/react/useMutation.jsx +141 -0
  183. package/templates/react/useMutation.tsx +180 -0
  184. package/templates/react/useMutationQueue.jsx +69 -0
  185. package/templates/react/useMutationQueue.tsx +83 -0
  186. package/templates/react/useNetworkStatus.jsx +61 -0
  187. package/templates/react/useNetworkStatus.tsx +76 -0
  188. package/templates/react/usePrefetch.jsx +32 -0
  189. package/templates/react/usePrefetch.tsx +42 -0
  190. package/templates/{hooks → react}/usePushSubscription.jsx +4 -4
  191. package/templates/{hooks → react}/usePushSubscription.tsx +4 -4
  192. package/templates/{hooks → react}/useSWUpdate.jsx +3 -3
  193. package/templates/{hooks → react}/useSWUpdate.tsx +4 -4
  194. package/templates/react/useSwoffReset.jsx +32 -0
  195. package/templates/react/useSwoffReset.tsx +39 -0
  196. package/docs/API.md +0 -565
  197. package/docs/ARCHITECTURE.md +0 -374
  198. package/docs/CLI.md +0 -148
  199. package/docs/COMPARISON.md +0 -192
  200. package/docs/CONFIG.md +0 -277
  201. package/docs/ECOSYSTEM.md +0 -33
  202. package/templates/hooks/useAuth.jsx +0 -30
  203. package/templates/hooks/useAuth.tsx +0 -30
  204. package/templates/hooks/useCacheInvalidation.tsx +0 -19
  205. package/templates/hooks/useCachedFetch.jsx +0 -69
  206. package/templates/hooks/useCachedFetch.tsx +0 -81
  207. package/templates/hooks/useMutation.jsx +0 -76
  208. package/templates/hooks/useMutation.tsx +0 -109
  209. package/templates/hooks/useMutationQueue.jsx +0 -43
  210. package/templates/hooks/useMutationQueue.tsx +0 -50
  211. package/templates/hooks/useNetworkStatus.jsx +0 -19
  212. package/templates/hooks/useNetworkStatus.tsx +0 -19
  213. package/templates/hooks/usePrefetch.jsx +0 -10
  214. package/templates/hooks/usePrefetch.tsx +0 -25
  215. /package/templates/{hooks → react}/useMutationState.jsx +0 -0
  216. /package/templates/{hooks → react}/useMutationState.tsx +0 -0
@@ -0,0 +1,148 @@
1
+ import { T, R } from "./utils.js";
2
+ export function generateServerPushCode(ctx, pushType, endpoint, reconnectDelayMs) {
3
+ const { ext, ts } = ctx;
4
+ const invalidate = `
5
+ // Forward to SW for cache invalidation
6
+ if (navigator.serviceWorker.controller) {
7
+ for (const tag of tags) {
8
+ navigator.serviceWorker.controller.postMessage({ type: "INVALIDATE_TAG", tag });
9
+ }
10
+ }`;
11
+ const sseConnect = `
12
+ return new Promise((resolve) => {
13
+ fetch(API_BASE + "${endpoint}", {
14
+ headers: { Accept: "text/event-stream" },
15
+ credentials: "include",
16
+ signal: options.signal,
17
+ }).then(async (response) => {
18
+ if (!response.ok || !response.body) { resolve(); return; }
19
+ notifyStatus(true);
20
+ const reader = response.body.getReader();
21
+ const decoder = new TextDecoder();
22
+ let buffer = "";
23
+ let eventType = "";
24
+ let dataStr = "";
25
+
26
+ while (true) {
27
+ const { done, value } = await reader.read();
28
+ if (done) break;
29
+ buffer += decoder.decode(value, { stream: true });
30
+ const lines = buffer.split("\\n");
31
+ buffer = lines.pop() || "";
32
+ for (const line of lines) {
33
+ if (line.startsWith("event: ")) eventType = line.slice(7).trim();
34
+ else if (line.startsWith("data: ")) dataStr = line.slice(6);
35
+ else if (line === "" && eventType === "invalidate" && dataStr) {
36
+ try {
37
+ const p = JSON.parse(dataStr); if (p.tags) handleInvalidation(p.tags);
38
+ } catch {
39
+ // Handle invalidation data
40
+ }
41
+ eventType = ""; dataStr = "";
42
+ }
43
+ }
44
+ }
45
+ }).catch(() => {}).finally(() => {
46
+ notifyStatus(false);
47
+ resolve();
48
+ });
49
+ });`;
50
+ const wsConnect = `
51
+ return new Promise((resolve) => {
52
+ try {
53
+ const ws = new WebSocket(API_BASE + "${endpoint}");
54
+ ws.onopen = () => notifyStatus(true);
55
+ ws.onmessage = (event) => {
56
+ try { const d = JSON.parse(event.data); if (d.type === "invalidate" && d.tags) handleInvalidation(d.tags); } catch {}
57
+ };
58
+ ws.onclose = () => { notifyStatus(false); resolve(); };
59
+ ws.onerror = () => { ws.close(); resolve(); };
60
+ if (options.signal) {
61
+ options.signal.addEventListener("abort", () => ws.close());
62
+ }
63
+ } catch { resolve(); }
64
+ });`;
65
+ return `/**
66
+ * Swoff Server Push Events
67
+ *
68
+ * The service worker connects to ${endpoint} and invalidates cache tags
69
+ * when the server sends "invalidate" events — no polling needed.
70
+ * This client-side helper provides status tracking and a manual start/stop API.
71
+ *
72
+ * Usage:
73
+ * import { startPushEvents, stopPushEvents, isPushConnected } from "./realtime/server-push.${ext}";
74
+ * startPushEvents();
75
+ *
76
+ * Server sends:
77
+ * event: invalidate
78
+ * data: {"tags": ["todos", "todo:42"]}
79
+ */
80
+
81
+ import { API_BASE } from "../config.${ext}";
82
+
83
+ type PushEventOptions= {
84
+ signal${T(ts, "AbortSignal")};
85
+ };
86
+
87
+ let active${T(ts, "boolean")} = false;
88
+ let swConnected${T(ts, "boolean")} = false;
89
+ let reconnectTimer${T(ts, "ReturnType<typeof setTimeout> | null")} = null;
90
+
91
+ function handleInvalidation(tags${T(ts, "string[]")})${R(ts, "void")}{${invalidate}
92
+ window.dispatchEvent(new CustomEvent("cache-invalidated", { detail: { tags } }));
93
+ }
94
+
95
+ function notifyStatus(connected${T(ts, "boolean")})${R(ts, "void")}{
96
+ window.dispatchEvent(new CustomEvent("push-events-status", { detail: { connected } }));
97
+ }
98
+
99
+ // Listen for SSE status from the SW
100
+ if (typeof navigator !== "undefined" && navigator.serviceWorker) {
101
+ navigator.serviceWorker.addEventListener("message", (event) => {
102
+ if (event.data?.type === "SSE_STATUS") {
103
+ swConnected = event.data.connected;
104
+ notifyStatus(swConnected);
105
+ }
106
+ });
107
+ }
108
+
109
+ async function connect(options${T(ts, "PushEventOptions")} = {} as PushEventOptions)${R(ts, "Promise<void>")}{
110
+ ${pushType === "sse" ? sseConnect : wsConnect}
111
+ }
112
+
113
+ /** Start listening for server push events. Only connects when the SW is not active — the SW is the primary connection manager. Retries on connection loss with exponential backoff. */
114
+ export async function startPushEvents(){
115
+ if (active) return;
116
+
117
+ // If SW is already controlling the page, skip client-side connection — the SW handles push events.
118
+ if (navigator.serviceWorker.controller) return;
119
+
120
+ // Listen for SW activation — if SW takes over, disconnect the client fallback.
121
+ const onControllerChange = () => { stopPushEvents(); };
122
+ navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
123
+
124
+ active = true;
125
+ let delay = ${reconnectDelayMs};
126
+ while (active) {
127
+ await connect();
128
+ if (!active) break;
129
+ await new Promise((r) => { reconnectTimer = setTimeout(r, delay); });
130
+ delay = Math.min(delay * 1.5, 30000); // cap at 30s
131
+ }
132
+
133
+ navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
134
+ }
135
+
136
+ /** Stop listening for push events. */
137
+ export function stopPushEvents(){
138
+ active = false;
139
+ if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
140
+ }
141
+
142
+ /** Check if the push connection is currently established. */
143
+ export function isPushConnected(){
144
+ return active || swConnected;
145
+ }
146
+ `;
147
+ }
148
+ //# sourceMappingURL=server-push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-push.js","sourceRoot":"","sources":["../../src/runtime/server-push.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,sBAAsB,CACpC,GAAmB,EACnB,QAAgB,EAChB,QAAgB,EAChB,gBAAwB;IAExB,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IAExB,MAAM,UAAU,GAAG;;;;;;IAMjB,CAAC;IAEH,MAAM,UAAU,GAAG;;wBAEG,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAoC1B,CAAC;IAEL,MAAM,SAAS,GAAG;;;6CAGyB,QAAQ;;;;;;;;;;;MAW/C,CAAC;IAEL,OAAO;;;oCAG2B,QAAQ;;;;;gGAKoD,GAAG;;;;;;;;sCAQ7D,GAAG;;;UAG/B,CAAC,CAAC,EAAE,EAAE,aAAa,CAAC;;;YAGlB,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;iBACX,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;oBACb,CAAC,CAAC,EAAE,EAAE,sCAAsC,CAAC;;kCAE/B,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,UAAU;;;;iCAIjD,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;;;;;;;;;;gCAclC,CAAC,CAAC,EAAE,EAAE,kBAAkB,CAAC,6BAA6B,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;IACxG,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;gBAe/B,gBAAgB;;;;;;;;;;;;;;;;;;;;;CAqB/B,CAAC;AACF,CAAC"}
@@ -0,0 +1,236 @@
1
+ import { T, R } from "./utils.js";
2
+ export function generateSwInjectorCode(ctx) {
3
+ const { ext, ts, autoUpdate, autoActivate, versionEnabled } = ctx;
4
+ if (!versionEnabled) {
5
+ return `/**
6
+ * Swoff SW Injector (Simple Mode)
7
+ * Fixed SW URL with hash-based cache name — no versioned URLs or update events.
8
+ *
9
+ * Usage:
10
+ * import { initServiceWorker } from './swoff/sw/injector.${ext}';
11
+ * initServiceWorker();
12
+ *
13
+ * Window events:
14
+ * sw-ready - SW active and controlling page
15
+ * sw-error - SW registration failed
16
+ *
17
+ * Window properties:
18
+ * window.currentSWVersion - "simple"
19
+ * window.swReady - SW is active
20
+ * window.swError - Registration failed
21
+ */
22
+ async function waitForController()${R(ts, "Promise<void>")}{
23
+ return new Promise<void>((resolve) => {
24
+ if (navigator.serviceWorker.controller) {
25
+ resolve();
26
+ } else {
27
+ const timeout = setTimeout(() => resolve(), 30000);
28
+ navigator.serviceWorker.addEventListener("controllerchange", () => {
29
+ clearTimeout(timeout);
30
+ resolve();
31
+ }, { once: true });
32
+ }
33
+ });
34
+ }
35
+
36
+ /** Register the SW with a fixed URL (non-versioned mode). Dispatches sw-ready/sw-error. */
37
+ export async function initServiceWorker()${R(ts, "Promise<void>")}{
38
+ if (!("serviceWorker" in navigator)) {
39
+ console.warn("Service Workers not supported");
40
+ return;
41
+ }
42
+ try {
43
+ const registration = await navigator.serviceWorker.register("/sw.js");
44
+ window.currentSWVersion = "simple";
45
+ window.dispatchEvent(new CustomEvent("sw-version-detected"));
46
+ await waitForController();
47
+ window.dispatchEvent(new CustomEvent("sw-ready"));
48
+ } catch (error) {
49
+ console.error("Service Worker registration failed:", error);
50
+ window.swError = true;
51
+ window.dispatchEvent(new CustomEvent("sw-error"));
52
+ }
53
+ }
54
+ `;
55
+ }
56
+ return `/**
57
+ * Swoff SW Injector
58
+ * Framework-agnostic SW registration with versioned URLs and update flow.
59
+ *
60
+ * Usage:
61
+ * import { initServiceWorker } from './swoff/sw/injector.${ext}';
62
+ * initServiceWorker();
63
+ *
64
+ * Window events:
65
+ * sw-version-detected - Version info available on window
66
+ * sw-update-available - New version ready for user consent (detail: { version })
67
+ * sw-progress - Download progress (detail: { percent, downloaded, total })
68
+ * sw-ready - SW active and controlling page
69
+ * sw-error - SW registration failed
70
+ *
71
+ * Window properties:
72
+ * window.latestSWVersion - Latest version from version.json
73
+ * window.currentSWVersion - Active SW version
74
+ * window.swAvailableVersion - Pending update version
75
+ * window.swUpdateRequired - Forced update needed (version < minSupportedVersion)
76
+ * window.swMinSupportedVersion - Minimum supported version
77
+ * window.swReady - SW is active
78
+ * window.swError - Registration failed
79
+ */
80
+ const AUTO_UPDATE = ${autoUpdate};
81
+ const AUTO_ACTIVATE = ${autoActivate};
82
+
83
+ function semverCompare(a${T(ts, "string")}, b${T(ts, "string")})${R(ts, "number")}{
84
+ const pa = a.split('.').map(Number);
85
+ const pb = b.split('.').map(Number);
86
+ for (let i = 0; i < 3; i++) {
87
+ if (pa[i] > pb[i]) return 1;
88
+ if (pa[i] < pb[i]) return -1;
89
+ }
90
+ return 0;
91
+ }
92
+
93
+ async function checkForUpdate() {
94
+ const response = await fetch("/version.json?t=" + Date.now());
95
+ if (!response.ok) {
96
+ throw new Error("Failed to fetch version.json");
97
+ }
98
+ return response.json();
99
+ }
100
+
101
+ async function waitForController()${R(ts, "Promise<void>")}{
102
+ return new Promise<void>((resolve) => {
103
+ if (navigator.serviceWorker.controller) {
104
+ resolve();
105
+ } else {
106
+ const timeout = setTimeout(() => resolve(), 30000);
107
+ navigator.serviceWorker.addEventListener("controllerchange", () => {
108
+ clearTimeout(timeout);
109
+ resolve();
110
+ }, { once: true });
111
+ }
112
+ });
113
+ }
114
+
115
+ async function doRegisterServiceWorker(version${T(ts, "string")})${R(ts, "Promise<ServiceWorkerRegistration>")}{
116
+ const swUrl = \`/sw-v\${version}.js\`;
117
+ const registration = await navigator.serviceWorker.register(swUrl);
118
+ localStorage.setItem("swRegisteredVersion", version);
119
+ window.currentSWVersion = version;
120
+ window.swRegisteredVersion = version;
121
+ window.dispatchEvent(new CustomEvent("sw-version-detected"));
122
+ await waitForController();
123
+ window.dispatchEvent(new CustomEvent("sw-ready"));
124
+ return registration;
125
+ }
126
+
127
+ /** Register the SW with version checking and update flow. Checks version.json, handles updates, and dispatches sw-ready/sw-error events. */
128
+ export async function initServiceWorker()${R(ts, "Promise<void>")}{
129
+ if (!("serviceWorker" in navigator)) {
130
+ console.warn("Service Workers not supported");
131
+ return;
132
+ }
133
+
134
+ try {
135
+ const manifest = await checkForUpdate();
136
+ const currentVersion = localStorage.getItem("swRegisteredVersion");
137
+ window.latestSWVersion = manifest.version;
138
+ window.swMinSupportedVersion = manifest.minSupportedVersion || "0.0.0";
139
+
140
+ if (currentVersion === manifest.version) {
141
+ const registration = await navigator.serviceWorker.getRegistration();
142
+ if (registration && registration.active) {
143
+ window.currentSWVersion = currentVersion ?? undefined;
144
+ window.dispatchEvent(new CustomEvent("sw-version-detected"));
145
+ await waitForController();
146
+ if (!window.swReady) {
147
+ window.dispatchEvent(new CustomEvent("sw-ready"));
148
+ }
149
+ }
150
+ } else if (currentVersion && currentVersion !== manifest.version) {
151
+ window.swAvailableVersion = manifest.version;
152
+ window.swUpdateRequired =
153
+ semverCompare(currentVersion, manifest.minSupportedVersion || "0.0.0") < 0;
154
+
155
+ if (AUTO_UPDATE) {
156
+ const registration = await navigator.serviceWorker.getRegistration();
157
+ if (registration && registration.waiting) {
158
+ if (AUTO_ACTIVATE) {
159
+ registration.waiting.postMessage({ type: "SKIP_WAITING" });
160
+ } else {
161
+ window.dispatchEvent(
162
+ new CustomEvent("sw-update-available", {
163
+ detail: { version: manifest.version },
164
+ })
165
+ );
166
+ }
167
+ } else {
168
+ const newReg = await doRegisterServiceWorker(manifest.version);
169
+ if (AUTO_ACTIVATE && newReg.waiting) {
170
+ newReg.waiting.postMessage({ type: "SKIP_WAITING" });
171
+ } else {
172
+ window.dispatchEvent(
173
+ new CustomEvent("sw-update-available", {
174
+ detail: { version: manifest.version },
175
+ })
176
+ );
177
+ }
178
+ }
179
+ } else {
180
+ window.dispatchEvent(
181
+ new CustomEvent("sw-update-available", {
182
+ detail: { version: manifest.version },
183
+ })
184
+ );
185
+ }
186
+ } else {
187
+ await doRegisterServiceWorker(manifest.version);
188
+ }
189
+ } catch (error) {
190
+ try {
191
+ const existing = await navigator.serviceWorker.getRegistration();
192
+ if (existing && existing.active) {
193
+ window.currentSWVersion = localStorage.getItem("swRegisteredVersion") || "unknown";
194
+ window.dispatchEvent(new CustomEvent("sw-version-detected"));
195
+ await waitForController();
196
+ if (!window.swReady) {
197
+ window.dispatchEvent(new CustomEvent("sw-ready"));
198
+ }
199
+ return;
200
+ }
201
+ } catch {
202
+ // Handle the case where no existing active registration is found
203
+ }
204
+
205
+ console.error("Service Worker initialization failed:", error);
206
+ window.swError = true;
207
+ window.dispatchEvent(new CustomEvent("sw-error"));
208
+ }
209
+ }
210
+
211
+ /** Accept a pending SW update. Reloads the page once the new SW takes control. */
212
+ export async function handleUpdateApproved(newVersion${T(ts, "string")})${R(ts, "Promise<void>")}{
213
+ const registration = await navigator.serviceWorker.getRegistration();
214
+ if (registration && registration.waiting) {
215
+ registration.waiting.postMessage({ type: "SKIP_WAITING" });
216
+ registration.addEventListener("controllerchange", () => {
217
+ window.location.reload();
218
+ });
219
+ } else {
220
+ await doRegisterServiceWorker(newVersion);
221
+ navigator.serviceWorker.addEventListener("controllerchange", () => {
222
+ window.location.reload();
223
+ });
224
+ }
225
+ }
226
+
227
+ /** Activate a waiting SW without reloading. Useful when you handle the transition yourself. */
228
+ export async function skipWaiting()${R(ts, "Promise<void>")}{
229
+ const registration = await navigator.serviceWorker.ready;
230
+ if (registration.waiting) {
231
+ registration.waiting.postMessage({ type: "SKIP_WAITING" });
232
+ }
233
+ }
234
+ `;
235
+ }
236
+ //# sourceMappingURL=sw-injector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sw-injector.js","sourceRoot":"","sources":["../../src/runtime/sw-injector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,sBAAsB,CACpC,GAA6F;IAE7F,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC;IAElE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO;;;;;8DAKmD,GAAG;;;;;;;;;;;;oCAY7B,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;2CAef,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;;;CAiBhE,CAAC;IACA,CAAC;IAED,OAAO;;;;;8DAKqD,GAAG;;;;;;;;;;;;;;;;;;;sBAmB3C,UAAU;wBACR,YAAY;;0BAEV,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;;;;;;;;;;;;;;;;;;oCAkB7C,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;gDAcV,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,oCAAoC,CAAC;;;;;;;;;;;;;2CAanE,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uDAoFV,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;;qCAgB3D,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;CAM1D,CAAC;AACF,CAAC"}
@@ -0,0 +1,138 @@
1
+ export function generateTypeDefinitionsCode() {
2
+ return `
3
+ // Background Sync API types
4
+ interface SyncManager {
5
+ register(tag: string): Promise<void>;
6
+ getTags(): Promise<string[]>;
7
+ }
8
+
9
+ interface SyncEvent extends Event {
10
+ readonly tag: string;
11
+ readonly lastChance: boolean;
12
+ }
13
+
14
+ interface ServiceWorkerGlobalScope {
15
+ onsync: ((this: ServiceWorkerGlobalScope, ev: SyncEvent) => unknown) | null;
16
+ }
17
+
18
+ declare global {
19
+ // Network Information API
20
+ interface NetworkInformation extends EventTarget {
21
+ readonly effectiveType: "slow-2g" | "2g" | "3g" | "4g";
22
+ readonly type: "bluetooth" | "cellular" | "ethernet" | "none" | "wifi" | "wimax" | "other" | "unknown";
23
+ readonly downlink: number;
24
+ readonly downlinkMax: number;
25
+ readonly rtt: number;
26
+ readonly saveData: boolean;
27
+ onchange: ((this: NetworkInformation, ev: Event) => unknown) | null;
28
+ }
29
+
30
+ interface Navigator {
31
+ readonly connection?: NetworkInformation;
32
+ }
33
+
34
+ interface BeforeInstallPromptEvent extends Event {
35
+ prompt(): Promise<void>;
36
+ userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
37
+ }
38
+
39
+ interface WindowEventMap {
40
+ beforeinstallprompt: BeforeInstallPromptEvent;
41
+ "sw-update-available": CustomEvent<{ version: string }>;
42
+ "sw-progress": CustomEvent<{ percent: number; downloaded: number; total: number }>;
43
+ "sw-ready": CustomEvent;
44
+ "sw-error": CustomEvent;
45
+ "sw-version-detected": CustomEvent;
46
+ "sw-auth-state-change": CustomEvent;
47
+ "mutation-sync-complete": CustomEvent<{ succeeded: number; failed: number }>;
48
+ "mutation-queue-changed": CustomEvent;
49
+ "cache-invalidated": CustomEvent<{ tags?: string[] }>;
50
+ "fetch-count-changed": CustomEvent<{ count: number }>;
51
+ "mutation-state-changed": CustomEvent<{ id: string; status: string; error?: Error; data?: unknown }>;
52
+ "background-sync-complete": CustomEvent<{ succeeded: number; failed: number }>;
53
+ "swoff:reset-start": CustomEvent;
54
+ "swoff:reset-complete": CustomEvent<{ unregistered: boolean; reregistered: boolean; warnings: string[] }>;
55
+ "push-subscription-changed": CustomEvent<{ subscribed: boolean }>;
56
+ "push-permission-changed": CustomEvent<{ permission: NotificationPermission }>;
57
+ }
58
+
59
+ interface Window {
60
+ deferredInstallPrompt: BeforeInstallPromptEvent | null;
61
+ pwaInstallable?: boolean;
62
+ latestSWVersion?: string;
63
+ currentSWVersion?: string;
64
+ swRegisteredVersion?: string;
65
+ swAvailableVersion?: string;
66
+ swUpdateRequired?: boolean;
67
+ swMinSupportedVersion?: string;
68
+ swReady?: boolean;
69
+ swError?: boolean;
70
+ swAuthState?: "authenticated" | "unauthenticated" | "loading";
71
+ swCurrentUser?: Record<string, unknown> | null;
72
+ SyncManager?: { new(): SyncManager };
73
+ }
74
+
75
+ // Extend ServiceWorkerRegistration in the global scope for TS
76
+ interface ServiceWorkerRegistration {
77
+ readonly sync: SyncManager;
78
+ }
79
+ }
80
+
81
+ export interface FetchWithCacheOptions extends RequestInit {
82
+ strategy?: "read" | "mutation";
83
+ tags?: string[];
84
+ }
85
+
86
+ export type MutationStatus = "idle" | "pending" | "success" | "error";
87
+
88
+ export interface MutationState {
89
+ id: string;
90
+ status: MutationStatus;
91
+ error: Error | null;
92
+ data: unknown;
93
+ timestamp: number;
94
+ }
95
+
96
+ export interface ResetSwoffOptions {
97
+ clearCache?: boolean;
98
+ clearIdb?: boolean;
99
+ clearStorage?: boolean;
100
+ unregisterSW?: boolean;
101
+ }
102
+
103
+ export interface GqlOptions {
104
+ variables?: Record<string, unknown>;
105
+ tags?: string[];
106
+ auth?: boolean;
107
+ queueOffline?: boolean;
108
+ invalidate?: 'auto' | string[] | false;
109
+ }
110
+
111
+ export interface GqlResult<T> {
112
+ data: T;
113
+ fromCache: boolean;
114
+ }
115
+
116
+ export interface MutationQueueItem {
117
+ id: string;
118
+ method: string;
119
+ url: string;
120
+ body: unknown;
121
+ bodyType?: "json" | "formdata" | "blob" | "buffer" | "text";
122
+ headers: Record<string, string>;
123
+ timestamp: number;
124
+ retryCount: number;
125
+ nextRetryAt?: number;
126
+ tags: string[];
127
+ }
128
+
129
+ export interface MutationQueueResult {
130
+ succeeded: number;
131
+ failed: number;
132
+ total?: number;
133
+ }
134
+
135
+ export {};
136
+ `;
137
+ }
138
+ //# sourceMappingURL=type-definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-definitions.js","sourceRoot":"","sources":["../../src/runtime/type-definitions.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,2BAA2B;IACzC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsIR,CAAC;AACF,CAAC"}
@@ -0,0 +1,27 @@
1
+ export function T(ts, type) {
2
+ return ts ? `: ${type}` : "";
3
+ }
4
+ export function R(ts, type) {
5
+ return ts ? `: ${type} ` : " ";
6
+ }
7
+ export function G(ts, type) {
8
+ return ts ? `<${type}>` : "";
9
+ }
10
+ /** Generic type parameter (alias for G) */
11
+ export function PT(ts, type) {
12
+ return G(ts, type);
13
+ }
14
+ /** Type assertion */
15
+ export function AS(ts, type) {
16
+ return ts ? ` as ${type}` : "";
17
+ }
18
+ export function genHelpers(ctx) {
19
+ return {
20
+ T: (type) => T(ctx.ts, type),
21
+ R: (type) => R(ctx.ts, type),
22
+ G: (type) => G(ctx.ts, type),
23
+ PT: (type) => PT(ctx.ts, type),
24
+ AS: (type) => AS(ctx.ts, type),
25
+ };
26
+ }
27
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/runtime/utils.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,CAAC,CAAC,EAAW,EAAE,IAAY;IACzC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,CAAC,CAAC,EAAW,EAAE,IAAY;IACzC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,CAAC,CAAC,EAAW,EAAE,IAAY;IACzC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,EAAE,CAAC,EAAW,EAAE,IAAY;IAC1C,OAAO,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,qBAAqB;AACrB,MAAM,UAAU,EAAE,CAAC,EAAW,EAAE,IAAY;IAC1C,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAmB;IAC5C,OAAO;QACL,CAAC,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;QACpC,CAAC,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;QACpC,CAAC,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;QACpC,EAAE,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;QACtC,EAAE,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;KACvC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swoff/cli",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "type": "module",
5
5
  "description": "CLI for Swoff - Offline-first web apps made easy",
6
6
  "bin": {
@@ -19,7 +19,6 @@
19
19
  "files": [
20
20
  "bin/",
21
21
  "dist/",
22
- "docs/",
23
22
  "templates/"
24
23
  ],
25
24
  "keywords": [
@@ -32,7 +31,9 @@
32
31
  "license": "MIT",
33
32
  "dependencies": {},
34
33
  "devDependencies": {
34
+ "@resvg/resvg-wasm": "^2.6.2",
35
35
  "@types/node": "^25.9.1",
36
+ "jimp": "^1.6.0",
36
37
  "typescript": "^5.0.0",
37
38
  "vitest": "^4.1.7"
38
39
  }