@swoff/cli 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/dist/__tests__/assemble-sw.test.js +8 -17
  2. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  3. package/dist/__tests__/cli-integration.test.js +2 -2
  4. package/dist/__tests__/cli-integration.test.js.map +1 -1
  5. package/dist/__tests__/config-types.test.js +18 -8
  6. package/dist/__tests__/config-types.test.js.map +1 -1
  7. package/dist/__tests__/file-generators.test.js +3 -3
  8. package/dist/__tests__/file-generators.test.js.map +1 -1
  9. package/dist/__tests__/loader.test.js +5 -1
  10. package/dist/__tests__/loader.test.js.map +1 -1
  11. package/dist/__tests__/validator.test.js +6 -13
  12. package/dist/__tests__/validator.test.js.map +1 -1
  13. package/dist/index.js +41 -35
  14. package/dist/index.js.map +1 -1
  15. package/dist/lib/cli/help.js +13 -8
  16. package/dist/lib/cli/help.js.map +1 -1
  17. package/dist/lib/commands/add.js +38 -8
  18. package/dist/lib/commands/add.js.map +1 -1
  19. package/dist/lib/commands/assets.js +38 -0
  20. package/dist/lib/commands/assets.js.map +1 -0
  21. package/dist/lib/commands/clean.js +33 -17
  22. package/dist/lib/commands/clean.js.map +1 -1
  23. package/dist/lib/commands/generate-assets.js +44 -0
  24. package/dist/lib/commands/generate-assets.js.map +1 -0
  25. package/dist/lib/commands/generate-guide.js +22 -29
  26. package/dist/lib/commands/generate-guide.js.map +1 -1
  27. package/dist/lib/commands/generate.js +16 -9
  28. package/dist/lib/commands/generate.js.map +1 -1
  29. package/dist/lib/commands/info.js +36 -45
  30. package/dist/lib/commands/info.js.map +1 -1
  31. package/dist/lib/commands/init.js +25 -3
  32. package/dist/lib/commands/init.js.map +1 -1
  33. package/dist/lib/commands/validate.js +1 -1
  34. package/dist/lib/commands/validate.js.map +1 -1
  35. package/dist/lib/config/loader.js +25 -20
  36. package/dist/lib/config/loader.js.map +1 -1
  37. package/dist/lib/config/validator.js +20 -11
  38. package/dist/lib/config/validator.js.map +1 -1
  39. package/dist/lib/generators/asset-generator/dependency-resolver.js +76 -0
  40. package/dist/lib/generators/asset-generator/dependency-resolver.js.map +1 -0
  41. package/dist/lib/generators/asset-generator/generate.js +111 -0
  42. package/dist/lib/generators/asset-generator/generate.js.map +1 -0
  43. package/dist/lib/generators/asset-generator/guide.js +43 -0
  44. package/dist/lib/generators/asset-generator/guide.js.map +1 -0
  45. package/dist/lib/generators/asset-generator/html-patcher.js +65 -0
  46. package/dist/lib/generators/asset-generator/html-patcher.js.map +1 -0
  47. package/dist/lib/generators/asset-generator/ico-encoder.js +29 -0
  48. package/dist/lib/generators/asset-generator/ico-encoder.js.map +1 -0
  49. package/dist/lib/generators/asset-generator/manifest-patcher.js +23 -0
  50. package/dist/lib/generators/asset-generator/manifest-patcher.js.map +1 -0
  51. package/dist/lib/generators/asset-generator/maskable.js +15 -0
  52. package/dist/lib/generators/asset-generator/maskable.js.map +1 -0
  53. package/dist/lib/generators/asset-generator/rasterize.js +32 -0
  54. package/dist/lib/generators/asset-generator/rasterize.js.map +1 -0
  55. package/dist/lib/generators/asset-generator/sizes.js +25 -0
  56. package/dist/lib/generators/asset-generator/sizes.js.map +1 -0
  57. package/dist/lib/generators/file-generators/api-config.js +6 -0
  58. package/dist/lib/generators/file-generators/api-config.js.map +1 -0
  59. package/dist/lib/generators/file-generators/auth-state.js +2 -34
  60. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  61. package/dist/lib/generators/file-generators/auth-store.js +2 -250
  62. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  63. package/dist/lib/generators/file-generators/auth-user.js +3 -81
  64. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  65. package/dist/lib/generators/file-generators/background-sync.js +2 -57
  66. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  67. package/dist/lib/generators/file-generators/cache.js +2 -30
  68. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  69. package/dist/lib/generators/file-generators/client-injector.js +2 -130
  70. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  71. package/dist/lib/generators/file-generators/fetch-state.js +6 -0
  72. package/dist/lib/generators/file-generators/fetch-state.js.map +1 -0
  73. package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -295
  74. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  75. package/dist/lib/generators/file-generators/generate-framework-adapters.js +56 -0
  76. package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -0
  77. package/dist/lib/generators/file-generators/generate-hooks.js +4 -3
  78. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  79. package/dist/lib/generators/file-generators/gql-wrapper.js +3 -132
  80. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
  81. package/dist/lib/generators/file-generators/guide-generator.js +29 -1
  82. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  83. package/dist/lib/generators/file-generators/invalidation-tags.js +3 -258
  84. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  85. package/dist/lib/generators/file-generators/manifest.js +13 -3
  86. package/dist/lib/generators/file-generators/manifest.js.map +1 -1
  87. package/dist/lib/generators/file-generators/mutation-queue.js +3 -305
  88. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  89. package/dist/lib/generators/file-generators/mutation-state.js +2 -117
  90. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
  91. package/dist/lib/generators/file-generators/push.js +6 -138
  92. package/dist/lib/generators/file-generators/push.js.map +1 -1
  93. package/dist/lib/generators/file-generators/pwa-install.js +11 -84
  94. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  95. package/dist/lib/generators/file-generators/reset.js +6 -0
  96. package/dist/lib/generators/file-generators/reset.js.map +1 -0
  97. package/dist/lib/generators/file-generators/server-push.js +3 -134
  98. package/dist/lib/generators/file-generators/server-push.js.map +1 -1
  99. package/dist/lib/generators/file-generators/sw-generator-build.js +4 -4
  100. package/dist/lib/generators/file-generators/sw-injector.js +9 -230
  101. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  102. package/dist/lib/generators/file-generators/type-definitions.js +2 -111
  103. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  104. package/dist/lib/generators/sw-generator.js +5 -5
  105. package/dist/lib/generators/sw-generator.js.map +1 -1
  106. package/dist/lib/generators/sw-sections/activate-handler.js +25 -3
  107. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  108. package/dist/lib/generators/sw-sections/assemble-sw.js +13 -12
  109. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  110. package/dist/lib/generators/sw-sections/background-sync-handler.js +14 -7
  111. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  112. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  113. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  114. package/dist/lib/generators/sw-sections/fetch-handler.js +165 -104
  115. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  116. package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +1 -0
  117. package/dist/lib/generators/sw-sections/server-push-handler.js +12 -0
  118. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
  119. package/dist/lib/generators/sw-sections/tag-management.js +6 -4
  120. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  121. package/dist/lib/generators/swoff-files-generator.js +66 -35
  122. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  123. package/dist/lib/shared/config-types.js +89 -28
  124. package/dist/lib/shared/config-types.js.map +1 -1
  125. package/dist/runtime/api-config.js +18 -0
  126. package/dist/runtime/api-config.js.map +1 -0
  127. package/dist/runtime/auth-state.js +35 -0
  128. package/dist/runtime/auth-state.js.map +1 -0
  129. package/dist/runtime/auth-store.js +273 -0
  130. package/dist/runtime/auth-store.js.map +1 -0
  131. package/dist/runtime/auth-user.js +168 -0
  132. package/dist/runtime/auth-user.js.map +1 -0
  133. package/dist/runtime/background-sync.js +59 -0
  134. package/dist/runtime/background-sync.js.map +1 -0
  135. package/dist/runtime/cache.js +30 -0
  136. package/dist/runtime/cache.js.map +1 -0
  137. package/dist/runtime/client-injector.js +136 -0
  138. package/dist/runtime/client-injector.js.map +1 -0
  139. package/dist/runtime/fetch-state.js +42 -0
  140. package/dist/runtime/fetch-state.js.map +1 -0
  141. package/dist/runtime/fetch-wrapper.js +355 -0
  142. package/dist/runtime/fetch-wrapper.js.map +1 -0
  143. package/dist/runtime/gql-wrapper.js +135 -0
  144. package/dist/runtime/gql-wrapper.js.map +1 -0
  145. package/dist/runtime/invalidation-tags.js +260 -0
  146. package/dist/runtime/invalidation-tags.js.map +1 -0
  147. package/dist/runtime/mutation-queue.js +304 -0
  148. package/dist/runtime/mutation-queue.js.map +1 -0
  149. package/dist/runtime/mutation-state.js +117 -0
  150. package/dist/runtime/mutation-state.js.map +1 -0
  151. package/dist/runtime/push.js +138 -0
  152. package/dist/runtime/push.js.map +1 -0
  153. package/dist/runtime/pwa-index.js +11 -0
  154. package/dist/runtime/pwa-index.js.map +1 -0
  155. package/dist/runtime/pwa-injector.js +44 -0
  156. package/dist/runtime/pwa-injector.js.map +1 -0
  157. package/dist/runtime/pwa-install.js +83 -0
  158. package/dist/runtime/pwa-install.js.map +1 -0
  159. package/dist/runtime/pwa-prompt.js +54 -0
  160. package/dist/runtime/pwa-prompt.js.map +1 -0
  161. package/dist/runtime/reset.js +148 -0
  162. package/dist/runtime/reset.js.map +1 -0
  163. package/dist/runtime/server-push.js +148 -0
  164. package/dist/runtime/server-push.js.map +1 -0
  165. package/dist/runtime/sw-injector.js +236 -0
  166. package/dist/runtime/sw-injector.js.map +1 -0
  167. package/dist/runtime/type-definitions.js +138 -0
  168. package/dist/runtime/type-definitions.js.map +1 -0
  169. package/dist/runtime/utils.js +27 -0
  170. package/dist/runtime/utils.js.map +1 -0
  171. package/package.json +3 -2
  172. package/templates/react/useAuth.jsx +106 -0
  173. package/templates/react/useAuth.tsx +118 -0
  174. package/templates/{hooks → react}/useBackgroundSync.jsx +6 -4
  175. package/templates/{hooks → react}/useBackgroundSync.tsx +6 -4
  176. package/templates/{hooks → react}/useCacheInvalidation.jsx +2 -2
  177. package/templates/react/useCacheInvalidation.tsx +52 -0
  178. package/templates/react/useCachedFetch.jsx +140 -0
  179. package/templates/react/useCachedFetch.tsx +160 -0
  180. package/templates/react/useIsFetching.jsx +32 -0
  181. package/templates/react/useIsFetching.tsx +32 -0
  182. package/templates/react/useMutation.jsx +141 -0
  183. package/templates/react/useMutation.tsx +180 -0
  184. package/templates/react/useMutationQueue.jsx +69 -0
  185. package/templates/react/useMutationQueue.tsx +83 -0
  186. package/templates/react/useNetworkStatus.jsx +61 -0
  187. package/templates/react/useNetworkStatus.tsx +76 -0
  188. package/templates/react/usePrefetch.jsx +32 -0
  189. package/templates/react/usePrefetch.tsx +42 -0
  190. package/templates/{hooks → react}/usePushSubscription.jsx +4 -4
  191. package/templates/{hooks → react}/usePushSubscription.tsx +4 -4
  192. package/templates/{hooks → react}/useSWUpdate.jsx +3 -3
  193. package/templates/{hooks → react}/useSWUpdate.tsx +4 -4
  194. package/templates/react/useSwoffReset.jsx +32 -0
  195. package/templates/react/useSwoffReset.tsx +39 -0
  196. package/docs/API.md +0 -565
  197. package/docs/ARCHITECTURE.md +0 -374
  198. package/docs/CLI.md +0 -148
  199. package/docs/COMPARISON.md +0 -192
  200. package/docs/CONFIG.md +0 -277
  201. package/docs/ECOSYSTEM.md +0 -33
  202. package/templates/hooks/useAuth.jsx +0 -30
  203. package/templates/hooks/useAuth.tsx +0 -30
  204. package/templates/hooks/useCacheInvalidation.tsx +0 -19
  205. package/templates/hooks/useCachedFetch.jsx +0 -69
  206. package/templates/hooks/useCachedFetch.tsx +0 -81
  207. package/templates/hooks/useMutation.jsx +0 -76
  208. package/templates/hooks/useMutation.tsx +0 -109
  209. package/templates/hooks/useMutationQueue.jsx +0 -43
  210. package/templates/hooks/useMutationQueue.tsx +0 -50
  211. package/templates/hooks/useNetworkStatus.jsx +0 -19
  212. package/templates/hooks/useNetworkStatus.tsx +0 -19
  213. package/templates/hooks/usePrefetch.jsx +0 -10
  214. package/templates/hooks/usePrefetch.tsx +0 -25
  215. /package/templates/{hooks → react}/useMutationState.jsx +0 -0
  216. /package/templates/{hooks → react}/useMutationState.tsx +0 -0
@@ -0,0 +1,136 @@
1
+ export function generateClientInjectorCode(ctx, pwaEnabled, mutationQueueEnabled, serverPushEnabled) {
2
+ const { ext, ts } = ctx;
3
+ const pwaImport = pwaEnabled
4
+ ? `import { setupPwaInstall } from "./pwa/injector.${ext}";
5
+ `
6
+ : "";
7
+ const pwaCall = pwaEnabled ? `setupPwaInstall();\n` : "";
8
+ const pushImport = serverPushEnabled
9
+ ? `import { startPushEvents } from "./realtime/server-push.${ext}";
10
+ `
11
+ : "";
12
+ const pushCall = serverPushEnabled ? `startPushEvents();\n` : "";
13
+ const mutationImport = mutationQueueEnabled
14
+ ? `import { processMutationQueue } from "./offline/queue.${ext}";
15
+ `
16
+ : "";
17
+ const mutationOnlineListener = mutationQueueEnabled
18
+ ? `
19
+ // --- Mutation Queue Online Listener ---
20
+ window.addEventListener("online", processMutationQueue);
21
+ `
22
+ : "";
23
+ const onlineRefetchListener = `
24
+ // --- Online Refetch Listener ---
25
+ // When connectivity returns, the SW checks stale cache entries and refetches them.
26
+ if (typeof window !== "undefined") {
27
+ window.addEventListener("online", () => {
28
+ if (navigator.serviceWorker.controller) {
29
+ navigator.serviceWorker.controller.postMessage({ type: "ONLINE" });
30
+ }
31
+ });
32
+ }
33
+ `;
34
+ const focusListener = `
35
+ // --- Focus Listener for Reactive Strategy ---
36
+ // Notifies the SW when the tab gains focus so it can refresh stale reactive entries.
37
+ // Uses visibilitychange only (covers tab switch, window refocus, alt-tab) — single source avoids duplicate FOCUS messages.
38
+ if (typeof document !== "undefined") {
39
+ document.addEventListener("visibilitychange", () => {
40
+ if (document.visibilityState === "visible" && navigator.serviceWorker.controller) {
41
+ navigator.serviceWorker.controller.postMessage({ type: "FOCUS" });
42
+ }
43
+ });
44
+ }
45
+ `;
46
+ const invalidationHandler = `
47
+ if (event.data.type === "TAG_INVALIDATED" && event.data.tag) {
48
+ window.dispatchEvent(
49
+ new CustomEvent("cache-invalidated", {
50
+ detail: { tags: [event.data.tag] },
51
+ })
52
+ );
53
+ }
54
+ `;
55
+ const swImport = `import { initServiceWorker as swInit } from "./sw/injector.${ext}";
56
+ `;
57
+ return `/**
58
+ * Swoff Client Injector
59
+ * Orchestrates SW registration, PWA install, and cross-tab sync.
60
+ * Single entry point for all Swoff client-side features.
61
+ *
62
+ * Usage:
63
+ * import { initServiceWorker } from './swoff/client-injector.${ext}';
64
+ * initServiceWorker();
65
+ *
66
+ * Window events (dispatched by this module):
67
+ * sw-progress - Download progress (detail: { percent, downloaded, total })
68
+ * sw-ready - SW active and controlling page
69
+ * sw-error - SW registration failed
70
+ * cache-invalidated - Cache entries cleared on SW confirmation (detail: { tags })
71
+ * swoff:cache-updated - Background refresh completed (detail: { url })
72
+ * mutation-sync-complete - Queued mutations synced (detail: { succeeded, failed })
73
+ * mutation-sync-progress - Batch progress during sync (detail: { succeeded, failed, total, current })
74
+ * mutation-queue-changed - Queue modified
75
+ *
76
+ * Window events (dispatched by feature modules):
77
+ * pwa-installable - PWA can be installed (detail: { isInstallable: true })
78
+ * pwa-installed - User accepted install (detail: { outcome: 'accepted' })
79
+ * sw-auth-unauthorized - 401 response received
80
+ * sw-auth-state-change - Login or logout (detail: { authenticated: boolean })
81
+ * sw-update-available - New version ready (detail: { version })
82
+ * sw-version-detected - Version info available
83
+ */
84
+ ${pwaImport}${mutationImport}${swImport}${pushImport}
85
+ ${pwaCall}${mutationOnlineListener}${pushCall}${onlineRefetchListener}${focusListener}
86
+ // --- SW Message Listener ---
87
+ if (typeof window !== "undefined" && "serviceWorker" in navigator) {
88
+ navigator.serviceWorker.addEventListener("message", (event) => {
89
+ if (event.data.type === "CACHE_UPDATED") {
90
+ window.dispatchEvent(
91
+ new CustomEvent("swoff:cache-updated", {
92
+ detail: { url: event.data.url },
93
+ })
94
+ );
95
+ }
96
+ if (event.data.type === "SW_PROGRESS") {
97
+ const { percent, downloaded, total } = event.data;
98
+ window.dispatchEvent(
99
+ new CustomEvent("sw-progress", {
100
+ detail: { percent, downloaded, total },
101
+ })
102
+ );
103
+ }
104
+ if (event.data.type === "BACKGROUND_SYNC_PROGRESS") {
105
+ window.dispatchEvent(
106
+ new CustomEvent("mutation-sync-progress", {
107
+ detail: event.data.detail,
108
+ })
109
+ );
110
+ }
111
+ if (event.data.type === "BACKGROUND_SYNC_COMPLETE") {
112
+ const { succeeded, failed, tags } = event.data.detail;
113
+ window.dispatchEvent(
114
+ new CustomEvent("mutation-sync-complete", {
115
+ detail: { succeeded, failed },
116
+ })
117
+ );
118
+ if (tags && tags.length > 0) {
119
+ window.dispatchEvent(
120
+ new CustomEvent("cache-invalidated", { detail: { tags } })
121
+ );
122
+ }
123
+ window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
124
+ }
125
+ if (event.data.type === "MUTATION_STORED") {
126
+ typeof processMutationQueue !== "undefined" && processMutationQueue();
127
+ }${invalidationHandler} });
128
+ }
129
+
130
+ /** Initialize SW registration and all client-side features (PWA install, mutation queue, cross-tab sync). Call once at app startup. */
131
+ export async function initServiceWorker()${ts ? ": Promise<void>" : " "}{
132
+ await swInit();
133
+ }
134
+ `;
135
+ }
136
+ //# sourceMappingURL=client-injector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-injector.js","sourceRoot":"","sources":["../../src/runtime/client-injector.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,0BAA0B,CACxC,GAAmB,EACnB,UAAmB,EACnB,oBAA6B,EAC7B,iBAA0B;IAE1B,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IAExB,MAAM,SAAS,GAAG,UAAU;QAC1B,CAAC,CAAC,mDAAmD,GAAG;CAC3D;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;IAEzD,MAAM,UAAU,GAAG,iBAAiB;QAClC,CAAC,CAAC,2DAA2D,GAAG;CACnE;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,QAAQ,GAAG,iBAAiB,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjE,MAAM,cAAc,GAAG,oBAAoB;QACzC,CAAC,CAAC,yDAAyD,GAAG;CACjE;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,sBAAsB,GAAG,oBAAoB;QACjD,CAAC,CAAC;;;CAGL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,qBAAqB,GAAG;;;;;;;;;;CAU/B,CAAC;IAEA,MAAM,aAAa,GAAG;;;;;;;;;;;CAWvB,CAAC;IAEA,MAAM,mBAAmB,GAAG;;;;;;;;CAQ7B,CAAC;IAEA,MAAM,QAAQ,GAAG,8DAA8D,GAAG;CACnF,CAAC;IAEA,OAAO;;;;;;kEAMyD,GAAG;;;;;;;;;;;;;;;;;;;;;EAqBnE,SAAS,GAAG,cAAc,GAAG,QAAQ,GAAG,UAAU;EAClD,OAAO,GAAG,sBAAsB,GAAG,QAAQ,GAAG,qBAAqB,GAAG,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0C9E,mBAAmB;;;;2CAIiB,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG;;;CAGtE,CAAC;AACF,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { R } from "./utils.js";
2
+ export function generateFetchStateCode(ctx) {
3
+ const { ext, ts } = ctx;
4
+ return `/**
5
+ * Swoff Fetch State
6
+ * Global in-flight fetch counter. Incremented by fetchWithCache and used by
7
+ * useIsFetching for a reactive global loading indicator.
8
+ *
9
+ * Usage:
10
+ * import { incrementFetchCount, decrementFetchCount, getFetchCount } from './swoff/fetch-state.${ext}';
11
+ *
12
+ * // Manual tracking (if you bypass fetchWithCache):
13
+ * incrementFetchCount();
14
+ * // ... fetch ...
15
+ * decrementFetchCount();
16
+ *
17
+ * Custom event:
18
+ * fetch-count-changed — dispatched on every increment/decrement
19
+ * detail: { count: number }
20
+ */
21
+
22
+ let _fetchCount = 0;
23
+
24
+ /** Increment the global fetch counter. Dispatches fetch-count-changed. */
25
+ export function incrementFetchCount()${R(ts, "void")}{
26
+ _fetchCount++;
27
+ window.dispatchEvent(new CustomEvent("fetch-count-changed", { detail: { count: _fetchCount } }));
28
+ }
29
+
30
+ /** Decrement the global fetch counter. Dispatches fetch-count-changed. Never goes below 0. */
31
+ export function decrementFetchCount()${R(ts, "void")}{
32
+ _fetchCount = Math.max(0, _fetchCount - 1);
33
+ window.dispatchEvent(new CustomEvent("fetch-count-changed", { detail: { count: _fetchCount } }));
34
+ }
35
+
36
+ /** Synchronously get the current fetch count. */
37
+ export function getFetchCount()${R(ts, "number")}{
38
+ return _fetchCount;
39
+ }
40
+ `;
41
+ }
42
+ //# sourceMappingURL=fetch-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-state.js","sourceRoot":"","sources":["../../src/runtime/fetch-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAE/B,MAAM,UAAU,sBAAsB,CAAC,GAAmB;IACxD,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;;oGAM2F,GAAG;;;;;;;;;;;;;;;uCAehE,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;;uCAMb,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;;iCAMnB,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;;;CAG/C,CAAC;AACF,CAAC"}
@@ -0,0 +1,355 @@
1
+ import { T, R, G } from "./utils.js";
2
+ export function generateFetchWrapperCode(ctx, authEnabled, userEndpoint, mutationQueue, requestBatchWindowMs) {
3
+ const { ext, ts } = ctx;
4
+ const importLines = [
5
+ `import { API_BASE } from "../config.${ext}";`,
6
+ `import { generateTags, invalidateUrl${mutationQueue ? ", expandCascading" : ""} } from "../cache/tags.${ext}";`,
7
+ `import { invalidateByTags } from "../cache/index.${ext}";`,
8
+ authEnabled
9
+ ? `import { getAuth, clearAuth, withAuthHeaders, isAuthUrl, ensureValidAuth, AUTH_WITH_CREDENTIALS } from "../auth/store.${ext}";`
10
+ : "",
11
+ mutationQueue
12
+ ? `import { queueMutation } from "../offline/queue.${ext}";`
13
+ : "",
14
+ `import { incrementFetchCount, decrementFetchCount } from "../fetch/state.${ext}";`,
15
+ ]
16
+ .filter(Boolean)
17
+ .join("\n");
18
+ const interfaceBlock = ts
19
+ ? `
20
+ export interface FetchWithCacheResult${G(ts, "T")} {
21
+ response${T(ts, "Response & { json(): Promise<T> }")};
22
+ fromCache${T(ts, "boolean")};
23
+ queued${T(ts, "boolean")};
24
+ }
25
+ `
26
+ : "";
27
+ const optionsInterface = ts
28
+ ? `
29
+ export interface FetchWithCacheOptions extends RequestInit {
30
+ tags?: string[];
31
+ auth?: boolean;
32
+ queueOffline?: boolean;
33
+ invalidate?: 'auto' | string[] | false;
34
+ type?: 'read' | 'mutation';
35
+ strategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate' | 'cache-only' | 'network-only' | 'reactive';
36
+ staleTime?: number;
37
+ refetchInterval?: number;
38
+ refetchOnFocus?: boolean;
39
+ refetchOnReconnect?: boolean;
40
+ /** Custom response validation for mutation success. Default: res.ok. Use this when your API returns 200 with { success: false } for logical failures. */
41
+ validateSuccess?: (response: Response) => boolean | Promise<boolean>;
42
+ /** Override the URL used for auto-invalidation. Defaults to the request URL. Useful when the mutation URL differs from the cache tag URL. */
43
+ invalidateUrl?: string;
44
+ /** Skip global fetch counter tracking. Used by prefetchCache to avoid triggering loading bars. */
45
+ skipFetchCount?: boolean;
46
+ }
47
+ `
48
+ : "";
49
+ const authBlock = authEnabled
50
+ ? `
51
+ if (options.auth) {
52
+ const auth = await getAuth();
53
+ withAuthHeaders(headers, auth);
54
+ }`
55
+ : "";
56
+ const authUrlsBlock = authEnabled
57
+ ? `
58
+ // Auth endpoints bypass SW cache
59
+ if (options.auth && isAuthUrl(url) && !headers.has("X-SW-Cache-Strategy")) {
60
+ headers.set("X-SW-Cache-Strategy", "mutation");
61
+ }`
62
+ : "";
63
+ const authCredentialsBlock = authEnabled
64
+ ? `
65
+ if (AUTH_WITH_CREDENTIALS) {
66
+ fetchOptions.credentials = "include";
67
+ }`
68
+ : "";
69
+ const auth401Block = authEnabled
70
+ ? `
71
+ if (options.auth && response.status === 401) {
72
+ // Check if the token is actually expired by probing the user endpoint
73
+ try {
74
+ const authCheck = await fetch(API_BASE + "${userEndpoint}", {
75
+ headers: { "Authorization": headers.get("Authorization") } as HeadersInit,
76
+ credentials: AUTH_WITH_CREDENTIALS ? "include" : undefined,
77
+ });
78
+ if (authCheck.status === 401) {
79
+ // Token expired — try silent refresh
80
+ const refreshed = await ensureValidAuth();
81
+ if (refreshed?.token) {
82
+ // Retry original request with fresh token
83
+ const retryHeaders = new Headers(options.headers);
84
+ withAuthHeaders(retryHeaders, refreshed);
85
+ response = await fetch(resolvedInput, { ...fetchOptions, headers: retryHeaders });
86
+ } else {
87
+ await clearAuth();
88
+ window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
89
+ }
90
+ }
91
+ // authCheck 200: user IS authenticated but lacks permission — let original 401 propagate
92
+ } catch {
93
+ await clearAuth();
94
+ window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
95
+ }
96
+ }`
97
+ : "";
98
+ const mutationTagsBlock = mutationQueue
99
+ ? ` // Pre-compute invalidation tags for SW-side IDB storage
100
+ let mutationTags: string[] = [];
101
+ if (!isRead) {
102
+ const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
103
+ if (invalidateSetting !== false) {
104
+ if (Array.isArray(invalidateSetting)) {
105
+ mutationTags = invalidateSetting;
106
+ } else {
107
+ mutationTags = generateTags(url);
108
+ mutationTags = expandCascading(mutationTags);
109
+ }
110
+ headers.set("X-SW-Invalidate-Tags", mutationTags.join(","));
111
+ }
112
+ }`
113
+ : "";
114
+ const offlineReadCatchBlock = ` if (options.signal?.aborted) throw new DOMException("The operation was aborted", "AbortError");
115
+ const cached = await caches.match(resolvedInput);
116
+ if (cached) return { response: cached, fromCache: true, queued: false };
117
+ throw new Error("Offline: no cached data available", { cause: err });`;
118
+ const offlineWriteFallbackBlock = mutationQueue
119
+ ? ` if (options.queueOffline !== false) {
120
+ const ct = headers.get("Content-Type");
121
+ await queueMutation({
122
+ method,
123
+ url,
124
+ body: options.body,
125
+ headers: ct ? { "Content-Type": ct } : {},
126
+ tags: mutationTags,
127
+ timestamp: Date.now(),
128
+ });
129
+ return {
130
+ response: new Response(JSON.stringify({ queued: true }), { status: 202, headers: { "Content-Type": "application/json" } }),
131
+ fromCache: false,
132
+ queued: true,
133
+ };
134
+ }`
135
+ : ``;
136
+ const autoInvalidateBlock = `
137
+ // Auto-invalidate after mutation success (skip if SW queued it)
138
+ const mutationSuccess = options.validateSuccess ? await options.validateSuccess(response) : response.ok;
139
+ const mutationQueued = response.headers.get("X-SW-Mutation-Queued") === "true";
140
+ if (!isRead && mutationSuccess && !mutationQueued) {
141
+ const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
142
+ if (invalidateSetting !== false) {
143
+ const invalidateTarget = options.invalidateUrl || url;
144
+ if (Array.isArray(invalidateSetting)) {
145
+ await invalidateByTags(invalidateSetting);
146
+ } else {
147
+ await invalidateUrl(invalidateTarget);
148
+ }
149
+ }
150
+ }`;
151
+ const autoTagsBlock = `
152
+ // Auto-generate tags from URL if not provided
153
+ if (!options.tags && isRead) {
154
+ const urlTags = generateTags(url);
155
+ if (urlTags.length > 0) {
156
+ headers.set("X-SW-Cache-Tags", urlTags.join(","));
157
+ }
158
+ }`;
159
+ return `/**
160
+ * Swoff Fetch Wrapper
161
+ * Unified fetch with caching, auth, offline queue, auto-invalidation, staleTime, prefetching, and
162
+ * per-request strategy override.
163
+ *
164
+ * Usage:
165
+ * import { fetchWithCache, prefetchCache } from './swoff/fetch/core.${ext}';
166
+ *
167
+ * // GET — cached with auto-generated tags
168
+ * const { response } = await fetchWithCache("/api/todos");
169
+ * const data = await response.json();
170
+ *
171
+ * // POST — mutation with auto-invalidation
172
+ * await fetchWithCache("/api/todos", {
173
+ * method: "POST",
174
+ * body: JSON.stringify({ title: "New task" }),
175
+ * });
176
+ *
177
+ * // Skip auto-invalidation, invalidate manually later
178
+ * await fetchWithCache("/api/todos", {
179
+ * method: "POST",
180
+ * body: JSON.stringify({ title: "New" }),
181
+ * invalidate: false,
182
+ * });
183
+ *
184
+ * // Validate mutation success with custom logic
185
+ * await fetchWithCache("/api/todos", {
186
+ * method: "POST",
187
+ * body: JSON.stringify({ title: "New" }),
188
+ * validateSuccess: (res) => res.status === 200,
189
+ * });
190
+ *
191
+ * // Custom tags for caching (read) + explicit invalidate tags (mutation)
192
+ * await fetchWithCache("/api/todos", {
193
+ * tags: ["custom-tag"],
194
+ * invalidate: ["custom-tag"],
195
+ * });
196
+ *
197
+ * // Authenticated request (works with bearer, cookie, custom)
198
+ * const { response: userRes } = await fetchWithCache("/api/me", { auth: true });
199
+ *
200
+ * // Prefetch (fire-and-forget warm the cache)
201
+ * prefetchCache("/api/todos");
202
+ *
203
+ * // Override caching strategy per-request (highest priority)
204
+ * await fetchWithCache("/api/checkout", {
205
+ * method: "POST",
206
+ * type: "read",
207
+ * strategy: "network-only",
208
+ * });
209
+ *
210
+ * // Support AbortController for cancellation
211
+ * const controller = new AbortController();
212
+ * setTimeout(() => controller.abort(), 5000);
213
+ * const { response } = await fetchWithCache("/api/todos", { signal: controller.signal });
214
+ *
215
+ * // Offline: auto-queues writes (disable with queueOffline: false)
216
+ * await fetchWithCache("/api/todos", {
217
+ * method: "POST",
218
+ * body: JSON.stringify({ title: "Offline task" }),
219
+ * });
220
+ * // When back online, processMutationQueue() replays them
221
+ */
222
+
223
+ ${importLines}
224
+ ${interfaceBlock}${optionsInterface}
225
+ const inFlightRequests = new Map${G(ts, "string, Promise<Response>")}();
226
+ const pendingBatches = new Map${G(ts, "string, { resolvers: Array<(r: Response) => void>; rejectors: Array<(e: unknown) => void>; timer: ReturnType<typeof setTimeout> }")}();
227
+ const BATCH_WINDOW_MS = ${requestBatchWindowMs};
228
+
229
+ /** 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. */
230
+ export async function fetchWithCache${G(ts, "T")}(input${T(ts, "RequestInfo")}, options${T(ts, "RequestInit & FetchWithCacheOptions")} = {})${R(ts, "Promise<FetchWithCacheResult<T>>")}{
231
+ if (!options.skipFetchCount) incrementFetchCount();
232
+ try {
233
+ const method = (options.method || "GET").toUpperCase();
234
+ const isRead = options.type === "read" || (options.type !== "mutation" && (method === "GET" || method === "HEAD" || method === "OPTIONS"));
235
+ const resolvedInput = typeof input === "string" && !input.startsWith("http") && !input.startsWith("//") ? API_BASE + input : input;
236
+ const url = typeof resolvedInput === "string" ? resolvedInput : resolvedInput.url;
237
+
238
+ const headers = new Headers(options.headers);
239
+
240
+ // Set cache strategy
241
+ if (!headers.has("X-SW-Cache-Strategy")) {
242
+ headers.set("X-SW-Cache-Strategy", isRead ? "read" : "mutation");
243
+ }
244
+ ${autoTagsBlock}
245
+ // Custom tags override auto-generated
246
+ if (options.tags && options.tags.length > 0) {
247
+ headers.set("X-SW-Cache-Tags", options.tags.join(","));
248
+ }
249
+
250
+ if (options.strategy) {
251
+ headers.set("X-SW-Strategy", options.strategy);
252
+ }
253
+ if (options.staleTime !== undefined) {
254
+ headers.set("X-SW-Stale-Time", String(options.staleTime));
255
+ }
256
+ if (options.refetchInterval !== undefined) {
257
+ headers.set("X-SW-Refetch-Interval", String(options.refetchInterval));
258
+ }
259
+ if (options.refetchOnFocus !== undefined) {
260
+ headers.set("X-SW-Refetch-On-Focus", String(options.refetchOnFocus));
261
+ }
262
+ if (options.refetchOnReconnect !== undefined) {
263
+ headers.set("X-SW-Refetch-On-Reconnect", String(options.refetchOnReconnect));
264
+ }
265
+ ${authBlock}${authUrlsBlock}
266
+ // Forward no-queue option to SW
267
+ if (options.queueOffline === false) {
268
+ headers.set("X-SW-No-Queue", "true");
269
+ }
270
+
271
+ const fetchOptions${T(ts, "RequestInit")} = { ...options, headers };
272
+ ${authCredentialsBlock}
273
+
274
+ // Check for abort before proceeding
275
+ if (options.signal?.aborted) {
276
+ throw new DOMException("The operation was aborted", "AbortError");
277
+ }
278
+ ${mutationTagsBlock}
279
+ // Request batching + dedup for concurrent reads
280
+ let responsePromise${T(ts, "Promise<Response>")};
281
+ if (isRead && inFlightRequests.has(url)) {
282
+ responsePromise = inFlightRequests.get(url)!.then((r) => r.clone());
283
+ } else if (isRead && pendingBatches.has(url)) {
284
+ responsePromise = new Promise((resolve, reject) => {
285
+ pendingBatches.get(url)!.resolvers.push(resolve);
286
+ pendingBatches.get(url)!.rejectors.push(reject);
287
+ });
288
+ } else {
289
+ const abortHandler = () => {
290
+ inFlightRequests.delete(url);
291
+ };
292
+ if (options.signal) {
293
+ options.signal.addEventListener("abort", abortHandler, { once: true });
294
+ }
295
+ if (isRead && BATCH_WINDOW_MS > 0) {
296
+ const batch${T(ts, "{ resolvers: Array<(r: Response) => void>; rejectors: Array<(e: unknown) => void>; timer: ReturnType<typeof setTimeout> }")} = { resolvers: [], rejectors: [], timer: 0 };
297
+ pendingBatches.set(url, batch);
298
+ batch.timer = setTimeout(() => {
299
+ pendingBatches.delete(url);
300
+ const promise = fetch(resolvedInput, fetchOptions);
301
+ const cleanup = () => {
302
+ inFlightRequests.delete(url);
303
+ if (options.signal) {
304
+ options.signal.removeEventListener("abort", abortHandler);
305
+ }
306
+ };
307
+ inFlightRequests.set(url, promise.finally(cleanup));
308
+ promise.then((r) => {
309
+ batch.resolvers.forEach((res) => res(r.clone()));
310
+ }).catch((err) => {
311
+ batch.rejectors.forEach((rej) => rej(err));
312
+ });
313
+ }, BATCH_WINDOW_MS);
314
+ responsePromise = new Promise((resolve, reject) => {
315
+ batch.resolvers.push(resolve);
316
+ batch.rejectors.push(reject);
317
+ });
318
+ } else {
319
+ responsePromise = fetch(resolvedInput, fetchOptions);
320
+ }
321
+ }
322
+
323
+ let response${T(ts, "Response")};
324
+ try {
325
+ response = await responsePromise;
326
+ } catch (err) {
327
+ if (err instanceof TypeError) {
328
+ if (isRead) {
329
+ ${offlineReadCatchBlock}
330
+ }
331
+ // SW not controlling — fallback to client-side queue
332
+ if (!navigator.serviceWorker?.controller) {
333
+ ${offlineWriteFallbackBlock}
334
+ }
335
+ }
336
+ throw err;
337
+ }
338
+ ${autoInvalidateBlock}${auth401Block}
339
+ const fromCache = response.headers.get("X-SW-From-Cache") === "true";
340
+ const queued = response.headers.get("X-SW-Mutation-Queued") === "true";
341
+ return { response, fromCache, queued };
342
+ } finally {
343
+ if (!options.skipFetchCount) decrementFetchCount();
344
+ }
345
+ }
346
+
347
+ /** Fire-and-forget prefetch: warms the cache for a URL without blocking. Useful for route prefetching or link hover prefetching. */
348
+ export function prefetchCache(input${T(ts, "RequestInfo")}, options${T(ts, "RequestInit & FetchWithCacheOptions")} = {})${R(ts, "void")}{
349
+ fetchWithCache(input, { ...options, skipFetchCount: true }).catch(() => {
350
+ // Prefetch failures are intentionally silent
351
+ });
352
+ }
353
+ `;
354
+ }
355
+ //# sourceMappingURL=fetch-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../src/runtime/fetch-wrapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ import { T, R, G, AS } from "./utils.js";
2
+ export function generateGqlWrapperCode(ctx, endpoint) {
3
+ const { ext, ts } = ctx;
4
+ const optionsInterface = ts
5
+ ? `
6
+ export interface GqlOptions {
7
+ variables?: Record<string, unknown>;
8
+ tags?: string[];
9
+ auth?: boolean;
10
+ queueOffline?: boolean;
11
+ invalidate?: 'auto' | string[] | false;
12
+ }
13
+ `
14
+ : "";
15
+ return `/**
16
+ * Swoff GraphQL Wrapper
17
+ * Brings Swoff's caching, offline queue, and tag-based invalidation to GraphQL APIs.
18
+ * Hashes query + variables for deterministic cache keys and auto-generates tags
19
+ * from operation names. Built on top of fetchWithCache.
20
+ *
21
+ * Usage:
22
+ * import { fetchWithGql, queryGql, mutateGql } from './swoff/gql-wrapper.${ext}';
23
+ *
24
+ * // Query — cached with body-hash key
25
+ * const { data } = await queryGql("{ todos { id title } }");
26
+ *
27
+ * // Query with variables
28
+ * const { data: todo } = await queryGql(
29
+ * "query GetTodo($id: ID!) { todo(id: $id) { id title } }",
30
+ * { id: "42" }
31
+ * );
32
+ *
33
+ * // Mutation — auto-invalidates related cache tags
34
+ * const { data: created } = await mutateGql(
35
+ * "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",
36
+ * { title: "New task" }
37
+ * );
38
+ *
39
+ * // Authenticated
40
+ * const { data } = await queryGql("query Me { me { name } }", {}, { auth: true });
41
+ *
42
+ * // Offline: mutations are auto-queued
43
+ * const { data } = await mutateGql(
44
+ * "mutation CreateTodo($title: String!) { createTodo(title: $title) { id } }",
45
+ * { title: "Offline task" },
46
+ * { queueOffline: true }
47
+ * );
48
+ */
49
+
50
+ import { fetchWithCache } from "../fetch/core.${ext}";
51
+ import type { GqlResult } from "../swoff";
52
+ ${optionsInterface}
53
+ /** Extract operation name from a GraphQL document. Returns null for anonymous queries (e.g. "{ todos { id } }"). */
54
+ function getOperationName(query${T(ts, "string")})${T(ts, "string | null")}{
55
+ const match = query.match(/(query|mutation|subscription)\\s+(\\w+)/);
56
+ return match ? match[2] : null;
57
+ }
58
+
59
+ /** Determine if a GraphQL document is a query (read) vs mutation/subscription (write). */
60
+ function isReadOperation(query${T(ts, "string")})${T(ts, "boolean")}{
61
+ const trimmed = query.trim();
62
+ if (trimmed.startsWith("mutation") || trimmed.startsWith("subscription")) return false;
63
+ return true;
64
+ }
65
+
66
+ /** Hash an object into a hex string for deterministic cache keying. Uses SHA-256 via SubtleCrypto. */
67
+ async function bodyHash(obj${T(ts, "unknown")})${T(ts, "Promise<string>")}{
68
+ const json = JSON.stringify(obj);
69
+ const bytes = new TextEncoder().encode(json);
70
+ const hash = await crypto.subtle.digest("SHA-256", bytes);
71
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
72
+ }
73
+
74
+ /** Generate cache-relevant tags from a GQL operation name. e.g. "getTodos" → ["todos"], "createTodo" → ["todos", "todo"]. */
75
+ function tagsFromOpName(name${T(ts, "string | null")})${T(ts, "string[]")}{
76
+ if (!name) return [];
77
+ const stripped = name.replace(/^(get|fetch|list|all|query)/i, "").replace(/^(create|set|add|new|update|delete|remove)/i, "");
78
+ if (!stripped) return [name.toLowerCase()];
79
+ const tag = stripped.charAt(0).toLowerCase() + stripped.slice(1);
80
+ const plural = tag.replace(/s$/, "") + "s";
81
+ return [plural, tag];
82
+ }
83
+
84
+ /** Fetch a GraphQL endpoint with Swoff's caching, auth, offline queue, and auto-invalidation. Hashes query + variables for deterministic cache keys. */
85
+ export async function fetchWithGql${G(ts, "T")}(
86
+ query${T(ts, "string")},
87
+ options${T(ts, "GqlOptions")} = {}
88
+ )${R(ts, "Promise<GqlResult<T>>")}{
89
+ const isRead = isReadOperation(query);
90
+ const opName = getOperationName(query);
91
+ const variables = options.variables;
92
+ const hash = await bodyHash({ query, variables });
93
+ const tags = options.tags || tagsFromOpName(opName);
94
+
95
+ const { response, fromCache } = await fetchWithCache("${endpoint}", {
96
+ method: "POST",
97
+ body: JSON.stringify({ query, variables }),
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ "X-SW-Cache-Key": "gql:" + hash,
101
+ },
102
+ tags,
103
+ type: isRead ? "read" : "mutation",
104
+ auth: options.auth,
105
+ queueOffline: options.queueOffline,
106
+ invalidate: options.invalidate,
107
+ });
108
+
109
+ if (!response.ok) {
110
+ throw new Error("GraphQL request failed with status " + response.status);
111
+ }
112
+ const result = await response.json();
113
+ return { data: result.data${AS(ts, "T")}, fromCache };
114
+ }
115
+
116
+ /** Shorthand for GraphQL queries (type: "read"). Cached by default with body-hash key. */
117
+ export async function queryGql${G(ts, "T")}(
118
+ query${T(ts, "string")},
119
+ variables${T(ts, "Record<string, unknown> | undefined")} = undefined,
120
+ options${T(ts, "GqlOptions")} = {}
121
+ )${R(ts, "Promise<GqlResult<T>>")}{
122
+ return fetchWithGql${G(ts, "T")}(query, { ...options, variables });
123
+ }
124
+
125
+ /** Shorthand for GraphQL mutations (type: "mutation"). Auto-invalidates related cache tags. */
126
+ export async function mutateGql${G(ts, "T")}(
127
+ mutation${T(ts, "string")},
128
+ variables${T(ts, "Record<string, unknown> | undefined")} = undefined,
129
+ options${T(ts, "GqlOptions")} = {}
130
+ )${R(ts, "Promise<GqlResult<T>>")}{
131
+ return fetchWithGql${G(ts, "T")}(mutation, { ...options, variables });
132
+ }
133
+ `;
134
+ }
135
+ //# sourceMappingURL=gql-wrapper.js.map