@swoff/cli 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +53 -606
  2. package/dist/__tests__/assemble-sw.test.js +8 -38
  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 +38 -18
  7. package/dist/__tests__/config-types.test.js.map +1 -1
  8. package/dist/__tests__/file-generators.test.js +2 -2
  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 +12 -12
  15. package/dist/__tests__/loader.test.js.map +1 -1
  16. package/dist/__tests__/validator.test.js +170 -74
  17. package/dist/__tests__/validator.test.js.map +1 -1
  18. package/dist/index.js +12 -11
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/cli/help.js +0 -5
  21. package/dist/lib/cli/help.js.map +1 -1
  22. package/dist/lib/commands/add.js +52 -33
  23. package/dist/lib/commands/add.js.map +1 -1
  24. package/dist/lib/commands/clean.js +1 -15
  25. package/dist/lib/commands/clean.js.map +1 -1
  26. package/dist/lib/commands/generate-guide.js +5 -5
  27. package/dist/lib/commands/generate-guide.js.map +1 -1
  28. package/dist/lib/commands/generate.js +7 -21
  29. package/dist/lib/commands/generate.js.map +1 -1
  30. package/dist/lib/commands/info.js +37 -12
  31. package/dist/lib/commands/info.js.map +1 -1
  32. package/dist/lib/commands/init.js +2 -10
  33. package/dist/lib/commands/init.js.map +1 -1
  34. package/dist/lib/commands/validate.js +3 -3
  35. package/dist/lib/commands/validate.js.map +1 -1
  36. package/dist/lib/config/loader.js +3 -6
  37. package/dist/lib/config/loader.js.map +1 -1
  38. package/dist/lib/config/validator.js +192 -42
  39. package/dist/lib/config/validator.js.map +1 -1
  40. package/dist/lib/generators/file-generators/cache.js +2 -10
  41. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  42. package/dist/lib/generators/file-generators/client-injector.js +51 -12
  43. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  44. package/dist/lib/generators/file-generators/fetch-wrapper.js +127 -58
  45. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  46. package/dist/lib/generators/file-generators/generate-hooks.js +6 -2
  47. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  48. package/dist/lib/generators/file-generators/gql-wrapper.js +139 -0
  49. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -0
  50. package/dist/lib/generators/file-generators/guide-generator.js +6 -436
  51. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  52. package/dist/lib/generators/file-generators/invalidation-tags.js +206 -19
  53. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  54. package/dist/lib/generators/file-generators/manifest.js +2 -2
  55. package/dist/lib/generators/file-generators/manifest.js.map +1 -1
  56. package/dist/lib/generators/file-generators/mutation-queue.js +140 -52
  57. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  58. package/dist/lib/generators/file-generators/mutation-state.js +126 -0
  59. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -0
  60. package/dist/lib/generators/file-generators/push.js +6 -7
  61. package/dist/lib/generators/file-generators/push.js.map +1 -1
  62. package/dist/lib/generators/file-generators/quick-readme.js +8 -75
  63. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
  64. package/dist/lib/generators/file-generators/server-push.js +146 -0
  65. package/dist/lib/generators/file-generators/server-push.js.map +1 -0
  66. package/dist/lib/generators/file-generators/sw-generator-build.js +8 -9
  67. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  68. package/dist/lib/generators/file-generators/sw-injector.js +2 -1
  69. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  70. package/dist/lib/generators/file-generators/type-definitions.js +26 -1
  71. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  72. package/dist/lib/generators/sw-generator.js +8 -18
  73. package/dist/lib/generators/sw-generator.js.map +1 -1
  74. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  75. package/dist/lib/generators/sw-sections/assemble-sw.js +19 -11
  76. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  77. package/dist/lib/generators/sw-sections/background-sync-handler.js +80 -24
  78. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  79. package/dist/lib/generators/sw-sections/config-header.js +2 -2
  80. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  81. package/dist/lib/generators/sw-sections/default-template.js +2 -1
  82. package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
  83. package/dist/lib/generators/sw-sections/fetch-handler.js +461 -121
  84. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  85. package/dist/lib/generators/sw-sections/message-handler.js +55 -15
  86. package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
  87. package/dist/lib/generators/sw-sections/server-push-handler.js +114 -0
  88. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -0
  89. package/dist/lib/generators/sw-sections/sw-push.js +67 -0
  90. package/dist/lib/generators/sw-sections/sw-push.js.map +1 -0
  91. package/dist/lib/generators/sw-sections/tag-management.js +62 -21
  92. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  93. package/dist/lib/generators/swoff-files-generator.js +12 -16
  94. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  95. package/dist/lib/shared/config-types.js +133 -45
  96. package/dist/lib/shared/config-types.js.map +1 -1
  97. package/dist/lib/shared/glob-matcher.js +77 -0
  98. package/dist/lib/shared/glob-matcher.js.map +1 -0
  99. package/dist/lib/utils/tty-status.js +21 -0
  100. package/dist/lib/utils/tty-status.js.map +1 -0
  101. package/docs/API.md +565 -0
  102. package/docs/ARCHITECTURE.md +374 -0
  103. package/docs/CLI.md +148 -0
  104. package/docs/COMPARISON.md +192 -0
  105. package/docs/CONFIG.md +277 -0
  106. package/docs/ECOSYSTEM.md +33 -0
  107. package/package.json +2 -1
  108. package/templates/hooks/useAuth.jsx +2 -2
  109. package/templates/hooks/useAuth.tsx +2 -2
  110. package/templates/hooks/useCachedFetch.jsx +21 -7
  111. package/templates/hooks/useCachedFetch.tsx +23 -11
  112. package/templates/hooks/useMutation.jsx +76 -0
  113. package/templates/hooks/useMutation.tsx +109 -0
  114. package/templates/hooks/useMutationQueue.jsx +20 -11
  115. package/templates/hooks/useMutationQueue.tsx +31 -15
  116. package/templates/hooks/useMutationState.jsx +13 -0
  117. package/templates/hooks/useMutationState.tsx +25 -0
  118. package/templates/hooks/usePrefetch.jsx +10 -0
  119. package/templates/hooks/usePrefetch.tsx +25 -0
  120. package/templates/hooks/usePushSubscription.jsx +3 -3
  121. package/templates/hooks/usePushSubscription.tsx +3 -3
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Generates fetch-wrapper.ts/js — unified fetch with caching, auth, offline queue, and auto-invalidation.
2
+ * Generates fetch-wrapper.ts/js — unified fetch with caching, auth, offline queue, auto-invalidation,
3
+ * staleTime, prefetching, and cancellation.
3
4
  */
4
5
  import { writeFile } from "./context.js";
5
6
  export function generateFetchWrapper(ctx) {
@@ -8,12 +9,12 @@ export function generateFetchWrapper(ctx) {
8
9
  const T = (type) => (ts ? `: ${type}` : "");
9
10
  const R = (type) => (ts ? `: ${type} ` : " ");
10
11
  const G = (type) => (ts ? `<${type}>` : "");
11
- const tagInvalidation = ctx.config.features.tagInvalidation;
12
+ const tagInvalidation = ctx.config.features.tagInvalidation.enabled;
12
13
  const authEnabled = ctx.config.features.auth.enabled;
13
- const mutationQueue = ctx.config.features.mutationQueue;
14
+ const mutationQueue = ctx.config.features.mutationQueue.enabled;
14
15
  const importLines = [
15
16
  tagInvalidation
16
- ? `import { generateTags } from "./invalidation-tags.${ext}";`
17
+ ? `import { generateTags, invalidateUrl${tagInvalidation && mutationQueue ? ", expandCascading" : ""} } from "./invalidation-tags.${ext}";`
17
18
  : "",
18
19
  tagInvalidation ? `import { invalidateByTags } from "./cache.${ext}";` : "",
19
20
  authEnabled
@@ -30,20 +31,24 @@ export function generateFetchWrapper(ctx) {
30
31
  export interface FetchWithCacheResult${G("T")} {
31
32
  response${T("Response & { json(): Promise<T> }")};
32
33
  fromCache${T("boolean")};
34
+ queued${T("boolean")};
33
35
  }
34
36
  `
35
37
  : "";
36
- const optionsType = ts ? `FetchWithCacheOptions` : `options`;
37
38
  const optionsInterface = ts
38
39
  ? `
39
40
  export interface FetchWithCacheOptions extends RequestInit {
40
41
  tags?: string[];
41
- staleWhileRevalidate?: boolean;
42
42
  auth?: boolean;
43
43
  queueOffline?: boolean;
44
44
  invalidate?: 'auto' | string[] | false;
45
45
  type?: 'read' | 'mutation';
46
- strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only';
46
+ strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only' | 'reactive';
47
+ staleTime?: number;
48
+ /** Custom response validation for mutation success. Default: res.ok. Use this when your API returns 200 with { success: false } for logical failures. */
49
+ validateSuccess?: (response: Response) => boolean | Promise<boolean>;
50
+ /** Override the URL used for auto-invalidation. Defaults to the request URL. Useful when the mutation URL differs from the cache tag URL. */
51
+ invalidateUrl?: string;
47
52
  }
48
53
  `
49
54
  : "";
@@ -74,44 +79,56 @@ export interface FetchWithCacheOptions extends RequestInit {
74
79
  window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
75
80
  }`
76
81
  : "";
77
- const offlineReadBlock = ` if (isRead) {
82
+ const mutationTagsBlock = tagInvalidation && mutationQueue
83
+ ? ` // Pre-compute invalidation tags for SW-side IDB storage
84
+ let mutationTags: string[] = [];
85
+ if (!isRead) {
86
+ const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
87
+ if (invalidateSetting !== false) {
88
+ if (Array.isArray(invalidateSetting)) {
89
+ mutationTags = invalidateSetting;
90
+ } else {
91
+ mutationTags = generateTags(url);
92
+ mutationTags = expandCascading(mutationTags);
93
+ }
94
+ headers.set("X-SW-Invalidate-Tags", mutationTags.join(","));
95
+ }
96
+ }`
97
+ : ` let mutationTags: string[] = options.tags || [];`;
98
+ const offlineReadCatchBlock = ` if (options.signal?.aborted) throw new DOMException("The operation was aborted", "AbortError");
78
99
  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
- });
100
+ if (cached) return { response: cached, fromCache: true, queued: false };
101
+ throw new Error("Offline: no cached data available");`;
102
+ const offlineWriteFallbackBlock = mutationQueue
103
+ ? ` if (options.queueOffline !== false) {
89
104
  await queueMutation({
90
105
  method,
91
106
  url,
92
107
  body: options.body,
93
- headers: headerObj,
94
- tags: options.tags || [],
108
+ headers: {},
109
+ tags: mutationTags,
95
110
  timestamp: Date.now(),
96
111
  });
97
112
  return {
98
- response: new Response(null, { status: 202, statusText: "Queued" }),
113
+ response: new Response(JSON.stringify({ queued: true }), { status: 202, headers: { "Content-Type": "application/json" } }),
99
114
  fromCache: false,
115
+ queued: true,
100
116
  };
101
- }
102
- throw new Error("Offline: write operation failed");`
103
- : ` throw new Error("Offline: write operation failed");`;
117
+ }`
118
+ : ``;
104
119
  const autoInvalidateBlock = tagInvalidation
105
120
  ? `
106
- // Auto-invalidate after mutation success
107
- if (!isRead && response.ok) {
121
+ // Auto-invalidate after mutation success (skip if SW queued it)
122
+ const mutationSuccess = options.validateSuccess ? await options.validateSuccess(response) : response.ok;
123
+ const mutationQueued = response.headers.get("X-SW-Mutation-Queued") === "true";
124
+ if (!isRead && mutationSuccess && !mutationQueued) {
108
125
  const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
109
126
  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);
127
+ const invalidateTarget = options.invalidateUrl || url;
128
+ if (Array.isArray(invalidateSetting)) {
129
+ await invalidateByTags(invalidateSetting);
130
+ } else {
131
+ await invalidateUrl(invalidateTarget);
115
132
  }
116
133
  }
117
134
  }`
@@ -128,11 +145,11 @@ export interface FetchWithCacheOptions extends RequestInit {
128
145
  : "";
129
146
  const code = `/**
130
147
  * Swoff Fetch Wrapper
131
- * Unified fetch with caching, auth, offline queue, auto-invalidation, and
148
+ * Unified fetch with caching, auth, offline queue, auto-invalidation, staleTime, prefetching, and
132
149
  * per-request strategy override.
133
150
  *
134
151
  * Usage:
135
- * import { fetchWithCache } from './swoff/fetch-wrapper.${ext}';
152
+ * import { fetchWithCache, prefetchCache } from './swoff/fetch-wrapper.${ext}';
136
153
  *
137
154
  * // GET — cached with auto-generated tags
138
155
  * const { response } = await fetchWithCache("/api/todos");
@@ -144,14 +161,31 @@ export interface FetchWithCacheOptions extends RequestInit {
144
161
  * body: JSON.stringify({ title: "New task" }),
145
162
  * });
146
163
  *
164
+ * // Skip auto-invalidation, invalidate manually later
165
+ * await fetchWithCache("/api/todos", {
166
+ * method: "POST",
167
+ * body: JSON.stringify({ title: "New" }),
168
+ * invalidate: false,
169
+ * });
170
+ *
171
+ * // Validate mutation success with custom logic
172
+ * await fetchWithCache("/api/todos", {
173
+ * method: "POST",
174
+ * body: JSON.stringify({ title: "New" }),
175
+ * validateSuccess: (res) => res.status === 200,
176
+ * });
177
+ *
178
+ * // Custom tags for caching (read) + explicit invalidate tags (mutation)
179
+ * await fetchWithCache("/api/todos", {
180
+ * tags: ["custom-tag"],
181
+ * invalidate: ["custom-tag"],
182
+ * });
183
+ *
147
184
  * // Authenticated request (works with bearer, cookie, custom)
148
185
  * const { response: userRes } = await fetchWithCache("/api/me", { auth: true });
149
186
  *
150
- * // Custom tags + stale-while-revalidate
151
- * const { response: staleRes, fromCache } = await fetchWithCache("/api/data", {
152
- * tags: ["data"],
153
- * staleWhileRevalidate: true,
154
- * });
187
+ * // Prefetch (fire-and-forget warm the cache)
188
+ * prefetchCache("/api/todos");
155
189
  *
156
190
  * // Override caching strategy per-request (highest priority)
157
191
  * await fetchWithCache("/api/checkout", {
@@ -160,13 +194,10 @@ export interface FetchWithCacheOptions extends RequestInit {
160
194
  * strategy: "network-only",
161
195
  * });
162
196
  *
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
- * });
197
+ * // Support AbortController for cancellation
198
+ * const controller = new AbortController();
199
+ * setTimeout(() => controller.abort(), 5000);
200
+ * const { response } = await fetchWithCache("/api/todos", { signal: controller.signal });
170
201
  *
171
202
  * // Offline: auto-queues writes (disable with queueOffline: false)
172
203
  * await fetchWithCache("/api/todos", {
@@ -181,9 +212,9 @@ ${interfaceBlock}${optionsInterface}
181
212
  const inFlightRequests = new Map${G("string, Promise<Response>")}();
182
213
 
183
214
  /** 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>>")}{
215
+ export async function fetchWithCache${G("T")}(input${T("RequestInfo")}, options${T("RequestInit & FetchWithCacheOptions")} = {})${R("Promise<FetchWithCacheResult<T>>")}{
185
216
  const method = (options.method || "GET").toUpperCase();
186
- const isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD"));
217
+ const isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD" || method === "OPTIONS"));
187
218
  const url = typeof input === "string" ? input : input.url;
188
219
 
189
220
  const headers = new Headers(options.headers);
@@ -198,37 +229,75 @@ ${autoTagsBlock}
198
229
  headers.set("X-SW-Cache-Tags", options.tags.join(","));
199
230
  }
200
231
 
201
- if (options.staleWhileRevalidate && !options.strategy) {
202
- headers.set("X-SW-Strategy", "stale-while-revalidate");
203
- }
204
232
  if (options.strategy) {
205
233
  headers.set("X-SW-Strategy", options.strategy);
206
234
  }
235
+ if (options.staleTime !== undefined) {
236
+ headers.set("X-SW-Stale-Time", String(options.staleTime));
237
+ }
207
238
  ${authBlock}${authUrlsBlock}
239
+ // Forward no-queue option to SW
240
+ if (options.queueOffline === false) {
241
+ headers.set("X-SW-No-Queue", "true");
242
+ }
243
+
208
244
  const fetchOptions${T("RequestInit")} = { ...options, headers };
209
245
  ${authCredentialsBlock}
210
246
 
211
- // Offline handling
212
- if (!navigator.onLine) {
213
- ${offlineReadBlock}
214
- ${offlineWriteBlock}
247
+ // Check for abort before proceeding
248
+ if (options.signal?.aborted) {
249
+ throw new DOMException("The operation was aborted", "AbortError");
215
250
  }
216
-
251
+ ${mutationTagsBlock}
217
252
  // Deduplicate in-flight GET requests
218
253
  let responsePromise${T("Promise<Response>")};
219
254
  if (isRead && inFlightRequests.has(url)) {
220
255
  responsePromise = inFlightRequests.get(url)!.then((r) => r.clone());
221
256
  } else {
257
+ const abortHandler = () => {
258
+ inFlightRequests.delete(url);
259
+ };
260
+ if (options.signal) {
261
+ options.signal.addEventListener("abort", abortHandler, { once: true });
262
+ }
222
263
  responsePromise = fetch(input, fetchOptions);
223
264
  if (isRead) {
224
- inFlightRequests.set(url, responsePromise.finally(() => inFlightRequests.delete(url)));
265
+ const cleanup = () => {
266
+ inFlightRequests.delete(url);
267
+ if (options.signal) {
268
+ options.signal.removeEventListener("abort", abortHandler);
269
+ }
270
+ };
271
+ inFlightRequests.set(url, responsePromise.finally(cleanup));
225
272
  }
226
273
  }
227
274
 
228
- const response = await responsePromise;
275
+ let response${T("Response")};
276
+ try {
277
+ response = await responsePromise;
278
+ } catch (err) {
279
+ if (err instanceof TypeError) {
280
+ if (isRead) {
281
+ ${offlineReadCatchBlock}
282
+ }
283
+ // SW not controlling — fallback to client-side queue
284
+ if (!navigator.serviceWorker?.controller) {
285
+ ${offlineWriteFallbackBlock}
286
+ }
287
+ }
288
+ throw err;
289
+ }
229
290
  ${autoInvalidateBlock}${auth401Block}
230
291
  const fromCache = response.headers.get("X-SW-From-Cache") === "true";
231
- return { response, fromCache };
292
+ const queued = response.headers.get("X-SW-Mutation-Queued") === "true";
293
+ return { response, fromCache, queued };
294
+ }
295
+
296
+ /** Fire-and-forget prefetch: warms the cache for a URL without blocking. Useful for route prefetching or link hover prefetching. */
297
+ export function prefetchCache(input${T("RequestInfo")}, options${T("RequestInit & FetchWithCacheOptions")} = {})${R("void")}{
298
+ fetchWithCache(input, { ...options }).catch(() => {
299
+ // Prefetch failures are intentionally silent
300
+ });
232
301
  }
233
302
  `;
234
303
  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":"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"}
1
+ {"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"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"}
@@ -15,15 +15,19 @@ export function generateHooks(ctx) {
15
15
  if (!existsSync(hooksDir)) {
16
16
  mkdirSync(hooksDir, { recursive: true });
17
17
  }
18
+ // Always generated hooks
18
19
  copyHook(hooksDir, "useNetworkStatus", ext);
19
20
  copyHook(hooksDir, "useCachedFetch", ext);
21
+ copyHook(hooksDir, "useMutation", ext);
22
+ copyHook(hooksDir, "usePrefetch", ext);
23
+ copyHook(hooksDir, "useMutationState", ext);
20
24
  if (config.features.pwa.enabled) {
21
25
  copyHook(hooksDir, "useSWUpdate", ext);
22
26
  }
23
27
  if (config.features.auth.enabled) {
24
28
  copyHook(hooksDir, "useAuth", ext);
25
29
  }
26
- if (config.features.mutationQueue) {
30
+ if (config.features.mutationQueue.enabled) {
27
31
  copyHook(hooksDir, "useMutationQueue", ext);
28
32
  }
29
33
  if (config.features.pushNotifications?.enabled) {
@@ -32,7 +36,7 @@ export function generateHooks(ctx) {
32
36
  if (config.features.backgroundSync) {
33
37
  copyHook(hooksDir, "useBackgroundSync", ext);
34
38
  }
35
- if (config.features.tagInvalidation) {
39
+ if (config.features.tagInvalidation.enabled) {
36
40
  copyHook(hooksDir, "useCacheInvalidation", ext);
37
41
  }
38
42
  }
@@ -1 +1 @@
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"}
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,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAE5C,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,CAAC,OAAO,EAAE,CAAC;QAC1C,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,CAAC,OAAO,EAAE,CAAC;QAC5C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}
@@ -0,0 +1,139 @@
1
+ import { writeFile } from "./context.js";
2
+ export function generateGqlWrapper(ctx) {
3
+ const ext = ctx.ext;
4
+ const ts = ext === "ts";
5
+ const T = (type) => (ts ? `: ${type}` : "");
6
+ const R = (type) => (ts ? `: ${type} ` : " ");
7
+ const G = (type) => (ts ? `<${type}>` : "");
8
+ const AS = (type) => (ts ? ` as ${type}` : "");
9
+ const endpoint = ctx.config.features.graphql.endpoint;
10
+ const optionsInterface = ts
11
+ ? `
12
+ export interface GqlOptions {
13
+ variables?: Record<string, unknown>;
14
+ tags?: string[];
15
+ auth?: boolean;
16
+ queueOffline?: boolean;
17
+ invalidate?: 'auto' | string[] | false;
18
+ }
19
+ `
20
+ : "";
21
+ const code = `/**
22
+ * Swoff GraphQL Wrapper
23
+ * Brings Swoff's caching, offline queue, and tag-based invalidation to GraphQL APIs.
24
+ * Hashes query + variables for deterministic cache keys and auto-generates tags
25
+ * from operation names. Built on top of fetchWithCache.
26
+ *
27
+ * Usage:
28
+ * import { fetchWithGql, queryGql, mutateGql } from './swoff/gql-wrapper.${ext}';
29
+ *
30
+ * // Query — cached with body-hash key
31
+ * const { data } = await queryGql("{ todos { id title } }");
32
+ *
33
+ * // Query with variables
34
+ * const { data: todo } = await queryGql(
35
+ * "query GetTodo($id: ID!) { todo(id: $id) { id title } }",
36
+ * { id: "42" }
37
+ * );
38
+ *
39
+ * // Mutation — auto-invalidates related cache tags
40
+ * const { data: created } = await mutateGql(
41
+ * "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",
42
+ * { title: "New task" }
43
+ * );
44
+ *
45
+ * // Authenticated
46
+ * const { data } = await queryGql("query Me { me { name } }", {}, { auth: true });
47
+ *
48
+ * // Offline: mutations are auto-queued
49
+ * const { data } = await mutateGql(
50
+ * "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",
51
+ * { title: "Offline task" },
52
+ * { queueOffline: true }
53
+ * );
54
+ */
55
+
56
+ import { fetchWithCache } from "./fetch-wrapper.${ext}";
57
+ import type { GqlResult } from "./swoff";
58
+ ${optionsInterface}
59
+ /** Extract operation name from a GraphQL document. Returns null for anonymous queries (e.g. "{ todos { id } }"). */
60
+ function getOperationName(query${T("string")})${T("string | null")}{
61
+ const match = query.match(/(query|mutation|subscription)\\s+(\\w+)/);
62
+ return match ? match[2] : null;
63
+ }
64
+
65
+ /** Determine if a GraphQL document is a query (read) vs mutation/subscription (write). */
66
+ function isReadOperation(query${T("string")})${T("boolean")}{
67
+ const trimmed = query.trim();
68
+ if (trimmed.startsWith("mutation") || trimmed.startsWith("subscription")) return false;
69
+ return true;
70
+ }
71
+
72
+ /** Hash an object into a hex string for deterministic cache keying. Uses SHA-256 via SubtleCrypto. */
73
+ async function bodyHash(obj${T("unknown")})${T("Promise<string>")}{
74
+ const json = JSON.stringify(obj);
75
+ const bytes = new TextEncoder().encode(json);
76
+ const hash = await crypto.subtle.digest("SHA-256", bytes);
77
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
78
+ }
79
+
80
+ /** Generate cache-relevant tags from a GQL operation name. e.g. "getTodos" → ["todos"], "createTodo" → ["todos", "todo"]. */
81
+ function tagsFromOpName(name${T("string | null")})${T("string[]")}{
82
+ if (!name) return [];
83
+ const stripped = name.replace(/^(get|fetch|list|all|query)/i, "").replace(/^(create|set|add|new|update|delete|remove)/i, "");
84
+ if (!stripped) return [name.toLowerCase()];
85
+ const tag = stripped.charAt(0).toLowerCase() + stripped.slice(1);
86
+ const plural = tag.replace(/s$/, "") + "s";
87
+ return [plural, tag];
88
+ }
89
+
90
+ /** Fetch a GraphQL endpoint with Swoff's caching, auth, offline queue, and auto-invalidation. Hashes query + variables for deterministic cache keys. */
91
+ export async function fetchWithGql${G("T")}(
92
+ query${T("string")},
93
+ options${T("GqlOptions")} = {}
94
+ )${R("Promise<GqlResult<T>>")}{
95
+ const isRead = isReadOperation(query);
96
+ const opName = getOperationName(query);
97
+ const variables = options.variables;
98
+ const hash = await bodyHash({ query, variables });
99
+ const tags = options.tags || tagsFromOpName(opName);
100
+
101
+ const { response, fromCache } = await fetchWithCache("${endpoint}", {
102
+ method: "POST",
103
+ body: JSON.stringify({ query, variables }),
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ "X-SW-Cache-Key": "gql:" + hash,
107
+ },
108
+ tags,
109
+ type: isRead ? "read" : "mutation",
110
+ auth: options.auth,
111
+ queueOffline: options.queueOffline,
112
+ invalidate: options.invalidate,
113
+ });
114
+
115
+ const result = await response.json();
116
+ return { data: result.data${AS("T")}, fromCache };
117
+ }
118
+
119
+ /** Shorthand for GraphQL queries (type: "read"). Cached by default with body-hash key. */
120
+ export async function queryGql${G("T")}(
121
+ query${T("string")},
122
+ variables${T("Record<string, unknown> | undefined")} = undefined,
123
+ options${T("GqlOptions")} = {}
124
+ )${R("Promise<GqlResult<T>>")}{
125
+ return fetchWithGql${G("T")}(query, { ...options, variables });
126
+ }
127
+
128
+ /** Shorthand for GraphQL mutations (type: "mutation"). Auto-invalidates related cache tags. */
129
+ export async function mutateGql${G("T")}(
130
+ mutation${T("string")},
131
+ variables${T("Record<string, unknown> | undefined")} = undefined,
132
+ options${T("GqlOptions")} = {}
133
+ )${R("Promise<GqlResult<T>>")}{
134
+ return fetchWithGql${G("T")}(mutation, { ...options, variables });
135
+ }
136
+ `;
137
+ writeFile(ctx, `gql-wrapper.${ext}`, code);
138
+ }
139
+ //# sourceMappingURL=gql-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gql-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/gql-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,kBAAkB,CAAC,GAAqB;IACtD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;IAEtD,MAAM,gBAAgB,GAAG,EAAE;QACzB,CAAC,CAAC;;;;;;;;CAQL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,IAAI,GAAG;;;;;;;8EAO+D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;kDA4B/B,GAAG;;EAEnD,gBAAgB;;iCAEe,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;gCAMlC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;;;;;;;6BAO9B,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC;;;;;;;;8BAQnC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;oCAU7B,CAAC,CAAC,GAAG,CAAC;SACjC,CAAC,CAAC,QAAQ,CAAC;WACT,CAAC,CAAC,YAAY,CAAC;GACvB,CAAC,CAAC,uBAAuB,CAAC;;;;;;;0DAO6B,QAAQ;;;;;;;;;;;;;;;8BAepC,EAAE,CAAC,GAAG,CAAC;;;;gCAIL,CAAC,CAAC,GAAG,CAAC;SAC7B,CAAC,CAAC,QAAQ,CAAC;aACP,CAAC,CAAC,qCAAqC,CAAC;WAC1C,CAAC,CAAC,YAAY,CAAC;GACvB,CAAC,CAAC,uBAAuB,CAAC;uBACN,CAAC,CAAC,GAAG,CAAC;;;;iCAII,CAAC,CAAC,GAAG,CAAC;YAC3B,CAAC,CAAC,QAAQ,CAAC;aACV,CAAC,CAAC,qCAAqC,CAAC;WAC1C,CAAC,CAAC,YAAY,CAAC;GACvB,CAAC,CAAC,uBAAuB,CAAC;uBACN,CAAC,CAAC,GAAG,CAAC;;CAE5B,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC"}