@swoff/cli 0.3.5 → 0.3.7

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 (121) hide show
  1. package/README.md +610 -24
  2. package/bin/swoff +8 -14
  3. package/dist/__tests__/assemble-sw.test.js +17 -3
  4. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  5. package/dist/__tests__/cli-integration.test.js +9 -9
  6. package/dist/__tests__/cli-integration.test.js.map +1 -1
  7. package/dist/__tests__/config-types.test.js +10 -9
  8. package/dist/__tests__/config-types.test.js.map +1 -1
  9. package/dist/__tests__/file-generators.test.js +65 -68
  10. package/dist/__tests__/file-generators.test.js.map +1 -1
  11. package/dist/__tests__/loader.test.js +12 -14
  12. package/dist/__tests__/loader.test.js.map +1 -1
  13. package/dist/__tests__/validator.test.js +41 -22
  14. package/dist/__tests__/validator.test.js.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/cli/help.js +3 -3
  18. package/dist/lib/cli/help.js.map +1 -1
  19. package/dist/lib/commands/add.js +11 -5
  20. package/dist/lib/commands/add.js.map +1 -1
  21. package/dist/lib/commands/clean.js +5 -0
  22. package/dist/lib/commands/clean.js.map +1 -1
  23. package/dist/lib/commands/generate-guide.js +43 -13
  24. package/dist/lib/commands/generate-guide.js.map +1 -1
  25. package/dist/lib/commands/generate.js +7 -3
  26. package/dist/lib/commands/generate.js.map +1 -1
  27. package/dist/lib/commands/info.js +149 -32
  28. package/dist/lib/commands/info.js.map +1 -1
  29. package/dist/lib/commands/init.js +6 -2
  30. package/dist/lib/commands/init.js.map +1 -1
  31. package/dist/lib/commands/validate.js +3 -1
  32. package/dist/lib/commands/validate.js.map +1 -1
  33. package/dist/lib/config/validator.js +25 -16
  34. package/dist/lib/config/validator.js.map +1 -1
  35. package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
  36. package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
  37. package/dist/lib/generators/file-generators/auth-state.js +8 -5
  38. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  39. package/dist/lib/generators/file-generators/auth-store.js +181 -41
  40. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  41. package/dist/lib/generators/file-generators/auth-user.js +28 -32
  42. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  43. package/dist/lib/generators/file-generators/background-sync.js +9 -5
  44. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  45. package/dist/lib/generators/file-generators/cache.js +8 -5
  46. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  47. package/dist/lib/generators/file-generators/client-injector.js +101 -0
  48. package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
  49. package/dist/lib/generators/file-generators/context.js +7 -6
  50. package/dist/lib/generators/file-generators/context.js.map +1 -1
  51. package/dist/lib/generators/file-generators/fetch-wrapper.js +196 -41
  52. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  53. package/dist/lib/generators/file-generators/generate-hooks.js +23 -151
  54. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  55. package/dist/lib/generators/file-generators/guide-generator.js +450 -0
  56. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
  57. package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
  58. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  59. package/dist/lib/generators/file-generators/mutation-queue.js +65 -87
  60. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  61. package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
  62. package/dist/lib/generators/file-generators/mutation-reconcile.js.map +1 -0
  63. package/dist/lib/generators/file-generators/push.js +143 -0
  64. package/dist/lib/generators/file-generators/push.js.map +1 -0
  65. package/dist/lib/generators/file-generators/pwa-install.js +41 -9
  66. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  67. package/dist/lib/generators/file-generators/quick-readme.js +88 -0
  68. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
  69. package/dist/lib/generators/file-generators/reconcile.js +7 -12
  70. package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
  71. package/dist/lib/generators/file-generators/store.js +30 -20
  72. package/dist/lib/generators/file-generators/store.js.map +1 -1
  73. package/dist/lib/generators/file-generators/sw-generator-build.js +38 -17
  74. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  75. package/dist/lib/generators/file-generators/sw-injector.js +82 -111
  76. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  77. package/dist/lib/generators/file-generators/sw-push.js +67 -0
  78. package/dist/lib/generators/file-generators/sw-push.js.map +1 -0
  79. package/dist/lib/generators/file-generators/sw-template.js +3 -1
  80. package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
  81. package/dist/lib/generators/file-generators/type-definitions.js +45 -22
  82. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  83. package/dist/lib/generators/sw-generator.js +27 -19
  84. package/dist/lib/generators/sw-generator.js.map +1 -1
  85. package/dist/lib/generators/sw-sections/activate-handler.js +6 -2
  86. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  87. package/dist/lib/generators/sw-sections/assemble-sw.js +36 -12
  88. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  89. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  90. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  91. package/dist/lib/generators/sw-sections/default-template.js +1 -30
  92. package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
  93. package/dist/lib/generators/sw-sections/fetch-handler.js +176 -90
  94. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  95. package/dist/lib/generators/sw-sections/install-handler.js +12 -10
  96. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
  97. package/dist/lib/generators/sw-sections/tag-management.js +36 -5
  98. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  99. package/dist/lib/generators/swoff-files-generator.js +14 -12
  100. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  101. package/dist/lib/shared/config-types.js +26 -9
  102. package/dist/lib/shared/config-types.js.map +1 -1
  103. package/dist/lib/utils/build-script.js +35 -0
  104. package/dist/lib/utils/build-script.js.map +1 -0
  105. package/package.json +3 -2
  106. package/templates/hooks/useAuth.jsx +30 -0
  107. package/templates/hooks/useAuth.tsx +30 -0
  108. package/templates/hooks/useBackgroundSync.jsx +31 -0
  109. package/templates/hooks/useBackgroundSync.tsx +31 -0
  110. package/templates/hooks/useCacheInvalidation.jsx +19 -0
  111. package/templates/hooks/useCacheInvalidation.tsx +19 -0
  112. package/templates/hooks/useCachedFetch.jsx +55 -0
  113. package/templates/hooks/useCachedFetch.tsx +69 -0
  114. package/templates/hooks/useMutationQueue.jsx +34 -0
  115. package/templates/hooks/useMutationQueue.tsx +34 -0
  116. package/templates/hooks/useNetworkStatus.jsx +19 -0
  117. package/templates/hooks/useNetworkStatus.tsx +19 -0
  118. package/templates/hooks/usePushSubscription.jsx +67 -0
  119. package/templates/hooks/usePushSubscription.tsx +67 -0
  120. package/templates/hooks/useSWUpdate.jsx +76 -0
  121. package/templates/hooks/useSWUpdate.tsx +76 -0
@@ -3,14 +3,15 @@
3
3
  * Contains config, paths, and the generated files tracker.
4
4
  */
5
5
  import { existsSync, mkdirSync, writeFileSync } from "fs";
6
- import { join } from "path";
7
- export function ensureSwoffDir(ctx) {
8
- if (!existsSync(ctx.swoffDir))
9
- mkdirSync(ctx.swoffDir, { recursive: true });
6
+ import { join, dirname } from "path";
7
+ export function ensureDir(dir) {
8
+ if (!existsSync(dir))
9
+ mkdirSync(dir, { recursive: true });
10
10
  }
11
11
  export function writeFile(ctx, filename, code) {
12
- ensureSwoffDir(ctx);
13
- writeFileSync(join(ctx.swoffDir, filename), code);
12
+ const fullPath = join(ctx.swoffDir, filename);
13
+ ensureDir(dirname(fullPath));
14
+ writeFileSync(fullPath, code);
14
15
  ctx.generatedFiles.push(`swoff/${filename}`);
15
16
  }
16
17
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAY5B,MAAM,UAAU,cAAc,CAAC,GAAqB;IAClD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAqB,EAAE,QAAgB,EAAE,IAAY;IAC7E,cAAc,CAAC,GAAG,CAAC,CAAC;IACpB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IAClD,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAYrC,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAqB,EAAE,QAAgB,EAAE,IAAY;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9C,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC"}
@@ -1,79 +1,234 @@
1
1
  /**
2
- * Generates fetch-wrapper.js - fetch with cache strategy headers and deduplication.
2
+ * Generates fetch-wrapper.ts/js unified fetch with caching, auth, offline queue, and auto-invalidation.
3
3
  */
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateFetchWrapper(ctx) {
6
6
  const ext = ctx.ext;
7
+ const ts = ext === "ts";
8
+ const T = (type) => (ts ? `: ${type}` : "");
9
+ const R = (type) => (ts ? `: ${type} ` : " ");
10
+ const G = (type) => (ts ? `<${type}>` : "");
11
+ const tagInvalidation = ctx.config.features.tagInvalidation;
12
+ const authEnabled = ctx.config.features.auth.enabled;
13
+ const mutationQueue = ctx.config.features.mutationQueue;
14
+ const importLines = [
15
+ tagInvalidation
16
+ ? `import { generateTags } from "./invalidation-tags.${ext}";`
17
+ : "",
18
+ tagInvalidation ? `import { invalidateByTags } from "./cache.${ext}";` : "",
19
+ authEnabled
20
+ ? `import { getAuth, clearAuth, withAuthHeaders, isAuthUrl, AUTH_WITH_CREDENTIALS } from "./auth/store.${ext}";`
21
+ : "",
22
+ mutationQueue
23
+ ? `import { queueMutation } from "./mutation-queue.${ext}";`
24
+ : "",
25
+ ]
26
+ .filter(Boolean)
27
+ .join("\n");
28
+ const interfaceBlock = ts
29
+ ? `
30
+ export interface FetchWithCacheResult${G("T")} {
31
+ response${T("Response & { json(): Promise<T> }")};
32
+ fromCache${T("boolean")};
33
+ }
34
+ `
35
+ : "";
36
+ const optionsType = ts ? `FetchWithCacheOptions` : `options`;
37
+ const optionsInterface = ts
38
+ ? `
39
+ export interface FetchWithCacheOptions extends RequestInit {
40
+ tags?: string[];
41
+ staleWhileRevalidate?: boolean;
42
+ auth?: boolean;
43
+ queueOffline?: boolean;
44
+ invalidate?: 'auto' | string[] | false;
45
+ type?: 'read' | 'mutation';
46
+ strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only';
47
+ }
48
+ `
49
+ : "";
50
+ const authBlock = authEnabled
51
+ ? `
52
+ if (options.auth) {
53
+ const auth = await getAuth();
54
+ withAuthHeaders(headers, auth);
55
+ }`
56
+ : "";
57
+ const authUrlsBlock = authEnabled
58
+ ? `
59
+ // Auth endpoints bypass SW cache
60
+ if (options.auth && isAuthUrl(url) && !headers.has("X-SW-Cache-Strategy")) {
61
+ headers.set("X-SW-Cache-Strategy", "mutation");
62
+ }`
63
+ : "";
64
+ const authCredentialsBlock = authEnabled
65
+ ? `
66
+ if (AUTH_WITH_CREDENTIALS) {
67
+ fetchOptions.credentials = "include";
68
+ }`
69
+ : "";
70
+ const auth401Block = authEnabled
71
+ ? `
72
+ if (options.auth && response.status === 401) {
73
+ await clearAuth();
74
+ window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
75
+ }`
76
+ : "";
77
+ const offlineReadBlock = ` if (isRead) {
78
+ const cached = await caches.match(input);
79
+ if (cached) return { response: cached, fromCache: true };
80
+ throw new Error("Offline: no cached data available");
81
+ }`;
82
+ const offlineWriteBlock = mutationQueue
83
+ ? ` // Write offline — auto-queue
84
+ if (options.queueOffline !== false) {
85
+ const headerObj: Record<string, string> = {};
86
+ headers.forEach((value, key) => {
87
+ headerObj[key] = value;
88
+ });
89
+ await queueMutation({
90
+ method,
91
+ url,
92
+ body: options.body,
93
+ headers: headerObj,
94
+ tags: options.tags || [],
95
+ timestamp: Date.now(),
96
+ });
97
+ return {
98
+ response: new Response(null, { status: 202, statusText: "Queued" }),
99
+ fromCache: false,
100
+ };
101
+ }
102
+ throw new Error("Offline: write operation failed");`
103
+ : ` throw new Error("Offline: write operation failed");`;
104
+ const autoInvalidateBlock = tagInvalidation
105
+ ? `
106
+ // Auto-invalidate after mutation success
107
+ if (!isRead && response.ok) {
108
+ const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
109
+ if (invalidateSetting !== false) {
110
+ const tagsToInvalidate${T("string[]")} = Array.isArray(invalidateSetting)
111
+ ? invalidateSetting
112
+ : (options.tags || generateTags(url));
113
+ if (tagsToInvalidate.length > 0) {
114
+ await invalidateByTags(tagsToInvalidate);
115
+ }
116
+ }
117
+ }`
118
+ : "";
119
+ const autoTagsBlock = tagInvalidation
120
+ ? `
121
+ // Auto-generate tags from URL if not provided
122
+ if (!options.tags && isRead) {
123
+ const urlTags = generateTags(url);
124
+ if (urlTags.length > 0) {
125
+ headers.set("X-SW-Cache-Tags", urlTags.join(","));
126
+ }
127
+ }`
128
+ : "";
7
129
  const code = `/**
8
130
  * Swoff Fetch Wrapper
9
- * Framework-agnostic fetch with cache strategy, tags, and query deduplication.
131
+ * Unified fetch with caching, auth, offline queue, auto-invalidation, and
132
+ * per-request strategy override.
10
133
  *
11
134
  * Usage:
12
135
  * import { fetchWithCache } from './swoff/fetch-wrapper.${ext}';
13
136
  *
14
- * // GET - cached with tag
15
- * const todos = await fetchWithCache("/api/todos", { tags: ["todos"] }).then(r => r.json());
137
+ * // GET cached with auto-generated tags
138
+ * const { response } = await fetchWithCache("/api/todos");
139
+ * const data = await response.json();
16
140
  *
17
- * // POST - mutation (passes through to server)
141
+ * // POST mutation with auto-invalidation
18
142
  * await fetchWithCache("/api/todos", {
19
143
  * method: "POST",
20
144
  * body: JSON.stringify({ title: "New task" }),
21
145
  * });
22
146
  *
23
- * // Stale-while-revalidate
24
- * const data = await fetchWithCache("/api/data", {
147
+ * // Authenticated request (works with bearer, cookie, custom)
148
+ * const { response: userRes } = await fetchWithCache("/api/me", { auth: true });
149
+ *
150
+ * // Custom tags + stale-while-revalidate
151
+ * const { response: staleRes, fromCache } = await fetchWithCache("/api/data", {
25
152
  * tags: ["data"],
26
153
  * staleWhileRevalidate: true,
27
- * }).then(r => r.json());
154
+ * });
155
+ *
156
+ * // Override caching strategy per-request (highest priority)
157
+ * await fetchWithCache("/api/checkout", {
158
+ * method: "POST",
159
+ * type: "read",
160
+ * strategy: "network-only",
161
+ * });
162
+ *
163
+ * // Override method-based caching with explicit type
164
+ * // Use type: "mutation" for POST-based reads (search, GraphQL)
165
+ * await fetchWithCache("/api/search", {
166
+ * method: "POST",
167
+ * type: "read",
168
+ * body: JSON.stringify({ query: "hello" }),
169
+ * });
170
+ *
171
+ * // Offline: auto-queues writes (disable with queueOffline: false)
172
+ * await fetchWithCache("/api/todos", {
173
+ * method: "POST",
174
+ * body: JSON.stringify({ title: "Offline task" }),
175
+ * });
176
+ * // When back online, processMutationQueue() replays them
28
177
  */
29
178
 
30
- const inFlightRequests = new Map();
179
+ ${importLines}
180
+ ${interfaceBlock}${optionsInterface}
181
+ const inFlightRequests = new Map${G("string, Promise<Response>")}();
182
+
183
+ /** Fetch with caching, auth, offline queue, auto-invalidation, and per-request strategy override. Returns { response, fromCache }. Use { auth: true } for authenticated requests — works with bearer, cookie, and custom auth types. */
184
+ export async function fetchWithCache${G("T")}(input${T("RequestInfo")}, options${T("RequestInit & { tags?: string[]; staleWhileRevalidate?: boolean; auth?: boolean; queueOffline?: boolean; invalidate?: 'auto' | string[] | false; type?: 'read' | 'mutation'; strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only' }")} = {})${R("Promise<FetchWithCacheResult<T>>")}{
185
+ const method = (options.method || "GET").toUpperCase();
186
+ const isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD"));
187
+ const url = typeof input === "string" ? input : input.url;
31
188
 
32
- export async function fetchWithCache(input, options = {}) {
33
189
  const headers = new Headers(options.headers);
34
- const method = options.method || "GET";
35
190
 
191
+ // Set cache strategy
36
192
  if (!headers.has("X-SW-Cache-Strategy")) {
37
- headers.set(
38
- "X-SW-Cache-Strategy",
39
- method === "GET" || method === "HEAD" ? "read" : "mutation"
40
- );
193
+ headers.set("X-SW-Cache-Strategy", isRead ? "read" : "mutation");
41
194
  }
42
-
43
- if (options.staleWhileRevalidate) {
44
- headers.set("X-SW-Stale", "true");
45
- }
46
-
195
+ ${autoTagsBlock}
196
+ // Custom tags override auto-generated
47
197
  if (options.tags && options.tags.length > 0) {
48
198
  headers.set("X-SW-Cache-Tags", options.tags.join(","));
49
199
  }
50
200
 
51
- if (method === "GET" || method === "HEAD") {
52
- const url = typeof input === "string" ? input : input.url;
53
- if (inFlightRequests.has(url)) {
54
- return inFlightRequests.get(url).then((r) => r.clone());
55
- }
56
- const promise = fetch(input, { ...options, headers }).finally(() => {
57
- inFlightRequests.delete(url);
58
- });
59
- inFlightRequests.set(url, promise);
60
- return promise;
201
+ if (options.staleWhileRevalidate && !options.strategy) {
202
+ headers.set("X-SW-Strategy", "stale-while-revalidate");
61
203
  }
204
+ if (options.strategy) {
205
+ headers.set("X-SW-Strategy", options.strategy);
206
+ }
207
+ ${authBlock}${authUrlsBlock}
208
+ const fetchOptions${T("RequestInit")} = { ...options, headers };
209
+ ${authCredentialsBlock}
62
210
 
63
- return fetch(input, { ...options, headers });
64
- }
65
-
66
- export async function fetchWithCacheOrQueue(input, options = {}) {
211
+ // Offline handling
67
212
  if (!navigator.onLine) {
68
- if (options.method === "GET" || options.method === "HEAD") {
69
- const cached = await caches.match(input);
70
- if (cached) return cached;
71
- throw new Error("Offline: no cached data");
72
- } else {
73
- throw new Error("Offline: mutation queued");
213
+ ${offlineReadBlock}
214
+ ${offlineWriteBlock}
215
+ }
216
+
217
+ // Deduplicate in-flight GET requests
218
+ let responsePromise${T("Promise<Response>")};
219
+ if (isRead && inFlightRequests.has(url)) {
220
+ responsePromise = inFlightRequests.get(url)!.then((r) => r.clone());
221
+ } else {
222
+ responsePromise = fetch(input, fetchOptions);
223
+ if (isRead) {
224
+ inFlightRequests.set(url, responsePromise.finally(() => inFlightRequests.delete(url)));
74
225
  }
75
226
  }
76
- return fetchWithCache(input, options);
227
+
228
+ const response = await responsePromise;
229
+ ${autoInvalidateBlock}${auth401Block}
230
+ const fromCache = response.headers.get("X-SW-From-Cache") === "true";
231
+ return { response, fromCache };
77
232
  }
78
233
  `;
79
234
  writeFile(ctx, `fetch-wrapper.${ext}`, code);
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;6DAK8C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkE/D,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,12 @@
1
- import { writeFileSync, existsSync, mkdirSync } from "fs";
2
- import { join } from "path";
1
+ import { copyFileSync, existsSync, mkdirSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const templatesDir = join(__dirname, "../../../../templates/hooks");
6
+ function copyHook(hooksDir, name, ext) {
7
+ const src = join(templatesDir, `${name}.${ext}x`);
8
+ copyFileSync(src, join(hooksDir, `${name}.${ext}x`));
9
+ }
3
10
  export function generateHooks(ctx) {
4
11
  const { config, swoffDir, ext, frameworkName } = ctx;
5
12
  if (frameworkName !== "react")
@@ -8,160 +15,25 @@ export function generateHooks(ctx) {
8
15
  if (!existsSync(hooksDir)) {
9
16
  mkdirSync(hooksDir, { recursive: true });
10
17
  }
18
+ copyHook(hooksDir, "useNetworkStatus", ext);
19
+ copyHook(hooksDir, "useCachedFetch", ext);
11
20
  if (config.features.pwa.enabled) {
12
- writePWAHooks(hooksDir, ext);
21
+ copyHook(hooksDir, "useSWUpdate", ext);
13
22
  }
14
23
  if (config.features.auth.enabled) {
15
- writeAuthHook(hooksDir, ext);
24
+ copyHook(hooksDir, "useAuth", ext);
16
25
  }
17
26
  if (config.features.mutationQueue) {
18
- writeMutationQueueHook(hooksDir, ext);
27
+ copyHook(hooksDir, "useMutationQueue", ext);
28
+ }
29
+ if (config.features.pushNotifications?.enabled) {
30
+ copyHook(hooksDir, "usePushSubscription", ext);
31
+ }
32
+ if (config.features.backgroundSync) {
33
+ copyHook(hooksDir, "useBackgroundSync", ext);
34
+ }
35
+ if (config.features.tagInvalidation) {
36
+ copyHook(hooksDir, "useCacheInvalidation", ext);
19
37
  }
20
- }
21
- function writePWAHooks(dir, ext) {
22
- writeFileSync(join(dir, `usePWAUpdate.${ext}x`), `import { useState, useEffect, useCallback } from "react";
23
-
24
- export function usePWAUpdate() {
25
- const [state, setState] = useState({
26
- updateStatus: "idle",
27
- currentVersion: null as string | null,
28
- availableVersion: null as string | null,
29
- progress: 0,
30
- forceUpdate: false,
31
- error: null as string | null,
32
- });
33
-
34
- useEffect(() => {
35
- setState((s) => ({ ...s, currentVersion: (window as any).currentSWVersion || null }));
36
-
37
- const onAvailable = (e: CustomEvent) => setState((s) => ({
38
- ...s,
39
- updateStatus: "available" as const,
40
- availableVersion: e.detail.version,
41
- forceUpdate: (window as any).swUpdateRequired || false,
42
- }));
43
- const onProgress = (e: CustomEvent) => setState((s) => ({
44
- ...s,
45
- updateStatus: "downloading" as const,
46
- progress: e.detail.percent,
47
- }));
48
- const onReady = () => setState((s) => ({ ...s, updateStatus: "idle" as const, progress: 0 }));
49
- const onError = () => setState((s) => ({ ...s, error: "SW registration failed" }));
50
-
51
- window.addEventListener("sw-update-available", onAvailable as EventListener);
52
- window.addEventListener("sw-progress", onProgress as EventListener);
53
- window.addEventListener("sw-ready", onReady);
54
- window.addEventListener("sw-error", onError);
55
- return () => {
56
- window.removeEventListener("sw-update-available", onAvailable as EventListener);
57
- window.removeEventListener("sw-progress", onProgress as EventListener);
58
- window.removeEventListener("sw-ready", onReady);
59
- window.removeEventListener("sw-error", onError);
60
- };
61
- }, []);
62
-
63
- const acceptUpdate = useCallback(async () => {
64
- if (!state.availableVersion) return;
65
- const { handleUpdateApproved } = await import("../sw-injector.${ext}");
66
- await handleUpdateApproved(state.availableVersion);
67
- }, [state.availableVersion]);
68
-
69
- const dismissUpdate = useCallback(() => {
70
- sessionStorage.setItem("sw-dismissed-update", "true");
71
- setState((s) => ({ ...s, updateStatus: "idle" }));
72
- }, []);
73
-
74
- return { ...state, acceptUpdate, dismissUpdate };
75
- }
76
-
77
- export function useSWProgress() {
78
- const [state, setState] = useState({
79
- status: "idle" as "idle" | "installing",
80
- progress: 0,
81
- });
82
-
83
- useEffect(() => {
84
- const onProgress = (e: CustomEvent) => setState({
85
- status: "installing",
86
- progress: e.detail.percent,
87
- });
88
- const onReady = () => setState({ status: "idle", progress: 0 });
89
-
90
- window.addEventListener("sw-progress", onProgress as EventListener);
91
- window.addEventListener("sw-ready", onReady);
92
- return () => {
93
- window.removeEventListener("sw-progress", onProgress as EventListener);
94
- window.removeEventListener("sw-ready", onReady);
95
- };
96
- }, []);
97
-
98
- return state;
99
- }
100
- `);
101
- // useSWProgress is included in the same file
102
- }
103
- function writeAuthHook(dir, ext) {
104
- writeFileSync(join(dir, `useAuth.${ext}x`), `import { useState, useEffect } from "react";
105
- import { getAuthState } from "../auth-state.${ext}";
106
-
107
- export function useAuth() {
108
- const [state, setState] = useState({
109
- authenticated: false,
110
- user: null as Record<string, unknown> | null,
111
- online: navigator.onLine,
112
- });
113
-
114
- useEffect(() => {
115
- getAuthState().then(setState);
116
-
117
- const onOnline = () => setState((s) => ({ ...s, online: true }));
118
- const onOffline = () => setState((s) => ({ ...s, online: false }));
119
- const onAuthChange = () => getAuthState().then(setState);
120
-
121
- window.addEventListener("online", onOnline);
122
- window.addEventListener("offline", onOffline);
123
- window.addEventListener("sw-auth-state-change", onAuthChange);
124
-
125
- return () => {
126
- window.removeEventListener("online", onOnline);
127
- window.removeEventListener("offline", onOffline);
128
- window.removeEventListener("sw-auth-state-change", onAuthChange);
129
- };
130
- }, []);
131
-
132
- return state;
133
- }
134
- `);
135
- }
136
- function writeMutationQueueHook(dir, ext) {
137
- writeFileSync(join(dir, `useMutationQueue.${ext}x`), `import { useState, useEffect } from "react";
138
- import { getPendingCount } from "../mutation-queue.${ext}";
139
-
140
- export function useMutationQueue() {
141
- const [state, setState] = useState<{
142
- pending: number;
143
- lastSync: { succeeded: number; failed: number } | null;
144
- }>({ pending: 0, lastSync: null });
145
-
146
- useEffect(() => {
147
- getPendingCount().then((count: number) => setState((s) => ({ ...s, pending: count })));
148
-
149
- const onSync = (e: CustomEvent) => setState({
150
- pending: 0,
151
- lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
152
- });
153
- const onChange = async () => setState((s) => ({ ...s, pending: await getPendingCount() }));
154
-
155
- window.addEventListener("mutation-sync-complete", onSync as EventListener);
156
- window.addEventListener("mutation-queue-changed", onChange);
157
- return () => {
158
- window.removeEventListener("mutation-sync-complete", onSync as EventListener);
159
- window.removeEventListener("mutation-queue-changed", onChange);
160
- };
161
- }, []);
162
-
163
- return state;
164
- }
165
- `);
166
38
  }
167
39
  //# sourceMappingURL=generate-hooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC;IACrD,IAAI,aAAa,KAAK,OAAO;QAAE,OAAO;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAChC,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAClC,sBAAsB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,GAAW,EAAE,GAAW;IAC7C,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,GAAG,CAAC,EACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEA2CgE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCtE,CACE,CAAC;IAEF,6CAA6C;AAC/C,CAAC;AAED,SAAS,aAAa,CAAC,GAAW,EAAE,GAAW;IAC7C,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,WAAW,GAAG,GAAG,CAAC,EAC5B;8CAC0C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BhD,CACE,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAW,EAAE,GAAW;IACtD,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,oBAAoB,GAAG,GAAG,CAAC,EACrC;qDACiD,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BvD,CACE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAGpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,6BAA6B,CAAC,CAAC;AAEpE,SAAS,QAAQ,CAAC,QAAgB,EAAE,IAAY,EAAE,GAAW;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC;IAClD,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC;IACrD,IAAI,aAAa,KAAK,OAAO;QAAE,OAAO;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAE1C,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,EAAE,CAAC;QAC/C,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC;QACnC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC;QACpC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}