@swoff/cli 0.3.6 → 0.3.8

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 (109) hide show
  1. package/README.md +43 -326
  2. package/bin/swoff +8 -14
  3. package/dist/__tests__/assemble-sw.test.js +6 -6
  4. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  5. package/dist/__tests__/cli-integration.test.js +0 -1
  6. package/dist/__tests__/cli-integration.test.js.map +1 -1
  7. package/dist/__tests__/config-types.test.js +9 -5
  8. package/dist/__tests__/config-types.test.js.map +1 -1
  9. package/dist/__tests__/file-generators.test.js +12 -23
  10. package/dist/__tests__/file-generators.test.js.map +1 -1
  11. package/dist/__tests__/loader.test.js +0 -1
  12. package/dist/__tests__/loader.test.js.map +1 -1
  13. package/dist/__tests__/validator.test.js +50 -2
  14. package/dist/__tests__/validator.test.js.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/commands/add.js +7 -6
  18. package/dist/lib/commands/add.js.map +1 -1
  19. package/dist/lib/commands/generate-guide.js +36 -9
  20. package/dist/lib/commands/generate-guide.js.map +1 -1
  21. package/dist/lib/commands/info.js +45 -8
  22. package/dist/lib/commands/info.js.map +1 -1
  23. package/dist/lib/commands/init.js +6 -2
  24. package/dist/lib/commands/init.js.map +1 -1
  25. package/dist/lib/config/validator.js +85 -4
  26. package/dist/lib/config/validator.js.map +1 -1
  27. package/dist/lib/generators/file-generators/auth-fetch.js +3 -3
  28. package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
  29. package/dist/lib/generators/file-generators/auth-store.js +114 -5
  30. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  31. package/dist/lib/generators/file-generators/auth-user.js +2 -2
  32. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  33. package/dist/lib/generators/file-generators/cache.js +1 -3
  34. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  35. package/dist/lib/generators/file-generators/client-injector.js +5 -17
  36. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  37. package/dist/lib/generators/file-generators/fetch-wrapper.js +86 -20
  38. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  39. package/dist/lib/generators/file-generators/generate-hooks.js +28 -210
  40. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  41. package/dist/lib/generators/file-generators/gql-wrapper.js +152 -0
  42. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -0
  43. package/dist/lib/generators/file-generators/guide-generator.js +357 -36
  44. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  45. package/dist/lib/generators/file-generators/mutation-queue.js +130 -34
  46. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  47. package/dist/lib/generators/file-generators/mutation-state.js +126 -0
  48. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -0
  49. package/dist/lib/generators/file-generators/push.js +143 -0
  50. package/dist/lib/generators/file-generators/push.js.map +1 -0
  51. package/dist/lib/generators/file-generators/quick-readme.js +71 -11
  52. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
  53. package/dist/lib/generators/file-generators/server-push.js +136 -0
  54. package/dist/lib/generators/file-generators/server-push.js.map +1 -0
  55. package/dist/lib/generators/file-generators/sw-generator-build.js +1 -1
  56. package/dist/lib/generators/file-generators/sw-injector.js +1 -1
  57. package/dist/lib/generators/file-generators/sw-push.js +67 -0
  58. package/dist/lib/generators/file-generators/sw-push.js.map +1 -0
  59. package/dist/lib/generators/file-generators/sw-template.js +2 -0
  60. package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
  61. package/dist/lib/generators/file-generators/type-definitions.js +39 -11
  62. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  63. package/dist/lib/generators/sw-generator.js +8 -9
  64. package/dist/lib/generators/sw-generator.js.map +1 -1
  65. package/dist/lib/generators/sw-sections/activate-handler.js +6 -2
  66. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  67. package/dist/lib/generators/sw-sections/assemble-sw.js +11 -2
  68. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  69. package/dist/lib/generators/sw-sections/background-sync-handler.js +22 -8
  70. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  71. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  72. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  73. package/dist/lib/generators/sw-sections/default-template.js +2 -30
  74. package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
  75. package/dist/lib/generators/sw-sections/fetch-handler.js +230 -120
  76. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  77. package/dist/lib/generators/sw-sections/install-handler.js +12 -10
  78. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
  79. package/dist/lib/generators/sw-sections/server-push-handler.js +114 -0
  80. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -0
  81. package/dist/lib/generators/sw-sections/tag-management.js +17 -5
  82. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  83. package/dist/lib/generators/swoff-files-generator.js +16 -10
  84. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  85. package/dist/lib/shared/config-types.js +44 -4
  86. package/dist/lib/shared/config-types.js.map +1 -1
  87. package/package.json +3 -2
  88. package/templates/hooks/useAuth.jsx +30 -0
  89. package/templates/hooks/useAuth.tsx +30 -0
  90. package/templates/hooks/useBackgroundSync.jsx +31 -0
  91. package/templates/hooks/useBackgroundSync.tsx +31 -0
  92. package/templates/hooks/useCacheInvalidation.jsx +19 -0
  93. package/templates/hooks/useCacheInvalidation.tsx +19 -0
  94. package/templates/hooks/useCachedFetch.jsx +94 -0
  95. package/templates/hooks/useCachedFetch.tsx +112 -0
  96. package/templates/hooks/useMutation.jsx +76 -0
  97. package/templates/hooks/useMutation.tsx +97 -0
  98. package/templates/hooks/useMutationQueue.jsx +42 -0
  99. package/templates/hooks/useMutationQueue.tsx +49 -0
  100. package/templates/hooks/useMutationState.jsx +27 -0
  101. package/templates/hooks/useMutationState.tsx +41 -0
  102. package/templates/hooks/useNetworkStatus.jsx +19 -0
  103. package/templates/hooks/useNetworkStatus.tsx +19 -0
  104. package/templates/hooks/usePrefetch.jsx +10 -0
  105. package/templates/hooks/usePrefetch.tsx +25 -0
  106. package/templates/hooks/usePushSubscription.jsx +67 -0
  107. package/templates/hooks/usePushSubscription.tsx +67 -0
  108. package/templates/hooks/useSWUpdate.jsx +76 -0
  109. package/templates/hooks/useSWUpdate.tsx +76 -0
@@ -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) {
@@ -10,14 +11,14 @@ export function generateFetchWrapper(ctx) {
10
11
  const G = (type) => (ts ? `<${type}>` : "");
11
12
  const tagInvalidation = ctx.config.features.tagInvalidation;
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
17
  ? `import { generateTags } from "./invalidation-tags.${ext}";`
17
18
  : "",
18
19
  tagInvalidation ? `import { invalidateByTags } from "./cache.${ext}";` : "",
19
20
  authEnabled
20
- ? `import { getAuth, clearAuth } from "./auth/store.${ext}";`
21
+ ? `import { getAuth, clearAuth, withAuthHeaders, isAuthUrl, AUTH_WITH_CREDENTIALS } from "./auth/store.${ext}";`
21
22
  : "",
22
23
  mutationQueue
23
24
  ? `import { queueMutation } from "./mutation-queue.${ext}";`
@@ -27,13 +28,19 @@ export function generateFetchWrapper(ctx) {
27
28
  .join("\n");
28
29
  const interfaceBlock = ts
29
30
  ? `
30
- export interface FetchWithCacheResult {
31
- response${T("Response")};
31
+ export interface FetchWithCacheResult${G("T")} {
32
+ response${T("Response & { json(): Promise<T> }")};
32
33
  fromCache${T("boolean")};
33
34
  }
35
+
36
+ export interface ResolvedFetchConfig {
37
+ staleTime${T("number")};
38
+ refetchOnWindowFocus${T("boolean")};
39
+ refetchOnReconnect${T("boolean")};
40
+ refetchInterval${T("number")};
41
+ }
34
42
  `
35
43
  : "";
36
- const optionsType = ts ? `FetchWithCacheOptions` : `options`;
37
44
  const optionsInterface = ts
38
45
  ? `
39
46
  export interface FetchWithCacheOptions extends RequestInit {
@@ -42,6 +49,9 @@ export interface FetchWithCacheOptions extends RequestInit {
42
49
  auth?: boolean;
43
50
  queueOffline?: boolean;
44
51
  invalidate?: 'auto' | string[] | false;
52
+ type?: 'read' | 'mutation';
53
+ strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only';
54
+ staleTime?: number;
45
55
  }
46
56
  `
47
57
  : "";
@@ -49,17 +59,20 @@ export interface FetchWithCacheOptions extends RequestInit {
49
59
  ? `
50
60
  if (options.auth) {
51
61
  const auth = await getAuth();
52
- if (auth?.token) {
53
- headers.set("Authorization", \`Bearer \${auth.token}\`);
54
- }
62
+ withAuthHeaders(headers, auth);
55
63
  }`
56
64
  : "";
57
65
  const authUrlsBlock = authEnabled
58
66
  ? `
59
67
  // Auth endpoints bypass SW cache
60
- const authPaths = ["/login", "/logout", "/register"];
61
- if (options.auth && authPaths.some((p) => url.includes(p)) && !headers.has("X-SW-Cache-Strategy")) {
68
+ if (options.auth && isAuthUrl(url) && !headers.has("X-SW-Cache-Strategy")) {
62
69
  headers.set("X-SW-Cache-Strategy", "mutation");
70
+ }`
71
+ : "";
72
+ const authCredentialsBlock = authEnabled
73
+ ? `
74
+ if (AUTH_WITH_CREDENTIALS) {
75
+ fetchOptions.credentials = "include";
63
76
  }`
64
77
  : "";
65
78
  const auth401Block = authEnabled
@@ -70,6 +83,7 @@ export interface FetchWithCacheOptions extends RequestInit {
70
83
  }`
71
84
  : "";
72
85
  const offlineReadBlock = ` if (isRead) {
86
+ if (options.signal?.aborted) throw new DOMException("The operation was aborted", "AbortError");
73
87
  const cached = await caches.match(input);
74
88
  if (cached) return { response: cached, fromCache: true };
75
89
  throw new Error("Offline: no cached data available");
@@ -121,12 +135,17 @@ export interface FetchWithCacheOptions extends RequestInit {
121
135
  }
122
136
  }`
123
137
  : "";
138
+ const staleTimeHeaders = ` // Pass staleTime to SW if set at per-request level
139
+ if (options.staleTime !== undefined) {
140
+ headers.set("X-SW-Stale-Time", String(options.staleTime));
141
+ }`;
124
142
  const code = `/**
125
143
  * Swoff Fetch Wrapper
126
- * Unified fetch with caching, auth, offline queue, and auto-invalidation.
144
+ * Unified fetch with caching, auth, offline queue, auto-invalidation, staleTime, prefetching, and
145
+ * per-request strategy override.
127
146
  *
128
147
  * Usage:
129
- * import { fetchWithCache } from './swoff/fetch-wrapper.${ext}';
148
+ * import { fetchWithCache, prefetchCache } from './swoff/fetch-wrapper.${ext}';
130
149
  *
131
150
  * // GET — cached with auto-generated tags
132
151
  * const { response } = await fetchWithCache("/api/todos");
@@ -138,7 +157,7 @@ export interface FetchWithCacheOptions extends RequestInit {
138
157
  * body: JSON.stringify({ title: "New task" }),
139
158
  * });
140
159
  *
141
- * // Authenticated request
160
+ * // Authenticated request (works with bearer, cookie, custom)
142
161
  * const { response: userRes } = await fetchWithCache("/api/me", { auth: true });
143
162
  *
144
163
  * // Custom tags + stale-while-revalidate
@@ -147,6 +166,24 @@ export interface FetchWithCacheOptions extends RequestInit {
147
166
  * staleWhileRevalidate: true,
148
167
  * });
149
168
  *
169
+ * // Set staleTime per-request (overrides config — SW handles bg refresh)
170
+ * await fetchWithCache("/api/todos", { staleTime: 30 });
171
+ *
172
+ * // Prefetch (fire-and-forget warm the cache)
173
+ * prefetchCache("/api/todos");
174
+ *
175
+ * // Override caching strategy per-request (highest priority)
176
+ * await fetchWithCache("/api/checkout", {
177
+ * method: "POST",
178
+ * type: "read",
179
+ * strategy: "network-only",
180
+ * });
181
+ *
182
+ * // Support AbortController for cancellation
183
+ * const controller = new AbortController();
184
+ * setTimeout(() => controller.abort(), 5000);
185
+ * const { response } = await fetchWithCache("/api/todos", { signal: controller.signal });
186
+ *
150
187
  * // Offline: auto-queues writes (disable with queueOffline: false)
151
188
  * await fetchWithCache("/api/todos", {
152
189
  * method: "POST",
@@ -159,10 +196,10 @@ ${importLines}
159
196
  ${interfaceBlock}${optionsInterface}
160
197
  const inFlightRequests = new Map${G("string, Promise<Response>")}();
161
198
 
162
- /** Fetch with caching, auth, offline queue, and auto-invalidation. Returns { response, fromCache }. */
163
- export async function fetchWithCache(input${T("RequestInfo")}, options${T("RequestInit & { tags?: string[]; staleWhileRevalidate?: boolean; auth?: boolean; queueOffline?: boolean; invalidate?: 'auto' | string[] | false }")} = {})${R("Promise<FetchWithCacheResult>")}{
199
+ /** 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. */
200
+ 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'; staleTime?: number }")} = {})${R("Promise<FetchWithCacheResult<T>>")}{
164
201
  const method = (options.method || "GET").toUpperCase();
165
- const isRead = method === "GET" || method === "HEAD";
202
+ const isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD"));
166
203
  const url = typeof input === "string" ? input : input.url;
167
204
 
168
205
  const headers = new Headers(options.headers);
@@ -177,11 +214,16 @@ ${autoTagsBlock}
177
214
  headers.set("X-SW-Cache-Tags", options.tags.join(","));
178
215
  }
179
216
 
180
- if (options.staleWhileRevalidate) {
181
- headers.set("X-SW-Stale", "true");
217
+ if (options.staleWhileRevalidate && !options.strategy) {
218
+ headers.set("X-SW-Strategy", "stale-while-revalidate");
219
+ }
220
+ if (options.strategy) {
221
+ headers.set("X-SW-Strategy", options.strategy);
182
222
  }
223
+ ${staleTimeHeaders}
183
224
  ${authBlock}${authUrlsBlock}
184
225
  const fetchOptions${T("RequestInit")} = { ...options, headers };
226
+ ${authCredentialsBlock}
185
227
 
186
228
  // Offline handling
187
229
  if (!navigator.onLine) {
@@ -189,14 +231,31 @@ ${offlineReadBlock}
189
231
  ${offlineWriteBlock}
190
232
  }
191
233
 
234
+ // Check for abort before proceeding
235
+ if (options.signal?.aborted) {
236
+ throw new DOMException("The operation was aborted", "AbortError");
237
+ }
238
+
192
239
  // Deduplicate in-flight GET requests
193
240
  let responsePromise${T("Promise<Response>")};
194
241
  if (isRead && inFlightRequests.has(url)) {
195
242
  responsePromise = inFlightRequests.get(url)!.then((r) => r.clone());
196
243
  } else {
244
+ const abortHandler = () => {
245
+ inFlightRequests.delete(url);
246
+ };
247
+ if (options.signal) {
248
+ options.signal.addEventListener("abort", abortHandler, { once: true });
249
+ }
197
250
  responsePromise = fetch(input, fetchOptions);
198
251
  if (isRead) {
199
- inFlightRequests.set(url, responsePromise.finally(() => inFlightRequests.delete(url)));
252
+ const cleanup = () => {
253
+ inFlightRequests.delete(url);
254
+ if (options.signal) {
255
+ options.signal.removeEventListener("abort", abortHandler);
256
+ }
257
+ };
258
+ inFlightRequests.set(url, responsePromise.finally(cleanup));
200
259
  }
201
260
  }
202
261
 
@@ -205,6 +264,13 @@ ${autoInvalidateBlock}${auth401Block}
205
264
  const fromCache = response.headers.get("X-SW-From-Cache") === "true";
206
265
  return { response, fromCache };
207
266
  }
267
+
268
+ /** Fire-and-forget prefetch: warms the cache for a URL without blocking. Useful for route prefetching or link hover prefetching. */
269
+ export function prefetchCache(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'; staleTime?: number }")} = {})${R("void")}{
270
+ fetchWithCache(input, { ...options }).catch(() => {
271
+ // Prefetch failures are intentionally silent
272
+ });
273
+ }
208
274
  `;
209
275
  writeFile(ctx, `fetch-wrapper.${ext}`, code);
210
276
  }
@@ -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":"AAAA;;;GAGG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACxD,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,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;IAC5D,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;IACrD,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;IAEhE,MAAM,WAAW,GAAG;QAClB,eAAe;YACb,CAAC,CAAC,qDAAqD,GAAG,IAAI;YAC9D,CAAC,CAAC,EAAE;QACN,eAAe,CAAC,CAAC,CAAC,6CAA6C,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE;QAC3E,WAAW;YACT,CAAC,CAAC,uGAAuG,GAAG,IAAI;YAChH,CAAC,CAAC,EAAE;QACN,aAAa;YACX,CAAC,CAAC,mDAAmD,GAAG,IAAI;YAC5D,CAAC,CAAC,EAAE;KACP;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,cAAc,GAAG,EAAE;QACvB,CAAC,CAAC;uCACiC,CAAC,CAAC,GAAG,CAAC;YACjC,CAAC,CAAC,mCAAmC,CAAC;aACrC,CAAC,CAAC,SAAS,CAAC;;;;aAIZ,CAAC,CAAC,QAAQ,CAAC;wBACA,CAAC,CAAC,SAAS,CAAC;sBACd,CAAC,CAAC,SAAS,CAAC;mBACf,CAAC,CAAC,QAAQ,CAAC;;CAE7B;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,gBAAgB,GAAG,EAAE;QACzB,CAAC,CAAC;;;;;;;;;;;CAWL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,SAAS,GAAG,WAAW;QAC3B,CAAC,CAAC;;;;IAIF;QACA,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW;QAC/B,CAAC,CAAC;;;;IAIF;QACA,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,oBAAoB,GAAG,WAAW;QACtC,CAAC,CAAC;;;IAGF;QACA,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW;QAC9B,CAAC,CAAC;;;;IAIF;QACA,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,gBAAgB,GAAG;;;;;MAKrB,CAAC;IAEL,MAAM,iBAAiB,GAAG,aAAa;QACrC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;wDAmBkD;QACpD,CAAC,CAAC,yDAAyD,CAAC;IAE9D,MAAM,mBAAmB,GAAG,eAAe;QACzC,CAAC,CAAC;;;;;8BAKwB,CAAC,CAAC,UAAU,CAAC;;;;;;;IAOvC;QACA,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,aAAa,GAAG,eAAe;QACnC,CAAC,CAAC;;;;;;;IAOF;QACA,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,gBAAgB,GAAG;;;IAGvB,CAAC;IAEH,MAAM,IAAI,GAAG;;;;;;4EAM6D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+C7E,WAAW;EACX,cAAc,GAAG,gBAAgB;kCACD,CAAC,CAAC,2BAA2B,CAAC;;;sCAG1B,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,0SAA0S,CAAC,SAAS,CAAC,CAAC,kCAAkC,CAAC;;;;;;;;;;;EAW1a,aAAa;;;;;;;;;;;;EAYb,gBAAgB;EAChB,SAAS,GAAG,aAAa;sBACL,CAAC,CAAC,aAAa,CAAC;EACpC,oBAAoB;;;;EAIpB,gBAAgB;EAChB,iBAAiB;;;;;;;;;uBASI,CAAC,CAAC,mBAAmB,CAAC;;;;;;;;;;;;;;;;;;;;;;;EAuB3C,mBAAmB,GAAG,YAAY;;;;;;qCAMC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,0SAA0S,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;;;;;CAK/X,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC"}
@@ -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,218 +15,29 @@ export function generateHooks(ctx) {
8
15
  if (!existsSync(hooksDir)) {
9
16
  mkdirSync(hooksDir, { recursive: true });
10
17
  }
18
+ // Always generated hooks
19
+ copyHook(hooksDir, "useNetworkStatus", ext);
20
+ copyHook(hooksDir, "useCachedFetch", ext);
21
+ copyHook(hooksDir, "useMutation", ext);
22
+ copyHook(hooksDir, "usePrefetch", ext);
23
+ copyHook(hooksDir, "useMutationState", ext);
11
24
  if (config.features.pwa.enabled) {
12
- writePWAHooks(hooksDir, ext);
25
+ copyHook(hooksDir, "useSWUpdate", ext);
13
26
  }
14
27
  if (config.features.auth.enabled) {
15
- writeAuthHook(hooksDir, ext);
28
+ copyHook(hooksDir, "useAuth", ext);
16
29
  }
17
- if (config.features.mutationQueue) {
18
- writeMutationQueueHook(hooksDir, ext);
30
+ if (config.features.mutationQueue.enabled) {
31
+ copyHook(hooksDir, "useMutationQueue", ext);
32
+ }
33
+ if (config.features.pushNotifications?.enabled) {
34
+ copyHook(hooksDir, "usePushSubscription", ext);
35
+ }
36
+ if (config.features.backgroundSync) {
37
+ copyHook(hooksDir, "useBackgroundSync", ext);
38
+ }
39
+ if (config.features.tagInvalidation) {
40
+ copyHook(hooksDir, "useCacheInvalidation", ext);
19
41
  }
20
- writeCachedFetchHook(hooksDir, ext, config.features.tagInvalidation);
21
- }
22
- function writePWAHooks(dir, ext) {
23
- writeFileSync(join(dir, `usePWAUpdate.${ext}x`), `import { useState, useEffect, useCallback } from "react";
24
- import { handleUpdateApproved } from "../client-injector.${ext}";
25
-
26
- export function usePWAUpdate() {
27
- const [state, setState] = useState({
28
- updateStatus: "idle",
29
- currentVersion: null as string | null,
30
- availableVersion: null as string | null,
31
- progress: 0,
32
- forceUpdate: false,
33
- error: null as string | null,
34
- });
35
-
36
- useEffect(() => {
37
- setState((s) => ({ ...s, currentVersion: (window as any).currentSWVersion || null }));
38
-
39
- const onAvailable = (e: CustomEvent) => setState((s) => ({
40
- ...s,
41
- updateStatus: "available" as const,
42
- availableVersion: e.detail.version,
43
- forceUpdate: (window as any).swUpdateRequired || false,
44
- }));
45
- const onProgress = (e: CustomEvent) => setState((s) => ({
46
- ...s,
47
- updateStatus: "downloading" as const,
48
- progress: e.detail.percent,
49
- }));
50
- const onReady = () => setState((s) => ({ ...s, updateStatus: "idle" as const, progress: 0 }));
51
- const onError = () => setState((s) => ({ ...s, error: "SW registration failed" }));
52
-
53
- window.addEventListener("sw-update-available", onAvailable as EventListener);
54
- window.addEventListener("sw-progress", onProgress as EventListener);
55
- window.addEventListener("sw-ready", onReady);
56
- window.addEventListener("sw-error", onError);
57
- return () => {
58
- window.removeEventListener("sw-update-available", onAvailable as EventListener);
59
- window.removeEventListener("sw-progress", onProgress as EventListener);
60
- window.removeEventListener("sw-ready", onReady);
61
- window.removeEventListener("sw-error", onError);
62
- };
63
- }, []);
64
-
65
- const acceptUpdate = useCallback(async () => {
66
- if (!state.availableVersion) return;
67
- await handleUpdateApproved(state.availableVersion);
68
- }, [state.availableVersion]);
69
-
70
- const dismissUpdate = useCallback(() => {
71
- sessionStorage.setItem("sw-dismissed-update", "true");
72
- setState((s) => ({ ...s, updateStatus: "idle" }));
73
- }, []);
74
-
75
- return { ...state, acceptUpdate, dismissUpdate };
76
- }
77
-
78
- export function useSWProgress() {
79
- const [state, setState] = useState({
80
- status: "idle" as "idle" | "installing",
81
- progress: 0,
82
- });
83
-
84
- useEffect(() => {
85
- const onProgress = (e: CustomEvent) => setState({
86
- status: "installing",
87
- progress: e.detail.percent,
88
- });
89
- const onReady = () => setState({ status: "idle", progress: 0 });
90
-
91
- window.addEventListener("sw-progress", onProgress as EventListener);
92
- window.addEventListener("sw-ready", onReady);
93
- return () => {
94
- window.removeEventListener("sw-progress", onProgress as EventListener);
95
- window.removeEventListener("sw-ready", onReady);
96
- };
97
- }, []);
98
-
99
- return state;
100
- }
101
- `);
102
- // useSWProgress is included in the same file
103
- }
104
- function writeAuthHook(dir, ext) {
105
- writeFileSync(join(dir, `useAuth.${ext}x`), `import { useState, useEffect } from "react";
106
- import { getAuthState } from "../auth/state.${ext}";
107
-
108
- export function useAuth() {
109
- const [state, setState] = useState({
110
- authenticated: false,
111
- user: null as Record<string, unknown> | null,
112
- online: navigator.onLine,
113
- });
114
-
115
- useEffect(() => {
116
- getAuthState().then(setState);
117
-
118
- const onOnline = () => setState((s) => ({ ...s, online: true }));
119
- const onOffline = () => setState((s) => ({ ...s, online: false }));
120
- const onAuthChange = () => getAuthState().then(setState);
121
-
122
- window.addEventListener("online", onOnline);
123
- window.addEventListener("offline", onOffline);
124
- window.addEventListener("sw-auth-state-change", onAuthChange);
125
-
126
- return () => {
127
- window.removeEventListener("online", onOnline);
128
- window.removeEventListener("offline", onOffline);
129
- window.removeEventListener("sw-auth-state-change", onAuthChange);
130
- };
131
- }, []);
132
-
133
- return state;
134
- }
135
- `);
136
- }
137
- function writeCachedFetchHook(dir, ext, tagInvalidation) {
138
- const ts = ext === "ts";
139
- const extraEffect = tagInvalidation
140
- ? `
141
- useEffect(() => {
142
- const onInvalidated${ts ? ": EventListener" : ""} = (e${ts ? ": Event" : ""}) => {
143
- const detail = (e as CustomEvent).detail;
144
- const tags = detail?.tags;
145
- if (!tags || !Array.isArray(tags)) return;
146
- const urlTags = generateTags(url);
147
- if (tags.some((t) => urlTags.includes(t))) {
148
- setRefetchCount((c) => c + 1);
149
- }
150
- };
151
- window.addEventListener("cache-invalidated", onInvalidated);
152
- return () => window.removeEventListener("cache-invalidated", onInvalidated);
153
- }, [url]);`
154
- : "";
155
- const importTags = tagInvalidation
156
- ? `import { generateTags } from "../invalidation-tags.${ext}";\n`
157
- : "";
158
- const stringType = ts ? ": string" : "";
159
- const respType = ts ? "<Response | null>" : "";
160
- writeFileSync(join(dir, `useCachedFetch.${ext}x`), `import { useState, useEffect, useCallback } from "react";
161
- import { fetchWithCache } from "../fetch-wrapper.${ext}";
162
- ${importTags}
163
- export function useCachedFetch(url${stringType}, options = {}) {
164
- const [data, setData] = useState${respType}(null);
165
- const [error, setError] = useState(null);
166
- const [loading, setLoading] = useState(true);
167
- const [refetchCount, setRefetchCount] = useState(0);
168
-
169
- const refetch = useCallback(() => setRefetchCount((c) => c + 1), []);
170
-
171
- useEffect(() => {
172
- let cancelled = false;
173
- setLoading(true);
174
- fetchWithCache(url, options)
175
- .then(({ response }) => {
176
- if (cancelled) return;
177
- setData(response);
178
- setError(null);
179
- })
180
- .catch((err) => {
181
- if (cancelled) return;
182
- setError(err);
183
- })
184
- .finally(() => {
185
- if (!cancelled) setLoading(false);
186
- });
187
- return () => { cancelled = true; };
188
- }, [url, refetchCount]);
189
- ${extraEffect}
190
- return { data, error, loading, refetch };
191
- }
192
- `);
193
- }
194
- function writeMutationQueueHook(dir, ext) {
195
- writeFileSync(join(dir, `useMutationQueue.${ext}x`), `import { useState, useEffect } from "react";
196
- import { getPendingCount } from "../mutation-queue.${ext}";
197
-
198
- export function useMutationQueue() {
199
- const [state, setState] = useState<{
200
- pending: number;
201
- lastSync: { succeeded: number; failed: number } | null;
202
- }>({ pending: 0, lastSync: null });
203
-
204
- useEffect(() => {
205
- getPendingCount().then((count: number) => setState((s) => ({ ...s, pending: count })));
206
-
207
- const onSync = (e: CustomEvent) => setState({
208
- pending: 0,
209
- lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
210
- });
211
- const onChange = async () => { const count = await getPendingCount(); setState((s) => ({ ...s, pending: count })); };
212
-
213
- window.addEventListener("mutation-sync-complete", onSync as EventListener);
214
- window.addEventListener("mutation-queue-changed", onChange);
215
- return () => {
216
- window.removeEventListener("mutation-sync-complete", onSync as EventListener);
217
- window.removeEventListener("mutation-queue-changed", onChange);
218
- };
219
- }, []);
220
-
221
- return state;
222
- }
223
- `);
224
42
  }
225
43
  //# 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":"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"}
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,EAAE,CAAC;QACpC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}
@@ -0,0 +1,152 @@
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
+ staleWhileRevalidate?: boolean;
16
+ auth?: boolean;
17
+ queueOffline?: boolean;
18
+ invalidate?: 'auto' | string[] | false;
19
+ }
20
+
21
+ export interface GqlResult${G("T")} {
22
+ data${T("T")};
23
+ fromCache${T("boolean")};
24
+ }
25
+ `
26
+ : "";
27
+ const code = `/**
28
+ * Swoff GraphQL Wrapper
29
+ * Brings Swoff's caching, offline queue, and tag-based invalidation to GraphQL APIs.
30
+ * Hashes query + variables for deterministic cache keys and auto-generates tags
31
+ * from operation names. Built on top of fetchWithCache.
32
+ *
33
+ * Usage:
34
+ * import { fetchWithGql, queryGql, mutateGql } from './swoff/gql-wrapper.${ext}';
35
+ *
36
+ * // Query — cached with body-hash key
37
+ * const { data } = await queryGql("{ todos { id title } }");
38
+ *
39
+ * // Query with variables
40
+ * const { data: todo } = await queryGql(
41
+ * "query GetTodo($id: ID!) { todo(id: $id) { id title } }",
42
+ * { id: "42" }
43
+ * );
44
+ *
45
+ * // Mutation — auto-invalidates related cache tags
46
+ * const { data: created } = await mutateGql(
47
+ * "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",
48
+ * { title: "New task" }
49
+ * );
50
+ *
51
+ * // Authenticated
52
+ * const { data } = await queryGql("query Me { me { name } }", {}, { auth: true });
53
+ *
54
+ * // Custom options
55
+ * const { data } = await queryGql(
56
+ * "query GetData { data { id } }",
57
+ * {},
58
+ * { staleWhileRevalidate: true, tags: ["data"] }
59
+ * );
60
+ *
61
+ * // Offline: mutations are auto-queued
62
+ * const { data } = await mutateGql(
63
+ * "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",
64
+ * { title: "Offline task" },
65
+ * { queueOffline: true }
66
+ * );
67
+ */
68
+
69
+ import { fetchWithCache } from "./fetch-wrapper.${ext}";
70
+ ${optionsInterface}
71
+ /** Extract operation name from a GraphQL document. Returns null for anonymous queries (e.g. "{ todos { id } }"). */
72
+ function getOperationName(query${T("string")})${T("string | null")}{
73
+ const match = query.match(/(query|mutation|subscription)\\s+(\\w+)/);
74
+ return match ? match[2] : null;
75
+ }
76
+
77
+ /** Determine if a GraphQL document is a query (read) vs mutation/subscription (write). */
78
+ function isReadOperation(query${T("string")})${T("boolean")}{
79
+ const trimmed = query.trim();
80
+ if (trimmed.startsWith("mutation") || trimmed.startsWith("subscription")) return false;
81
+ return true;
82
+ }
83
+
84
+ /** Hash an object into a hex string for deterministic cache keying. Uses SHA-256 via SubtleCrypto. */
85
+ async function bodyHash(obj${T("unknown")})${T("Promise<string>")}{
86
+ const json = JSON.stringify(obj);
87
+ const bytes = new TextEncoder().encode(json);
88
+ const hash = await crypto.subtle.digest("SHA-256", bytes);
89
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
90
+ }
91
+
92
+ /** Generate cache-relevant tags from a GQL operation name. e.g. "getTodos" → ["todos"], "createTodo" → ["todos", "todo"]. */
93
+ function tagsFromOpName(name${T("string | null")})${T("string[]")}{
94
+ if (!name) return [];
95
+ const stripped = name.replace(/^(get|fetch|list|all|query)/i, "").replace(/^(create|set|add|new|update|delete|remove)/i, "");
96
+ if (!stripped) return [name.toLowerCase()];
97
+ const tag = stripped.charAt(0).toLowerCase() + stripped.slice(1);
98
+ const plural = tag.replace(/s$/, "") + "s";
99
+ return [plural, tag];
100
+ }
101
+
102
+ /** Fetch a GraphQL endpoint with Swoff's caching, auth, offline queue, and auto-invalidation. Hashes query + variables for deterministic cache keys. */
103
+ export async function fetchWithGql${G("T")}(
104
+ query${T("string")},
105
+ options${T("GqlOptions")} = {}
106
+ )${R("Promise<GqlResult<T>>")}{
107
+ const isRead = isReadOperation(query);
108
+ const opName = getOperationName(query);
109
+ const variables = options.variables;
110
+ const hash = await bodyHash({ query, variables });
111
+ const tags = options.tags || tagsFromOpName(opName);
112
+
113
+ const { response, fromCache } = await fetchWithCache("${endpoint}", {
114
+ method: "POST",
115
+ body: JSON.stringify({ query, variables }),
116
+ headers: {
117
+ "Content-Type": "application/json",
118
+ "X-SW-Cache-Key": "gql:" + hash,
119
+ },
120
+ tags,
121
+ type: isRead ? "read" : "mutation",
122
+ staleWhileRevalidate: options.staleWhileRevalidate,
123
+ auth: options.auth,
124
+ queueOffline: options.queueOffline,
125
+ invalidate: options.invalidate,
126
+ });
127
+
128
+ const result = await response.json();
129
+ return { data: result.data${AS("T")}, fromCache };
130
+ }
131
+
132
+ /** Shorthand for GraphQL queries (type: "read"). Cached by default with body-hash key. */
133
+ export async function queryGql${G("T")}(
134
+ query${T("string")},
135
+ variables${T("Record<string, unknown> | undefined")} = undefined,
136
+ options${T("GqlOptions")} = {}
137
+ )${R("Promise<GqlResult<T>>")}{
138
+ return fetchWithGql${G("T")}(query, { ...options, variables });
139
+ }
140
+
141
+ /** Shorthand for GraphQL mutations (type: "mutation"). Auto-invalidates related cache tags. */
142
+ export async function mutateGql${G("T")}(
143
+ mutation${T("string")},
144
+ variables${T("Record<string, unknown> | undefined")} = undefined,
145
+ options${T("GqlOptions")} = {}
146
+ )${R("Promise<GqlResult<T>>")}{
147
+ return fetchWithGql${G("T")}(mutation, { ...options, variables });
148
+ }
149
+ `;
150
+ writeFile(ctx, `gql-wrapper.${ext}`, code);
151
+ }
152
+ //# 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;;;;;;;;;;4BAUsB,CAAC,CAAC,GAAG,CAAC;QAC1B,CAAC,CAAC,GAAG,CAAC;aACD,CAAC,CAAC,SAAS,CAAC;;CAExB;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,IAAI,GAAG;;;;;;;8EAO+D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kDAmC/B,GAAG;EACnD,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;;;;;;;;;;;;;;;;8BAgBpC,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"}