@swoff/cli 0.3.4 → 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 -26
- package/dist/__tests__/assemble-sw.test.js +41 -12
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +16 -18
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +17 -26
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +71 -150
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +15 -16
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +54 -65
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +6 -6
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +19 -6
- 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 +125 -0
- package/dist/lib/commands/generate-guide.js.map +1 -0
- package/dist/lib/commands/generate.js +11 -10
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +135 -30
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +6 -12
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +7 -3
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/validator.js +55 -65
- 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 +225 -0
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -0
- 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 +31 -21
- 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-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 +34 -13
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +2 -2
- 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 +10 -7
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +37 -26
- 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/dist/lib/utils/detect-framework.js +33 -0
- package/dist/lib/utils/detect-framework.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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"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
export function generateHooks(ctx) {
|
|
4
|
+
const { config, swoffDir, ext, frameworkName } = ctx;
|
|
5
|
+
if (frameworkName !== "react")
|
|
6
|
+
return;
|
|
7
|
+
const hooksDir = join(swoffDir, "hooks");
|
|
8
|
+
if (!existsSync(hooksDir)) {
|
|
9
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
if (config.features.pwa.enabled) {
|
|
12
|
+
writePWAHooks(hooksDir, ext);
|
|
13
|
+
}
|
|
14
|
+
if (config.features.auth.enabled) {
|
|
15
|
+
writeAuthHook(hooksDir, ext);
|
|
16
|
+
}
|
|
17
|
+
if (config.features.mutationQueue) {
|
|
18
|
+
writeMutationQueueHook(hooksDir, ext);
|
|
19
|
+
}
|
|
20
|
+
writeCachedFetchHook(hooksDir, ext, config.features.tagInvalidation);
|
|
21
|
+
}
|
|
22
|
+
function writePWAHooks(dir, ext) {
|
|
23
|
+
writeFileSync(join(dir, `usePWAUpdate.${ext}x`), `import { useState, useEffect, useCallback } from "react";
|
|
24
|
+
import { handleUpdateApproved } from "../client-injector.${ext}";
|
|
25
|
+
|
|
26
|
+
export function usePWAUpdate() {
|
|
27
|
+
const [state, setState] = useState({
|
|
28
|
+
updateStatus: "idle",
|
|
29
|
+
currentVersion: null as string | null,
|
|
30
|
+
availableVersion: null as string | null,
|
|
31
|
+
progress: 0,
|
|
32
|
+
forceUpdate: false,
|
|
33
|
+
error: null as string | null,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setState((s) => ({ ...s, currentVersion: (window as any).currentSWVersion || null }));
|
|
38
|
+
|
|
39
|
+
const onAvailable = (e: CustomEvent) => setState((s) => ({
|
|
40
|
+
...s,
|
|
41
|
+
updateStatus: "available" as const,
|
|
42
|
+
availableVersion: e.detail.version,
|
|
43
|
+
forceUpdate: (window as any).swUpdateRequired || false,
|
|
44
|
+
}));
|
|
45
|
+
const onProgress = (e: CustomEvent) => setState((s) => ({
|
|
46
|
+
...s,
|
|
47
|
+
updateStatus: "downloading" as const,
|
|
48
|
+
progress: e.detail.percent,
|
|
49
|
+
}));
|
|
50
|
+
const onReady = () => setState((s) => ({ ...s, updateStatus: "idle" as const, progress: 0 }));
|
|
51
|
+
const onError = () => setState((s) => ({ ...s, error: "SW registration failed" }));
|
|
52
|
+
|
|
53
|
+
window.addEventListener("sw-update-available", onAvailable as EventListener);
|
|
54
|
+
window.addEventListener("sw-progress", onProgress as EventListener);
|
|
55
|
+
window.addEventListener("sw-ready", onReady);
|
|
56
|
+
window.addEventListener("sw-error", onError);
|
|
57
|
+
return () => {
|
|
58
|
+
window.removeEventListener("sw-update-available", onAvailable as EventListener);
|
|
59
|
+
window.removeEventListener("sw-progress", onProgress as EventListener);
|
|
60
|
+
window.removeEventListener("sw-ready", onReady);
|
|
61
|
+
window.removeEventListener("sw-error", onError);
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const acceptUpdate = useCallback(async () => {
|
|
66
|
+
if (!state.availableVersion) return;
|
|
67
|
+
await handleUpdateApproved(state.availableVersion);
|
|
68
|
+
}, [state.availableVersion]);
|
|
69
|
+
|
|
70
|
+
const dismissUpdate = useCallback(() => {
|
|
71
|
+
sessionStorage.setItem("sw-dismissed-update", "true");
|
|
72
|
+
setState((s) => ({ ...s, updateStatus: "idle" }));
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return { ...state, acceptUpdate, dismissUpdate };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function useSWProgress() {
|
|
79
|
+
const [state, setState] = useState({
|
|
80
|
+
status: "idle" as "idle" | "installing",
|
|
81
|
+
progress: 0,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const onProgress = (e: CustomEvent) => setState({
|
|
86
|
+
status: "installing",
|
|
87
|
+
progress: e.detail.percent,
|
|
88
|
+
});
|
|
89
|
+
const onReady = () => setState({ status: "idle", progress: 0 });
|
|
90
|
+
|
|
91
|
+
window.addEventListener("sw-progress", onProgress as EventListener);
|
|
92
|
+
window.addEventListener("sw-ready", onReady);
|
|
93
|
+
return () => {
|
|
94
|
+
window.removeEventListener("sw-progress", onProgress as EventListener);
|
|
95
|
+
window.removeEventListener("sw-ready", onReady);
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
`);
|
|
102
|
+
// useSWProgress is included in the same file
|
|
103
|
+
}
|
|
104
|
+
function writeAuthHook(dir, ext) {
|
|
105
|
+
writeFileSync(join(dir, `useAuth.${ext}x`), `import { useState, useEffect } from "react";
|
|
106
|
+
import { getAuthState } from "../auth/state.${ext}";
|
|
107
|
+
|
|
108
|
+
export function useAuth() {
|
|
109
|
+
const [state, setState] = useState({
|
|
110
|
+
authenticated: false,
|
|
111
|
+
user: null as Record<string, unknown> | null,
|
|
112
|
+
online: navigator.onLine,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
getAuthState().then(setState);
|
|
117
|
+
|
|
118
|
+
const onOnline = () => setState((s) => ({ ...s, online: true }));
|
|
119
|
+
const onOffline = () => setState((s) => ({ ...s, online: false }));
|
|
120
|
+
const onAuthChange = () => getAuthState().then(setState);
|
|
121
|
+
|
|
122
|
+
window.addEventListener("online", onOnline);
|
|
123
|
+
window.addEventListener("offline", onOffline);
|
|
124
|
+
window.addEventListener("sw-auth-state-change", onAuthChange);
|
|
125
|
+
|
|
126
|
+
return () => {
|
|
127
|
+
window.removeEventListener("online", onOnline);
|
|
128
|
+
window.removeEventListener("offline", onOffline);
|
|
129
|
+
window.removeEventListener("sw-auth-state-change", onAuthChange);
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
return state;
|
|
134
|
+
}
|
|
135
|
+
`);
|
|
136
|
+
}
|
|
137
|
+
function writeCachedFetchHook(dir, ext, tagInvalidation) {
|
|
138
|
+
const ts = ext === "ts";
|
|
139
|
+
const extraEffect = tagInvalidation
|
|
140
|
+
? `
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const onInvalidated${ts ? ": EventListener" : ""} = (e${ts ? ": Event" : ""}) => {
|
|
143
|
+
const detail = (e as CustomEvent).detail;
|
|
144
|
+
const tags = detail?.tags;
|
|
145
|
+
if (!tags || !Array.isArray(tags)) return;
|
|
146
|
+
const urlTags = generateTags(url);
|
|
147
|
+
if (tags.some((t) => urlTags.includes(t))) {
|
|
148
|
+
setRefetchCount((c) => c + 1);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
window.addEventListener("cache-invalidated", onInvalidated);
|
|
152
|
+
return () => window.removeEventListener("cache-invalidated", onInvalidated);
|
|
153
|
+
}, [url]);`
|
|
154
|
+
: "";
|
|
155
|
+
const importTags = tagInvalidation
|
|
156
|
+
? `import { generateTags } from "../invalidation-tags.${ext}";\n`
|
|
157
|
+
: "";
|
|
158
|
+
const stringType = ts ? ": string" : "";
|
|
159
|
+
const respType = ts ? "<Response | null>" : "";
|
|
160
|
+
writeFileSync(join(dir, `useCachedFetch.${ext}x`), `import { useState, useEffect, useCallback } from "react";
|
|
161
|
+
import { fetchWithCache } from "../fetch-wrapper.${ext}";
|
|
162
|
+
${importTags}
|
|
163
|
+
export function useCachedFetch(url${stringType}, options = {}) {
|
|
164
|
+
const [data, setData] = useState${respType}(null);
|
|
165
|
+
const [error, setError] = useState(null);
|
|
166
|
+
const [loading, setLoading] = useState(true);
|
|
167
|
+
const [refetchCount, setRefetchCount] = useState(0);
|
|
168
|
+
|
|
169
|
+
const refetch = useCallback(() => setRefetchCount((c) => c + 1), []);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
let cancelled = false;
|
|
173
|
+
setLoading(true);
|
|
174
|
+
fetchWithCache(url, options)
|
|
175
|
+
.then(({ response }) => {
|
|
176
|
+
if (cancelled) return;
|
|
177
|
+
setData(response);
|
|
178
|
+
setError(null);
|
|
179
|
+
})
|
|
180
|
+
.catch((err) => {
|
|
181
|
+
if (cancelled) return;
|
|
182
|
+
setError(err);
|
|
183
|
+
})
|
|
184
|
+
.finally(() => {
|
|
185
|
+
if (!cancelled) setLoading(false);
|
|
186
|
+
});
|
|
187
|
+
return () => { cancelled = true; };
|
|
188
|
+
}, [url, refetchCount]);
|
|
189
|
+
${extraEffect}
|
|
190
|
+
return { data, error, loading, refetch };
|
|
191
|
+
}
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
194
|
+
function writeMutationQueueHook(dir, ext) {
|
|
195
|
+
writeFileSync(join(dir, `useMutationQueue.${ext}x`), `import { useState, useEffect } from "react";
|
|
196
|
+
import { getPendingCount } from "../mutation-queue.${ext}";
|
|
197
|
+
|
|
198
|
+
export function useMutationQueue() {
|
|
199
|
+
const [state, setState] = useState<{
|
|
200
|
+
pending: number;
|
|
201
|
+
lastSync: { succeeded: number; failed: number } | null;
|
|
202
|
+
}>({ pending: 0, lastSync: null });
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
getPendingCount().then((count: number) => setState((s) => ({ ...s, pending: count })));
|
|
206
|
+
|
|
207
|
+
const onSync = (e: CustomEvent) => setState({
|
|
208
|
+
pending: 0,
|
|
209
|
+
lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
|
|
210
|
+
});
|
|
211
|
+
const onChange = async () => { const count = await getPendingCount(); setState((s) => ({ ...s, pending: count })); };
|
|
212
|
+
|
|
213
|
+
window.addEventListener("mutation-sync-complete", onSync as EventListener);
|
|
214
|
+
window.addEventListener("mutation-queue-changed", onChange);
|
|
215
|
+
return () => {
|
|
216
|
+
window.removeEventListener("mutation-sync-complete", onSync as EventListener);
|
|
217
|
+
window.removeEventListener("mutation-queue-changed", onChange);
|
|
218
|
+
};
|
|
219
|
+
}, []);
|
|
220
|
+
|
|
221
|
+
return state;
|
|
222
|
+
}
|
|
223
|
+
`);
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=generate-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-hooks.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/generate-hooks.ts"],"names":[],"mappings":"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"}
|