@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.
Files changed (121) hide show
  1. package/README.md +610 -24
  2. package/bin/swoff +8 -14
  3. package/dist/__tests__/assemble-sw.test.js +17 -3
  4. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  5. package/dist/__tests__/cli-integration.test.js +9 -9
  6. package/dist/__tests__/cli-integration.test.js.map +1 -1
  7. package/dist/__tests__/config-types.test.js +10 -9
  8. package/dist/__tests__/config-types.test.js.map +1 -1
  9. package/dist/__tests__/file-generators.test.js +65 -68
  10. package/dist/__tests__/file-generators.test.js.map +1 -1
  11. package/dist/__tests__/loader.test.js +12 -14
  12. package/dist/__tests__/loader.test.js.map +1 -1
  13. package/dist/__tests__/validator.test.js +41 -22
  14. package/dist/__tests__/validator.test.js.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/cli/help.js +3 -3
  18. package/dist/lib/cli/help.js.map +1 -1
  19. package/dist/lib/commands/add.js +11 -5
  20. package/dist/lib/commands/add.js.map +1 -1
  21. package/dist/lib/commands/clean.js +5 -0
  22. package/dist/lib/commands/clean.js.map +1 -1
  23. package/dist/lib/commands/generate-guide.js +43 -13
  24. package/dist/lib/commands/generate-guide.js.map +1 -1
  25. package/dist/lib/commands/generate.js +7 -3
  26. package/dist/lib/commands/generate.js.map +1 -1
  27. package/dist/lib/commands/info.js +149 -32
  28. package/dist/lib/commands/info.js.map +1 -1
  29. package/dist/lib/commands/init.js +6 -2
  30. package/dist/lib/commands/init.js.map +1 -1
  31. package/dist/lib/commands/validate.js +3 -1
  32. package/dist/lib/commands/validate.js.map +1 -1
  33. package/dist/lib/config/validator.js +25 -16
  34. package/dist/lib/config/validator.js.map +1 -1
  35. package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
  36. package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
  37. package/dist/lib/generators/file-generators/auth-state.js +8 -5
  38. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  39. package/dist/lib/generators/file-generators/auth-store.js +181 -41
  40. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  41. package/dist/lib/generators/file-generators/auth-user.js +28 -32
  42. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  43. package/dist/lib/generators/file-generators/background-sync.js +9 -5
  44. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  45. package/dist/lib/generators/file-generators/cache.js +8 -5
  46. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  47. package/dist/lib/generators/file-generators/client-injector.js +101 -0
  48. package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
  49. package/dist/lib/generators/file-generators/context.js +7 -6
  50. package/dist/lib/generators/file-generators/context.js.map +1 -1
  51. package/dist/lib/generators/file-generators/fetch-wrapper.js +196 -41
  52. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  53. package/dist/lib/generators/file-generators/generate-hooks.js +23 -151
  54. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  55. package/dist/lib/generators/file-generators/guide-generator.js +450 -0
  56. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
  57. package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
  58. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  59. package/dist/lib/generators/file-generators/mutation-queue.js +65 -87
  60. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  61. package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
  62. package/dist/lib/generators/file-generators/mutation-reconcile.js.map +1 -0
  63. package/dist/lib/generators/file-generators/push.js +143 -0
  64. package/dist/lib/generators/file-generators/push.js.map +1 -0
  65. package/dist/lib/generators/file-generators/pwa-install.js +41 -9
  66. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  67. package/dist/lib/generators/file-generators/quick-readme.js +88 -0
  68. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
  69. package/dist/lib/generators/file-generators/reconcile.js +7 -12
  70. package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
  71. package/dist/lib/generators/file-generators/store.js +30 -20
  72. package/dist/lib/generators/file-generators/store.js.map +1 -1
  73. package/dist/lib/generators/file-generators/sw-generator-build.js +38 -17
  74. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  75. package/dist/lib/generators/file-generators/sw-injector.js +82 -111
  76. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  77. package/dist/lib/generators/file-generators/sw-push.js +67 -0
  78. package/dist/lib/generators/file-generators/sw-push.js.map +1 -0
  79. package/dist/lib/generators/file-generators/sw-template.js +3 -1
  80. package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
  81. package/dist/lib/generators/file-generators/type-definitions.js +45 -22
  82. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  83. package/dist/lib/generators/sw-generator.js +27 -19
  84. package/dist/lib/generators/sw-generator.js.map +1 -1
  85. package/dist/lib/generators/sw-sections/activate-handler.js +6 -2
  86. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  87. package/dist/lib/generators/sw-sections/assemble-sw.js +36 -12
  88. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  89. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  90. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  91. package/dist/lib/generators/sw-sections/default-template.js +1 -30
  92. package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
  93. package/dist/lib/generators/sw-sections/fetch-handler.js +176 -90
  94. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  95. package/dist/lib/generators/sw-sections/install-handler.js +12 -10
  96. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
  97. package/dist/lib/generators/sw-sections/tag-management.js +36 -5
  98. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  99. package/dist/lib/generators/swoff-files-generator.js +14 -12
  100. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  101. package/dist/lib/shared/config-types.js +26 -9
  102. package/dist/lib/shared/config-types.js.map +1 -1
  103. package/dist/lib/utils/build-script.js +35 -0
  104. package/dist/lib/utils/build-script.js.map +1 -0
  105. package/package.json +3 -2
  106. package/templates/hooks/useAuth.jsx +30 -0
  107. package/templates/hooks/useAuth.tsx +30 -0
  108. package/templates/hooks/useBackgroundSync.jsx +31 -0
  109. package/templates/hooks/useBackgroundSync.tsx +31 -0
  110. package/templates/hooks/useCacheInvalidation.jsx +19 -0
  111. package/templates/hooks/useCacheInvalidation.tsx +19 -0
  112. package/templates/hooks/useCachedFetch.jsx +55 -0
  113. package/templates/hooks/useCachedFetch.tsx +69 -0
  114. package/templates/hooks/useMutationQueue.jsx +34 -0
  115. package/templates/hooks/useMutationQueue.tsx +34 -0
  116. package/templates/hooks/useNetworkStatus.jsx +19 -0
  117. package/templates/hooks/useNetworkStatus.tsx +19 -0
  118. package/templates/hooks/usePushSubscription.jsx +67 -0
  119. package/templates/hooks/usePushSubscription.tsx +67 -0
  120. package/templates/hooks/useSWUpdate.jsx +76 -0
  121. package/templates/hooks/useSWUpdate.tsx +76 -0
@@ -1,14 +1,160 @@
1
1
  /**
2
- * Generates auth-store.ts/js — token storage with memory-only for token,
3
- * IndexedDB for offline user info only (no token persisted).
2
+ * Generates auth-store.ts/js — token storage, auth headers, auth URL detection,
3
+ * and token refresh. All auth logic lives here so fetch-wrapper can import it
4
+ * without circular dependencies.
4
5
  *
5
6
  * Security: the Bearer token lives in memory only and is cleared on page
6
7
  * refresh. Only { user, expiresAt } is persisted to IndexedDB for offline
7
8
  * user display. After a page refresh, re-login is required.
8
9
  */
9
10
  import { writeFile } from "./context.js";
11
+ function generateWithAuthHeaders(authType, ts, R, T) {
12
+ const a = T("AuthData | null");
13
+ const h = T("Headers");
14
+ const hd = T("Headers");
15
+ switch (authType) {
16
+ case "cookie":
17
+ return `/** Inject auth headers. For cookie auth, credentials are handled via AUTH_WITH_CREDENTIALS. */
18
+ export function withAuthHeaders(headers${hd}, _auth${a})${h}{
19
+ return headers;
20
+ }`;
21
+ case "bearer":
22
+ return `/** Inject Bearer token into request headers. */
23
+ export function withAuthHeaders(headers${hd}, auth${a})${h}{
24
+ if (auth?.token) {
25
+ headers.set("Authorization", \`Bearer \${auth.token}\`);
26
+ }
27
+ return headers;
28
+ }`;
29
+ case "custom":
30
+ return `/** Inject custom auth headers. Edit this function to match your backend. */
31
+ export function withAuthHeaders(headers${hd}, auth${a})${h}{
32
+ // --- EDIT THIS BLOCK FOR YOUR BACKEND ---
33
+ // if (auth?.token) {
34
+ // headers.set("X-Auth-Token", auth.token);
35
+ // }
36
+ // --- END OF EDITABLE BLOCK ---
37
+ return headers;
38
+ }`;
39
+ default:
40
+ return `export function withAuthHeaders(headers${hd}, auth${a})${h}{
41
+ if (auth?.token) {
42
+ headers.set("Authorization", \`Bearer \${auth.token}\`);
43
+ }
44
+ return headers;
45
+ }`;
46
+ }
47
+ }
48
+ function generateIsAuthUrl(refreshPath, userEndpoint, ts, R, T) {
49
+ return `/** Check if a URL is an auth endpoint that should bypass the SW cache. */
50
+ export function isAuthUrl(url${T("string")})${R("boolean")}{
51
+ const authPaths = [
52
+ "/login",
53
+ "/logout",
54
+ "/register",
55
+ "/api/login",
56
+ "/api/logout",
57
+ "/api/register",
58
+ "${refreshPath}",
59
+ "${userEndpoint}",
60
+ ];
61
+ return authPaths.some((path) => url.includes(path));
62
+ }`;
63
+ }
64
+ function generateEnsureValidAuth(cookieAuth, ts, R, T, PT, refreshPath, ext) {
65
+ const cr = cookieAuth ? ` credentials: "include" as RequestCredentials,` : "";
66
+ return `/** Refresh the auth token via the refresh endpoint. Uses plain fetch to bypass SW cache. */
67
+ let refreshPromise${T("Promise<AuthData | null> | null")} = null;
68
+
69
+ export async function ensureValidAuth()${R("Promise<AuthData | null>")}{
70
+ const auth = await getAuth();
71
+ if (!auth) return null;
72
+ if (!auth.expiresAt || Date.now() < auth.expiresAt) return auth;
73
+
74
+ if (!refreshPromise) {
75
+ refreshPromise = (async () => {
76
+ const headers = new Headers({ "Content-Type": "application/json" });
77
+ withAuthHeaders(headers, auth);
78
+ try {
79
+ const response = await fetch("${refreshPath}", {
80
+ method: "POST",
81
+ headers,
82
+ ${cr} });
83
+
84
+ if (!response.ok) {
85
+ await clearAuth();
86
+ window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
87
+ return null;
88
+ }
89
+
90
+ const data = await response.json();
91
+ const updated = { ...auth, token: data.token, expiresAt: data.expiresAt };
92
+ await setAuth(updated);
93
+ return updated;
94
+ } catch {
95
+ await clearAuth();
96
+ window.dispatchEvent(new CustomEvent("sw-auth-unauthorized"));
97
+ return null;
98
+ }
99
+ })();
100
+ }
101
+
102
+ try {
103
+ return await refreshPromise;
104
+ } finally {
105
+ refreshPromise = null;
106
+ }
107
+ }`;
108
+ }
10
109
  export function generateAuthStore(ctx) {
11
110
  const ext = ctx.ext;
111
+ const ts = ext === "ts";
112
+ const T = (type) => (ts ? `: ${type}` : "");
113
+ const R = (type) => (ts ? `: ${type} ` : " ");
114
+ const PT = (type) => (ts ? `<${type}>` : "");
115
+ const AS = (type) => (ts ? ` as ${type}` : "");
116
+ const authConfig = ctx.config.features.auth;
117
+ const { refreshPath, userEndpoint, type } = authConfig;
118
+ const authDataInterface = ts
119
+ ? `export interface AuthData {
120
+ token?: string;
121
+ user?: Record<string, unknown>;
122
+ expiresAt?: number;
123
+ }
124
+
125
+ export interface AuthResponse extends Record<string, unknown> {
126
+ // Uncomment and type the fields your backend's login response returns:
127
+ // token: string;
128
+ // user: Record<string, unknown>;
129
+ // expiresAt?: number;
130
+ }
131
+
132
+ `
133
+ : "";
134
+ const createAuthFromResponseBlock = ts
135
+ ? `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
136
+ export function createAuthFromResponse(response: AuthResponse): AuthData {
137
+ // --- EDIT THIS TO MATCH YOUR BACKEND ---
138
+ const data = response as Record<string, unknown>;
139
+ return {
140
+ token: data.token as string,
141
+ user: data.user as Record<string, unknown> | undefined,
142
+ expiresAt: data.expiresAt as number | undefined,
143
+ };
144
+ // --- END OF EDITABLE BLOCK ---
145
+ }
146
+ `
147
+ : `/** Extract AuthData from your backend's login response. Edit this function to match your backend's response shape. */
148
+ export function createAuthFromResponse(response) {
149
+ // --- EDIT THIS TO MATCH YOUR BACKEND ---
150
+ return {
151
+ token: response.token,
152
+ user: response.user,
153
+ expiresAt: response.expiresAt,
154
+ };
155
+ // --- END OF EDITABLE BLOCK ---
156
+ }
157
+ `;
12
158
  const code = `/**
13
159
  * Auth Store — Token in memory only; user info in IndexedDB for offline access.
14
160
  *
@@ -18,110 +164,104 @@ export function generateAuthStore(ctx) {
18
164
  * user info offline. After a page refresh, re-login is required.
19
165
  *
20
166
  * Usage:
21
- * import { setAuth, getAuth, clearAuth, isAuthValid } from "./auth-store.${ext}";
167
+ * import { setAuth, getAuth, clearAuth, isAuthValid } from "./auth/store.${ext}";
22
168
  *
23
169
  * await setAuth({ token, user, expiresAt });
24
170
  * const auth = await getAuth();
25
171
  * await clearAuth();
26
172
  */
27
173
 
28
- const DB_NAME = "swoff-auth";
174
+ ${authDataInterface}const DB_NAME = "swoff-auth";
29
175
  const STORE_NAME = "auth";
30
176
 
31
- let memoryAuth = null;
177
+ let memoryAuth${T("AuthData | null")} = null;
32
178
 
33
- function openAuthDB() {
34
- return new Promise((resolve, reject) => {
179
+ ${createAuthFromResponseBlock}
180
+ function openAuthDB()${R("Promise<IDBDatabase>")}{
181
+ return new Promise${PT("IDBDatabase")}((resolve, reject) => {
35
182
  const request = indexedDB.open(DB_NAME, 1);
36
183
  request.onupgradeneeded = (e) => {
37
- const db = e.target.result;
184
+ const db = (e.target${AS("IDBOpenDBRequest")}).result;
38
185
  if (!db.objectStoreNames.contains(STORE_NAME)) {
39
186
  db.createObjectStore(STORE_NAME, { keyPath: "key" });
40
187
  }
41
188
  };
42
- request.onsuccess = (e) => resolve(e.target.result);
43
- request.onerror = (e) => reject(e.target.error);
189
+ request.onsuccess = (e) => resolve((e.target${AS("IDBOpenDBRequest")}).result);
190
+ request.onerror = (e) => reject((e.target${AS("IDBRequest")}).error);
44
191
  });
45
192
  }
46
193
 
47
- async function persistUserData(authData) {
194
+ async function persistUserData(authData${T("AuthData | null")})${R("Promise<void>")}{
48
195
  // Only persist { user, expiresAt } — never the token
49
196
  const userData = { user: authData?.user, expiresAt: authData?.expiresAt };
50
197
  const db = await openAuthDB();
51
- return new Promise((resolve, reject) => {
198
+ return new Promise${PT("void")}((resolve, reject) => {
52
199
  const tx = db.transaction(STORE_NAME, "readwrite");
53
200
  const store = tx.objectStore(STORE_NAME);
54
201
  const request = store.put({ key: "session", value: userData });
55
202
  request.onsuccess = () => resolve();
56
- request.onerror = () => reject(request.error);
203
+ request.onerror = () => reject((request${AS("IDBRequest")}).error);
57
204
  });
58
205
  }
59
206
 
60
- async function loadUserData() {
207
+ async function loadUserData()${R("Promise<{ user?: Record<string, unknown>; expiresAt?: number } | null>")}{
61
208
  const db = await openAuthDB();
62
209
  return new Promise((resolve, reject) => {
63
210
  const tx = db.transaction(STORE_NAME, "readonly");
64
211
  const store = tx.objectStore(STORE_NAME);
65
212
  const request = store.get("session");
66
- request.onsuccess = () => resolve(request.result?.value || null);
67
- request.onerror = () => reject(request.error);
213
+ request.onsuccess = () => resolve((request${AS("IDBRequest")}).result?.value ?? null);
214
+ request.onerror = () => reject((request${AS("IDBRequest")}).error);
68
215
  });
69
216
  }
70
217
 
71
- async function clearPersistedData() {
218
+ async function clearPersistedData()${R("Promise<void>")}{
72
219
  const db = await openAuthDB();
73
- return new Promise((resolve, reject) => {
220
+ return new Promise${PT("void")}((resolve, reject) => {
74
221
  const tx = db.transaction(STORE_NAME, "readwrite");
75
222
  const store = tx.objectStore(STORE_NAME);
76
223
  const request = store.delete("session");
77
224
  request.onsuccess = () => resolve();
78
- request.onerror = () => reject(request.error);
225
+ request.onerror = () => reject((request${AS("IDBRequest")}).error);
79
226
  });
80
227
  }
81
228
 
82
- /**
83
- * Store auth data — token stays in memory only.
84
- * Only user info and expiry are persisted to IndexedDB.
85
- * @param {{ token?: string, user?: object, expiresAt?: number }} authData
86
- */
87
- export async function setAuth(authData) {
229
+ /** Store auth data in memory and persist user info to IndexedDB for offline access. */
230
+ export async function setAuth(authData${T("AuthData")})${R("Promise<void>")}{
88
231
  memoryAuth = authData;
89
232
  await persistUserData(authData);
90
233
  }
91
234
 
92
- /**
93
- * Get stored auth — returns memory copy (with token) or
94
- * falls back to IndexedDB (user info only, no token after refresh).
95
- */
96
- export async function getAuth() {
235
+ /** Get auth data from memory (or IndexedDB if memory is empty after page refresh). */
236
+ export async function getAuth()${R("Promise<AuthData | null>")}{
97
237
  if (memoryAuth) return memoryAuth;
98
238
 
99
239
  const userData = await loadUserData();
100
240
  if (userData) {
101
- // After refresh: user info available, but no token — re-login required
102
241
  memoryAuth = userData;
103
242
  }
104
243
  return memoryAuth;
105
244
  }
106
245
 
107
- /**
108
- * Clear auth — removes from memory and IndexedDB.
109
- */
110
- export async function clearAuth() {
246
+ /** Clear auth from memory and IndexedDB. Call on logout or 401. */
247
+ export async function clearAuth()${R("Promise<void>")}{
111
248
  memoryAuth = null;
112
249
  await clearPersistedData();
113
250
  }
114
251
 
115
- /**
116
- * Check if auth exists and is not expired.
117
- * @param {object | null} auth — The auth object from getAuth()
118
- */
119
- export function isAuthValid(auth) {
252
+ /** Check if auth exists and has not expired. Returns true if no expiresAt is set. */
253
+ export function isAuthValid(auth${T("AuthData | null")})${R("boolean")}{
120
254
  if (!auth) return false;
121
255
  if (!auth.expiresAt) return true;
122
256
  return Date.now() < auth.expiresAt;
123
257
  }
258
+
259
+ ${type === "cookie" ? `export const AUTH_WITH_CREDENTIALS = true;` : `export const AUTH_WITH_CREDENTIALS = false;`}
260
+
261
+ ${generateWithAuthHeaders(type, ts, R, T)}
262
+ ${generateIsAuthUrl(refreshPath, userEndpoint, ts, R, T)}
263
+ ${generateEnsureValidAuth(type === "cookie", ts, R, T, PT, refreshPath, ext)}
124
264
  `;
125
- writeFile(ctx, `auth-store.${ext}`, code);
265
+ writeFile(ctx, `auth/store.${ext}`, code);
126
266
  }
127
267
  //# sourceMappingURL=auth-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAEpB,MAAM,IAAI,GAAG;;;;;;;;;8EAS+D,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGhF,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-store.ts"],"names":[],"mappings":"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"}
@@ -4,43 +4,45 @@
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateAuthUser(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 PT = (type) => (ts ? `<${type}>` : "");
11
+ const AS = (type) => (ts ? ` as ${type}` : "");
7
12
  const userEndpoint = ctx.config.features.auth.userEndpoint;
8
13
  const code = `/**
9
14
  * Current User — fetch, cache, and invalidate the current user for offline access.
10
15
  *
11
16
  * Usage:
12
- * import { fetchCurrentUser, cacheUser, getCachedUser, clearCachedUser } from "./auth-user.${ext}";
17
+ * import { fetchCurrentUser, cacheUser, getCachedUser, clearCachedUser } from "./auth/user.${ext}";
13
18
  *
14
19
  * await fetchCurrentUser(); // Fetch from server & cache
15
20
  * const user = await getCachedUser(); // Get cached (offline-capable)
16
21
  * await clearCachedUser(); // Clear on logout
17
22
  */
18
23
 
19
- import { authenticatedFetch } from "./auth-fetch.${ext}";
24
+ import { fetchWithCache } from "../fetch-wrapper.${ext}";
20
25
 
21
26
  const DB_NAME = "swoff-auth-user";
22
27
  const STORE_NAME = "current-user";
23
28
 
24
- function openAuthDB() {
25
- return new Promise((resolve, reject) => {
29
+ function openAuthDB()${R("Promise<IDBDatabase>")}{
30
+ return new Promise${PT("IDBDatabase")}((resolve, reject) => {
26
31
  const request = indexedDB.open(DB_NAME, 1);
27
32
  request.onupgradeneeded = (e) => {
28
- const db = e.target.result;
33
+ const db = (e.target${AS("IDBOpenDBRequest")}).result;
29
34
  if (!db.objectStoreNames.contains(STORE_NAME)) {
30
35
  db.createObjectStore(STORE_NAME, { keyPath: "key" });
31
36
  }
32
37
  };
33
- request.onsuccess = (e) => resolve(e.target.result);
34
- request.onerror = (e) => reject(e.target.error);
38
+ request.onsuccess = (e) => resolve((e.target${AS("IDBOpenDBRequest")}).result);
39
+ request.onerror = (e) => reject((e.target${AS("IDBRequest")}).error);
35
40
  });
36
41
  }
37
42
 
38
- /**
39
- * Fetch current user from your backend and cache locally.
40
- * Uses userEndpoint from swoff.config.json.
41
- */
42
- export async function fetchCurrentUser() {
43
- const response = await authenticatedFetch("${userEndpoint}");
43
+ /** Fetch current user from the user endpoint and cache the result in IndexedDB. */
44
+ export async function fetchCurrentUser()${R("Promise<Record<string, unknown>>")}{
45
+ const { response } = await fetchWithCache("${userEndpoint}", { auth: true });
44
46
  if (!response.ok) throw new Error("Failed to fetch user");
45
47
 
46
48
  const user = await response.json();
@@ -48,12 +50,10 @@ export async function fetchCurrentUser() {
48
50
  return user;
49
51
  }
50
52
 
51
- /**
52
- * Store user in IndexedDB for offline access.
53
- */
54
- export async function cacheUser(user) {
53
+ /** Persist user data to IndexedDB for offline access. */
54
+ export async function cacheUser(user${T("Record<string, unknown>")})${R("Promise<void>")}{
55
55
  const db = await openAuthDB();
56
- return new Promise((resolve, reject) => {
56
+ return new Promise<void>((resolve, reject) => {
57
57
  const tx = db.transaction(STORE_NAME, "readwrite");
58
58
  const store = tx.objectStore(STORE_NAME);
59
59
  store.put({ key: "user", value: user });
@@ -62,34 +62,30 @@ export async function cacheUser(user) {
62
62
  });
63
63
  }
64
64
 
65
- /**
66
- * Get cached user works offline.
67
- */
68
- export async function getCachedUser() {
65
+ /** Load user data from IndexedDB cache (no token — only user object survives refresh). */
66
+ export async function getCachedUser()${R("Promise<Record<string, unknown> | null>")}{
69
67
  const db = await openAuthDB();
70
- return new Promise((resolve, reject) => {
68
+ return new Promise${PT("Record<string, unknown> | null")}((resolve, reject) => {
71
69
  const tx = db.transaction(STORE_NAME, "readonly");
72
70
  const store = tx.objectStore(STORE_NAME);
73
71
  const request = store.get("user");
74
- request.onsuccess = () => resolve(request.result?.value || null);
75
- request.onerror = () => reject(request.error);
72
+ request.onsuccess = () => resolve((request${AS("IDBRequest")}).result?.value ?? null);
73
+ request.onerror = () => reject((request${AS("IDBRequest")}).error);
76
74
  });
77
75
  }
78
76
 
79
- /**
80
- * Clear cached user on logout.
81
- */
82
- export async function clearCachedUser() {
77
+ /** Remove user data from IndexedDB cache. Call on logout. */
78
+ export async function clearCachedUser()${R("Promise<void>")}{
83
79
  const db = await openAuthDB();
84
- return new Promise((resolve, reject) => {
80
+ return new Promise${PT("void")}((resolve, reject) => {
85
81
  const tx = db.transaction(STORE_NAME, "readwrite");
86
82
  const store = tx.objectStore(STORE_NAME);
87
83
  store.delete("user");
88
84
  tx.oncomplete = () => resolve();
89
- tx.onerror = () => reject(tx.error);
85
+ tx.onerror = () => reject((tx${AS("IDBTransaction")}).error);
90
86
  });
91
87
  }
92
88
  `;
93
- writeFile(ctx, `auth-user.${ext}`, code);
89
+ writeFile(ctx, `auth/user.${ext}`, code);
94
90
  }
95
91
  //# sourceMappingURL=auth-user.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-user.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,gBAAgB,CAAC,GAAqB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;IAE3D,MAAM,IAAI,GAAG;;;;gGAIiF,GAAG;;;;;;;mDAOhD,GAAG;;;;;;;;;;;;;;;;;;;;;;;;+CAwBP,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD1D,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"auth-user.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/auth-user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,gBAAgB,CAAC,GAAqB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;IAE3D,MAAM,IAAI,GAAG;;;;gGAIiF,GAAG;;;;;;;mDAOhD,GAAG;;;;;uBAK/B,CAAC,CAAC,sBAAsB,CAAC;sBAC1B,EAAE,CAAC,aAAa,CAAC;;;4BAGX,EAAE,CAAC,kBAAkB,CAAC;;;;;kDAKA,EAAE,CAAC,kBAAkB,CAAC;+CACzB,EAAE,CAAC,YAAY,CAAC;;;;;0CAKrB,CAAC,CAAC,kCAAkC,CAAC;+CAChC,YAAY;;;;;;;;;sCASrB,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;uCAYjD,CAAC,CAAC,yCAAyC,CAAC;;sBAE7D,EAAE,CAAC,gCAAgC,CAAC;;;;gDAIV,EAAE,CAAC,YAAY,CAAC;6CACnB,EAAE,CAAC,YAAY,CAAC;;;;;yCAKpB,CAAC,CAAC,eAAe,CAAC;;sBAErC,EAAE,CAAC,MAAM,CAAC;;;;;mCAKG,EAAE,CAAC,gBAAgB,CAAC;;;CAGtD,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC"}
@@ -4,6 +4,9 @@
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateBackgroundSync(ctx) {
6
6
  const ext = ctx.ext;
7
+ const ts = ext === "ts";
8
+ const T = (type) => (ts ? `: ${type}` : "");
9
+ const R = (type) => (ts ? `: ${type} ` : " ");
7
10
  const code = `/**
8
11
  * Swoff Background Sync
9
12
  * Register sync events for processing mutation queue after tab close.
@@ -17,8 +20,7 @@ export function generateBackgroundSync(ctx) {
17
20
  * url: "/api/todos",
18
21
  * body: { title: "Grocery" },
19
22
  * tags: ["todos"],
20
- * storeName: "todos",
21
- * tempId: "temp_abc123",
23
+ * tags: ["todos"],
22
24
  * });
23
25
  */
24
26
 
@@ -26,7 +28,7 @@ import { queueMutation, processMutationQueue, getPendingCount } from "./mutation
26
28
 
27
29
  const SYNC_TAG = "sync-mutations";
28
30
 
29
- async function registerSync() {
31
+ async function registerSync()${R("Promise<void>")}{
30
32
  if (!("serviceWorker" in navigator) || !("SyncManager" in window)) {
31
33
  window.addEventListener("online", processMutationQueue, { once: true });
32
34
  return;
@@ -40,12 +42,14 @@ async function registerSync() {
40
42
  }
41
43
  }
42
44
 
43
- export async function syncWhenPossible(mutation) {
45
+ /** Queue a mutation and register a background sync event so it runs even after tab close. */
46
+ export async function syncWhenPossible(mutation${T("object")})${R("Promise<void>")}{
44
47
  await queueMutation(mutation);
45
48
  await registerSync();
46
49
  }
47
50
 
48
- export async function retrySync() {
51
+ /** Re-register a background sync if mutations are still pending. Called automatically after each sync cycle. */
52
+ export async function retrySync()${R("Promise<void>")}{
49
53
  if (!("serviceWorker" in navigator) || !("SyncManager" in window)) return;
50
54
  const count = await getPendingCount();
51
55
  if (count > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/background-sync.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,IAAI,GAAG;;;;;;iEAMkD,GAAG;;;;;;;;;;;;yFAYqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgC3F,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"background-sync.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/background-sync.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,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG;;;;;;iEAMkD,GAAG;;;;;;;;;;;yFAWqB,GAAG;;;;+BAI7D,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;iDAeA,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;mCAM/C,CAAC,CAAC,eAAe,CAAC;;;;;;;;;CASpD,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
@@ -4,6 +4,9 @@
4
4
  import { writeFile } from "./context.js";
5
5
  export function generateCache(ctx) {
6
6
  const ext = ctx.ext;
7
+ const ts = ext === "ts";
8
+ const T = (type) => (ts ? `: ${type}` : "");
9
+ const R = (type) => (ts ? `: ${type} ` : " ");
7
10
  const code = `/**
8
11
  * Swoff Cache Invalidation
9
12
  * Framework-agnostic cache tag invalidation.
@@ -15,7 +18,8 @@ export function generateCache(ctx) {
15
18
  * await invalidateByTag("todos");
16
19
  */
17
20
 
18
- export async function invalidateByTag(tag) {
21
+ /** Invalidate all cached responses tagged with the given tag. Dispatches cache-invalidated event. */
22
+ export async function invalidateByTag(tag${T("string")})${R("Promise<void>")}{
19
23
  if (!navigator.serviceWorker?.controller) return;
20
24
 
21
25
  navigator.serviceWorker.controller.postMessage({
@@ -28,10 +32,9 @@ export async function invalidateByTag(tag) {
28
32
  );
29
33
  }
30
34
 
31
- export async function invalidateByTags(tags) {
32
- for (const tag of tags) {
33
- await invalidateByTag(tag);
34
- }
35
+ /** Invalidate all cached responses matching any of the given tags. */
36
+ export async function invalidateByTags(tags${T("string[]")})${R("Promise<void>")}{
37
+ await Promise.all(tags.map((tag) => invalidateByTag(tag)));
35
38
  }
36
39
  `;
37
40
  writeFile(ctx, `cache.${ext}`, code);
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAEpB,MAAM,IAAI,GAAG;;;;;sDAKuC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBxD,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../../src/lib/generators/file-generators/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,UAAU,aAAa,CAAC,GAAqB;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG;;;;;sDAKuC,GAAG;;;;;;;2CAOd,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;;;;;;;;;;;;6CAc/B,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC;;;CAG/E,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,101 @@
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 pwaImport = pwaEnabled
12
+ ? `import { setupPwaInstall } from "./pwa/install.${ext}";
13
+ `
14
+ : "";
15
+ const pwaCall = pwaEnabled ? `setupPwaInstall();\n` : "";
16
+ const mutationImport = mutationQueueEnabled
17
+ ? `import { processMutationQueue } from "./mutation-queue.${ext}";
18
+ `
19
+ : "";
20
+ const mutationOnlineListener = mutationQueueEnabled
21
+ ? `
22
+ // --- Mutation Queue Online Listener ---
23
+ window.addEventListener("online", processMutationQueue);
24
+ `
25
+ : "";
26
+ const crossTabHandler = crossTabSync
27
+ ? `
28
+ if (event.data.type === "TAG_INVALIDATED" && event.data.tag) {
29
+ window.dispatchEvent(
30
+ new CustomEvent("cache-invalidated", {
31
+ detail: { tags: [event.data.tag] },
32
+ })
33
+ );
34
+ }
35
+ `
36
+ : "";
37
+ const swImport = `import { initServiceWorker as swInit } from "./sw/injector.${ext}";
38
+ `;
39
+ const code = `/**
40
+ * Swoff Client Injector
41
+ * Orchestrates SW registration, PWA install, and cross-tab sync.
42
+ * Single entry point for all Swoff client-side features.
43
+ *
44
+ * Usage:
45
+ * import { initServiceWorker } from './swoff/client-injector.${ext}';
46
+ * initServiceWorker();
47
+ *
48
+ * Window events (dispatched by this module):
49
+ * sw-progress - Download progress (detail: { percent, downloaded, total })
50
+ * sw-ready - SW active and controlling page
51
+ * sw-error - SW registration failed
52
+ * cache-invalidated - Cache entries cleared (detail: { tags })
53
+ * mutation-sync-complete - Queued mutations synced (detail: { succeeded, failed })
54
+ * mutation-queue-changed - Queue modified
55
+ *
56
+ * Window events (dispatched by feature modules):
57
+ * pwa-installable - PWA can be installed (detail: { isInstallable: true })
58
+ * pwa-installed - User accepted install (detail: { outcome: 'accepted' })
59
+ * sw-auth-unauthorized - 401 response received
60
+ * sw-auth-state-change - Login or logout (detail: { authenticated: boolean })
61
+ * mutation-rollback - Mutation exhausted retries
62
+ * sw-update-available - New version ready (detail: { version })
63
+ * sw-version-detected - Version info available
64
+ */
65
+ ${pwaImport}${mutationImport}${swImport}
66
+ ${pwaCall}${mutationOnlineListener}
67
+ // --- SW Message Listener ---
68
+ if (typeof window !== "undefined" && "serviceWorker" in navigator) {
69
+ navigator.serviceWorker.addEventListener("message", (event) => {
70
+ if (event.data.type === "SW_PROGRESS") {
71
+ const { percent, downloaded, total } = event.data;
72
+ window.dispatchEvent(
73
+ new CustomEvent("sw-progress", {
74
+ detail: { percent, downloaded, total },
75
+ })
76
+ );
77
+ }
78
+ if (event.data.type === "BACKGROUND_SYNC_COMPLETE") {
79
+ const { succeeded, failed, tags } = event.data.detail;
80
+ window.dispatchEvent(
81
+ new CustomEvent("mutation-sync-complete", {
82
+ detail: { succeeded, failed },
83
+ })
84
+ );
85
+ if (tags && tags.length > 0) {
86
+ window.dispatchEvent(
87
+ new CustomEvent("cache-invalidated", { detail: { tags } })
88
+ );
89
+ }
90
+ window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
91
+ }${crossTabHandler} });
92
+ }
93
+
94
+ /** Initialize SW registration and all client-side features (PWA install, mutation queue, cross-tab sync). Call once at app startup. */
95
+ export async function initServiceWorker()${ts ? ": Promise<void>" : " "}{
96
+ await swInit();
97
+ }
98
+ `;
99
+ writeFile(ctx, `client-injector.${ext}`, code);
100
+ }
101
+ //# 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;IAEtD,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,8DAA8D,GAAG;CACnF,CAAC;IAEA,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;;;CAGtE,CAAC;IAEA,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}