@swoff/cli 0.3.13 → 0.3.15

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 (269) hide show
  1. package/README.md +28 -32
  2. package/dist/index.js +4 -15
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/cli/help.js +4 -9
  5. package/dist/lib/cli/help.js.map +1 -1
  6. package/dist/lib/commands/clean.js +8 -17
  7. package/dist/lib/commands/clean.js.map +1 -1
  8. package/dist/lib/commands/generate-guide.js +16 -30
  9. package/dist/lib/commands/generate-guide.js.map +1 -1
  10. package/dist/lib/commands/generate.js +33 -7
  11. package/dist/lib/commands/generate.js.map +1 -1
  12. package/dist/lib/commands/init.js +259 -23
  13. package/dist/lib/commands/init.js.map +1 -1
  14. package/dist/lib/commands/validate.js +2 -4
  15. package/dist/lib/commands/validate.js.map +1 -1
  16. package/dist/lib/config/loader.js +34 -25
  17. package/dist/lib/config/loader.js.map +1 -1
  18. package/dist/lib/config/minimal-config.js +64 -0
  19. package/dist/lib/config/minimal-config.js.map +1 -0
  20. package/dist/lib/config/validator.js +219 -110
  21. package/dist/lib/config/validator.js.map +1 -1
  22. package/dist/lib/generators/file-generators/api-config.js +1 -1
  23. package/dist/lib/generators/file-generators/api-config.js.map +1 -1
  24. package/dist/lib/generators/file-generators/auth-adapter.js +13 -0
  25. package/dist/lib/generators/file-generators/auth-adapter.js.map +1 -0
  26. package/dist/lib/generators/file-generators/auth-check.js +40 -0
  27. package/dist/lib/generators/file-generators/auth-check.js.map +1 -0
  28. package/dist/lib/generators/file-generators/auth-store.js +2 -5
  29. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  30. package/dist/lib/generators/file-generators/background-sync.js +1 -1
  31. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  32. package/dist/lib/generators/file-generators/cache.js +2 -1
  33. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  34. package/dist/lib/generators/file-generators/client-injector-bundle.js +15 -0
  35. package/dist/lib/generators/file-generators/client-injector-bundle.js.map +1 -0
  36. package/dist/lib/generators/file-generators/client-injector.js +2 -1
  37. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  38. package/dist/lib/generators/file-generators/connectivity.js +12 -0
  39. package/dist/lib/generators/file-generators/connectivity.js.map +1 -0
  40. package/dist/lib/generators/file-generators/context.js +7 -0
  41. package/dist/lib/generators/file-generators/context.js.map +1 -1
  42. package/dist/lib/generators/file-generators/fetch-wrapper.js +1 -1
  43. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  44. package/dist/lib/generators/file-generators/generate-framework-adapters.js +29 -20
  45. package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -1
  46. package/dist/lib/generators/file-generators/gql-wrapper.js +2 -2
  47. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
  48. package/dist/lib/generators/file-generators/guide-generator.js +51 -1
  49. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  50. package/dist/lib/generators/file-generators/invalidation-tags.js +2 -3
  51. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  52. package/dist/lib/generators/file-generators/mutation-queue.js +2 -2
  53. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  54. package/dist/lib/generators/file-generators/mutation-state.js +1 -1
  55. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
  56. package/dist/lib/generators/file-generators/open-db.js +6 -0
  57. package/dist/lib/generators/file-generators/open-db.js.map +1 -0
  58. package/dist/lib/generators/file-generators/push.js +1 -2
  59. package/dist/lib/generators/file-generators/push.js.map +1 -1
  60. package/dist/lib/generators/file-generators/pwa-install.js +2 -6
  61. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  62. package/dist/lib/generators/file-generators/server-push.js +1 -1
  63. package/dist/lib/generators/file-generators/server-push.js.map +1 -1
  64. package/dist/lib/generators/file-generators/storage.js +6 -0
  65. package/dist/lib/generators/file-generators/storage.js.map +1 -0
  66. package/dist/lib/generators/file-generators/sw-generator-build.js +105 -48
  67. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  68. package/dist/lib/generators/file-generators/sw-injector.js +0 -4
  69. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  70. package/dist/lib/generators/file-generators/sw-template.js +8 -9
  71. package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
  72. package/dist/lib/generators/file-generators/swoff-api-bundle.js +51 -0
  73. package/dist/lib/generators/file-generators/swoff-api-bundle.js.map +1 -0
  74. package/dist/lib/generators/sw-build-utils.js +69 -0
  75. package/dist/lib/generators/sw-build-utils.js.map +1 -0
  76. package/dist/lib/generators/sw-generator.js +23 -34
  77. package/dist/lib/generators/sw-generator.js.map +1 -1
  78. package/dist/lib/generators/sw-sections/activate-handler.js +77 -23
  79. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  80. package/dist/lib/generators/sw-sections/assemble-sw.js +20 -95
  81. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  82. package/dist/lib/generators/sw-sections/background-precache.js +126 -0
  83. package/dist/lib/generators/sw-sections/background-precache.js.map +1 -0
  84. package/dist/lib/generators/sw-sections/background-sync-handler.js +74 -72
  85. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  86. package/dist/lib/generators/sw-sections/batch-refresh-queue.js +102 -0
  87. package/dist/lib/generators/sw-sections/batch-refresh-queue.js.map +1 -0
  88. package/dist/lib/generators/sw-sections/config-header.js +2 -3
  89. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  90. package/dist/lib/generators/sw-sections/default-template.js +64 -7
  91. package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
  92. package/dist/lib/generators/sw-sections/fetch-handler.js +621 -495
  93. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  94. package/dist/lib/generators/sw-sections/install-handler.js +41 -23
  95. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
  96. package/dist/lib/generators/sw-sections/message-handler.js +34 -8
  97. package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
  98. package/dist/lib/generators/sw-sections/server-push-handler.js +10 -14
  99. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
  100. package/dist/lib/generators/sw-sections/shared.js +70 -0
  101. package/dist/lib/generators/sw-sections/shared.js.map +1 -0
  102. package/dist/lib/generators/sw-sections/sw-push.js +2 -6
  103. package/dist/lib/generators/sw-sections/sw-push.js.map +1 -1
  104. package/dist/lib/generators/sw-sections/tag-management.js +55 -86
  105. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  106. package/dist/lib/generators/swoff-files-generator.js +183 -59
  107. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  108. package/dist/lib/shared/config-types.js +84 -79
  109. package/dist/lib/shared/config-types.js.map +1 -1
  110. package/dist/lib/shared/feature-registry.js +198 -0
  111. package/dist/lib/shared/feature-registry.js.map +1 -0
  112. package/dist/lib/utils/detect-framework.js +52 -9
  113. package/dist/lib/utils/detect-framework.js.map +1 -1
  114. package/dist/runtime/api-config.js +2 -2
  115. package/dist/runtime/api-config.js.map +1 -1
  116. package/dist/runtime/auth-adapter.js +179 -0
  117. package/dist/runtime/auth-adapter.js.map +1 -0
  118. package/dist/runtime/auth-state.js +16 -6
  119. package/dist/runtime/auth-state.js.map +1 -1
  120. package/dist/runtime/auth-store.js +219 -213
  121. package/dist/runtime/auth-store.js.map +1 -1
  122. package/dist/runtime/background-sync.js +5 -2
  123. package/dist/runtime/background-sync.js.map +1 -1
  124. package/dist/runtime/cache.js +91 -3
  125. package/dist/runtime/cache.js.map +1 -1
  126. package/dist/runtime/client-injector-bundle.js +314 -0
  127. package/dist/runtime/client-injector-bundle.js.map +1 -0
  128. package/dist/runtime/client-injector.js +188 -30
  129. package/dist/runtime/client-injector.js.map +1 -1
  130. package/dist/runtime/connectivity.js +83 -0
  131. package/dist/runtime/connectivity.js.map +1 -0
  132. package/dist/runtime/fetch-wrapper.js +26 -59
  133. package/dist/runtime/fetch-wrapper.js.map +1 -1
  134. package/dist/runtime/gql-wrapper.js +15 -9
  135. package/dist/runtime/gql-wrapper.js.map +1 -1
  136. package/dist/runtime/invalidation-tags.js +12 -89
  137. package/dist/runtime/invalidation-tags.js.map +1 -1
  138. package/dist/runtime/mutation-queue.js +83 -31
  139. package/dist/runtime/mutation-queue.js.map +1 -1
  140. package/dist/runtime/mutation-state.js +3 -3
  141. package/dist/runtime/mutation-state.js.map +1 -1
  142. package/dist/runtime/open-db.js +41 -0
  143. package/dist/runtime/open-db.js.map +1 -0
  144. package/dist/runtime/push.js +15 -27
  145. package/dist/runtime/push.js.map +1 -1
  146. package/dist/runtime/pwa-prompt.js +35 -3
  147. package/dist/runtime/pwa-prompt.js.map +1 -1
  148. package/dist/runtime/reset.js +39 -42
  149. package/dist/runtime/reset.js.map +1 -1
  150. package/dist/runtime/server-push.js +23 -13
  151. package/dist/runtime/server-push.js.map +1 -1
  152. package/dist/runtime/storage.js +32 -0
  153. package/dist/runtime/storage.js.map +1 -0
  154. package/dist/runtime/sw-injector.js +19 -178
  155. package/dist/runtime/sw-injector.js.map +1 -1
  156. package/dist/runtime/swoff-api-bundle.js +1491 -0
  157. package/dist/runtime/swoff-api-bundle.js.map +1 -0
  158. package/dist/runtime/type-definitions.js +8 -7
  159. package/dist/runtime/type-definitions.js.map +1 -1
  160. package/dist/runtime/utils.js +4 -0
  161. package/dist/runtime/utils.js.map +1 -1
  162. package/package.json +9 -3
  163. package/templates/react/useAuth.jsx +7 -4
  164. package/templates/react/useAuth.tsx +14 -14
  165. package/templates/react/useBackgroundSync.jsx +5 -4
  166. package/templates/react/useBackgroundSync.tsx +5 -4
  167. package/templates/react/useCachedFetch.jsx +8 -0
  168. package/templates/react/useCachedFetch.tsx +8 -0
  169. package/templates/react/useMutation.jsx +60 -56
  170. package/templates/react/useMutation.tsx +65 -64
  171. package/templates/react/useMutationQueue.jsx +5 -1
  172. package/templates/react/useMutationQueue.tsx +8 -2
  173. package/templates/react/useMutationState.jsx +3 -4
  174. package/templates/react/useMutationState.tsx +4 -8
  175. package/templates/react/useNetworkStatus.jsx +57 -17
  176. package/templates/react/useNetworkStatus.tsx +42 -20
  177. package/templates/react/useOfflineAnalytics.jsx +42 -0
  178. package/templates/react/useOfflineAnalytics.tsx +50 -0
  179. package/templates/react/usePwaInstall.jsx +26 -0
  180. package/templates/react/usePwaInstall.tsx +26 -0
  181. package/templates/react/useSWUpdate.jsx +7 -52
  182. package/templates/react/useSWUpdate.tsx +12 -51
  183. package/templates/react/useStorageEstimate.jsx +56 -0
  184. package/templates/react/useStorageEstimate.tsx +67 -0
  185. package/dist/__tests__/assemble-sw.test.js +0 -145
  186. package/dist/__tests__/assemble-sw.test.js.map +0 -1
  187. package/dist/__tests__/cli-integration.test.js +0 -231
  188. package/dist/__tests__/cli-integration.test.js.map +0 -1
  189. package/dist/__tests__/config-types.test.js +0 -124
  190. package/dist/__tests__/config-types.test.js.map +0 -1
  191. package/dist/__tests__/detect-language.test.js +0 -51
  192. package/dist/__tests__/detect-language.test.js.map +0 -1
  193. package/dist/__tests__/file-generators.test.js +0 -228
  194. package/dist/__tests__/file-generators.test.js.map +0 -1
  195. package/dist/__tests__/glob-matcher.test.js +0 -86
  196. package/dist/__tests__/glob-matcher.test.js.map +0 -1
  197. package/dist/__tests__/invalidation-tags.test.js +0 -283
  198. package/dist/__tests__/invalidation-tags.test.js.map +0 -1
  199. package/dist/__tests__/loader.test.js +0 -77
  200. package/dist/__tests__/loader.test.js.map +0 -1
  201. package/dist/__tests__/validator.test.js +0 -340
  202. package/dist/__tests__/validator.test.js.map +0 -1
  203. package/dist/lib/commands/add.js +0 -120
  204. package/dist/lib/commands/add.js.map +0 -1
  205. package/dist/lib/commands/assets.js +0 -45
  206. package/dist/lib/commands/assets.js.map +0 -1
  207. package/dist/lib/commands/generate-assets.js +0 -44
  208. package/dist/lib/commands/generate-assets.js.map +0 -1
  209. package/dist/lib/commands/info.js +0 -183
  210. package/dist/lib/commands/info.js.map +0 -1
  211. package/dist/lib/generators/asset-generator/dependency-resolver.js +0 -85
  212. package/dist/lib/generators/asset-generator/dependency-resolver.js.map +0 -1
  213. package/dist/lib/generators/asset-generator/generate.js +0 -111
  214. package/dist/lib/generators/asset-generator/generate.js.map +0 -1
  215. package/dist/lib/generators/asset-generator/guide.js +0 -43
  216. package/dist/lib/generators/asset-generator/guide.js.map +0 -1
  217. package/dist/lib/generators/asset-generator/html-patcher.js +0 -65
  218. package/dist/lib/generators/asset-generator/html-patcher.js.map +0 -1
  219. package/dist/lib/generators/asset-generator/ico-encoder.js +0 -29
  220. package/dist/lib/generators/asset-generator/ico-encoder.js.map +0 -1
  221. package/dist/lib/generators/asset-generator/manifest-patcher.js +0 -23
  222. package/dist/lib/generators/asset-generator/manifest-patcher.js.map +0 -1
  223. package/dist/lib/generators/asset-generator/maskable.js +0 -15
  224. package/dist/lib/generators/asset-generator/maskable.js.map +0 -1
  225. package/dist/lib/generators/asset-generator/rasterize.js +0 -32
  226. package/dist/lib/generators/asset-generator/rasterize.js.map +0 -1
  227. package/dist/lib/generators/asset-generator/sizes.js +0 -25
  228. package/dist/lib/generators/asset-generator/sizes.js.map +0 -1
  229. package/dist/lib/generators/file-generators/auth-fetch.js +0 -151
  230. package/dist/lib/generators/file-generators/auth-fetch.js.map +0 -1
  231. package/dist/lib/generators/file-generators/auth-user.js +0 -13
  232. package/dist/lib/generators/file-generators/auth-user.js.map +0 -1
  233. package/dist/lib/generators/file-generators/fetch-state.js +0 -6
  234. package/dist/lib/generators/file-generators/fetch-state.js.map +0 -1
  235. package/dist/lib/generators/file-generators/generate-hooks.js +0 -44
  236. package/dist/lib/generators/file-generators/generate-hooks.js.map +0 -1
  237. package/dist/lib/generators/file-generators/indexeddb.js +0 -74
  238. package/dist/lib/generators/file-generators/indexeddb.js.map +0 -1
  239. package/dist/lib/generators/file-generators/manifest.js +0 -46
  240. package/dist/lib/generators/file-generators/manifest.js.map +0 -1
  241. package/dist/lib/generators/file-generators/mutation-reconcile.js +0 -57
  242. package/dist/lib/generators/file-generators/mutation-reconcile.js.map +0 -1
  243. package/dist/lib/generators/file-generators/quick-readme.js +0 -21
  244. package/dist/lib/generators/file-generators/quick-readme.js.map +0 -1
  245. package/dist/lib/generators/file-generators/reconcile.js +0 -50
  246. package/dist/lib/generators/file-generators/reconcile.js.map +0 -1
  247. package/dist/lib/generators/file-generators/store.js +0 -86
  248. package/dist/lib/generators/file-generators/store.js.map +0 -1
  249. package/dist/lib/generators/file-generators/sw-push.js +0 -67
  250. package/dist/lib/generators/file-generators/sw-push.js.map +0 -1
  251. package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +0 -1
  252. package/dist/lib/utils/build-script.js +0 -35
  253. package/dist/lib/utils/build-script.js.map +0 -1
  254. package/dist/lib/utils/tty-status.js +0 -21
  255. package/dist/lib/utils/tty-status.js.map +0 -1
  256. package/dist/runtime/auth-user.js +0 -168
  257. package/dist/runtime/auth-user.js.map +0 -1
  258. package/dist/runtime/fetch-state.js +0 -42
  259. package/dist/runtime/fetch-state.js.map +0 -1
  260. package/dist/runtime/pwa-index.js +0 -11
  261. package/dist/runtime/pwa-index.js.map +0 -1
  262. package/dist/runtime/pwa-injector.js +0 -44
  263. package/dist/runtime/pwa-injector.js.map +0 -1
  264. package/dist/runtime/pwa-install.js +0 -83
  265. package/dist/runtime/pwa-install.js.map +0 -1
  266. package/templates/react/useCacheInvalidation.jsx +0 -19
  267. package/templates/react/useCacheInvalidation.tsx +0 -52
  268. package/templates/react/useIsFetching.jsx +0 -32
  269. package/templates/react/useIsFetching.tsx +0 -32
@@ -1,259 +1,177 @@
1
- import { T, R, PT, AS } from "./utils.js";
2
- function generateWithAuthHeaders(authType, ts) {
3
- const hd = T(ts, "Headers");
4
- const a = T(ts, "AuthData | null");
5
- switch (authType) {
6
- case "cookie":
7
- return `/** Inject auth headers. For cookie auth, credentials are handled via AUTH_WITH_CREDENTIALS. */
8
- export function withAuthHeaders(headers${hd}, _auth${a})${hd}{
9
- return headers;
10
- }`;
11
- case "bearer":
12
- return `/** Inject Bearer token into request headers. */
13
- export function withAuthHeaders(headers${hd}, auth${a})${hd}{
14
- if (auth?.token) {
15
- headers.set("Authorization", \`Bearer \${auth.token}\`);
16
- }
17
- return headers;
18
- }`;
19
- case "custom":
20
- return `/** Inject custom auth headers. Edit this function to match your backend. */
21
- export function withAuthHeaders(headers${hd}, auth${a})${hd}{
22
- // --- EDIT THIS BLOCK FOR YOUR BACKEND ---
23
- // if (auth?.token) {
24
- // headers.set("X-Auth-Token", auth.token);
25
- // }
26
- // --- END OF EDITABLE BLOCK ---
27
- return headers;
28
- }`;
29
- default:
30
- return `export function withAuthHeaders(headers${hd}, auth${a})${hd}{
31
- if (auth?.token) {
32
- headers.set("Authorization", \`Bearer \${auth.token}\`);
33
- }
34
- return headers;
35
- }`;
36
- }
37
- }
38
- function generateIsAuthUrl(refreshPath, userEndpoint, ts) {
39
- return `/** Check if a URL is an auth endpoint that should bypass the SW cache. */
40
- export function isAuthUrl(url${T(ts, "string")})${R(ts, "boolean")}{
41
- const authPaths = [
1
+ import { T, R, PT } from "./utils.js";
2
+ const DEFAULT_AUTH_ROUTES = [
42
3
  "/login",
43
4
  "/logout",
44
5
  "/register",
45
6
  "/api/login",
46
7
  "/api/logout",
47
8
  "/api/register",
48
- "${refreshPath}",
49
- "${userEndpoint}",
50
- ];
51
- return authPaths.some((path) => url.includes(path));
52
- }`;
53
- }
54
- function generateEnsureValidAuth(cookieAuth, ts, refreshPath, ext) {
55
- const tokenArg = cookieAuth ? "" : "auth?.token";
56
- return `/** Try to restore the session after page refresh — delegates to refreshSession() in ./user.${ext}. */
57
- let restorePromise${T(ts, "Promise<AuthData | null> | null")} = null;
58
-
59
- async function tryRestoreSession()${R(ts, "Promise<AuthData | null>")}{
60
- if (restorePromise) {
61
- try { return await restorePromise; } finally { restorePromise = null; }
62
- }
63
- restorePromise = (async () => {
64
- try {
65
- const response = await refreshSession();
66
- if (!response.ok) return null;
67
- const data = await response.json();
68
- const userData = await loadUserData();
69
- const auth = { ...userData, token: data.token, expiresAt: data.expiresAt };
70
- await setAuth(auth);
71
- return auth;
72
- } catch { return null; }
73
- })();
74
- try { return await restorePromise; } finally { restorePromise = null; }
75
- }
76
-
77
- let refreshPromise${T(ts, "Promise<AuthData | null> | null")} = null;
78
-
79
- export async function ensureValidAuth()${R(ts, "Promise<AuthData | null>")}{
80
- const auth = await getAuth();
81
- if (!auth) return null;
82
-
83
- // Token missing after page refresh — try silent session restoration
84
- if (!auth.token) {
85
- return tryRestoreSession();
86
- }
87
-
88
- if (!auth.expiresAt || Date.now() < auth.expiresAt) return auth;
89
-
90
- if (!refreshPromise) {
91
- refreshPromise = (async () => {
92
- try {
93
- const response = await refreshSession(${tokenArg});
94
- if (!response.ok) {
95
- await clearAuth();
96
- window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
97
- return null;
98
- }
99
-
100
- const data = await response.json();
101
- const updated = { ...auth, token: data.token, expiresAt: data.expiresAt };
102
- await setAuth(updated);
103
- return updated;
104
- } catch {
105
- await clearAuth();
106
- window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
107
- return null;
108
- }
109
- })();
110
- }
111
-
112
- try {
113
- return await refreshPromise;
114
- } finally {
115
- refreshPromise = null;
116
- }
117
- }`;
118
- }
119
- export function generateAuthStoreCode(ctx, authType, refreshPath, userEndpoint) {
9
+ "/api/refresh",
10
+ "/api/me",
11
+ ];
12
+ export function generateAuthStoreCode(ctx, authType, authRoutePaths = DEFAULT_AUTH_ROUTES, mutationQueueEnabled = false) {
120
13
  const { ext, ts } = ctx;
121
- const authDataInterface = ts
122
- ? `export interface AuthData {
123
- token?: string;
124
- user?: Record<string, unknown>;
125
- expiresAt?: number;
126
- }
127
-
128
- export interface AuthResponse extends Record<string, unknown> {
129
- // Uncomment and type the fields your backend's login response returns:
130
- // token: string;
131
- // user: Record<string, unknown>;
132
- // expiresAt?: number;
133
- }
134
-
135
- `
136
- : "";
137
- const createAuthFromResponseBlock = ts
138
- ? `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
139
- export function createAuthFromResponse(response: AuthResponse): AuthData {
140
- // --- EDIT THIS TO MATCH YOUR BACKEND ---
141
- const data = response as Record<string, unknown>;
142
- return {
143
- token: data.token as string,
144
- user: data.user as Record<string, unknown> | undefined,
145
- expiresAt: data.expiresAt as number | undefined,
146
- };
147
- // --- END OF EDITABLE BLOCK ---
148
- }
149
- `
150
- : `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
151
- export function createAuthFromResponse(response) {
152
- // --- EDIT THIS TO MATCH YOUR BACKEND ---
153
- return {
154
- token: response.token,
155
- user: response.user,
156
- expiresAt: response.expiresAt,
157
- };
158
- // --- END OF EDITABLE BLOCK ---
159
- }
160
- `;
14
+ const isCookie = authType === "cookie";
15
+ const cookieAuthComment = isCookie
16
+ ? ` *
17
+ * Cookie auth: the server manages the session. setAuth() stores user info
18
+ * for offline display. The browser sends the session cookie automatically.`
19
+ : ` *
20
+ * Bearer auth: token lives in memory only — cleared on page refresh.
21
+ * ensureValidAuth() calls adapter.refresh() when the token expires.
22
+ * Only { user, expiresAt } persists to IndexedDB for offline display.`;
161
23
  return `/**
162
- * Auth Store — Token in memory only; user info in IndexedDB for offline access.
24
+ * Auth Store — Token in memory only; user info in IndexedDB for offline access.${cookieAuthComment}
163
25
  *
164
- * Security:
165
- * The Bearer token lives in JavaScript memory and is cleared on page refresh.
166
- * Only { user, expiresAt } is persisted to IndexedDB so the app can display
167
- * user info offline. After a page refresh, re-login is required.
26
+ * Public API (functions listed here):
27
+ * setAuth, getAuth, clearAuth, clearMemoryAuth, isAuthValid,
28
+ * ensureValidAuth, withAuthHeaders,
29
+ * AUTH_WITH_CREDENTIALS, isAuthUrl
30
+ *
31
+ * Internal helpers (not exported — used by Swoff internally):
32
+ * persistUserData, loadUserData, clearPersistedData
168
33
  *
169
34
  * Usage:
170
- * import { setAuth, getAuth, clearAuth, isAuthValid } from "./auth/store.${ext}";
35
+ * import { setAuth, getAuth, clearAuth, clearMemoryAuth, isAuthValid, ensureValidAuth, withAuthHeaders } from "./auth/store.${ext}";
171
36
  *
172
- * await setAuth({ token, user, expiresAt });
37
+ * await setAuth({ user });
173
38
  * const auth = await getAuth();
174
39
  * await clearAuth();
40
+ * await clearMemoryAuth();
41
+ * await ensureValidAuth();
175
42
  */
176
43
 
177
- import { refreshSession } from "./user.${ext}";
44
+ import { adapter } from "./adapter.${ext}";
45
+ ${ts ? `import type { AuthData } from "./adapter.${ext}";` : ""}
46
+ import { openDB } from "../db.${ext}";
47
+ ${mutationQueueEnabled ? `import { clearQueue } from "../mutation/queue.${ext}";\n` : ""}
178
48
 
179
- ${authDataInterface}const DB_NAME = "swoff-auth";
49
+ const DB_NAME = "swoff-auth";
180
50
  const STORE_NAME = "auth";
181
- // Bump this when adding new indexes/stores for schema migration
182
- const DB_VERSION = 1;
183
-
184
51
  let memoryAuth${T(ts, "AuthData | null")} = null;
52
+ let _fetchingUser = false;
185
53
 
186
- ${createAuthFromResponseBlock}
187
- function openAuthDB()${R(ts, "Promise<IDBDatabase>")}{
188
- return new Promise${PT(ts, "IDBDatabase")}((resolve, reject) => {
189
- const request = indexedDB.open(DB_NAME, DB_VERSION);
190
- request.onupgradeneeded = (e) => {
191
- const db = (e.target${AS(ts, "IDBOpenDBRequest")}).result;
192
- if (!db.objectStoreNames.contains(STORE_NAME)) {
193
- db.createObjectStore(STORE_NAME, { keyPath: "key" });
194
- }
195
- };
196
- request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
197
- request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
198
- });
199
- }
54
+ // ── Internal: persistence helpers ────────────────────────────────────
200
55
 
201
56
  async function persistUserData(authData${T(ts, "AuthData | null")})${R(ts, "Promise<void>")}{
202
- // Only persist { user, expiresAt } — never the token
203
57
  const userData = { user: authData?.user, expiresAt: authData?.expiresAt };
204
- const db = await openAuthDB();
205
- return new Promise${PT(ts, "void")}((resolve, reject) => {
206
- const tx = db.transaction(STORE_NAME, "readwrite");
207
- const store = tx.objectStore(STORE_NAME);
208
- const request = store.put({ key: "session", value: userData });
209
- request.onsuccess = () => resolve();
210
- request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
211
- });
58
+ const db = await openDB(DB_NAME, STORE_NAME, "key");
59
+ try {
60
+ await new Promise${PT(ts, "void")}((resolve, reject) => {
61
+ const tx = db.transaction(STORE_NAME, "readwrite");
62
+ const store = tx.objectStore(STORE_NAME);
63
+ const request = store.put({ key: "session", value: userData });
64
+ request.onsuccess = () => resolve();
65
+ request.onerror = () => reject(request.error);
66
+ });
67
+ } finally {
68
+ db.close();
69
+ }
212
70
  }
213
71
 
214
- async function loadUserData()${R(ts, "Promise<{ user?: Record<string, unknown>; expiresAt?: number } | null>")}{
215
- const db = await openAuthDB();
216
- return new Promise((resolve, reject) => {
217
- const tx = db.transaction(STORE_NAME, "readonly");
218
- const store = tx.objectStore(STORE_NAME);
219
- const request = store.get("session");
220
- request.onsuccess = () => resolve((request${AS(ts, "IDBRequest")}).result?.value ?? null);
221
- request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
222
- });
72
+ async function loadUserData()${R(ts, "Promise<AuthData | null>")}{
73
+ const db = await openDB(DB_NAME, STORE_NAME, "key");
74
+ try {
75
+ return await new Promise((resolve, reject) => {
76
+ const tx = db.transaction(STORE_NAME, "readonly");
77
+ const store = tx.objectStore(STORE_NAME);
78
+ const request = store.get("session");
79
+ request.onsuccess = () => resolve(request.result?.value ?? null);
80
+ request.onerror = () => reject(request.error);
81
+ });
82
+ } finally {
83
+ db.close();
84
+ }
223
85
  }
224
86
 
225
87
  async function clearPersistedData()${R(ts, "Promise<void>")}{
226
- const db = await openAuthDB();
227
- return new Promise${PT(ts, "void")}((resolve, reject) => {
228
- const tx = db.transaction(STORE_NAME, "readwrite");
229
- const store = tx.objectStore(STORE_NAME);
230
- const request = store.delete("session");
231
- request.onsuccess = () => resolve();
232
- request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
233
- });
88
+ const db = await openDB(DB_NAME, STORE_NAME, "key");
89
+ try {
90
+ await new Promise${PT(ts, "void")}((resolve, reject) => {
91
+ const tx = db.transaction(STORE_NAME, "readwrite");
92
+ const store = tx.objectStore(STORE_NAME);
93
+ const request = store.delete("session");
94
+ request.onsuccess = () => resolve();
95
+ request.onerror = () => reject(request.error);
96
+ });
97
+ } finally {
98
+ db.close();
99
+ }
234
100
  }
235
101
 
102
+ // ── Public API: auth data operations ─────────────────────────────────
103
+
236
104
  /** Store auth data in memory and persist user info to IndexedDB for offline access. */
237
105
  export async function setAuth(authData${T(ts, "AuthData")})${R(ts, "Promise<void>")}{
238
106
  memoryAuth = authData;
239
107
  await persistUserData(authData);
240
108
  }
241
109
 
242
- /** Get auth data from memory (or IndexedDB if memory is empty after page refresh). */
110
+ /** Get auth data from memory, then adapter, then IndexedDB fallback (user info only). */
243
111
  export async function getAuth()${R(ts, "Promise<AuthData | null>")}{
244
112
  if (memoryAuth) return memoryAuth;
245
113
 
114
+ // Try adapter first (for provider-managed auth like Better Auth)
115
+ try {
116
+ const adapterAuth = await adapter.getAuth();
117
+ if (adapterAuth) {
118
+ memoryAuth = adapterAuth;
119
+ await persistUserData(adapterAuth);
120
+ return memoryAuth;
121
+ }
122
+ // Adapter confirmed no session — don't fall through to stale IDB
123
+ return null;
124
+ } catch {
125
+ // Adapter error (network failure) — fall through to IndexedDB
126
+ }
127
+
128
+ // Fall back to IndexedDB user cache (only reached on adapter error)
246
129
  const userData = await loadUserData();
247
130
  if (userData) {
248
131
  memoryAuth = userData;
132
+ return memoryAuth;
249
133
  }
250
- return memoryAuth;
134
+
135
+ // Last resort — try server fetch
136
+ if (_fetchingUser) return null;
137
+ _fetchingUser = true;
138
+ try {
139
+ const fetched = await adapter.fetchUser();
140
+ if (fetched) {
141
+ memoryAuth = fetched;
142
+ await persistUserData(fetched);
143
+ return memoryAuth;
144
+ }
145
+ } catch {
146
+ // Server unreachable — no auth data available
147
+ } finally {
148
+ _fetchingUser = false;
149
+ }
150
+
151
+ return null;
152
+ }
153
+
154
+ /** Null memory auth only. Use for cross-tab sync — SW broadcasts AUTH_CLEARED, other tabs call this. */
155
+ export function clearMemoryAuth()${R(ts, "void")}{
156
+ memoryAuth = null;
251
157
  }
252
158
 
253
- /** Clear auth from memory and IndexedDB. Call on logout or 401. */
254
- export async function clearAuth()${R(ts, "Promise<void>")}{
159
+ /** Clear auth from memory, IndexedDB, runtime caches, and dispatch event. Sends AUTH_CLEARED to SW so other tabs clear memory too. Call once on logout. */
160
+ export async function clearAuth(options${T(ts, "{ broadcast?: boolean }")} = {})${R(ts, "Promise<void>")}{
161
+ if (options.broadcast !== false) {
162
+ navigator.serviceWorker.controller?.postMessage({ type: "AUTH_CLEARED" });
163
+ }
255
164
  memoryAuth = null;
256
165
  await clearPersistedData();
166
+ try {
167
+ const cacheNames = await caches.keys();
168
+ await Promise.all(
169
+ cacheNames
170
+ .filter(name => name.startsWith("swoff-runtime"))
171
+ .map(name => caches.delete(name))
172
+ );
173
+ } catch { /* caches API unavailable */ }
174
+ ${mutationQueueEnabled ? `await clearQueue();\n` : ""} window.dispatchEvent(new CustomEvent("sw-auth-state-change", { detail: { type: "clear" } }));
257
175
  }
258
176
 
259
177
  /** Check if auth exists and has not expired. Returns true if no expiresAt is set. */
@@ -263,11 +181,99 @@ export function isAuthValid(auth${T(ts, "AuthData | null")})${R(ts, "boolean")}{
263
181
  return Date.now() < auth.expiresAt;
264
182
  }
265
183
 
266
- ${authType === "cookie" ? `export const AUTH_WITH_CREDENTIALS = true;` : `export const AUTH_WITH_CREDENTIALS = false;`}
184
+ // ── Public API: adapter-delegated ────────────────────────────────────
185
+
186
+ /** Whether to use credentials: "include" for fetch requests. True for cookie-based auth. */
187
+ export const AUTH_WITH_CREDENTIALS = adapter.type === "cookie";
188
+
189
+ /** Inject auth headers into a Headers object. Delegates to the auth adapter. */
190
+ export function withAuthHeaders(headers${T(ts, "Headers")}, auth${T(ts, "AuthData | null")})${R(ts, "Headers")}{
191
+ const adapterHeaders = adapter.getHeaders(auth);
192
+ for (const [key, value] of Object.entries(adapterHeaders)) {
193
+ headers.set(key, value);
194
+ }
195
+ return headers;
196
+ }
197
+
198
+ /** Check if a URL is an auth endpoint that should bypass the SW cache. */
199
+ export function isAuthUrl(url${T(ts, "string")})${R(ts, "boolean")}{
200
+ var urlPath = url.indexOf("://") !== -1 ? new URL(url).pathname.replace(/\/$/, "") : url.replace(/\/$/, "");
201
+ return ${JSON.stringify(authRoutePaths)}.some(function(path) { return urlPath === path; });
202
+ }
203
+
204
+ // ── Public API: session refresh ──────────────────────────────────────
205
+
206
+ ${isCookie
207
+ ? `
208
+ /**
209
+ * Cookie auth: the server manages the session. No token to restore or refresh.
210
+ * The browser sends the session cookie automatically.
211
+ * getAuth() + IndexedDB user cache provides offline access.
212
+ */
213
+ export async function ensureValidAuth()${R(ts, "Promise<AuthData | null>")}{
214
+ return getAuth();
215
+ }
216
+ `
217
+ : `
218
+ /** Try to restore session after page refresh. Delegates to adapter.refresh(). */
219
+ async function tryRestoreSession()${R(ts, "Promise<AuthData | null>")}{
220
+ try {
221
+ const auth = await getAuth();
222
+ if (!auth) return null;
223
+ const refreshed = await adapter.refresh(auth);
224
+ if (refreshed) {
225
+ await setAuth(refreshed);
226
+ return refreshed;
227
+ }
228
+ return null;
229
+ } catch { return null; }
230
+ }
231
+
232
+ let restorePromise${T(ts, "Promise<AuthData | null> | null")} = null;
233
+ let refreshPromise${T(ts, "Promise<AuthData | null> | null")} = null;
234
+
235
+ /**
236
+ * Bearer auth: token expires — try silent refresh via adapter.refresh().
237
+ * If refresh fails, auth is cleared and sw-auth-unauthorized is dispatched.
238
+ */
239
+ export async function ensureValidAuth()${R(ts, "Promise<AuthData | null>")}{
240
+ const auth = await getAuth();
241
+ if (!auth) return null;
242
+
243
+ // No token after page refresh — try silent session restoration
244
+ if (!auth.token) {
245
+ if (restorePromise) {
246
+ try { return await restorePromise; } finally { restorePromise = null; }
247
+ }
248
+ restorePromise = tryRestoreSession();
249
+ try { return await restorePromise; } finally { restorePromise = null; }
250
+ }
251
+
252
+ // Not expired yet
253
+ if (!auth.expiresAt || Date.now() < auth.expiresAt) return auth;
254
+
255
+ // Token expired — try refresh via adapter
256
+ if (!refreshPromise) {
257
+ refreshPromise = (async () => {
258
+ try {
259
+ const refreshed = await adapter.refresh(auth);
260
+ if (refreshed) {
261
+ await setAuth(refreshed);
262
+ return refreshed;
263
+ }
264
+ await clearAuth();
265
+ return null;
266
+ } catch {
267
+ await clearAuth();
268
+ return null;
269
+ }
270
+ })();
271
+ }
272
+
273
+ try { return await refreshPromise; } finally { refreshPromise = null; }
274
+ }
275
+ `}
267
276
 
268
- ${generateWithAuthHeaders(authType, ts)}
269
- ${generateIsAuthUrl(refreshPath, userEndpoint, ts)}
270
- ${generateEnsureValidAuth(authType === "cookie", ts, refreshPath, ext)}
271
277
  `;
272
278
  }
273
279
  //# sourceMappingURL=auth-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../src/runtime/auth-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../src/runtime/auth-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,mBAAmB,GAAG;IAC1B,QAAQ;IACR,SAAS;IACT,WAAW;IACX,YAAY;IACZ,aAAa;IACb,eAAe;IACf,cAAc;IACd,SAAS;CACV,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACnC,GAAmB,EACnB,QAAgB,EAChB,iBAA2B,mBAAmB,EAC9C,uBAAgC,KAAK;IAErC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,MAAM,QAAQ,GAAG,QAAQ,KAAK,QAAQ,CAAC;IAEvC,MAAM,iBAAiB,GAAG,QAAQ;QAChC,CAAC,CAAC;;4EAEsE;QACxE,CAAC,CAAC;;;uEAGiE,CAAC;IAEtE,OAAO;kFACyE,iBAAiB;;;;;;;;;;;iIAW8B,GAAG;;;;;;;;;qCAS/F,GAAG;EACtC,EAAE,CAAC,CAAC,CAAC,4CAA4C,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE;gCAC/B,GAAG;EACjC,oBAAoB,CAAC,CAAC,CAAC,iDAAiD,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE;;;;gBAIxE,CAAC,CAAC,EAAE,EAAE,iBAAiB,CAAC;;;;;yCAKC,CAAC,CAAC,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;uBAIpE,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;;;;;;;;+BAYN,CAAC,CAAC,EAAE,EAAE,0BAA0B,CAAC;;;;;;;;;;;;;;;qCAe3B,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;uBAGpC,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;wCAeG,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;iCAMlD,CAAC,CAAC,EAAE,EAAE,0BAA0B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA4C/B,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC;;;;;yCAKP,CAAC,CAAC,EAAE,EAAE,yBAAyB,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;IAcpG,oBAAoB,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE;;;;kCAIrB,CAAC,CAAC,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;;;;;;;;;;;yCAYrC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;;;;;;;;+BAS/E,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;WAEvD,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;EAMvC,QAAQ;QACN,CAAC,CAAC;;;;;;yCAMmC,CAAC,CAAC,EAAE,EAAE,0BAA0B,CAAC;;;CAGzE;QACG,CAAC,CAAC;;oCAE8B,CAAC,CAAC,EAAE,EAAE,0BAA0B,CAAC;;;;;;;;;;;;;oBAajD,CAAC,CAAC,EAAE,EAAE,iCAAiC,CAAC;oBACxC,CAAC,CAAC,EAAE,EAAE,iCAAiC,CAAC;;;;;;yCAMnB,CAAC,CAAC,EAAE,EAAE,0BAA0B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqC1E;;CAEC,CAAC;AACF,CAAC"}
@@ -14,7 +14,6 @@ export function generateBackgroundSyncCode(ctx) {
14
14
  * url: "/api/todos",
15
15
  * body: { title: "Grocery" },
16
16
  * tags: ["todos"],
17
- * tags: ["todos"],
18
17
  * });
19
18
  */
20
19
 
@@ -44,6 +43,10 @@ export async function syncWhenPossible(mutation${T(ts, "object")})${R(ts, "Promi
44
43
 
45
44
  /** Re-register a background sync if mutations are still pending. Called automatically after each sync cycle. */
46
45
  export async function retrySync()${R(ts, "Promise<void>")}{
46
+ // Re-register self-cleaning listener for next cycle
47
+ if (typeof window !== "undefined") {
48
+ window.addEventListener("mutation-sync-complete", retrySync, { once: true });
49
+ }
47
50
  if (!("serviceWorker" in navigator) || !("SyncManager" in window)) return;
48
51
  const count = await getPendingCount();
49
52
  if (count > 0) {
@@ -52,7 +55,7 @@ export async function retrySync()${R(ts, "Promise<void>")}{
52
55
  }
53
56
 
54
57
  if (typeof window !== "undefined") {
55
- window.addEventListener("mutation-sync-complete", retrySync);
58
+ window.addEventListener("mutation-sync-complete", retrySync, { once: true });
56
59
  }
57
60
  `;
58
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../src/runtime/background-sync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,0BAA0B,CAAC,GAAmB;IAC5D,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;;iEAMwD,GAAG;;;;;;;;;;;gFAWY,GAAG;;;;+BAIpD,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;iDAeJ,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;mCAMvD,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;CAWxD,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../src/runtime/background-sync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,0BAA0B,CAAC,GAAmB;IAC5D,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;;iEAMwD,GAAG;;;;;;;;;;gFAUY,GAAG;;;;+BAIpD,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;iDAeJ,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;mCAMvD,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;;;;;;CAexD,CAAC;AACF,CAAC"}
@@ -1,17 +1,30 @@
1
1
  import { T, R } from "./utils.js";
2
- export function generateCacheCode(ctx) {
2
+ function generateCascadingCode(cascading) {
3
+ if (!cascading || Object.keys(cascading).length === 0)
4
+ return "null";
5
+ return JSON.stringify(cascading);
6
+ }
7
+ export function generateCacheCode(ctx, cascading) {
3
8
  const { ext, ts } = ctx;
9
+ const cascadingCode = generateCascadingCode(cascading);
4
10
  return `/**
5
11
  * Swoff Cache Invalidation
6
- * Framework-agnostic cache tag invalidation.
12
+ * Framework-agnostic cache tag invalidation, URL-based invalidation,
13
+ * cascading dependency expansion, introspection, and glob matching.
7
14
  *
8
15
  * Usage:
9
- * import { invalidateByTag, invalidateByTags } from './swoff/cache.${ext}';
16
+ * import { invalidateByTag, invalidateByTags, invalidateUrl } from './swoff/cache/invalidate.${ext}';
10
17
  *
11
18
  * // After a mutation, invalidate related cache
12
19
  * await invalidateByTag("todos");
20
+ * await invalidateUrl("/api/todos/42");
13
21
  */
14
22
 
23
+ import { generateTags, generateTagsFromMethod } from "./tags.${ext}";
24
+
25
+ // Cascading invalidation map (tag → dependent tags)
26
+ const CASCADING${T(ts, "Record<string, string[]> | null")} = ${cascadingCode};
27
+
15
28
  /** Invalidate all cached responses tagged with the given tag. Sends INVALIDATE_TAG to the SW; the client-injector dispatches cache-invalidated on SW confirmation. */
16
29
  export async function invalidateByTag(tag${T(ts, "string")})${R(ts, "Promise<void>")}{
17
30
  if (!navigator.serviceWorker?.controller) return;
@@ -25,6 +38,81 @@ export async function invalidateByTag(tag${T(ts, "string")})${R(ts, "Promise<voi
25
38
  export async function invalidateByTags(tags${T(ts, "string[]")})${R(ts, "Promise<void>")}{
26
39
  await Promise.all(tags.map((tag) => invalidateByTag(tag)));
27
40
  }
41
+
42
+ /** Expand tags with their cascading dependencies, deduplicated. */
43
+ export function expandCascading(tags${T(ts, "string[]")})${R(ts, "string[]")}{
44
+ if (!CASCADING) return [...tags];
45
+ const result = new Set(tags);
46
+ for (const tag of tags) {
47
+ const deps = CASCADING[tag];
48
+ if (deps) {
49
+ for (const dep of deps) {
50
+ result.add(dep);
51
+ }
52
+ }
53
+ }
54
+ return [...result];
55
+ }
56
+
57
+ /** Invalidate cached responses related to a URL, including cascading dependencies. */
58
+ export async function invalidateUrl(url${T(ts, "string | URL")})${R(ts, "Promise<void>")}{
59
+ const tags = generateTags(url);
60
+ const allTags = CASCADING ? expandCascading(tags) : tags;
61
+ await invalidateByTags(allTags);
62
+ }
63
+
64
+ /** Invalidate cached responses tagged with method-prefixed tags, including cascading dependencies. */
65
+ export async function invalidateByMethod(method${T(ts, "string")}, url${T(ts, "string | URL")})${R(ts, "Promise<void>")}{
66
+ const tags = generateTagsFromMethod(method, url);
67
+ const allTags = CASCADING ? expandCascading(tags) : tags;
68
+ await invalidateByTags(allTags);
69
+ }
70
+
71
+ /** Invalidate all cached responses whose URL matches the given glob pattern. */
72
+ export async function invalidateMatching(glob${T(ts, "string")})${R(ts, "Promise<void>")}{
73
+ const controller = navigator.serviceWorker?.controller;
74
+ if (!controller) return;
75
+ controller.postMessage({
76
+ type: "INVALIDATE_MATCHING",
77
+ glob,
78
+ });
79
+ }
80
+
81
+ /** Introspect: get all URLs cached under a given tag. */
82
+ export async function getUrlsForTag(tag${T(ts, "string")})${R(ts, "Promise<{ url: string; actualUrl: string }[]>")}{
83
+ const controller = navigator.serviceWorker?.controller;
84
+ if (!controller) return [];
85
+ return new Promise((resolve) => {
86
+ const channel = new MessageChannel();
87
+ const timeout = setTimeout(() => { channel.port1.close(); resolve([]); }, 5000);
88
+ channel.port1.onmessage = (event) => {
89
+ clearTimeout(timeout);
90
+ resolve(event.data.urls || []);
91
+ };
92
+ controller.postMessage(
93
+ { type: "GET_URLS_FOR_TAG", tag },
94
+ [channel.port2],
95
+ );
96
+ });
97
+ }
98
+
99
+ /** Introspect: get all tags associated with a given URL. */
100
+ export async function getTagsForUrl(url${T(ts, "string")})${R(ts, "Promise<string[]>")}{
101
+ const controller = navigator.serviceWorker?.controller;
102
+ if (!controller) return [];
103
+ return new Promise((resolve) => {
104
+ const channel = new MessageChannel();
105
+ const timeout = setTimeout(() => { channel.port1.close(); resolve([]); }, 5000);
106
+ channel.port1.onmessage = (event) => {
107
+ clearTimeout(timeout);
108
+ resolve(event.data.tags || []);
109
+ };
110
+ controller.postMessage(
111
+ { type: "GET_TAGS_FOR_URL", url },
112
+ [channel.port2],
113
+ );
114
+ });
115
+ }
28
116
  `;
29
117
  }
30
118
  //# sourceMappingURL=cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/runtime/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,UAAU,iBAAiB,CAAC,GAAmB;IACnD,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,OAAO;;;;;wEAK+D,GAAG;;;;;;;2CAOhC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;6CASvC,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;CAGvF,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/runtime/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAElC,SAAS,qBAAqB,CAAC,SAAmC;IAChE,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACrE,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,GAAmB,EACnB,SAAmC;IAEnC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACxB,MAAM,aAAa,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAEvD,OAAO;;;;;;kGAMyF,GAAG;;;;;;;+DAOtC,GAAG;;;iBAGjD,CAAC,CAAC,EAAE,EAAE,iCAAiC,CAAC,MAAM,aAAa;;;2CAGjC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;6CASvC,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;sCAKlD,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC;;;;;;;;;;;;;;;yCAenC,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;iDAOvC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;+CAOxE,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;;;;;yCAU/C,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,+CAA+C,CAAC;;;;;;;;;;;;;;;;;;yCAkBzE,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,mBAAmB,CAAC;;;;;;;;;;;;;;;;CAgBrF,CAAC;AACF,CAAC"}