@swoff/cli 0.3.5 → 0.3.6
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 +327 -25
- package/dist/__tests__/assemble-sw.test.js +15 -1
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +9 -8
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +6 -6
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +67 -59
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +12 -13
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +41 -21
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +1 -1
- 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 +7 -1
- 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 +12 -9
- 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 +135 -32
- package/dist/lib/commands/info.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 +71 -40
- package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
- package/dist/lib/generators/file-generators/auth-user.js +27 -31
- 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 +7 -2
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +113 -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 +170 -40
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +61 -3
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/guide-generator.js +333 -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 +34 -84
- 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/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 +78 -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 +37 -16
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +81 -110
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-template.js +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +33 -10
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +19 -10
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +32 -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/fetch-handler.js +25 -10
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/tag-management.js +25 -5
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +7 -5
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +21 -6
- 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 +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates client-injector.{js|ts} - orchestrator that wires feature modules together.
|
|
3
|
+
*/
|
|
4
|
+
import { writeFile } from "./context.js";
|
|
5
|
+
export function generateClientInjector(ctx) {
|
|
6
|
+
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const pwaEnabled = ctx.config.features.pwa.enabled;
|
|
9
|
+
const mutationQueueEnabled = ctx.config.features.mutationQueue;
|
|
10
|
+
const crossTabSync = ctx.config.features.crossTabSync;
|
|
11
|
+
const clientRegistration = ctx.config.features.clientRegistration;
|
|
12
|
+
const pwaImport = pwaEnabled
|
|
13
|
+
? `import { setupPwaInstall } from "./pwa/install.${ext}";
|
|
14
|
+
`
|
|
15
|
+
: "";
|
|
16
|
+
const pwaCall = pwaEnabled ? `setupPwaInstall();\n` : "";
|
|
17
|
+
const mutationImport = mutationQueueEnabled
|
|
18
|
+
? `import { processMutationQueue } from "./mutation-queue.${ext}";
|
|
19
|
+
`
|
|
20
|
+
: "";
|
|
21
|
+
const mutationOnlineListener = mutationQueueEnabled
|
|
22
|
+
? `
|
|
23
|
+
// --- Mutation Queue Online Listener ---
|
|
24
|
+
window.addEventListener("online", processMutationQueue);
|
|
25
|
+
`
|
|
26
|
+
: "";
|
|
27
|
+
const crossTabHandler = crossTabSync
|
|
28
|
+
? `
|
|
29
|
+
if (event.data.type === "TAG_INVALIDATED" && event.data.tag) {
|
|
30
|
+
window.dispatchEvent(
|
|
31
|
+
new CustomEvent("cache-invalidated", {
|
|
32
|
+
detail: { tags: [event.data.tag] },
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
`
|
|
37
|
+
: "";
|
|
38
|
+
const swImport = clientRegistration
|
|
39
|
+
? `import {
|
|
40
|
+
initServiceWorker as swInit,
|
|
41
|
+
handleUpdateApproved,
|
|
42
|
+
skipWaiting,
|
|
43
|
+
} from "./sw/injector.${ext}";
|
|
44
|
+
`
|
|
45
|
+
: "";
|
|
46
|
+
const swExports = clientRegistration
|
|
47
|
+
? `
|
|
48
|
+
export { handleUpdateApproved, skipWaiting };
|
|
49
|
+
`
|
|
50
|
+
: "";
|
|
51
|
+
const code = `/**
|
|
52
|
+
* Swoff Client Injector
|
|
53
|
+
* Orchestrates SW registration, PWA install, and cross-tab sync.
|
|
54
|
+
* Single entry point for all Swoff client-side features.
|
|
55
|
+
*
|
|
56
|
+
* Usage:
|
|
57
|
+
* import { initServiceWorker } from './swoff/client-injector.${ext}';
|
|
58
|
+
* initServiceWorker();
|
|
59
|
+
*
|
|
60
|
+
* Window events (dispatched by this module):
|
|
61
|
+
* sw-progress - Download progress (detail: { percent, downloaded, total })
|
|
62
|
+
* sw-ready - SW active and controlling page
|
|
63
|
+
* sw-error - SW registration failed
|
|
64
|
+
* cache-invalidated - Cache entries cleared (detail: { tags })
|
|
65
|
+
* mutation-sync-complete - Queued mutations synced (detail: { succeeded, failed })
|
|
66
|
+
* mutation-queue-changed - Queue modified
|
|
67
|
+
*
|
|
68
|
+
* Window events (dispatched by feature modules):
|
|
69
|
+
* pwa-installable - PWA can be installed (detail: { isInstallable: true })
|
|
70
|
+
* pwa-installed - User accepted install (detail: { outcome: 'accepted' })
|
|
71
|
+
* sw-auth-unauthorized - 401 response received
|
|
72
|
+
* sw-auth-state-change - Login or logout (detail: { authenticated: boolean })
|
|
73
|
+
* mutation-rollback - Mutation exhausted retries
|
|
74
|
+
* sw-update-available - New version ready (detail: { version })
|
|
75
|
+
* sw-version-detected - Version info available
|
|
76
|
+
*/
|
|
77
|
+
${pwaImport}${mutationImport}${swImport}
|
|
78
|
+
${pwaCall}${mutationOnlineListener}
|
|
79
|
+
// --- SW Message Listener ---
|
|
80
|
+
if (typeof window !== "undefined" && "serviceWorker" in navigator) {
|
|
81
|
+
navigator.serviceWorker.addEventListener("message", (event) => {
|
|
82
|
+
if (event.data.type === "SW_PROGRESS") {
|
|
83
|
+
const { percent, downloaded, total } = event.data;
|
|
84
|
+
window.dispatchEvent(
|
|
85
|
+
new CustomEvent("sw-progress", {
|
|
86
|
+
detail: { percent, downloaded, total },
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (event.data.type === "BACKGROUND_SYNC_COMPLETE") {
|
|
91
|
+
const { succeeded, failed, tags } = event.data.detail;
|
|
92
|
+
window.dispatchEvent(
|
|
93
|
+
new CustomEvent("mutation-sync-complete", {
|
|
94
|
+
detail: { succeeded, failed },
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
if (tags && tags.length > 0) {
|
|
98
|
+
window.dispatchEvent(
|
|
99
|
+
new CustomEvent("cache-invalidated", { detail: { tags } })
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
|
|
103
|
+
}${crossTabHandler} });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Initialize SW registration and all client-side features (PWA install, mutation queue, cross-tab sync). Call once at app startup. */
|
|
107
|
+
export async function initServiceWorker()${ts ? ": Promise<void>" : " "}{
|
|
108
|
+
${clientRegistration ? " await swInit();" : " // No SW registration configured"}
|
|
109
|
+
}${swExports}
|
|
110
|
+
`;
|
|
111
|
+
writeFile(ctx, `client-injector.${ext}`, code);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=client-injector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-injector.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/client-injector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,sBAAsB,CAAC,GAAqB;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;IACnD,MAAM,oBAAoB,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC;IAC/D,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;IACtD,MAAM,kBAAkB,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAElE,MAAM,SAAS,GAAG,UAAU;QAC1B,CAAC,CAAC,kDAAkD,GAAG;CAC1D;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;IAEzD,MAAM,cAAc,GAAG,oBAAoB;QACzC,CAAC,CAAC,0DAA0D,GAAG;CAClE;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,sBAAsB,GAAG,oBAAoB;QACjD,CAAC,CAAC;;;CAGL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,eAAe,GAAG,YAAY;QAClC,CAAC,CAAC;;;;;;;;CAQL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,QAAQ,GAAG,kBAAkB;QACjC,CAAC,CAAC;;;;wBAIkB,GAAG;CAC1B;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,SAAS,GAAG,kBAAkB;QAClC,CAAC,CAAC;;CAEL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,IAAI,GAAG;;;;;;kEAMmD,GAAG;;;;;;;;;;;;;;;;;;;;EAoBnE,SAAS,GAAG,cAAc,GAAG,QAAQ;EACrC,OAAO,GAAG,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;OAyB3B,eAAe;;;;2CAIqB,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG;EACrE,kBAAkB,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,oCAAoC;GAC9E,SAAS;CACX,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
* Contains config, paths, and the generated files tracker.
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
export function
|
|
8
|
-
if (!existsSync(
|
|
9
|
-
mkdirSync(
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
export function ensureDir(dir) {
|
|
8
|
+
if (!existsSync(dir))
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
10
|
}
|
|
11
11
|
export function writeFile(ctx, filename, code) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const fullPath = join(ctx.swoffDir, filename);
|
|
13
|
+
ensureDir(dirname(fullPath));
|
|
14
|
+
writeFileSync(fullPath, code);
|
|
14
15
|
ctx.generatedFiles.push(`swoff/${filename}`);
|
|
15
16
|
}
|
|
16
17
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAYrC,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAqB,EAAE,QAAgB,EAAE,IAAY;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9C,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7B,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -1,79 +1,209 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generates fetch-wrapper.js
|
|
2
|
+
* Generates fetch-wrapper.ts/js — unified fetch with caching, auth, offline queue, and auto-invalidation.
|
|
3
3
|
*/
|
|
4
4
|
import { writeFile } from "./context.js";
|
|
5
5
|
export function generateFetchWrapper(ctx) {
|
|
6
6
|
const ext = ctx.ext;
|
|
7
|
+
const ts = ext === "ts";
|
|
8
|
+
const T = (type) => (ts ? `: ${type}` : "");
|
|
9
|
+
const R = (type) => (ts ? `: ${type} ` : " ");
|
|
10
|
+
const G = (type) => (ts ? `<${type}>` : "");
|
|
11
|
+
const tagInvalidation = ctx.config.features.tagInvalidation;
|
|
12
|
+
const authEnabled = ctx.config.features.auth.enabled;
|
|
13
|
+
const mutationQueue = ctx.config.features.mutationQueue;
|
|
14
|
+
const importLines = [
|
|
15
|
+
tagInvalidation
|
|
16
|
+
? `import { generateTags } from "./invalidation-tags.${ext}";`
|
|
17
|
+
: "",
|
|
18
|
+
tagInvalidation ? `import { invalidateByTags } from "./cache.${ext}";` : "",
|
|
19
|
+
authEnabled
|
|
20
|
+
? `import { getAuth, clearAuth } from "./auth/store.${ext}";`
|
|
21
|
+
: "",
|
|
22
|
+
mutationQueue
|
|
23
|
+
? `import { queueMutation } from "./mutation-queue.${ext}";`
|
|
24
|
+
: "",
|
|
25
|
+
]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join("\n");
|
|
28
|
+
const interfaceBlock = ts
|
|
29
|
+
? `
|
|
30
|
+
export interface FetchWithCacheResult {
|
|
31
|
+
response${T("Response")};
|
|
32
|
+
fromCache${T("boolean")};
|
|
33
|
+
}
|
|
34
|
+
`
|
|
35
|
+
: "";
|
|
36
|
+
const optionsType = ts ? `FetchWithCacheOptions` : `options`;
|
|
37
|
+
const optionsInterface = ts
|
|
38
|
+
? `
|
|
39
|
+
export interface FetchWithCacheOptions extends RequestInit {
|
|
40
|
+
tags?: string[];
|
|
41
|
+
staleWhileRevalidate?: boolean;
|
|
42
|
+
auth?: boolean;
|
|
43
|
+
queueOffline?: boolean;
|
|
44
|
+
invalidate?: 'auto' | string[] | false;
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
: "";
|
|
48
|
+
const authBlock = authEnabled
|
|
49
|
+
? `
|
|
50
|
+
if (options.auth) {
|
|
51
|
+
const auth = await getAuth();
|
|
52
|
+
if (auth?.token) {
|
|
53
|
+
headers.set("Authorization", \`Bearer \${auth.token}\`);
|
|
54
|
+
}
|
|
55
|
+
}`
|
|
56
|
+
: "";
|
|
57
|
+
const authUrlsBlock = authEnabled
|
|
58
|
+
? `
|
|
59
|
+
// Auth endpoints bypass SW cache
|
|
60
|
+
const authPaths = ["/login", "/logout", "/register"];
|
|
61
|
+
if (options.auth && authPaths.some((p) => url.includes(p)) && !headers.has("X-SW-Cache-Strategy")) {
|
|
62
|
+
headers.set("X-SW-Cache-Strategy", "mutation");
|
|
63
|
+
}`
|
|
64
|
+
: "";
|
|
65
|
+
const auth401Block = authEnabled
|
|
66
|
+
? `
|
|
67
|
+
if (options.auth && response.status === 401) {
|
|
68
|
+
await clearAuth();
|
|
69
|
+
window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
|
|
70
|
+
}`
|
|
71
|
+
: "";
|
|
72
|
+
const offlineReadBlock = ` if (isRead) {
|
|
73
|
+
const cached = await caches.match(input);
|
|
74
|
+
if (cached) return { response: cached, fromCache: true };
|
|
75
|
+
throw new Error("Offline: no cached data available");
|
|
76
|
+
}`;
|
|
77
|
+
const offlineWriteBlock = mutationQueue
|
|
78
|
+
? ` // Write offline — auto-queue
|
|
79
|
+
if (options.queueOffline !== false) {
|
|
80
|
+
const headerObj: Record<string, string> = {};
|
|
81
|
+
headers.forEach((value, key) => {
|
|
82
|
+
headerObj[key] = value;
|
|
83
|
+
});
|
|
84
|
+
await queueMutation({
|
|
85
|
+
method,
|
|
86
|
+
url,
|
|
87
|
+
body: options.body,
|
|
88
|
+
headers: headerObj,
|
|
89
|
+
tags: options.tags || [],
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
response: new Response(null, { status: 202, statusText: "Queued" }),
|
|
94
|
+
fromCache: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Offline: write operation failed");`
|
|
98
|
+
: ` throw new Error("Offline: write operation failed");`;
|
|
99
|
+
const autoInvalidateBlock = tagInvalidation
|
|
100
|
+
? `
|
|
101
|
+
// Auto-invalidate after mutation success
|
|
102
|
+
if (!isRead && response.ok) {
|
|
103
|
+
const invalidateSetting = options.invalidate !== false ? (options.invalidate || 'auto') : false;
|
|
104
|
+
if (invalidateSetting !== false) {
|
|
105
|
+
const tagsToInvalidate${T("string[]")} = Array.isArray(invalidateSetting)
|
|
106
|
+
? invalidateSetting
|
|
107
|
+
: (options.tags || generateTags(url));
|
|
108
|
+
if (tagsToInvalidate.length > 0) {
|
|
109
|
+
await invalidateByTags(tagsToInvalidate);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}`
|
|
113
|
+
: "";
|
|
114
|
+
const autoTagsBlock = tagInvalidation
|
|
115
|
+
? `
|
|
116
|
+
// Auto-generate tags from URL if not provided
|
|
117
|
+
if (!options.tags && isRead) {
|
|
118
|
+
const urlTags = generateTags(url);
|
|
119
|
+
if (urlTags.length > 0) {
|
|
120
|
+
headers.set("X-SW-Cache-Tags", urlTags.join(","));
|
|
121
|
+
}
|
|
122
|
+
}`
|
|
123
|
+
: "";
|
|
7
124
|
const code = `/**
|
|
8
125
|
* Swoff Fetch Wrapper
|
|
9
|
-
*
|
|
126
|
+
* Unified fetch with caching, auth, offline queue, and auto-invalidation.
|
|
10
127
|
*
|
|
11
128
|
* Usage:
|
|
12
129
|
* import { fetchWithCache } from './swoff/fetch-wrapper.${ext}';
|
|
13
130
|
*
|
|
14
|
-
* // GET
|
|
15
|
-
* const
|
|
131
|
+
* // GET — cached with auto-generated tags
|
|
132
|
+
* const { response } = await fetchWithCache("/api/todos");
|
|
133
|
+
* const data = await response.json();
|
|
16
134
|
*
|
|
17
|
-
* // POST
|
|
135
|
+
* // POST — mutation with auto-invalidation
|
|
18
136
|
* await fetchWithCache("/api/todos", {
|
|
19
137
|
* method: "POST",
|
|
20
138
|
* body: JSON.stringify({ title: "New task" }),
|
|
21
139
|
* });
|
|
22
140
|
*
|
|
23
|
-
* //
|
|
24
|
-
* const
|
|
141
|
+
* // Authenticated request
|
|
142
|
+
* const { response: userRes } = await fetchWithCache("/api/me", { auth: true });
|
|
143
|
+
*
|
|
144
|
+
* // Custom tags + stale-while-revalidate
|
|
145
|
+
* const { response: staleRes, fromCache } = await fetchWithCache("/api/data", {
|
|
25
146
|
* tags: ["data"],
|
|
26
147
|
* staleWhileRevalidate: true,
|
|
27
|
-
* })
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* // Offline: auto-queues writes (disable with queueOffline: false)
|
|
151
|
+
* await fetchWithCache("/api/todos", {
|
|
152
|
+
* method: "POST",
|
|
153
|
+
* body: JSON.stringify({ title: "Offline task" }),
|
|
154
|
+
* });
|
|
155
|
+
* // When back online, processMutationQueue() replays them
|
|
28
156
|
*/
|
|
29
157
|
|
|
30
|
-
|
|
158
|
+
${importLines}
|
|
159
|
+
${interfaceBlock}${optionsInterface}
|
|
160
|
+
const inFlightRequests = new Map${G("string, Promise<Response>")}();
|
|
161
|
+
|
|
162
|
+
/** Fetch with caching, auth, offline queue, and auto-invalidation. Returns { response, fromCache }. */
|
|
163
|
+
export async function fetchWithCache(input${T("RequestInfo")}, options${T("RequestInit & { tags?: string[]; staleWhileRevalidate?: boolean; auth?: boolean; queueOffline?: boolean; invalidate?: 'auto' | string[] | false }")} = {})${R("Promise<FetchWithCacheResult>")}{
|
|
164
|
+
const method = (options.method || "GET").toUpperCase();
|
|
165
|
+
const isRead = method === "GET" || method === "HEAD";
|
|
166
|
+
const url = typeof input === "string" ? input : input.url;
|
|
31
167
|
|
|
32
|
-
export async function fetchWithCache(input, options = {}) {
|
|
33
168
|
const headers = new Headers(options.headers);
|
|
34
|
-
const method = options.method || "GET";
|
|
35
169
|
|
|
170
|
+
// Set cache strategy
|
|
36
171
|
if (!headers.has("X-SW-Cache-Strategy")) {
|
|
37
|
-
headers.set(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
172
|
+
headers.set("X-SW-Cache-Strategy", isRead ? "read" : "mutation");
|
|
173
|
+
}
|
|
174
|
+
${autoTagsBlock}
|
|
175
|
+
// Custom tags override auto-generated
|
|
176
|
+
if (options.tags && options.tags.length > 0) {
|
|
177
|
+
headers.set("X-SW-Cache-Tags", options.tags.join(","));
|
|
41
178
|
}
|
|
42
179
|
|
|
43
180
|
if (options.staleWhileRevalidate) {
|
|
44
181
|
headers.set("X-SW-Stale", "true");
|
|
45
182
|
}
|
|
183
|
+
${authBlock}${authUrlsBlock}
|
|
184
|
+
const fetchOptions${T("RequestInit")} = { ...options, headers };
|
|
46
185
|
|
|
47
|
-
|
|
48
|
-
|
|
186
|
+
// Offline handling
|
|
187
|
+
if (!navigator.onLine) {
|
|
188
|
+
${offlineReadBlock}
|
|
189
|
+
${offlineWriteBlock}
|
|
49
190
|
}
|
|
50
191
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
192
|
+
// Deduplicate in-flight GET requests
|
|
193
|
+
let responsePromise${T("Promise<Response>")};
|
|
194
|
+
if (isRead && inFlightRequests.has(url)) {
|
|
195
|
+
responsePromise = inFlightRequests.get(url)!.then((r) => r.clone());
|
|
196
|
+
} else {
|
|
197
|
+
responsePromise = fetch(input, fetchOptions);
|
|
198
|
+
if (isRead) {
|
|
199
|
+
inFlightRequests.set(url, responsePromise.finally(() => inFlightRequests.delete(url)));
|
|
55
200
|
}
|
|
56
|
-
const promise = fetch(input, { ...options, headers }).finally(() => {
|
|
57
|
-
inFlightRequests.delete(url);
|
|
58
|
-
});
|
|
59
|
-
inFlightRequests.set(url, promise);
|
|
60
|
-
return promise;
|
|
61
201
|
}
|
|
62
202
|
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!navigator.onLine) {
|
|
68
|
-
if (options.method === "GET" || options.method === "HEAD") {
|
|
69
|
-
const cached = await caches.match(input);
|
|
70
|
-
if (cached) return cached;
|
|
71
|
-
throw new Error("Offline: no cached data");
|
|
72
|
-
} else {
|
|
73
|
-
throw new Error("Offline: mutation queued");
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return fetchWithCache(input, options);
|
|
203
|
+
const response = await responsePromise;
|
|
204
|
+
${autoInvalidateBlock}${auth401Block}
|
|
205
|
+
const fromCache = response.headers.get("X-SW-From-Cache") === "true";
|
|
206
|
+
return { response, fromCache };
|
|
77
207
|
}
|
|
78
208
|
`;
|
|
79
209
|
writeFile(ctx, `fetch-wrapper.${ext}`, code);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG;;;;;6DAK8C,GAAG
|
|
1
|
+
{"version":3,"file":"fetch-wrapper.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/fetch-wrapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,9 +17,11 @@ export function generateHooks(ctx) {
|
|
|
17
17
|
if (config.features.mutationQueue) {
|
|
18
18
|
writeMutationQueueHook(hooksDir, ext);
|
|
19
19
|
}
|
|
20
|
+
writeCachedFetchHook(hooksDir, ext, config.features.tagInvalidation);
|
|
20
21
|
}
|
|
21
22
|
function writePWAHooks(dir, ext) {
|
|
22
23
|
writeFileSync(join(dir, `usePWAUpdate.${ext}x`), `import { useState, useEffect, useCallback } from "react";
|
|
24
|
+
import { handleUpdateApproved } from "../client-injector.${ext}";
|
|
23
25
|
|
|
24
26
|
export function usePWAUpdate() {
|
|
25
27
|
const [state, setState] = useState({
|
|
@@ -62,7 +64,6 @@ export function usePWAUpdate() {
|
|
|
62
64
|
|
|
63
65
|
const acceptUpdate = useCallback(async () => {
|
|
64
66
|
if (!state.availableVersion) return;
|
|
65
|
-
const { handleUpdateApproved } = await import("../sw-injector.${ext}");
|
|
66
67
|
await handleUpdateApproved(state.availableVersion);
|
|
67
68
|
}, [state.availableVersion]);
|
|
68
69
|
|
|
@@ -102,7 +103,7 @@ export function useSWProgress() {
|
|
|
102
103
|
}
|
|
103
104
|
function writeAuthHook(dir, ext) {
|
|
104
105
|
writeFileSync(join(dir, `useAuth.${ext}x`), `import { useState, useEffect } from "react";
|
|
105
|
-
import { getAuthState } from "../auth
|
|
106
|
+
import { getAuthState } from "../auth/state.${ext}";
|
|
106
107
|
|
|
107
108
|
export function useAuth() {
|
|
108
109
|
const [state, setState] = useState({
|
|
@@ -133,6 +134,63 @@ export function useAuth() {
|
|
|
133
134
|
}
|
|
134
135
|
`);
|
|
135
136
|
}
|
|
137
|
+
function writeCachedFetchHook(dir, ext, tagInvalidation) {
|
|
138
|
+
const ts = ext === "ts";
|
|
139
|
+
const extraEffect = tagInvalidation
|
|
140
|
+
? `
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const onInvalidated${ts ? ": EventListener" : ""} = (e${ts ? ": Event" : ""}) => {
|
|
143
|
+
const detail = (e as CustomEvent).detail;
|
|
144
|
+
const tags = detail?.tags;
|
|
145
|
+
if (!tags || !Array.isArray(tags)) return;
|
|
146
|
+
const urlTags = generateTags(url);
|
|
147
|
+
if (tags.some((t) => urlTags.includes(t))) {
|
|
148
|
+
setRefetchCount((c) => c + 1);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
window.addEventListener("cache-invalidated", onInvalidated);
|
|
152
|
+
return () => window.removeEventListener("cache-invalidated", onInvalidated);
|
|
153
|
+
}, [url]);`
|
|
154
|
+
: "";
|
|
155
|
+
const importTags = tagInvalidation
|
|
156
|
+
? `import { generateTags } from "../invalidation-tags.${ext}";\n`
|
|
157
|
+
: "";
|
|
158
|
+
const stringType = ts ? ": string" : "";
|
|
159
|
+
const respType = ts ? "<Response | null>" : "";
|
|
160
|
+
writeFileSync(join(dir, `useCachedFetch.${ext}x`), `import { useState, useEffect, useCallback } from "react";
|
|
161
|
+
import { fetchWithCache } from "../fetch-wrapper.${ext}";
|
|
162
|
+
${importTags}
|
|
163
|
+
export function useCachedFetch(url${stringType}, options = {}) {
|
|
164
|
+
const [data, setData] = useState${respType}(null);
|
|
165
|
+
const [error, setError] = useState(null);
|
|
166
|
+
const [loading, setLoading] = useState(true);
|
|
167
|
+
const [refetchCount, setRefetchCount] = useState(0);
|
|
168
|
+
|
|
169
|
+
const refetch = useCallback(() => setRefetchCount((c) => c + 1), []);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
let cancelled = false;
|
|
173
|
+
setLoading(true);
|
|
174
|
+
fetchWithCache(url, options)
|
|
175
|
+
.then(({ response }) => {
|
|
176
|
+
if (cancelled) return;
|
|
177
|
+
setData(response);
|
|
178
|
+
setError(null);
|
|
179
|
+
})
|
|
180
|
+
.catch((err) => {
|
|
181
|
+
if (cancelled) return;
|
|
182
|
+
setError(err);
|
|
183
|
+
})
|
|
184
|
+
.finally(() => {
|
|
185
|
+
if (!cancelled) setLoading(false);
|
|
186
|
+
});
|
|
187
|
+
return () => { cancelled = true; };
|
|
188
|
+
}, [url, refetchCount]);
|
|
189
|
+
${extraEffect}
|
|
190
|
+
return { data, error, loading, refetch };
|
|
191
|
+
}
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
136
194
|
function writeMutationQueueHook(dir, ext) {
|
|
137
195
|
writeFileSync(join(dir, `useMutationQueue.${ext}x`), `import { useState, useEffect } from "react";
|
|
138
196
|
import { getPendingCount } from "../mutation-queue.${ext}";
|
|
@@ -150,7 +208,7 @@ export function useMutationQueue() {
|
|
|
150
208
|
pending: 0,
|
|
151
209
|
lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
|
|
152
210
|
});
|
|
153
|
-
const onChange = async () => setState((s) => ({ ...s, pending:
|
|
211
|
+
const onChange = async () => { const count = await getPendingCount(); setState((s) => ({ ...s, pending: count })); };
|
|
154
212
|
|
|
155
213
|
window.addEventListener("mutation-sync-complete", onSync as EventListener);
|
|
156
214
|
window.addEventListener("mutation-queue-changed", onChange);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC;IACrD,IAAI,aAAa,KAAK,OAAO;QAAE,OAAO;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAChC,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAClC,sBAAsB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"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"}
|