@swoff/cli 0.3.9 → 0.3.11

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 (216) hide show
  1. package/dist/__tests__/assemble-sw.test.js +8 -17
  2. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  3. package/dist/__tests__/cli-integration.test.js +2 -2
  4. package/dist/__tests__/cli-integration.test.js.map +1 -1
  5. package/dist/__tests__/config-types.test.js +18 -8
  6. package/dist/__tests__/config-types.test.js.map +1 -1
  7. package/dist/__tests__/file-generators.test.js +3 -3
  8. package/dist/__tests__/file-generators.test.js.map +1 -1
  9. package/dist/__tests__/loader.test.js +5 -1
  10. package/dist/__tests__/loader.test.js.map +1 -1
  11. package/dist/__tests__/validator.test.js +6 -13
  12. package/dist/__tests__/validator.test.js.map +1 -1
  13. package/dist/index.js +41 -35
  14. package/dist/index.js.map +1 -1
  15. package/dist/lib/cli/help.js +13 -8
  16. package/dist/lib/cli/help.js.map +1 -1
  17. package/dist/lib/commands/add.js +38 -8
  18. package/dist/lib/commands/add.js.map +1 -1
  19. package/dist/lib/commands/assets.js +38 -0
  20. package/dist/lib/commands/assets.js.map +1 -0
  21. package/dist/lib/commands/clean.js +33 -17
  22. package/dist/lib/commands/clean.js.map +1 -1
  23. package/dist/lib/commands/generate-assets.js +44 -0
  24. package/dist/lib/commands/generate-assets.js.map +1 -0
  25. package/dist/lib/commands/generate-guide.js +22 -29
  26. package/dist/lib/commands/generate-guide.js.map +1 -1
  27. package/dist/lib/commands/generate.js +16 -9
  28. package/dist/lib/commands/generate.js.map +1 -1
  29. package/dist/lib/commands/info.js +36 -45
  30. package/dist/lib/commands/info.js.map +1 -1
  31. package/dist/lib/commands/init.js +25 -3
  32. package/dist/lib/commands/init.js.map +1 -1
  33. package/dist/lib/commands/validate.js +1 -1
  34. package/dist/lib/commands/validate.js.map +1 -1
  35. package/dist/lib/config/loader.js +25 -20
  36. package/dist/lib/config/loader.js.map +1 -1
  37. package/dist/lib/config/validator.js +20 -11
  38. package/dist/lib/config/validator.js.map +1 -1
  39. package/dist/lib/generators/asset-generator/dependency-resolver.js +76 -0
  40. package/dist/lib/generators/asset-generator/dependency-resolver.js.map +1 -0
  41. package/dist/lib/generators/asset-generator/generate.js +111 -0
  42. package/dist/lib/generators/asset-generator/generate.js.map +1 -0
  43. package/dist/lib/generators/asset-generator/guide.js +43 -0
  44. package/dist/lib/generators/asset-generator/guide.js.map +1 -0
  45. package/dist/lib/generators/asset-generator/html-patcher.js +65 -0
  46. package/dist/lib/generators/asset-generator/html-patcher.js.map +1 -0
  47. package/dist/lib/generators/asset-generator/ico-encoder.js +29 -0
  48. package/dist/lib/generators/asset-generator/ico-encoder.js.map +1 -0
  49. package/dist/lib/generators/asset-generator/manifest-patcher.js +23 -0
  50. package/dist/lib/generators/asset-generator/manifest-patcher.js.map +1 -0
  51. package/dist/lib/generators/asset-generator/maskable.js +15 -0
  52. package/dist/lib/generators/asset-generator/maskable.js.map +1 -0
  53. package/dist/lib/generators/asset-generator/rasterize.js +32 -0
  54. package/dist/lib/generators/asset-generator/rasterize.js.map +1 -0
  55. package/dist/lib/generators/asset-generator/sizes.js +25 -0
  56. package/dist/lib/generators/asset-generator/sizes.js.map +1 -0
  57. package/dist/lib/generators/file-generators/api-config.js +6 -0
  58. package/dist/lib/generators/file-generators/api-config.js.map +1 -0
  59. package/dist/lib/generators/file-generators/auth-state.js +2 -34
  60. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  61. package/dist/lib/generators/file-generators/auth-store.js +2 -250
  62. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  63. package/dist/lib/generators/file-generators/auth-user.js +3 -81
  64. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  65. package/dist/lib/generators/file-generators/background-sync.js +2 -57
  66. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  67. package/dist/lib/generators/file-generators/cache.js +2 -30
  68. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  69. package/dist/lib/generators/file-generators/client-injector.js +2 -130
  70. package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
  71. package/dist/lib/generators/file-generators/fetch-state.js +6 -0
  72. package/dist/lib/generators/file-generators/fetch-state.js.map +1 -0
  73. package/dist/lib/generators/file-generators/fetch-wrapper.js +3 -295
  74. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  75. package/dist/lib/generators/file-generators/generate-framework-adapters.js +56 -0
  76. package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -0
  77. package/dist/lib/generators/file-generators/generate-hooks.js +4 -3
  78. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  79. package/dist/lib/generators/file-generators/gql-wrapper.js +3 -132
  80. package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -1
  81. package/dist/lib/generators/file-generators/guide-generator.js +29 -1
  82. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
  83. package/dist/lib/generators/file-generators/invalidation-tags.js +3 -258
  84. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  85. package/dist/lib/generators/file-generators/manifest.js +13 -3
  86. package/dist/lib/generators/file-generators/manifest.js.map +1 -1
  87. package/dist/lib/generators/file-generators/mutation-queue.js +3 -305
  88. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  89. package/dist/lib/generators/file-generators/mutation-state.js +2 -117
  90. package/dist/lib/generators/file-generators/mutation-state.js.map +1 -1
  91. package/dist/lib/generators/file-generators/push.js +6 -138
  92. package/dist/lib/generators/file-generators/push.js.map +1 -1
  93. package/dist/lib/generators/file-generators/pwa-install.js +11 -84
  94. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  95. package/dist/lib/generators/file-generators/reset.js +6 -0
  96. package/dist/lib/generators/file-generators/reset.js.map +1 -0
  97. package/dist/lib/generators/file-generators/server-push.js +3 -134
  98. package/dist/lib/generators/file-generators/server-push.js.map +1 -1
  99. package/dist/lib/generators/file-generators/sw-generator-build.js +4 -4
  100. package/dist/lib/generators/file-generators/sw-injector.js +9 -230
  101. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  102. package/dist/lib/generators/file-generators/type-definitions.js +2 -111
  103. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  104. package/dist/lib/generators/sw-generator.js +5 -5
  105. package/dist/lib/generators/sw-generator.js.map +1 -1
  106. package/dist/lib/generators/sw-sections/activate-handler.js +25 -3
  107. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  108. package/dist/lib/generators/sw-sections/assemble-sw.js +13 -12
  109. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  110. package/dist/lib/generators/sw-sections/background-sync-handler.js +14 -7
  111. package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
  112. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  113. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  114. package/dist/lib/generators/sw-sections/fetch-handler.js +165 -104
  115. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  116. package/dist/lib/generators/sw-sections/glob-matcher-code.js.map +1 -0
  117. package/dist/lib/generators/sw-sections/server-push-handler.js +12 -0
  118. package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -1
  119. package/dist/lib/generators/sw-sections/tag-management.js +6 -4
  120. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  121. package/dist/lib/generators/swoff-files-generator.js +66 -35
  122. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  123. package/dist/lib/shared/config-types.js +89 -28
  124. package/dist/lib/shared/config-types.js.map +1 -1
  125. package/dist/runtime/api-config.js +18 -0
  126. package/dist/runtime/api-config.js.map +1 -0
  127. package/dist/runtime/auth-state.js +35 -0
  128. package/dist/runtime/auth-state.js.map +1 -0
  129. package/dist/runtime/auth-store.js +273 -0
  130. package/dist/runtime/auth-store.js.map +1 -0
  131. package/dist/runtime/auth-user.js +168 -0
  132. package/dist/runtime/auth-user.js.map +1 -0
  133. package/dist/runtime/background-sync.js +59 -0
  134. package/dist/runtime/background-sync.js.map +1 -0
  135. package/dist/runtime/cache.js +30 -0
  136. package/dist/runtime/cache.js.map +1 -0
  137. package/dist/runtime/client-injector.js +136 -0
  138. package/dist/runtime/client-injector.js.map +1 -0
  139. package/dist/runtime/fetch-state.js +42 -0
  140. package/dist/runtime/fetch-state.js.map +1 -0
  141. package/dist/runtime/fetch-wrapper.js +355 -0
  142. package/dist/runtime/fetch-wrapper.js.map +1 -0
  143. package/dist/runtime/gql-wrapper.js +135 -0
  144. package/dist/runtime/gql-wrapper.js.map +1 -0
  145. package/dist/runtime/invalidation-tags.js +260 -0
  146. package/dist/runtime/invalidation-tags.js.map +1 -0
  147. package/dist/runtime/mutation-queue.js +304 -0
  148. package/dist/runtime/mutation-queue.js.map +1 -0
  149. package/dist/runtime/mutation-state.js +117 -0
  150. package/dist/runtime/mutation-state.js.map +1 -0
  151. package/dist/runtime/push.js +138 -0
  152. package/dist/runtime/push.js.map +1 -0
  153. package/dist/runtime/pwa-index.js +11 -0
  154. package/dist/runtime/pwa-index.js.map +1 -0
  155. package/dist/runtime/pwa-injector.js +44 -0
  156. package/dist/runtime/pwa-injector.js.map +1 -0
  157. package/dist/runtime/pwa-install.js +83 -0
  158. package/dist/runtime/pwa-install.js.map +1 -0
  159. package/dist/runtime/pwa-prompt.js +54 -0
  160. package/dist/runtime/pwa-prompt.js.map +1 -0
  161. package/dist/runtime/reset.js +148 -0
  162. package/dist/runtime/reset.js.map +1 -0
  163. package/dist/runtime/server-push.js +148 -0
  164. package/dist/runtime/server-push.js.map +1 -0
  165. package/dist/runtime/sw-injector.js +236 -0
  166. package/dist/runtime/sw-injector.js.map +1 -0
  167. package/dist/runtime/type-definitions.js +138 -0
  168. package/dist/runtime/type-definitions.js.map +1 -0
  169. package/dist/runtime/utils.js +27 -0
  170. package/dist/runtime/utils.js.map +1 -0
  171. package/package.json +3 -2
  172. package/templates/react/useAuth.jsx +106 -0
  173. package/templates/react/useAuth.tsx +118 -0
  174. package/templates/{hooks → react}/useBackgroundSync.jsx +6 -4
  175. package/templates/{hooks → react}/useBackgroundSync.tsx +6 -4
  176. package/templates/{hooks → react}/useCacheInvalidation.jsx +2 -2
  177. package/templates/react/useCacheInvalidation.tsx +52 -0
  178. package/templates/react/useCachedFetch.jsx +140 -0
  179. package/templates/react/useCachedFetch.tsx +160 -0
  180. package/templates/react/useIsFetching.jsx +32 -0
  181. package/templates/react/useIsFetching.tsx +32 -0
  182. package/templates/react/useMutation.jsx +141 -0
  183. package/templates/react/useMutation.tsx +180 -0
  184. package/templates/react/useMutationQueue.jsx +69 -0
  185. package/templates/react/useMutationQueue.tsx +83 -0
  186. package/templates/react/useNetworkStatus.jsx +61 -0
  187. package/templates/react/useNetworkStatus.tsx +76 -0
  188. package/templates/react/usePrefetch.jsx +32 -0
  189. package/templates/react/usePrefetch.tsx +42 -0
  190. package/templates/{hooks → react}/usePushSubscription.jsx +4 -4
  191. package/templates/{hooks → react}/usePushSubscription.tsx +4 -4
  192. package/templates/{hooks → react}/useSWUpdate.jsx +3 -3
  193. package/templates/{hooks → react}/useSWUpdate.tsx +4 -4
  194. package/templates/react/useSwoffReset.jsx +32 -0
  195. package/templates/react/useSwoffReset.tsx +39 -0
  196. package/docs/API.md +0 -565
  197. package/docs/ARCHITECTURE.md +0 -374
  198. package/docs/CLI.md +0 -148
  199. package/docs/COMPARISON.md +0 -192
  200. package/docs/CONFIG.md +0 -277
  201. package/docs/ECOSYSTEM.md +0 -33
  202. package/templates/hooks/useAuth.jsx +0 -30
  203. package/templates/hooks/useAuth.tsx +0 -30
  204. package/templates/hooks/useCacheInvalidation.tsx +0 -19
  205. package/templates/hooks/useCachedFetch.jsx +0 -69
  206. package/templates/hooks/useCachedFetch.tsx +0 -81
  207. package/templates/hooks/useMutation.jsx +0 -76
  208. package/templates/hooks/useMutation.tsx +0 -109
  209. package/templates/hooks/useMutationQueue.jsx +0 -43
  210. package/templates/hooks/useMutationQueue.tsx +0 -50
  211. package/templates/hooks/useNetworkStatus.jsx +0 -19
  212. package/templates/hooks/useNetworkStatus.tsx +0 -19
  213. package/templates/hooks/usePrefetch.jsx +0 -10
  214. package/templates/hooks/usePrefetch.tsx +0 -25
  215. /package/templates/{hooks → react}/useMutationState.jsx +0 -0
  216. /package/templates/{hooks → react}/useMutationState.tsx +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gql-wrapper.js","sourceRoot":"","sources":["../../src/runtime/gql-wrapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,UAAU,sBAAsB,CACpC,GAAmB,EACnB,QAAgB;IAEhB,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IAExB,MAAM,gBAAgB,GAAG,EAAE;QACzB,CAAC,CAAC;;;;;;;;CAQL;QACG,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;;;8EAOqE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;gDA4BjC,GAAG;;EAEjD,gBAAgB;;iCAEe,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC;;;;;;gCAM1C,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;;;;;;;6BAOtC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,iBAAiB,CAAC;;;;;;;;8BAQ3C,CAAC,CAAC,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC;;;;;;;;;;oCAUrC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC;SACrC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;WACb,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC;GAC3B,CAAC,CAAC,EAAE,EAAE,uBAAuB,CAAC;;;;;;;0DAOyB,QAAQ;;;;;;;;;;;;;;;;;;8BAkBpC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC;;;;gCAIT,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC;SACjC,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;aACX,CAAC,CAAC,EAAE,EAAE,qCAAqC,CAAC;WAC9C,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC;GAC3B,CAAC,CAAC,EAAE,EAAE,uBAAuB,CAAC;uBACV,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC;;;;iCAIA,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC;YAC/B,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC;aACd,CAAC,CAAC,EAAE,EAAE,qCAAqC,CAAC;WAC9C,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC;GAC3B,CAAC,CAAC,EAAE,EAAE,uBAAuB,CAAC;uBACV,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC;;CAEhC,CAAC;AACF,CAAC"}
@@ -0,0 +1,260 @@
1
+ import { T, R } from "./utils.js";
2
+ function escapeRegex(str) {
3
+ return str.replace(/[.+^${}()|[\]\\]/g, "\\$&");
4
+ }
5
+ function compilePatternEntry(rawPattern, tagTemplates) {
6
+ const rawParts = rawPattern.split("/");
7
+ const parts = rawParts.filter((p) => p !== "");
8
+ const paramNames = [];
9
+ const regexParts = [];
10
+ for (let i = 0; i < parts.length; i++) {
11
+ const part = parts[i];
12
+ if (part === "**") {
13
+ regexParts.push("(?:\\/[^/]+)*");
14
+ continue;
15
+ }
16
+ if (regexParts.length > 0) {
17
+ regexParts.push("\\/");
18
+ }
19
+ if (part === "*") {
20
+ regexParts.push("[^/]+");
21
+ }
22
+ else if (part.startsWith(":")) {
23
+ paramNames.push(part.slice(1));
24
+ regexParts.push("([^/]+)");
25
+ }
26
+ else if (part.includes("{")) {
27
+ const close = part.indexOf("}");
28
+ if (close !== -1) {
29
+ const inner = part.slice(part.indexOf("{") + 1, close);
30
+ const alternatives = inner.split(",").map((s) => escapeRegex(s.trim()));
31
+ regexParts.push("(?:" + alternatives.join("|") + ")");
32
+ }
33
+ else {
34
+ regexParts.push(escapeRegex(part));
35
+ }
36
+ }
37
+ else {
38
+ regexParts.push(escapeRegex(part));
39
+ }
40
+ }
41
+ if (regexParts.length === 0)
42
+ return null;
43
+ const hasLeadingSlash = rawPattern.startsWith("/");
44
+ const regex = (hasLeadingSlash ? "^\\/" : "^") + regexParts.join("") + "$";
45
+ // Validate template params match captured params
46
+ const templatePlaceholders = new Set();
47
+ for (const tmpl of tagTemplates) {
48
+ const matches = tmpl.match(/\{(\w+)\}/g);
49
+ if (matches) {
50
+ for (const m of matches) {
51
+ templatePlaceholders.add(m.slice(1, -1));
52
+ }
53
+ }
54
+ }
55
+ for (const ph of templatePlaceholders) {
56
+ if (!paramNames.includes(ph)) {
57
+ return null;
58
+ }
59
+ }
60
+ return { regex, params: paramNames, templates: tagTemplates };
61
+ }
62
+ function generatePatternCode(patterns) {
63
+ const entries = [];
64
+ for (const [pattern, templates] of Object.entries(patterns)) {
65
+ const compiled = compilePatternEntry(pattern, templates);
66
+ if (compiled) {
67
+ entries.push(` { re: new RegExp("${compiled.regex.replace(/\\/g, "\\\\")}"), params: ${JSON.stringify(compiled.params)}, templates: ${JSON.stringify(compiled.templates)} }`);
68
+ }
69
+ }
70
+ return "[\n" + entries.join(",\n") + "\n]";
71
+ }
72
+ function generateSingularizationCode(singularization) {
73
+ if (!singularization || Object.keys(singularization).length === 0)
74
+ return "null";
75
+ return JSON.stringify(singularization);
76
+ }
77
+ function generateCascadingCode(cascading) {
78
+ if (!cascading || Object.keys(cascading).length === 0)
79
+ return "null";
80
+ return JSON.stringify(cascading);
81
+ }
82
+ export function generateInvalidationTagsCode(ctx, prefixes, patterns, singularization, cascading) {
83
+ const { ext, ts } = ctx;
84
+ const patternCode = generatePatternCode(patterns);
85
+ const singularizationCode = generateSingularizationCode(singularization);
86
+ const cascadingCode = generateCascadingCode(cascading);
87
+ const prefixesCode = JSON.stringify(prefixes);
88
+ return `/**
89
+ * Swoff Invalidation Tags Helper
90
+ * Pattern-based tag generation from URLs with configurable glob patterns,
91
+ * prefix skipping, singularization, and cascading invalidation.
92
+ *
93
+ * Usage:
94
+ * import { generateTags, invalidateUrl } from './swoff/cache/tags.${ext}';
95
+ *
96
+ * // Generate tags from URL
97
+ * generateTags("/api/todos"); // ["todos"]
98
+ * generateTags("/api/todos/42"); // ["todos", "todo:42"]
99
+ *
100
+ * // Use with fetch wrapper
101
+ * const data = await fetchWithCache("/api/todos", {
102
+ * tags: generateTags("/api/todos"),
103
+ * });
104
+ *
105
+ * // Invalidate after mutation
106
+ * await invalidateUrl("/api/todos/42");
107
+ */
108
+
109
+ import { invalidateByTags } from "./index.${ext}";
110
+ ${ts ? `
111
+ interface TagPattern {
112
+ re: RegExp;
113
+ params: string[];
114
+ templates: string[];
115
+ }
116
+ ` : ""}
117
+ // Pattern entries compiled from swoff.config.json
118
+ const TAG_PATTERNS${T(ts, "TagPattern[]")} = ${patternCode};
119
+
120
+ // URL path prefixes to skip during tag generation
121
+ const SKIP_PREFIXES = ${prefixesCode};
122
+
123
+ // Custom singularization map (plural → singular)
124
+ const SINGULARIZATION${T(ts, "Record<string, string> | null")} = ${singularizationCode};
125
+
126
+ // Cascading invalidation map (tag → dependent tags)
127
+ const CASCADING${T(ts, "Record<string, string[]> | null")} = ${cascadingCode};
128
+
129
+ /** Generate cache invalidation tags from a URL path. Tries configured patterns first, falls back to segment-based generation. */
130
+ export function generateTags(url${T(ts, "string | URL")})${R(ts, "string[]")}{
131
+ const parsed = typeof url === "string" ? new URL(url, window.location.origin) : url;
132
+ const path = parsed.pathname;
133
+
134
+ // Try configured patterns first
135
+ for (const entry of TAG_PATTERNS) {
136
+ const match = path.match(entry.re);
137
+ if (match) {
138
+ const params${T(ts, "Record<string, string>")} = {};
139
+ for (let i = 0; i < entry.params.length; i++) {
140
+ params[entry.params[i]] = match[i + 1];
141
+ }
142
+ return entry.templates.map((tmpl) => {
143
+ return tmpl.replace(/\\{(\\w+)\\}/g, (_, key) => params[key] ?? "");
144
+ });
145
+ }
146
+ }
147
+
148
+ // Fallback: segment-based generation
149
+ const segments = path.split("/").filter(Boolean);
150
+ if (segments.length === 0) return ["root"];
151
+
152
+ let startIdx = 0;
153
+ while (startIdx < segments.length && SKIP_PREFIXES.includes(segments[startIdx])) {
154
+ startIdx++;
155
+ }
156
+
157
+ const resourceSegments = segments.slice(startIdx);
158
+ if (resourceSegments.length === 0) return ["root"];
159
+
160
+ const tags = [];
161
+ tags.push(resourceSegments[0]);
162
+
163
+ if (resourceSegments.length >= 2 && !isNaN(Number(resourceSegments[1]))) {
164
+ const collection = resourceSegments[0];
165
+ const id = resourceSegments[1];
166
+ const singular = SINGULARIZATION && SINGULARIZATION[collection] !== undefined
167
+ ? SINGULARIZATION[collection]
168
+ : collection.replace(/s$/, "");
169
+ tags.push(\`\${singular}:\${id}\`);
170
+ }
171
+
172
+ for (let i = 2; i < resourceSegments.length; i++) {
173
+ if (isNaN(Number(resourceSegments[i]))) {
174
+ tags.push(resourceSegments[i]);
175
+ }
176
+ }
177
+
178
+ return tags;
179
+ }
180
+
181
+ /** Generate tags prefixed by HTTP method. e.g. POST /api/todos → ["post-todos"]. */
182
+ export function generateTagsFromMethod(method${T(ts, "string")}, url${T(ts, "string | URL")})${R(ts, "string[]")}{
183
+ const tags = generateTags(url);
184
+ if (method === "GET" || method === "HEAD") return tags;
185
+ return tags.map((tag) => \`\${method.toLowerCase()}-\${tag}\`);
186
+ }
187
+
188
+ /** Invalidate cached responses related to a URL, including cascading dependencies. */
189
+ export async function invalidateUrl(url${T(ts, "string | URL")})${R(ts, "Promise<void>")}{
190
+ const tags = generateTags(url);
191
+ const allTags = CASCADING ? expandCascading(tags) : tags;
192
+ await invalidateByTags(allTags);
193
+ }
194
+
195
+ /** Invalidate cached responses tagged with method-prefixed tags, including cascading dependencies. */
196
+ export async function invalidateByMethod(method${T(ts, "string")}, url${T(ts, "string | URL")})${R(ts, "Promise<void>")}{
197
+ const tags = generateTagsFromMethod(method, url);
198
+ const allTags = CASCADING ? expandCascading(tags) : tags;
199
+ await invalidateByTags(allTags);
200
+ }
201
+
202
+ /** Expand tags with their cascading dependencies, deduplicated. */
203
+ export function expandCascading(tags${T(ts, "string[]")})${R(ts, "string[]")}{
204
+ if (!CASCADING) return [...tags];
205
+ const result = new Set(tags);
206
+ for (const tag of tags) {
207
+ const deps = CASCADING[tag];
208
+ if (deps) {
209
+ for (const dep of deps) {
210
+ result.add(dep);
211
+ }
212
+ }
213
+ }
214
+ return [...result];
215
+ }
216
+
217
+ /** Introspect: get all URLs cached under a given tag. */
218
+ export async function getUrlsForTag(tag${T(ts, "string")})${R(ts, "Promise<{ url: string; actualUrl: string }[]>")}{
219
+ const controller = navigator.serviceWorker?.controller;
220
+ if (!controller) return [];
221
+ return new Promise((resolve) => {
222
+ const channel = new MessageChannel();
223
+ channel.port1.onmessage = (event) => {
224
+ resolve(event.data.urls || []);
225
+ };
226
+ controller.postMessage(
227
+ { type: "GET_URLS_FOR_TAG", tag },
228
+ [channel.port2],
229
+ );
230
+ });
231
+ }
232
+
233
+ /** Introspect: get all tags associated with a given URL. */
234
+ export async function getTagsForUrl(url${T(ts, "string")})${R(ts, "Promise<string[]>")}{
235
+ const controller = navigator.serviceWorker?.controller;
236
+ if (!controller) return [];
237
+ return new Promise((resolve) => {
238
+ const channel = new MessageChannel();
239
+ channel.port1.onmessage = (event) => {
240
+ resolve(event.data.tags || []);
241
+ };
242
+ controller.postMessage(
243
+ { type: "GET_TAGS_FOR_URL", url },
244
+ [channel.port2],
245
+ );
246
+ });
247
+ }
248
+
249
+ /** Invalidate all cached responses whose URL matches the given glob pattern. */
250
+ export async function invalidateMatching(glob${T(ts, "string")})${R(ts, "Promise<void>")}{
251
+ const controller = navigator.serviceWorker?.controller;
252
+ if (!controller) return;
253
+ controller.postMessage({
254
+ type: "INVALIDATE_MATCHING",
255
+ glob,
256
+ });
257
+ }
258
+ `;
259
+ }
260
+ //# sourceMappingURL=invalidation-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidation-tags.js","sourceRoot":"","sources":["../../src/runtime/invalidation-tags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ import { T, R, PT, AS } from "./utils.js";
2
+ export function generateMutationQueueCode(ctx, authEnabled, batchSize, batchDelayMs, maxRetries, retryBackoffMs) {
3
+ const { ext, ts } = ctx;
4
+ const importLines = authEnabled
5
+ ? `import { getAuth } from "../auth/store.${ext}";
6
+ `
7
+ : "";
8
+ const additionalImports = `import { invalidateByTags } from "../cache/index.${ext}";
9
+ ${ts
10
+ ? `import type { MutationQueueItem } from "../swoff.d.ts";
11
+ `
12
+ : ""}`;
13
+ const authReplayHeaders = authEnabled
14
+ ? ` const auth = await getAuth();
15
+ const authHeader${T(ts, "Record<string, string>")} = auth?.token ? { Authorization: \`Bearer \${auth.token}\` } : {};
16
+ `
17
+ : "";
18
+ const authHeaderSpread = authEnabled
19
+ ? ` ...authHeader,\n`
20
+ : " ";
21
+ const code = `/**
22
+ * Swoff Mutation Queue
23
+ * Queue offline writes and sync when connection returns.
24
+ * Supports configurable batch size, rate limiting, and exponential backoff.
25
+ *
26
+ * Usage:
27
+ * import { queueMutation, processMutationQueue, flushMutations, getPendingCount } from './swoff/offline/queue.${ext}';
28
+ *
29
+ * // Queue a mutation
30
+ * await queueMutation({
31
+ * method: "POST",
32
+ * url: "/api/todos",
33
+ * body: { title: "Grocery" },
34
+ * tags: ["todos"],
35
+ * });
36
+ *
37
+ * // Flush queued mutations (e.g., after login)
38
+ * await flushMutations();
39
+ *
40
+ * // Auto-processes on online event
41
+ *
42
+ * Config:
43
+ * batchSize: ${batchSize} — mutations per progress event
44
+ * batchDelayMs: ${batchDelayMs} — delay between mutations (rate limiting)
45
+ * maxRetries: ${maxRetries} — max retries before dropping
46
+ * retryBackoffMs: ${retryBackoffMs} — exponential backoff base
47
+ */
48
+
49
+ ${importLines}${additionalImports}const DB_NAME = "swoff-queue";
50
+ const STORE_NAME = "mutations";
51
+ // Bump this when adding new indexes/stores for schema migration
52
+ const DB_VERSION = 1;
53
+ const BATCH_SIZE = ${batchSize};
54
+ const BATCH_DELAY_MS = ${batchDelayMs};
55
+ const MAX_RETRIES = ${maxRetries};
56
+ const RETRY_BACKOFF_MS = ${retryBackoffMs};
57
+
58
+ function sleep(ms${T(ts, "number")})${R(ts, "Promise<void>")}{
59
+ return new Promise((r) => setTimeout(r, ms));
60
+ }
61
+
62
+ function openQueueDB()${R(ts, "Promise<IDBDatabase>")}{
63
+ return new Promise${PT(ts, "IDBDatabase")}((resolve, reject) => {
64
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
65
+ request.onupgradeneeded = (e) => {
66
+ const db = (e.target${AS(ts, "IDBOpenDBRequest")}).result;
67
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
68
+ const store = db.createObjectStore(STORE_NAME, { keyPath: "id" });
69
+ store.createIndex("by-timestamp", "timestamp");
70
+ }
71
+ };
72
+ request.onsuccess = (e) => resolve((e.target${AS(ts, "IDBOpenDBRequest")}).result);
73
+ request.onerror = (e) => reject((e.target${AS(ts, "IDBRequest")}).error);
74
+ });
75
+ }
76
+
77
+ let isSyncing = false;
78
+
79
+ /** Store a write operation in IndexedDB for later sync. Works offline — use it for POST/PUT/PATCH/DELETE when the user might be offline. */
80
+ export async function queueMutation(mutation${T(ts, "Partial<MutationQueueItem>")})${R(ts, "Promise<void>")}{
81
+ const db = await openQueueDB();
82
+ const tx = db.transaction(STORE_NAME, "readwrite");
83
+ const store = tx.objectStore(STORE_NAME);
84
+
85
+ let body = mutation.body;
86
+ let bodyType = "json";
87
+ if (typeof body === "string") {
88
+ bodyType = "text";
89
+ } else if (body instanceof FormData) {
90
+ bodyType = "formdata";
91
+ body = [...body.entries()];
92
+ } else if (body instanceof Blob) {
93
+ bodyType = "blob";
94
+ } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
95
+ bodyType = "buffer";
96
+ }
97
+
98
+ // Strip auth headers before storing — tokens must never persist in IDB
99
+ const safeHeaders${T(ts, "Record<string, string>")} = { ...(mutation.headers || {}) };
100
+ delete safeHeaders["authorization"];
101
+ delete safeHeaders["Authorization"];
102
+
103
+ store.add({
104
+ id: crypto.randomUUID(),
105
+ method: mutation.method,
106
+ url: mutation.url,
107
+ body,
108
+ bodyType,
109
+ headers: safeHeaders,
110
+ timestamp: Date.now(),
111
+ retryCount: 0,
112
+ nextRetryAt: 0,
113
+ tags: mutation.tags || [],
114
+ });
115
+
116
+ await new Promise<void>((resolve, reject) => {
117
+ tx.oncomplete = () => resolve();
118
+ tx.onerror = () => reject(tx.error);
119
+ });
120
+
121
+ window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
122
+ }
123
+
124
+ /** Replay a single queued mutation. Uses the provided db connection if available. Returns true on success, false on failure. */
125
+ async function replayMutation(item${T(ts, "MutationQueueItem")}, db${T(ts, "IDBDatabase | undefined")})${R(ts, "Promise<boolean>")}{
126
+ try {
127
+ ${authReplayHeaders} let replayBody${T(ts, "BodyInit | null")}${ts ? " = null" : ""};
128
+ let contentType${T(ts, "string | undefined")};
129
+ const bt = item.bodyType || "json";
130
+ if (bt === "formdata") {
131
+ replayBody = new FormData();
132
+ for (const [key, value] of (item.body || [])${AS(ts, "[string, FormDataEntryValue][]")}) {
133
+ replayBody.append(key, value);
134
+ }
135
+ } else if (bt === "blob") {
136
+ replayBody = item.body${AS(ts, "BodyInit | null")};
137
+ } else if (bt === "buffer") {
138
+ replayBody = item.body instanceof ArrayBuffer ? new Uint8Array(item.body)${AS(ts, "BodyInit")} : item.body${AS(ts, "BodyInit")};
139
+ } else if (bt === "text") {
140
+ replayBody = item.body${AS(ts, "BodyInit | null")};
141
+ } else if (item.body != null) {
142
+ replayBody = JSON.stringify(item.body);
143
+ contentType = "application/json";
144
+ }
145
+ const response = await fetch(item.url, {
146
+ method: item.method,
147
+ headers: {
148
+ ...(contentType ? { "Content-Type": contentType } : {}),
149
+ ...item.headers,
150
+ ${authHeaderSpread} },
151
+ ...(replayBody != null ? { body: replayBody } : {}),
152
+ });
153
+
154
+ if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
155
+
156
+ if (item.tags && item.tags.length > 0) {
157
+ await invalidateByTags(item.tags);
158
+ }
159
+
160
+ await removeFromQueue(item.id, db);
161
+ return true;
162
+ } catch {
163
+ item.retryCount++;
164
+ item.nextRetryAt = Date.now() + RETRY_BACKOFF_MS * Math.pow(2, item.retryCount - 1);
165
+ await updateInQueue(item, db);
166
+ return false;
167
+ }
168
+ }
169
+
170
+ /** Process all queued mutations in order. Sends them to the server. Runs automatically when mutations are queued or on reconnection. Respects batchSize for progress reporting and batchDelayMs for rate limiting. */
171
+ export async function processMutationQueue()${R(ts, "Promise<void>")}{
172
+ if (isSyncing) return;
173
+ isSyncing = true;
174
+
175
+ const db = await openQueueDB();
176
+ try {
177
+ const tx = db.transaction(STORE_NAME, "readonly");
178
+ const store = tx.objectStore(STORE_NAME);
179
+ const index = store.index("by-timestamp");
180
+ const queue${T(ts, "MutationQueueItem[]")} = await new Promise${PT(ts, "MutationQueueItem[]")}((resolve, reject) => {
181
+ const request = index.getAll();
182
+ request.onsuccess = () => resolve(request.result);
183
+ request.onerror = () => reject(request.error);
184
+ });
185
+
186
+ if (queue.length === 0) return;
187
+
188
+ let succeeded = 0;
189
+ let failed = 0;
190
+ const total = queue.length;
191
+ let earliestRetry = Infinity;
192
+
193
+ for (const item of queue) {
194
+ if (item.retryCount >= MAX_RETRIES) {
195
+ await removeFromQueue(item.id, db);
196
+ failed++;
197
+ continue;
198
+ }
199
+
200
+ // Skip items whose backoff delay hasn't elapsed yet
201
+ if (item.nextRetryAt && Date.now() < item.nextRetryAt) {
202
+ if (item.nextRetryAt < earliestRetry) earliestRetry = item.nextRetryAt;
203
+ continue;
204
+ }
205
+
206
+ const ok = await replayMutation(item, db);
207
+ if (ok) {
208
+ succeeded++;
209
+ } else {
210
+ failed++;
211
+ }
212
+
213
+ // Rate limiting delay between mutations
214
+ if (BATCH_DELAY_MS > 0 && succeeded + failed < total) {
215
+ await sleep(BATCH_DELAY_MS);
216
+ }
217
+
218
+ // Emit progress after every BATCH_SIZE mutations
219
+ if ((succeeded + failed) % BATCH_SIZE === 0 || succeeded + failed === total) {
220
+ window.dispatchEvent(
221
+ new CustomEvent("mutation-sync-complete", {
222
+ detail: { succeeded, failed, total, current: succeeded + failed },
223
+ })
224
+ );
225
+ }
226
+ }
227
+
228
+ window.dispatchEvent(
229
+ new CustomEvent("mutation-sync-complete", {
230
+ detail: { succeeded, failed, total },
231
+ })
232
+ );
233
+
234
+ // Schedule retry when the earliest backoff timer expires
235
+ if (earliestRetry < Infinity && earliestRetry > Date.now()) {
236
+ setTimeout(() => {
237
+ if (!isSyncing) processMutationQueue();
238
+ }, earliestRetry - Date.now());
239
+ }
240
+ } finally {
241
+ db.close();
242
+ isSyncing = false;
243
+ window.dispatchEvent(new CustomEvent("mutation-queue-changed"));
244
+ }
245
+ }
246
+
247
+ /** Immediately process all queued mutations. Call this after re-login to flush mutations that failed due to auth expiry. */
248
+ export async function flushMutations()${R(ts, "Promise<void>")}{
249
+ await processMutationQueue();
250
+ }
251
+
252
+ async function removeFromQueue(id${T(ts, "string")}, db${T(ts, "IDBDatabase | undefined")})${R(ts, "Promise<void>")}{
253
+ if (!db) db = await openQueueDB();
254
+ const tx = db.transaction(STORE_NAME, "readwrite");
255
+ tx.objectStore(STORE_NAME).delete(id);
256
+ await new Promise${PT(ts, "void")}((resolve, reject) => {
257
+ tx.oncomplete = () => resolve();
258
+ tx.onerror = () => reject(tx.error);
259
+ });
260
+ }
261
+
262
+ async function updateInQueue(item${T(ts, "MutationQueueItem")}, db${T(ts, "IDBDatabase | undefined")})${R(ts, "Promise<void>")}{
263
+ if (!db) db = await openQueueDB();
264
+ const tx = db.transaction(STORE_NAME, "readwrite");
265
+ tx.objectStore(STORE_NAME).put(item);
266
+ await new Promise${PT(ts, "void")}((resolve, reject) => {
267
+ tx.oncomplete = () => resolve();
268
+ tx.onerror = () => reject(tx.error);
269
+ });
270
+ }
271
+
272
+ /** Get the number of queued mutations waiting to be synced. Useful for showing a sync badge. */
273
+ export async function getPendingCount()${R(ts, "Promise<number>")}{
274
+ const db = await openQueueDB();
275
+ return new Promise${PT(ts, "number")}((resolve, reject) => {
276
+ const tx = db.transaction(STORE_NAME, "readonly");
277
+ const request = tx.objectStore(STORE_NAME).count();
278
+ request.onsuccess = () => resolve((request${AS(ts, "IDBRequest<number>")}).result);
279
+ request.onerror = () => reject((request${AS(ts, "IDBRequest")}).error);
280
+ });
281
+ }
282
+
283
+ /** Get the position of a mutation in the queue (0-based). Returns -1 if not found. */
284
+ export async function getQueuePosition(id${T(ts, "string")})${R(ts, "Promise<number>")}{
285
+ const items = await getQueueItems();
286
+ return items.findIndex((item) => item.id === id);
287
+ }
288
+
289
+ /** Get all pending queue items with their details. */
290
+ export async function getQueueItems()${R(ts, "Promise<MutationQueueItem[]>")}{
291
+ const db = await openQueueDB();
292
+ const tx = db.transaction(STORE_NAME, "readonly");
293
+ const store = tx.objectStore(STORE_NAME);
294
+ const index = store.index("by-timestamp");
295
+ return new Promise${PT(ts, "MutationQueueItem[]")}((resolve, reject) => {
296
+ const request = index.getAll();
297
+ request.onsuccess = () => resolve(request.result);
298
+ request.onerror = () => reject(request.error);
299
+ });
300
+ }
301
+ `;
302
+ return code;
303
+ }
304
+ //# sourceMappingURL=mutation-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-queue.js","sourceRoot":"","sources":["../../src/runtime/mutation-queue.ts"],"names":[],"mappings":"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"}