@swoff/cli 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +610 -24
- package/bin/swoff +8 -14
- package/dist/__tests__/assemble-sw.test.js +17 -3
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +9 -9
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +10 -9
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +65 -68
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +12 -14
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +41 -22
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +3 -3
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +11 -5
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/clean.js +5 -0
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +43 -13
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +7 -3
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +149 -32
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +6 -2
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +3 -1
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/validator.js +25 -16
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
- package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-state.js +8 -5
- package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-store.js +181 -41
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +28 -32
- package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
- package/dist/lib/generators/file-generators/background-sync.js +9 -5
- package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +8 -5
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +101 -0
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
- package/dist/lib/generators/file-generators/context.js +7 -6
- package/dist/lib/generators/file-generators/context.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +196 -41
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +23 -151
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +450 -0
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
- package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +65 -87
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
- package/dist/lib/generators/file-generators/mutation-reconcile.js.map +1 -0
- package/dist/lib/generators/file-generators/push.js +143 -0
- package/dist/lib/generators/file-generators/push.js.map +1 -0
- package/dist/lib/generators/file-generators/pwa-install.js +41 -9
- package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +88 -0
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
- package/dist/lib/generators/file-generators/reconcile.js +7 -12
- package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
- package/dist/lib/generators/file-generators/store.js +30 -20
- package/dist/lib/generators/file-generators/store.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-generator-build.js +38 -17
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +82 -111
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-push.js +67 -0
- package/dist/lib/generators/file-generators/sw-push.js.map +1 -0
- package/dist/lib/generators/file-generators/sw-template.js +3 -1
- package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +45 -22
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +27 -19
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +6 -2
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +36 -12
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +1 -1
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/default-template.js +1 -30
- package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +176 -90
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/install-handler.js +12 -10
- package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/tag-management.js +36 -5
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +14 -12
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +26 -9
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/utils/build-script.js +35 -0
- package/dist/lib/utils/build-script.js.map +1 -0
- package/package.json +3 -2
- package/templates/hooks/useAuth.jsx +30 -0
- package/templates/hooks/useAuth.tsx +30 -0
- package/templates/hooks/useBackgroundSync.jsx +31 -0
- package/templates/hooks/useBackgroundSync.tsx +31 -0
- package/templates/hooks/useCacheInvalidation.jsx +19 -0
- package/templates/hooks/useCacheInvalidation.tsx +19 -0
- package/templates/hooks/useCachedFetch.jsx +55 -0
- package/templates/hooks/useCachedFetch.tsx +69 -0
- package/templates/hooks/useMutationQueue.jsx +34 -0
- package/templates/hooks/useMutationQueue.tsx +34 -0
- package/templates/hooks/useNetworkStatus.jsx +19 -0
- package/templates/hooks/useNetworkStatus.tsx +19 -0
- package/templates/hooks/usePushSubscription.jsx +67 -0
- package/templates/hooks/usePushSubscription.tsx +67 -0
- package/templates/hooks/useSWUpdate.jsx +76 -0
- package/templates/hooks/useSWUpdate.tsx +76 -0
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates the SW fetch event handler with all caching strategies.
|
|
3
|
+
*
|
|
4
|
+
* Strategy dispatch tiers (highest to lowest priority):
|
|
5
|
+
* 1. X-SW-Strategy header — per-request override from fetchWithCache({ strategy })
|
|
6
|
+
* 2. URL pattern match — from features.serviceWorker.strategies in swoff.config.json
|
|
7
|
+
* 3. defaultStrategy — fallback from features.serviceWorker.defaultStrategy
|
|
8
|
+
*
|
|
9
|
+
* Cache strategy mode (features.serviceWorker.cacheStrategy):
|
|
10
|
+
* "all" — all GET/HEAD requests go through the strategy system (default)
|
|
11
|
+
* "explicit-only" — only requests with X-SW-Cache-Strategy header go through strategy;
|
|
12
|
+
* plain fetch() calls pass through the SW unmodified
|
|
13
|
+
*
|
|
14
|
+
* Request dispatch flow:
|
|
15
|
+
* navigation (SPA fallback) → precache → strategy (if applicable) → pass-through
|
|
3
16
|
*/
|
|
4
17
|
export function generateFetchHandler(swConfig, tagInvalidation) {
|
|
5
|
-
const { defaultStrategy, strategies, maxCacheEntries, maxCacheAge, navigationMode, spaEntry } = swConfig;
|
|
6
|
-
const
|
|
18
|
+
const { defaultStrategy, strategies, cacheStrategy = "all", maxCacheEntries, maxCacheAge, navigationPreload, navigationMode, spaEntry } = swConfig;
|
|
19
|
+
const hasPerRouteLimits = Object.values(strategies || {}).some((s) => typeof s === "object" && (s.maxCacheEntries || s.maxCacheAge));
|
|
20
|
+
const hasTrim = (maxCacheEntries ?? 0) > 0 || (maxCacheAge ?? 0) > 0 || hasPerRouteLimits;
|
|
7
21
|
const navMode = navigationMode ?? "spa";
|
|
8
22
|
const spaPath = spaEntry ?? "/index.html";
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
const staleVersionCode = tagInvalidation ? `
|
|
24
|
+
cleanStaleVersions();
|
|
25
|
+
if (staleVersions.has(request.url)) {
|
|
26
|
+
event.waitUntil(refreshCache(request).then(() => staleVersions.delete(request.url)));
|
|
27
|
+
}` : "";
|
|
28
|
+
const tagCode = tagInvalidation ? `
|
|
29
|
+
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
30
|
+
if (tagsHeader) {
|
|
31
|
+
const url = new URL(request.url).href;
|
|
32
|
+
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
33
|
+
await cacheTagUrl(url, tags);
|
|
14
34
|
}` : "";
|
|
15
|
-
const
|
|
16
|
-
const tagsHeader = event.request.headers.get("X-SW-Cache-Tags");
|
|
17
|
-
if (tagsHeader) {
|
|
18
|
-
const url = new URL(event.request.url).href;
|
|
19
|
-
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
20
|
-
await cacheTagUrl(url, tags);
|
|
21
|
-
}` : "";
|
|
22
|
-
const staleTagCode = tagInvalidation ? `
|
|
23
|
-
const tagsHeader = request.headers.get("X-SW-Cache-Tags");
|
|
24
|
-
if (tagsHeader) {
|
|
25
|
-
const url = new URL(request.url).href;
|
|
26
|
-
const tags = tagsHeader.split(",").map((t) => t.trim());
|
|
27
|
-
await cacheTagUrl(url, tags);
|
|
28
|
-
}` : "";
|
|
29
|
-
const trimCode = hasTrim ? ` await trimRuntimeCache(CACHE_NAME_RUNTIME);\n` : "";
|
|
35
|
+
const trimCode = hasTrim ? ` await trimRuntimeCache(CACHE_NAME_RUNTIME);\n` : "";
|
|
30
36
|
const trimFunction = hasTrim ? `
|
|
31
37
|
const MAX_CACHE_ENTRIES = ${maxCacheEntries ?? 0};
|
|
32
38
|
const MAX_CACHE_AGE = ${maxCacheAge ?? 0};
|
|
@@ -59,148 +65,228 @@ async function trimRuntimeCache(cacheName) {
|
|
|
59
65
|
}
|
|
60
66
|
` : "";
|
|
61
67
|
return `${trimFunction}
|
|
68
|
+
// --- Cache Helpers ---
|
|
69
|
+
|
|
62
70
|
async function fromPrecache(request) {
|
|
63
71
|
const cache = await caches.open(CACHE_NAME);
|
|
64
|
-
return cache.match(request);
|
|
72
|
+
return cache.match(new URL(request.url).pathname);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function fromRuntime(request) {
|
|
76
|
+
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
77
|
+
return cache.match(new URL(request.url).href);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function storeRuntime(request, response) {
|
|
81
|
+
const cache = await caches.open(CACHE_NAME_RUNTIME);
|
|
82
|
+
await cache.put(new URL(request.url).href, response.clone());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function cacheResponse(request, response) {
|
|
86
|
+
await storeRuntime(request, response);${tagCode}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function fromSpaFallback(request) {
|
|
90
|
+
if (request.mode === "navigate") {
|
|
91
|
+
const cache = await caches.open(CACHE_NAME);
|
|
92
|
+
return cache.match("${spaPath}");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Response Helpers ---
|
|
97
|
+
|
|
98
|
+
function markFromCache(response) {
|
|
99
|
+
const headers = new Headers(response.headers);
|
|
100
|
+
headers.set("X-SW-From-Cache", "true");
|
|
101
|
+
return new Response(response.body, {
|
|
102
|
+
status: response.status,
|
|
103
|
+
statusText: response.statusText,
|
|
104
|
+
headers,
|
|
105
|
+
});
|
|
65
106
|
}
|
|
66
107
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return request.method === "GET" || request.method === "HEAD";
|
|
108
|
+
// --- Strategy Selection ---
|
|
109
|
+
|
|
110
|
+
function resolveStrategyEntry(entry) {
|
|
111
|
+
return typeof entry === "string" ? { strategy: entry } : entry;
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
function determineCacheStrategy(request, customStrategies, defaultStrategy) {
|
|
115
|
+
const override = request.headers.get("X-SW-Strategy");
|
|
116
|
+
if (override) return { strategy: override };
|
|
75
117
|
const path = new URL(request.url).pathname;
|
|
76
|
-
for (const [pattern,
|
|
77
|
-
if (path.startsWith(pattern.replace("*", ""))) return
|
|
118
|
+
for (const [pattern, entry] of Object.entries(customStrategies)) {
|
|
119
|
+
if (path.startsWith(pattern.replace("*", ""))) return resolveStrategyEntry(entry);
|
|
120
|
+
}
|
|
121
|
+
return { strategy: defaultStrategy };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function applyStrategy(event, request, config) {
|
|
125
|
+
const { strategy, maxCacheEntries, maxCacheAge } = config;
|
|
126
|
+
if (strategy === "stale-while-revalidate") {
|
|
127
|
+
event.respondWith(staleWhileRevalidate(event, request, maxCacheEntries, maxCacheAge));
|
|
128
|
+
} else if (strategy === "network-first") {
|
|
129
|
+
event.respondWith(networkFirst(event, request, maxCacheEntries, maxCacheAge));
|
|
130
|
+
} else if (strategy === "cache-only") {
|
|
131
|
+
event.respondWith(cacheOnly(event, request, maxCacheEntries, maxCacheAge));
|
|
132
|
+
} else if (strategy === "network-only") {
|
|
133
|
+
event.respondWith(networkOnly(event, request));
|
|
134
|
+
} else {
|
|
135
|
+
event.respondWith(cacheFirst(event, request, maxCacheEntries, maxCacheAge));
|
|
78
136
|
}
|
|
79
|
-
return defaultStrategy;
|
|
80
137
|
}
|
|
81
138
|
|
|
82
139
|
self.addEventListener("fetch", (event) => {
|
|
83
|
-
|
|
140
|
+
const { request } = event;
|
|
141
|
+
if (request.method !== "GET" && request.method !== "HEAD") return;
|
|
142
|
+
${cacheStrategy === "explicit-only" ? `if (!request.headers.get("X-SW-Cache-Strategy")) return;` : ""}
|
|
143
|
+
applyStrategy(event, request, determineCacheStrategy(event.request, ${JSON.stringify(strategies)}, "${defaultStrategy}"));
|
|
144
|
+
});
|
|
84
145
|
|
|
85
|
-
|
|
146
|
+
// --- Strategies ---
|
|
86
147
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
148
|
+
${navigationPreload ? `
|
|
149
|
+
async function fetchWithPreload(event, request) {
|
|
150
|
+
try {
|
|
151
|
+
const preload = await event.preloadResponse;
|
|
152
|
+
if (preload) return preload;
|
|
153
|
+
} catch {}
|
|
154
|
+
return fetch(request);
|
|
155
|
+
}
|
|
156
|
+
` : ""}const _fetch = ${navigationPreload ? "fetchWithPreload" : `(event, request) => fetch(request)`};
|
|
91
157
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
158
|
+
const GLOBAL_MAX_ENTRIES = ${maxCacheEntries ?? 0};
|
|
159
|
+
const GLOBAL_MAX_AGE = ${maxCacheAge ?? 0};
|
|
96
160
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
161
|
+
${hasTrim ? `
|
|
162
|
+
async function trimRuntimeCache(cacheName, maxEntries, maxAge) {
|
|
163
|
+
const _maxEntries = maxEntries ?? GLOBAL_MAX_ENTRIES;
|
|
164
|
+
const _maxAge = maxAge ?? GLOBAL_MAX_AGE;
|
|
165
|
+
const cache = await caches.open(cacheName);
|
|
166
|
+
|
|
167
|
+
if (_maxEntries > 0) {
|
|
168
|
+
const keys = await cache.keys();
|
|
169
|
+
if (keys.length >= _maxEntries) {
|
|
170
|
+
const toDelete = keys.slice(0, keys.length - _maxEntries + 1);
|
|
171
|
+
await Promise.all(toDelete.map((key) => cache.delete(key)));
|
|
172
|
+
}
|
|
100
173
|
}
|
|
101
174
|
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
175
|
+
if (_maxAge > 0) {
|
|
176
|
+
const keys = await cache.keys();
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
for (const request of keys) {
|
|
179
|
+
const response = await cache.match(request);
|
|
180
|
+
const dateHeader = response?.headers.get("date");
|
|
181
|
+
if (dateHeader) {
|
|
182
|
+
const age = now - new Date(dateHeader).getTime();
|
|
183
|
+
if (age > _maxAge) {
|
|
184
|
+
await cache.delete(request);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
105
188
|
}
|
|
189
|
+
}
|
|
190
|
+
` : ""}function _trim(cacheName, maxEntries, maxAge) {
|
|
191
|
+
${hasTrim ? ` trimRuntimeCache(cacheName, maxEntries, maxAge);` : ""}
|
|
192
|
+
}
|
|
106
193
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
194
|
+
async function cacheFirst(event, request, maxEntries, maxAge) {
|
|
195
|
+
const cached = await fromRuntime(request);
|
|
196
|
+
if (cached) {${staleVersionCode}
|
|
197
|
+
return markFromCache(cached);
|
|
198
|
+
}
|
|
110
199
|
|
|
111
|
-
|
|
112
|
-
|
|
200
|
+
const precached = await fromPrecache(request);
|
|
201
|
+
if (precached) return markFromCache(precached);
|
|
113
202
|
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
203
|
+
const fallback = await fromSpaFallback(request);
|
|
204
|
+
if (fallback) return fallback;
|
|
116
205
|
|
|
117
|
-
const
|
|
118
|
-
if (precached) return precached;
|
|
119
|
-
${navCode}
|
|
120
|
-
const response = await fetch(request);
|
|
206
|
+
const response = await _fetch(event, request);
|
|
121
207
|
if (response.ok) {
|
|
122
|
-
const cloned = response.clone();
|
|
123
208
|
event.waitUntil(
|
|
124
209
|
(async () => {
|
|
125
|
-
await
|
|
126
|
-
|
|
210
|
+
await cacheResponse(request, response);
|
|
211
|
+
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
212
|
+
})(),
|
|
127
213
|
);
|
|
128
214
|
}
|
|
129
215
|
return response;
|
|
130
216
|
}
|
|
131
217
|
|
|
132
|
-
async function networkFirst(event, request) {
|
|
133
|
-
const runtimeCache = await caches.open(CACHE_NAME_RUNTIME);
|
|
134
|
-
|
|
218
|
+
async function networkFirst(event, request, maxEntries, maxAge) {
|
|
135
219
|
try {
|
|
136
|
-
const response = await
|
|
220
|
+
const response = await _fetch(event, request);
|
|
137
221
|
if (response.ok) {
|
|
138
|
-
const cloned = response.clone();
|
|
139
222
|
event.waitUntil(
|
|
140
223
|
(async () => {
|
|
141
|
-
await
|
|
142
|
-
|
|
224
|
+
await cacheResponse(request, response);
|
|
225
|
+
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
226
|
+
})(),
|
|
143
227
|
);
|
|
144
228
|
}
|
|
145
229
|
return response;
|
|
146
230
|
} catch {
|
|
147
|
-
const cached = await
|
|
231
|
+
const cached = await fromRuntime(request);
|
|
148
232
|
if (cached) return cached;
|
|
149
233
|
|
|
150
234
|
const precached = await fromPrecache(request);
|
|
151
235
|
if (precached) return precached;
|
|
152
|
-
|
|
236
|
+
|
|
237
|
+
const fallback = await fromSpaFallback(request);
|
|
238
|
+
if (fallback) return fallback;
|
|
239
|
+
|
|
153
240
|
throw new Error("Request failed and no cached response available");
|
|
154
241
|
}
|
|
155
242
|
}
|
|
156
243
|
|
|
157
|
-
async function staleWhileRevalidate(event, request) {
|
|
158
|
-
const
|
|
159
|
-
const cached = await runtimeCache.match(request);
|
|
160
|
-
|
|
244
|
+
async function staleWhileRevalidate(event, request, maxEntries, maxAge) {
|
|
245
|
+
const cached = await fromRuntime(request);
|
|
161
246
|
if (cached) {
|
|
162
|
-
event.waitUntil(refreshCache(
|
|
163
|
-
return cached;
|
|
247
|
+
event.waitUntil(refreshCache(request));
|
|
248
|
+
return markFromCache(cached);
|
|
164
249
|
}
|
|
165
250
|
|
|
166
251
|
const precached = await fromPrecache(request);
|
|
167
252
|
if (precached) {
|
|
168
|
-
event.waitUntil(refreshCache(
|
|
169
|
-
return precached;
|
|
253
|
+
event.waitUntil(refreshCache(request));
|
|
254
|
+
return markFromCache(precached);
|
|
170
255
|
}
|
|
171
256
|
|
|
172
|
-
const response = await
|
|
257
|
+
const response = await _fetch(event, request);
|
|
173
258
|
if (response.ok) {
|
|
174
|
-
await
|
|
175
|
-
|
|
259
|
+
await cacheResponse(request, response);
|
|
260
|
+
_trim(CACHE_NAME_RUNTIME, maxEntries, maxAge);
|
|
261
|
+
}
|
|
176
262
|
return response;
|
|
177
263
|
}
|
|
178
264
|
|
|
179
|
-
async function refreshCache(
|
|
265
|
+
async function refreshCache(request) {
|
|
180
266
|
try {
|
|
181
267
|
const response = await fetch(request);
|
|
182
268
|
if (response.ok) {
|
|
183
|
-
await
|
|
269
|
+
await storeRuntime(request, response);
|
|
184
270
|
}
|
|
185
271
|
} catch {
|
|
186
272
|
// Background refresh failed - stale cache remains usable
|
|
187
273
|
}
|
|
188
274
|
}
|
|
189
275
|
|
|
190
|
-
async function cacheOnly(event, request) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
276
|
+
async function cacheOnly(event, request, maxEntries, maxAge) {
|
|
277
|
+
const cached = await fromRuntime(request);
|
|
278
|
+
if (cached) {${staleVersionCode}
|
|
279
|
+
return markFromCache(cached);
|
|
280
|
+
}
|
|
195
281
|
|
|
196
282
|
const precached = await fromPrecache(request);
|
|
197
|
-
if (precached) return precached;
|
|
283
|
+
if (precached) return markFromCache(precached);
|
|
198
284
|
|
|
199
285
|
return new Response("Not in cache", { status: 404 });
|
|
200
286
|
}
|
|
201
287
|
|
|
202
288
|
async function networkOnly(event, request) {
|
|
203
|
-
return
|
|
289
|
+
return _fetch(event, request);
|
|
204
290
|
}`;
|
|
205
291
|
}
|
|
206
292
|
//# sourceMappingURL=fetch-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/fetch-handler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fetch-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/fetch-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,UAAU,oBAAoB,CAClC,QAAkT,EAClT,eAAwB;IAExB,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,GAAG,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAEnJ,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAC5D,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAE,CAAkC,CAAC,eAAe,IAAK,CAA8B,CAAC,WAAW,CAAC,CACrI,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,eAAe,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC;IAC1F,MAAM,OAAO,GAAG,cAAc,IAAI,KAAK,CAAC;IACxC,MAAM,OAAO,GAAG,QAAQ,IAAI,aAAa,CAAC;IAE1C,MAAM,gBAAgB,GAAG,eAAe,CAAC,CAAC,CAAC;;;;MAIvC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEV,MAAM,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC;;;;;;IAMhC,CAAC,CAAC,CAAC,EAAE,CAAC;IAER,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,iDAAiD,CAAC,CAAC,CAAC,EAAE,CAAC;IAElF,MAAM,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC;4BACL,eAAe,IAAI,CAAC;wBACxB,WAAW,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BvC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,GAAG,YAAY;;;;;;;;;;;;;;;;;;;0CAmBkB,OAAO;;;;;;0BAMvB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkD7B,aAAa,KAAK,eAAe,CAAC,CAAC,CAAC,0DAA0D,CAAC,CAAC,CAAC,EAAE;wEAC/B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,eAAe;;;;;EAKrH,iBAAiB,CAAC,CAAC,CAAC;;;;;;;;CAQrB,CAAC,CAAC,CAAC,EAAE,kBAAkB,iBAAiB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,oCAAoC;;6BAExE,eAAe,IAAI,CAAC;yBACxB,WAAW,IAAI,CAAC;;EAEvC,OAAO,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BX,CAAC,CAAC,CAAC,EAAE;EACJ,OAAO,CAAC,CAAC,CAAC,oDAAoD,CAAC,CAAC,CAAC,EAAE;;;;;iBAKpD,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkFhB,gBAAgB;;;;;;;;;;;;EAY/B,CAAC;AACH,CAAC"}
|
|
@@ -8,24 +8,26 @@ self.addEventListener("install", (event) => {
|
|
|
8
8
|
(async () => {
|
|
9
9
|
const cache = await caches.open(CACHE_NAME);
|
|
10
10
|
let downloaded = 0;
|
|
11
|
+
let attempted = 0;
|
|
11
12
|
for (const asset of ASSETS_TO_CACHE) {
|
|
13
|
+
attempted++;
|
|
12
14
|
try {
|
|
13
15
|
const request = new Request(asset.url, asset.options);
|
|
14
16
|
await cache.add(request);
|
|
15
17
|
downloaded++;
|
|
16
|
-
const percent = Math.round((downloaded / ASSETS_TO_CACHE.length) * 100);
|
|
17
|
-
const clients = await self.clients.matchAll({ includeUncontrolled: true });
|
|
18
|
-
clients.forEach((client) => {
|
|
19
|
-
client.postMessage({
|
|
20
|
-
type: "SW_PROGRESS",
|
|
21
|
-
percent,
|
|
22
|
-
downloaded,
|
|
23
|
-
total: ASSETS_TO_CACHE.length,
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
18
|
} catch (err) {
|
|
27
19
|
console.error(\`Failed to cache \${asset.url}:\`, err);
|
|
28
20
|
}
|
|
21
|
+
const percent = Math.round((attempted / ASSETS_TO_CACHE.length) * 100);
|
|
22
|
+
const clients = await self.clients.matchAll({ includeUncontrolled: true });
|
|
23
|
+
clients.forEach((client) => {
|
|
24
|
+
client.postMessage({
|
|
25
|
+
type: "SW_PROGRESS",
|
|
26
|
+
percent,
|
|
27
|
+
downloaded,
|
|
28
|
+
total: ASSETS_TO_CACHE.length,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
29
31
|
}
|
|
30
32
|
if (AUTO_SKIP_WAITING) self.skipWaiting();
|
|
31
33
|
})(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/install-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,sBAAsB;IACpC,OAAO
|
|
1
|
+
{"version":3,"file":"install-handler.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/install-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8BL,CAAC;AACL,CAAC"}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates IndexedDB-based tag invalidation logic for the SW.
|
|
3
|
+
* After invalidation, tries to re-fetch URLs in the background.
|
|
4
|
+
* On failure, keeps the old entry as stale-while-revalidate fallback.
|
|
3
5
|
*/
|
|
4
6
|
export function generateTagManagement() {
|
|
5
7
|
return `
|
|
8
|
+
const staleVersions = new Map();
|
|
9
|
+
const STALE_VERSIONS_MAX = 100;
|
|
10
|
+
const STALE_VERSION_TTL = 30 * 60 * 1000;
|
|
6
11
|
const TAG_DB_NAME = "swoff-cache-tags";
|
|
7
12
|
const TAG_STORE_NAME = "tags";
|
|
8
13
|
|
|
14
|
+
function cleanStaleVersions() {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
for (const [url, ts] of staleVersions) {
|
|
17
|
+
if (staleVersions.size > STALE_VERSIONS_MAX || now - ts > STALE_VERSION_TTL) {
|
|
18
|
+
staleVersions.delete(url);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
function openTagDB() {
|
|
10
24
|
return new Promise((resolve, reject) => {
|
|
11
25
|
const request = indexedDB.open(TAG_DB_NAME, 1);
|
|
@@ -32,6 +46,22 @@ async function cacheTagUrl(url, tags) {
|
|
|
32
46
|
});
|
|
33
47
|
}
|
|
34
48
|
|
|
49
|
+
async function refetchAfterInvalidation(url) {
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(url);
|
|
52
|
+
if (response.ok) {
|
|
53
|
+
const runtimeCache = await caches.open(CACHE_NAME_RUNTIME);
|
|
54
|
+
await runtimeCache.put(url, response);
|
|
55
|
+
staleVersions.delete(url);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// fetch failed (network error, auth required, etc.)
|
|
60
|
+
}
|
|
61
|
+
staleVersions.set(url, Date.now());
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
35
65
|
async function invalidateByTag(tag) {
|
|
36
66
|
const db = await openTagDB();
|
|
37
67
|
const tx = db.transaction(TAG_STORE_NAME, "readonly");
|
|
@@ -44,11 +74,7 @@ async function invalidateByTag(tag) {
|
|
|
44
74
|
});
|
|
45
75
|
await db.close();
|
|
46
76
|
|
|
47
|
-
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
-
await runtimeCache.delete(entry.url);
|
|
50
|
-
}
|
|
51
|
-
|
|
77
|
+
// Remove from tag index
|
|
52
78
|
const writeDb = await openTagDB();
|
|
53
79
|
const writeTx = writeDb.transaction(TAG_STORE_NAME, "readwrite");
|
|
54
80
|
const writeStore = writeTx.objectStore(TAG_STORE_NAME);
|
|
@@ -60,6 +86,11 @@ async function invalidateByTag(tag) {
|
|
|
60
86
|
writeTx.onerror = () => reject(writeTx.error);
|
|
61
87
|
});
|
|
62
88
|
|
|
89
|
+
// Background refetch each deleted URL; keep stale on failure
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
refetchAfterInvalidation(entry.url);
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
const clients = await self.clients.matchAll();
|
|
64
95
|
clients.forEach((client) => {
|
|
65
96
|
client.postMessage({ type: "TAG_INVALIDATED", tag });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tag-management.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/tag-management.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tag-management.js","sourceRoot":"","sources":["../../../../src/lib/generators/sw-sections/tag-management.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2FP,CAAC;AACH,CAAC"}
|
|
@@ -16,34 +16,33 @@ import { fileURLToPath } from "url";
|
|
|
16
16
|
import { loadConfig } from "../config/loader.js";
|
|
17
17
|
import { generateSwTemplate } from "./file-generators/sw-template.js";
|
|
18
18
|
import { generateSwInjector } from "./file-generators/sw-injector.js";
|
|
19
|
+
import { generateClientInjector } from "./file-generators/client-injector.js";
|
|
19
20
|
import { generateFetchWrapper } from "./file-generators/fetch-wrapper.js";
|
|
20
21
|
import { generateCache } from "./file-generators/cache.js";
|
|
21
22
|
import { generateMutationQueue } from "./file-generators/mutation-queue.js";
|
|
22
|
-
import { generateStore } from "./file-generators/store.js";
|
|
23
|
-
import { generateReconcile } from "./file-generators/reconcile.js";
|
|
24
23
|
import { generateBackgroundSync } from "./file-generators/background-sync.js";
|
|
25
24
|
import { generatePwaInstall } from "./file-generators/pwa-install.js";
|
|
26
25
|
import { generateManifest } from "./file-generators/manifest.js";
|
|
27
26
|
import { generateInvalidationTags } from "./file-generators/invalidation-tags.js";
|
|
27
|
+
import { generatePush } from "./file-generators/push.js";
|
|
28
28
|
import { generateAuthStore } from "./file-generators/auth-store.js";
|
|
29
|
-
import { generateAuthFetch } from "./file-generators/auth-fetch.js";
|
|
30
29
|
import { generateAuthUser } from "./file-generators/auth-user.js";
|
|
31
30
|
import { generateAuthState } from "./file-generators/auth-state.js";
|
|
32
31
|
import { generateSwGeneratorBuild } from "./file-generators/sw-generator-build.js";
|
|
33
32
|
import { generateTypeDefinitions } from "./file-generators/type-definitions.js";
|
|
34
33
|
import { generateHooks } from "./file-generators/generate-hooks.js";
|
|
34
|
+
import { generateGuide } from "./file-generators/guide-generator.js";
|
|
35
|
+
import { generateReadme } from "./file-generators/quick-readme.js";
|
|
35
36
|
export function generateFiles(ctx, onFile) {
|
|
36
37
|
const steps = [
|
|
37
38
|
{ name: "sw-template", gen: () => generateSwTemplate(ctx), enabled: true },
|
|
38
|
-
{ name: "sw-injector", gen: () => generateSwInjector(ctx), enabled:
|
|
39
|
+
{ name: "sw-injector", gen: () => generateSwInjector(ctx), enabled: true },
|
|
40
|
+
{ name: "client-injector", gen: () => generateClientInjector(ctx), enabled: true },
|
|
39
41
|
{ name: "fetch-wrapper", gen: () => generateFetchWrapper(ctx), enabled: true },
|
|
40
42
|
{ name: "cache", gen: () => generateCache(ctx), enabled: ctx.config.features.tagInvalidation },
|
|
41
|
-
{ name: "store", gen: () => generateStore(ctx), enabled: ctx.config.features.mutationQueue },
|
|
42
|
-
{ name: "reconcile", gen: () => generateReconcile(ctx), enabled: ctx.config.features.mutationQueue },
|
|
43
43
|
{ name: "mutation-queue", gen: () => generateMutationQueue(ctx), enabled: ctx.config.features.mutationQueue },
|
|
44
44
|
{ name: "background-sync", gen: () => generateBackgroundSync(ctx), enabled: ctx.config.features.backgroundSync },
|
|
45
45
|
{ name: "auth-store", gen: () => generateAuthStore(ctx), enabled: ctx.config.features.auth.enabled },
|
|
46
|
-
{ name: "auth-fetch", gen: () => generateAuthFetch(ctx), enabled: ctx.config.features.auth.enabled },
|
|
47
46
|
{ name: "auth-user", gen: () => generateAuthUser(ctx), enabled: ctx.config.features.auth.enabled },
|
|
48
47
|
{ name: "auth-state", gen: () => generateAuthState(ctx), enabled: ctx.config.features.auth.enabled },
|
|
49
48
|
{ name: "sw-generator", gen: () => generateSwGeneratorBuild(ctx), enabled: true },
|
|
@@ -51,7 +50,10 @@ export function generateFiles(ctx, onFile) {
|
|
|
51
50
|
{ name: "pwa-install", gen: () => generatePwaInstall(ctx), enabled: ctx.config.features.pwa.enabled },
|
|
52
51
|
{ name: "manifest.json", gen: () => generateManifest(ctx), enabled: ctx.config.features.pwa.enabled },
|
|
53
52
|
{ name: "invalidation-tags", gen: () => generateInvalidationTags(ctx), enabled: ctx.config.features.tagInvalidation },
|
|
53
|
+
{ name: "push", gen: () => generatePush(ctx), enabled: ctx.config.features.pushNotifications?.enabled ?? false },
|
|
54
54
|
{ name: "hooks", gen: () => generateHooks(ctx), enabled: ctx.config.framework === "react" },
|
|
55
|
+
{ name: "GUIDE.md", gen: () => generateGuide(ctx), enabled: true },
|
|
56
|
+
{ name: "README.md", gen: () => generateReadme(ctx), enabled: true },
|
|
55
57
|
];
|
|
56
58
|
for (const step of steps) {
|
|
57
59
|
if (!step.enabled)
|
|
@@ -62,12 +64,12 @@ export function generateFiles(ctx, onFile) {
|
|
|
62
64
|
return ctx.generatedFiles;
|
|
63
65
|
}
|
|
64
66
|
// --- CLI entry point ---
|
|
65
|
-
const args = process.argv.slice(2);
|
|
66
|
-
function getArg(name) {
|
|
67
|
-
const idx = args.findIndex((arg) => arg === `--${name}`);
|
|
68
|
-
return idx !== -1 ? args[idx + 1] : null;
|
|
69
|
-
}
|
|
70
67
|
if (fileURLToPath(import.meta.url) === fileURLToPath(new URL(process.argv[1], "file:"))) {
|
|
68
|
+
const args = process.argv.slice(2);
|
|
69
|
+
const getArg = (name) => {
|
|
70
|
+
const idx = args.indexOf(`--${name}`);
|
|
71
|
+
return idx !== -1 ? args[idx + 1] : null;
|
|
72
|
+
};
|
|
71
73
|
const projectRoot = getArg("project-root") || process.cwd();
|
|
72
74
|
const language = getArg("language") || "ts";
|
|
73
75
|
const configPath = getArg("config-path") || join(projectRoot, "swoff.config.json");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swoff-files-generator.js","sourceRoot":"","sources":["../../../src/lib/generators/swoff-files-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"swoff-files-generator.js","sourceRoot":"","sources":["../../../src/lib/generators/swoff-files-generator.ts"],"names":[],"mappings":"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"}
|