@adukiorg/anza 0.2.0

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 (349) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +215 -0
  3. package/bin/anza.js +63 -0
  4. package/bin/create.js +150 -0
  5. package/importmap.json +72 -0
  6. package/package.json +100 -0
  7. package/src/core/animations/index.js +55 -0
  8. package/src/core/animations/play.js +111 -0
  9. package/src/core/animations/registry.js +54 -0
  10. package/src/core/animations/scroll.js +50 -0
  11. package/src/core/animations/tokens.js +58 -0
  12. package/src/core/animations/usage.md +301 -0
  13. package/src/core/animations/waapi.js +86 -0
  14. package/src/core/api/cache.js +120 -0
  15. package/src/core/api/caches/glob.js +24 -0
  16. package/src/core/api/caches/index.js +118 -0
  17. package/src/core/api/events/index.js +75 -0
  18. package/src/core/api/fetch.js +99 -0
  19. package/src/core/api/index.js +158 -0
  20. package/src/core/api/pipeline.js +98 -0
  21. package/src/core/api/plan.md +209 -0
  22. package/src/core/api/prefixes/index.js +66 -0
  23. package/src/core/api/retry.js +69 -0
  24. package/src/core/api/stream.js +127 -0
  25. package/src/core/api/upload.js +180 -0
  26. package/src/core/api/usage.md +206 -0
  27. package/src/core/events/bus.js +38 -0
  28. package/src/core/events/delegate.js +79 -0
  29. package/src/core/events/index.js +26 -0
  30. package/src/core/events/listen.js +50 -0
  31. package/src/core/events/missing.md +103 -0
  32. package/src/core/events/once.js +49 -0
  33. package/src/core/events/plan.md +177 -0
  34. package/src/core/events/types/index.js +34 -0
  35. package/src/core/events/usage.md +107 -0
  36. package/src/core/offline/bridge.js +51 -0
  37. package/src/core/offline/clock.js +100 -0
  38. package/src/core/offline/connectivity.js +116 -0
  39. package/src/core/offline/index.js +41 -0
  40. package/src/core/offline/missing.md +89 -0
  41. package/src/core/offline/plan.md +143 -0
  42. package/src/core/offline/queue.js +168 -0
  43. package/src/core/offline/state.js +18 -0
  44. package/src/core/offline/sync.js +106 -0
  45. package/src/core/offline/usage.md +273 -0
  46. package/src/core/platform/guard.js +104 -0
  47. package/src/core/platform/index.js +42 -0
  48. package/src/core/platform/missing.md +119 -0
  49. package/src/core/platform/platform.d.ts +88 -0
  50. package/src/core/platform/polyfills/anchor.js +79 -0
  51. package/src/core/platform/polyfills/navigation.js +142 -0
  52. package/src/core/platform/polyfills/popover.js +142 -0
  53. package/src/core/platform/polyfills/scheduler.js +194 -0
  54. package/src/core/platform/polyfills/shadow.js +35 -0
  55. package/src/core/platform/polyfills/urlpattern.js +119 -0
  56. package/src/core/platform/supports.js +186 -0
  57. package/src/core/platform/usage.md +287 -0
  58. package/src/core/router/cache.js +95 -0
  59. package/src/core/router/container.js +146 -0
  60. package/src/core/router/handler.js +52 -0
  61. package/src/core/router/history.js +120 -0
  62. package/src/core/router/index.js +158 -0
  63. package/src/core/router/intercept.js +376 -0
  64. package/src/core/router/match.js +145 -0
  65. package/src/core/router/missing.md +716 -0
  66. package/src/core/router/outlet.js +139 -0
  67. package/src/core/router/plan.md +370 -0
  68. package/src/core/router/sync/index.js +16 -0
  69. package/src/core/router/sync/tab.js +115 -0
  70. package/src/core/router/sync/transport.js +139 -0
  71. package/src/core/router/transitions.js +59 -0
  72. package/src/core/router/usage.md +773 -0
  73. package/src/core/security/crypto.js +159 -0
  74. package/src/core/security/index.js +49 -0
  75. package/src/core/security/missing.md +97 -0
  76. package/src/core/security/permissions.js +64 -0
  77. package/src/core/security/sanitize.js +100 -0
  78. package/src/core/security/usage.md +283 -0
  79. package/src/core/state/derived.js +117 -0
  80. package/src/core/state/index.js +23 -0
  81. package/src/core/state/missing.md +165 -0
  82. package/src/core/state/persist.js +284 -0
  83. package/src/core/state/store.js +308 -0
  84. package/src/core/state/sync.js +46 -0
  85. package/src/core/state/usage.md +440 -0
  86. package/src/core/storage/cache.js +83 -0
  87. package/src/core/storage/idb.js +196 -0
  88. package/src/core/storage/index.js +373 -0
  89. package/src/core/storage/lru.js +107 -0
  90. package/src/core/storage/missing.md +165 -0
  91. package/src/core/storage/opfs.js +190 -0
  92. package/src/core/storage/plan.md +69 -0
  93. package/src/core/storage/quota.js +69 -0
  94. package/src/core/storage/usage.md +226 -0
  95. package/src/core/ui/base.js +50 -0
  96. package/src/core/ui/define/container.js +82 -0
  97. package/src/core/ui/define/define.js +12 -0
  98. package/src/core/ui/define/element.js +390 -0
  99. package/src/core/ui/define/index.js +9 -0
  100. package/src/core/ui/define/orchestrator.js +105 -0
  101. package/src/core/ui/define/proxy.js +644 -0
  102. package/src/core/ui/define/state.js +6 -0
  103. package/src/core/ui/define/utils.js +134 -0
  104. package/src/core/ui/implementation.md +170 -0
  105. package/src/core/ui/index.js +41 -0
  106. package/src/core/ui/observe.js +117 -0
  107. package/src/core/ui/plan.md +510 -0
  108. package/src/core/ui/schedule.js +60 -0
  109. package/src/core/ui/template.js +37 -0
  110. package/src/core/ui/transitions.js +37 -0
  111. package/src/core/ui/ui.types.md +890 -0
  112. package/src/core/ui/usage.md +1124 -0
  113. package/src/core/ui/watch.md +346 -0
  114. package/src/core/workers/broadcast.js +138 -0
  115. package/src/core/workers/dedicated.js +153 -0
  116. package/src/core/workers/index.js +169 -0
  117. package/src/core/workers/locks.js +160 -0
  118. package/src/core/workers/offscreen.js +166 -0
  119. package/src/core/workers/plan.md +381 -0
  120. package/src/core/workers/pool.js +267 -0
  121. package/src/core/workers/shared.js +137 -0
  122. package/src/core/workers/usage.md +622 -0
  123. package/src/elements/base.js +12 -0
  124. package/src/elements/data/card/index.html +9 -0
  125. package/src/elements/data/card/index.js +19 -0
  126. package/src/elements/data/card/index.tags.json +1 -0
  127. package/src/elements/data/card/style.css +46 -0
  128. package/src/elements/data/chart/index.html +1 -0
  129. package/src/elements/data/chart/index.js +143 -0
  130. package/src/elements/data/chart/index.tags.json +1 -0
  131. package/src/elements/data/chart/style.css +13 -0
  132. package/src/elements/data/list/index.html +3 -0
  133. package/src/elements/data/list/index.js +19 -0
  134. package/src/elements/data/list/index.tags.json +1 -0
  135. package/src/elements/data/list/style.css +39 -0
  136. package/src/elements/data/stat/index.html +9 -0
  137. package/src/elements/data/stat/index.js +19 -0
  138. package/src/elements/data/stat/index.tags.json +1 -0
  139. package/src/elements/data/stat/style.css +50 -0
  140. package/src/elements/data/table/index.html +1 -0
  141. package/src/elements/data/table/index.js +16 -0
  142. package/src/elements/data/table/index.tags.json +1 -0
  143. package/src/elements/data/table/style.css +50 -0
  144. package/src/elements/feedback/alert/index.html +11 -0
  145. package/src/elements/feedback/alert/index.js +28 -0
  146. package/src/elements/feedback/alert/index.tags.json +1 -0
  147. package/src/elements/feedback/alert/style.css +75 -0
  148. package/src/elements/feedback/empty/index.html +13 -0
  149. package/src/elements/feedback/empty/index.js +34 -0
  150. package/src/elements/feedback/empty/index.tags.json +1 -0
  151. package/src/elements/feedback/empty/style.css +45 -0
  152. package/src/elements/feedback/progress/index.html +7 -0
  153. package/src/elements/feedback/progress/index.js +46 -0
  154. package/src/elements/feedback/progress/index.tags.json +1 -0
  155. package/src/elements/feedback/progress/style.css +36 -0
  156. package/src/elements/feedback/skeleton/index.html +1 -0
  157. package/src/elements/feedback/skeleton/index.js +78 -0
  158. package/src/elements/feedback/skeleton/index.tags.json +1 -0
  159. package/src/elements/feedback/skeleton/style.css +28 -0
  160. package/src/elements/feedback/toast/index.html +3 -0
  161. package/src/elements/feedback/toast/index.js +65 -0
  162. package/src/elements/feedback/toast/index.tags.json +1 -0
  163. package/src/elements/feedback/toast/style.css +36 -0
  164. package/src/elements/forms/checkbox/index.html +7 -0
  165. package/src/elements/forms/checkbox/index.js +104 -0
  166. package/src/elements/forms/checkbox/index.tags.json +1 -0
  167. package/src/elements/forms/checkbox/style.css +86 -0
  168. package/src/elements/forms/field/index.html +13 -0
  169. package/src/elements/forms/field/index.js +42 -0
  170. package/src/elements/forms/field/index.tags.json +1 -0
  171. package/src/elements/forms/field/style.css +42 -0
  172. package/src/elements/forms/form/index.html +3 -0
  173. package/src/elements/forms/form/index.js +122 -0
  174. package/src/elements/forms/form/index.tags.json +1 -0
  175. package/src/elements/forms/form/style.css +11 -0
  176. package/src/elements/forms/input/index.html +4 -0
  177. package/src/elements/forms/input/index.js +103 -0
  178. package/src/elements/forms/input/index.tags.json +1 -0
  179. package/src/elements/forms/input/style.css +39 -0
  180. package/src/elements/forms/radio/index.html +4 -0
  181. package/src/elements/forms/radio/index.js +109 -0
  182. package/src/elements/forms/radio/index.tags.json +1 -0
  183. package/src/elements/forms/radio/style.css +65 -0
  184. package/src/elements/forms/select/index.html +9 -0
  185. package/src/elements/forms/select/index.js +114 -0
  186. package/src/elements/forms/select/index.tags.json +1 -0
  187. package/src/elements/forms/select/style.css +95 -0
  188. package/src/elements/forms/textarea/index.html +4 -0
  189. package/src/elements/forms/textarea/index.js +115 -0
  190. package/src/elements/forms/textarea/index.tags.json +1 -0
  191. package/src/elements/forms/textarea/style.css +46 -0
  192. package/src/elements/forms/toggle/index.html +4 -0
  193. package/src/elements/forms/toggle/index.js +89 -0
  194. package/src/elements/forms/toggle/index.tags.json +1 -0
  195. package/src/elements/forms/toggle/style.css +63 -0
  196. package/src/elements/forms/upload/index.html +13 -0
  197. package/src/elements/forms/upload/index.js +120 -0
  198. package/src/elements/forms/upload/index.tags.json +1 -0
  199. package/src/elements/forms/upload/style.css +61 -0
  200. package/src/elements/index.js +71 -0
  201. package/src/elements/layout/app/index.html +7 -0
  202. package/src/elements/layout/app/index.js +16 -0
  203. package/src/elements/layout/app/index.tags.json +1 -0
  204. package/src/elements/layout/app/style.css +41 -0
  205. package/src/elements/layout/grid/index.html +3 -0
  206. package/src/elements/layout/grid/index.js +41 -0
  207. package/src/elements/layout/grid/index.tags.json +1 -0
  208. package/src/elements/layout/grid/style.css +12 -0
  209. package/src/elements/layout/header/index.html +8 -0
  210. package/src/elements/layout/header/index.js +16 -0
  211. package/src/elements/layout/header/index.tags.json +1 -0
  212. package/src/elements/layout/header/style.css +28 -0
  213. package/src/elements/layout/scroll/index.html +3 -0
  214. package/src/elements/layout/scroll/index.js +19 -0
  215. package/src/elements/layout/scroll/index.tags.json +1 -0
  216. package/src/elements/layout/scroll/style.css +24 -0
  217. package/src/elements/layout/sidebar/index.html +3 -0
  218. package/src/elements/layout/sidebar/index.js +24 -0
  219. package/src/elements/layout/sidebar/index.tags.json +1 -0
  220. package/src/elements/layout/sidebar/style.css +30 -0
  221. package/src/elements/layout/split/index.html +3 -0
  222. package/src/elements/layout/split/index.js +18 -0
  223. package/src/elements/layout/split/index.tags.json +1 -0
  224. package/src/elements/layout/split/style.css +28 -0
  225. package/src/elements/layout/stack/index.html +3 -0
  226. package/src/elements/layout/stack/index.js +31 -0
  227. package/src/elements/layout/stack/index.tags.json +1 -0
  228. package/src/elements/layout/stack/style.css +15 -0
  229. package/src/elements/layout/surface/index.html +3 -0
  230. package/src/elements/layout/surface/index.js +19 -0
  231. package/src/elements/layout/surface/index.tags.json +1 -0
  232. package/src/elements/layout/surface/style.css +29 -0
  233. package/src/elements/navigation/breadcrumb/index.html +5 -0
  234. package/src/elements/navigation/breadcrumb/index.js +16 -0
  235. package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
  236. package/src/elements/navigation/breadcrumb/style.css +36 -0
  237. package/src/elements/navigation/nav/index.html +3 -0
  238. package/src/elements/navigation/nav/index.js +24 -0
  239. package/src/elements/navigation/nav/index.tags.json +1 -0
  240. package/src/elements/navigation/nav/style.css +38 -0
  241. package/src/elements/navigation/pagination/index.html +3 -0
  242. package/src/elements/navigation/pagination/index.js +94 -0
  243. package/src/elements/navigation/pagination/index.tags.json +1 -0
  244. package/src/elements/navigation/pagination/style.css +39 -0
  245. package/src/elements/navigation/steps/index.html +6 -0
  246. package/src/elements/navigation/steps/index.js +64 -0
  247. package/src/elements/navigation/steps/index.tags.json +1 -0
  248. package/src/elements/navigation/steps/style.css +78 -0
  249. package/src/elements/navigation/tabs/index.html +6 -0
  250. package/src/elements/navigation/tabs/index.js +132 -0
  251. package/src/elements/navigation/tabs/index.tags.json +1 -0
  252. package/src/elements/navigation/tabs/style.css +52 -0
  253. package/src/elements/overlay/dialog/index.html +5 -0
  254. package/src/elements/overlay/dialog/index.js +57 -0
  255. package/src/elements/overlay/dialog/index.tags.json +1 -0
  256. package/src/elements/overlay/dialog/style.css +31 -0
  257. package/src/elements/overlay/drawer/index.html +3 -0
  258. package/src/elements/overlay/drawer/index.js +56 -0
  259. package/src/elements/overlay/drawer/index.tags.json +1 -0
  260. package/src/elements/overlay/drawer/style.css +48 -0
  261. package/src/elements/overlay/menu/index.html +3 -0
  262. package/src/elements/overlay/menu/index.js +107 -0
  263. package/src/elements/overlay/menu/index.tags.json +1 -0
  264. package/src/elements/overlay/menu/style.css +43 -0
  265. package/src/elements/overlay/popover/index.html +3 -0
  266. package/src/elements/overlay/popover/index.js +44 -0
  267. package/src/elements/overlay/popover/index.tags.json +1 -0
  268. package/src/elements/overlay/popover/style.css +21 -0
  269. package/src/elements/overlay/sheet/index.html +8 -0
  270. package/src/elements/overlay/sheet/index.js +105 -0
  271. package/src/elements/overlay/sheet/index.tags.json +1 -0
  272. package/src/elements/overlay/sheet/style.css +64 -0
  273. package/src/elements/overlay/tooltip/index.html +6 -0
  274. package/src/elements/overlay/tooltip/index.js +16 -0
  275. package/src/elements/overlay/tooltip/index.tags.json +1 -0
  276. package/src/elements/overlay/tooltip/style.css +41 -0
  277. package/src/elements/primitives/avatar/index.html +2 -0
  278. package/src/elements/primitives/avatar/index.js +79 -0
  279. package/src/elements/primitives/avatar/index.tags.json +1 -0
  280. package/src/elements/primitives/avatar/style.css +36 -0
  281. package/src/elements/primitives/badge/index.html +3 -0
  282. package/src/elements/primitives/badge/index.js +20 -0
  283. package/src/elements/primitives/badge/index.tags.json +1 -0
  284. package/src/elements/primitives/badge/style.css +67 -0
  285. package/src/elements/primitives/button/index.html +3 -0
  286. package/src/elements/primitives/button/index.js +61 -0
  287. package/src/elements/primitives/button/index.tags.json +1 -0
  288. package/src/elements/primitives/button/style.css +66 -0
  289. package/src/elements/primitives/divider/index.html +1 -0
  290. package/src/elements/primitives/divider/index.js +43 -0
  291. package/src/elements/primitives/divider/index.tags.json +1 -0
  292. package/src/elements/primitives/divider/style.css +39 -0
  293. package/src/elements/primitives/icon/index.html +3 -0
  294. package/src/elements/primitives/icon/index.js +66 -0
  295. package/src/elements/primitives/icon/index.tags.json +1 -0
  296. package/src/elements/primitives/icon/style.css +20 -0
  297. package/src/elements/primitives/link/index.html +3 -0
  298. package/src/elements/primitives/link/index.js +129 -0
  299. package/src/elements/primitives/link/index.tags.json +1 -0
  300. package/src/elements/primitives/link/style.css +40 -0
  301. package/src/elements/primitives/spinner/index.html +1 -0
  302. package/src/elements/primitives/spinner/index.js +62 -0
  303. package/src/elements/primitives/spinner/index.tags.json +1 -0
  304. package/src/elements/primitives/spinner/style.css +20 -0
  305. package/src/elements/primitives/text/index.html +1 -0
  306. package/src/elements/primitives/text/index.js +79 -0
  307. package/src/elements/primitives/text/index.tags.json +1 -0
  308. package/src/elements/primitives/text/style.css +25 -0
  309. package/src/index.js +23 -0
  310. package/src/styles/base.css +66 -0
  311. package/src/styles/index.css +10 -0
  312. package/src/styles/layers.css +9 -0
  313. package/src/styles/reset.css +66 -0
  314. package/src/sw/activate.js +51 -0
  315. package/src/sw/expire.js +47 -0
  316. package/src/sw/index.js +28 -0
  317. package/src/sw/install.js +35 -0
  318. package/src/sw/push.js +58 -0
  319. package/src/sw/queue.js +60 -0
  320. package/src/sw/routes.js +71 -0
  321. package/src/sw/strategies.js +247 -0
  322. package/src/sw/sync.js +80 -0
  323. package/src/tokens/index.css +26 -0
  324. package/src/tokens/primitives/colors.css +54 -0
  325. package/src/tokens/primitives/motion.css +34 -0
  326. package/src/tokens/primitives/radius.css +16 -0
  327. package/src/tokens/primitives/shadow.css +34 -0
  328. package/src/tokens/primitives/spacing.css +27 -0
  329. package/src/tokens/primitives/typography.css +46 -0
  330. package/src/tokens/primitives/zindex.css +18 -0
  331. package/src/tokens/registered/colors.css +133 -0
  332. package/src/tokens/registered/dimensions.css +31 -0
  333. package/src/tokens/semantic/components.css +125 -0
  334. package/src/tokens/semantic/contrast.css +33 -0
  335. package/src/tokens/semantic/dark.css +61 -0
  336. package/src/tokens/semantic/light.css +64 -0
  337. package/types/core/animations/index.d.ts +52 -0
  338. package/types/core/api/index.d.ts +68 -0
  339. package/types/core/events/index.d.ts +50 -0
  340. package/types/core/offline/index.d.ts +68 -0
  341. package/types/core/platform/index.d.ts +60 -0
  342. package/types/core/router/index.d.ts +203 -0
  343. package/types/core/security/index.d.ts +33 -0
  344. package/types/core/state/index.d.ts +68 -0
  345. package/types/core/storage/index.d.ts +40 -0
  346. package/types/core/ui/index.d.ts +446 -0
  347. package/types/core/workers/index.d.ts +221 -0
  348. package/types/elements/index.d.ts +150 -0
  349. package/types/index.d.ts +18 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * src/core/offline/sync.js
3
+ *
4
+ * Background Sync Manager.
5
+ * Registers Service Worker Background Sync descriptors for Chromium-based browsers,
6
+ * falling back to custom window online-event triggers on Safari and Firefox.
7
+ *
8
+ * Source: doc 13 — Offline and Background §4
9
+ */
10
+
11
+ import { queue } from './queue.js';
12
+ import { state } from './state.js';
13
+
14
+ export class SyncManager {
15
+ constructor() {
16
+ if (
17
+ typeof navigator !== 'undefined' &&
18
+ 'serviceWorker' in navigator
19
+ ) {
20
+ navigator.serviceWorker.addEventListener('message', async (event) => {
21
+ const data = event.data;
22
+ if (data && (data.type === 'sync-success' || data.type === 'sync-failed')) {
23
+ try {
24
+ const list = await queue.list();
25
+ state.set('pending', list.length);
26
+ } catch (err) {
27
+ console.error('Failed to sync pending task count on service worker message:', err);
28
+ }
29
+ }
30
+ });
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Registers a sync tag with the Service Worker Background Sync API, falling back
36
+ * to manual online triggers if unsupported.
37
+ */
38
+ async register(tag) {
39
+ if (
40
+ typeof navigator !== 'undefined' &&
41
+ 'serviceWorker' in navigator
42
+ ) {
43
+ try {
44
+ const registration = await navigator.serviceWorker.ready;
45
+ if (registration.sync) {
46
+ await registration.sync.register(tag);
47
+ return true;
48
+ }
49
+ } catch (err) {
50
+ console.warn('Background Sync registration failed; falling back to listener:', err);
51
+ }
52
+ }
53
+
54
+ // Resilient fallback for Safari and Firefox
55
+ if (typeof window !== 'undefined') {
56
+ const trigger = () => {
57
+ window.removeEventListener('online', trigger);
58
+ // Dispatch a custom event detailing the pending tag
59
+ const event = new CustomEvent('core:sync-fallback', { detail: { tag } });
60
+ window.dispatchEvent(event);
61
+ };
62
+
63
+ window.addEventListener('online', trigger);
64
+ }
65
+ return false;
66
+ }
67
+
68
+ /**
69
+ * Subscribes to manual sync fallback triggers (useful for executing queue replays on Safari/Firefox).
70
+ */
71
+ onSyncFallback(fn, signal) {
72
+ if (signal?.aborted || typeof window === 'undefined') return () => {};
73
+
74
+ const listener = async (event) => {
75
+ // Coordinated fallback execution using Web Locks
76
+ if (typeof navigator !== 'undefined' && navigator.locks) {
77
+ try {
78
+ await navigator.locks.request('offline:sync', async () => {
79
+ await fn(event.detail.tag);
80
+ });
81
+ } catch (err) {
82
+ console.error('Failed to execute fallback sync under Web Lock:', err);
83
+ }
84
+ } else {
85
+ await fn(event.detail.tag);
86
+ }
87
+ };
88
+ window.addEventListener('core:sync-fallback', listener);
89
+
90
+ const dispose = () => {
91
+ window.removeEventListener('core:sync-fallback', listener);
92
+ if (signal) {
93
+ signal.removeEventListener('abort', dispose);
94
+ }
95
+ };
96
+
97
+ if (signal) {
98
+ signal.addEventListener('abort', dispose, { once: true });
99
+ }
100
+
101
+ return dispose;
102
+ }
103
+ }
104
+
105
+ export const sync = new SyncManager();
106
+
@@ -0,0 +1,273 @@
1
+ # Offline & Background Capabilities Usage Guide
2
+
3
+ The Native Offline layer provides tools for building local-first, network-resilient web applications. It encapsulates reactive connectivity state tracking, crash-resilient IndexedDB task queuing with write-ahead journaling, Web Lock-coordinated manual sync fallbacks, direct Service Worker bridge communication, and monotonic logical Lamport clocks for conflict-free replication.
4
+
5
+ Import from the offline entry point:
6
+
7
+ ```javascript
8
+ import { offline } from '@adukiorg/anza/offline';
9
+ ```
10
+
11
+ Or import individual components directly:
12
+
13
+ ```javascript
14
+ import { check, subscribe, queue, sync, bridge, state, clock } from '@adukiorg/anza/offline';
15
+ ```
16
+
17
+ ---
18
+
19
+ ## 1. Choosing an API
20
+
21
+ | Need | Use |
22
+ |---|---|
23
+ | Query network connectivity | `check` |
24
+ | Subscribe to network status events | `subscribe` |
25
+ | Get current network/queue state | `state.get` or `state.snapshot` |
26
+ | Subscribe to reactive state updates | `state.subscribe` |
27
+ | Enqueue a background sync task | `queue.push` |
28
+ | List chronological tasks in the queue | `queue.list` |
29
+ | Update task status or retry count | `queue.update` |
30
+ | Evict a resolved task from the queue | `queue.delete` |
31
+ | Clear the entire offline queue | `queue.clear` |
32
+ | Register background sync tags | `sync.register` |
33
+ | Coordinate manual fallback sync | `sync.onSyncFallback` |
34
+ | Send messages to Service Worker | `bridge.send` or `offline.send` |
35
+ | Retrieve client persistent actor ID | `clock.actor` |
36
+ | Monotonically tick local clock counter | `clock.tick` |
37
+ | Sync local clock against remote time | `clock.sync` |
38
+ | Generate logical clock stamp | `clock.stamp` |
39
+
40
+ ---
41
+
42
+ ## 2. Connectivity & State Tracking
43
+
44
+ The connectivity module handles network status monitoring and reachability checks.
45
+
46
+ ### Checking Connectivity
47
+ The `check(force?)` function checks network reachability. It automatically rate-limits HEAD probes to `/favicon.ico` (cached for 10 seconds) to avoid thrashing the network. Pass `true` to bypass throttling.
48
+
49
+ ```javascript
50
+ // Perform a throttled connectivity check
51
+ const online = await check();
52
+
53
+ // Force an immediate probe, bypassing the 10s throttling window
54
+ const absoluteOnline = await check(true);
55
+ ```
56
+
57
+ ### Event Subscriptions
58
+ Use `subscribe(callback, signal?)` to listen to connectivity status changes. You can pass an optional `AbortSignal` for automated unsubscription.
59
+
60
+ ```javascript
61
+ const controller = new AbortController();
62
+
63
+ const dispose = subscribe((online) => {
64
+ console.log(online ? 'Connected' : 'Disconnected');
65
+ }, controller.signal);
66
+
67
+ // Unsubscribe manually
68
+ dispose();
69
+
70
+ // Or automatically clean up by aborting the controller
71
+ controller.abort();
72
+ ```
73
+
74
+ ### Reactive State Store
75
+ The `state` store is a fine-grained `ReactiveStore` keeping track of network connectivity metrics and task queue length:
76
+ * `online` (boolean): Whether the client is currently online.
77
+ * `status` (string): `'online' | 'offline' | 'unknown'`.
78
+ * `pending` (number): Monitored count of uncompleted tasks in the queue.
79
+
80
+ ```javascript
81
+ // Subscribe to reactive key mutations
82
+ state.subscribe('pending', (count) => {
83
+ console.log(`Pending offline tasks: ${count}`);
84
+ });
85
+
86
+ // Access current state snapshot
87
+ const snapshot = state.snapshot();
88
+ console.log(`Status: ${snapshot.status}, Online: ${snapshot.online}`);
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 3. Persistent Task Queue (`OfflineQueue`)
94
+
95
+ The offline queue is backed by IndexedDB (`platform-offline-queue` / `tasks`). It preserves causal chronological sequencing (FIFO) and is protected by write-ahead journaling.
96
+
97
+ ### Enqueuing Tasks
98
+ Use `queue.push(taskName, payload?, options?)` to add tasks to the offline queue. If local client storage usage exceeds 80%, the write is blocked and throws a `QuotaExceededError`.
99
+
100
+ ```javascript
101
+ try {
102
+ const taskId = await queue.push(
103
+ 'order:submit',
104
+ { itemId: 42, quantity: 2 },
105
+ {
106
+ idempotencyKey: 'order-c104', // Optional: defaults to a random UUID
107
+ maxRetries: 3 // Optional: defaults to 5
108
+ }
109
+ );
110
+ console.log('Task buffered with ID:', taskId);
111
+ } catch (err) {
112
+ if (err.name === 'QuotaExceededError') {
113
+ console.error('Storage full. Clear space before enqueuing.');
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Managing Tasks
119
+ Retrieve, update, and delete tasks from the queue as they are processed:
120
+
121
+ ```javascript
122
+ // List all queued tasks sorted chronologically (oldest first)
123
+ const tasks = await queue.list();
124
+
125
+ for (const task of tasks) {
126
+ if (task.failed) {
127
+ console.warn(`Task permanently failed: ${task.error}`);
128
+ await queue.delete(task.id); // Evict from queue
129
+ continue;
130
+ }
131
+
132
+ try {
133
+ // Process the task...
134
+ await queue.delete(task.id);
135
+ } catch (err) {
136
+ task.retries++;
137
+ if (task.retries >= task.maxRetries) {
138
+ task.failed = true;
139
+ task.error = err.message;
140
+ }
141
+ await queue.update(task); // Persist updated state
142
+ }
143
+ }
144
+
145
+ // Clear the entire queue
146
+ await queue.clear();
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 4. Background Sync & Fallbacks
152
+
153
+ The `sync` manager coordinates queue replays using native browser Background Sync or event-driven fallbacks.
154
+
155
+ ### Registering Sync Events
156
+ Register sync tags with the Service Worker Background Sync API. On browsers without Background Sync support (e.g. Safari, Firefox), the manager registers a custom window-level online fallback listener.
157
+
158
+ ```javascript
159
+ const registered = await sync.register('pending');
160
+ if (registered) {
161
+ console.log('Native service worker background sync registered.');
162
+ } else {
163
+ console.log('Safari/Firefox fallback online listener registered.');
164
+ }
165
+ ```
166
+
167
+ ### Web Lock Coordinated Fallback
168
+ When a fallback online event fires in multi-tab sessions, all tabs simultaneously receive the event. To prevent concurrent database access and duplicate HTTP requests, the callback is automatically coordinated under an exclusive Web Lock named `"offline:sync"`.
169
+
170
+ Only one active tab will acquire the lock and execute the sync handler:
171
+
172
+ ```javascript
173
+ const controller = new AbortController();
174
+
175
+ const dispose = sync.onSyncFallback(async (tag) => {
176
+ // Awaiting Web Lock 'offline:sync' is handled internally
177
+ console.log(`Replaying queue for tag: ${tag}`);
178
+
179
+ // Replay tasks chronologically...
180
+ }, controller.signal);
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 5. Service Worker Bridge
186
+
187
+ The `bridge` facilitates direct message dispatch to the active Service Worker controller using native `MessageChannel` instances for response routing.
188
+
189
+ ```javascript
190
+ // Dispatch a task to the service worker and wait for a response
191
+ try {
192
+ const result = await bridge.send('cache:precache', {
193
+ urls: ['/index.html', '/styles.css']
194
+ });
195
+ console.log('Precached files successfully:', result);
196
+ } catch (err) {
197
+ console.error('Service worker message failed:', err.message);
198
+ }
199
+
200
+ // Shorthand syntax using the offline facade
201
+ await offline.send('sync:force');
202
+ ```
203
+
204
+ ---
205
+
206
+ ## 6. Logical Lamport Clocks
207
+
208
+ Distributed local-first applications cannot rely on physical clocks due to device clock drift. The `clock` manager implements logical Lamport clocks to stamp offline mutations, ensuring Last-Write-Wins (LWW) resolution is perfectly deterministic.
209
+
210
+ ```javascript
211
+ import { clock } from '@adukiorg/anza/offline';
212
+
213
+ // 1. Retrieve the local client's persistent actor UUID
214
+ const clientUUID = await clock.actor();
215
+
216
+ // 2. Monotonically increment and get the local logical clock
217
+ const time = await clock.tick();
218
+
219
+ // 3. Sync the local clock when observing a remote logical counter (e.g. from server response headers)
220
+ // Automatically advances the local clock to be greater than the observed remote counter
221
+ const syncedTime = await clock.sync(120);
222
+
223
+ // 4. Generate a combined logical stamp tuple
224
+ const stamp = await clock.stamp();
225
+ // Returns: { actor: "b9a2-...", clock: 121 }
226
+ ```
227
+
228
+ ### Deterministic LWW Resolution
229
+ Compare logical stamps during data conflict merges:
230
+ * The stamp with the higher `clock` counter wins.
231
+ * If `clock` counters are equal, the lexicographically greater `actor` UUID acts as the tiebreaker.
232
+
233
+ ---
234
+
235
+ ## 7. Web Component Integration
236
+
237
+ The offline modules integrate seamlessly with the declarative `@adukiorg/anza` UI elements framework. The component's `mount` hook receives an `AbortController` (`ctrl`) that is automatically aborted when the element is disconnected from the DOM, resolving all subscriber memory safety concerns.
238
+
239
+ ```javascript
240
+ import { ui } from '@adukiorg/anza/ui';
241
+ import { check, queue, sync, state } from '@adukiorg/anza/offline';
242
+
243
+ ui.element('offline-status-banner', {
244
+ template: `
245
+ <div class="banner">
246
+ <span class="message"></span>
247
+ <span class="count"></span>
248
+ </div>
249
+ `,
250
+ style: `
251
+ .banner { display: none; padding: 1rem; text-align: center; }
252
+ .banner.active { display: block; }
253
+ `,
254
+
255
+ mount({ el, ctrl }) {
256
+ const { signal } = ctrl;
257
+ const container = el.shadowRoot.querySelector('.banner');
258
+ const message = el.shadowRoot.querySelector('.message');
259
+ const countSpan = el.shadowRoot.querySelector('.count');
260
+
261
+ // Subscribe to connectivity status changes reactively
262
+ state.subscribe('online', (online) => {
263
+ container.classList.toggle('active', !online);
264
+ message.textContent = online ? '' : 'You are working offline.';
265
+ }, signal);
266
+
267
+ // Track pending queue count reactively
268
+ state.subscribe('pending', (count) => {
269
+ countSpan.textContent = count > 0 ? `(${count} changes pending)` : '';
270
+ }, signal);
271
+ }
272
+ });
273
+ ```
@@ -0,0 +1,104 @@
1
+ /**
2
+ * core/platform/guard.js
3
+ *
4
+ * Feature-gate wrapper and lazy polyfill loader.
5
+ * Ensures caller experiences uniform APIs regardless of native vs polyfill support.
6
+ * Source: doc 18 §12, library2.md §Phase 1-A
7
+ */
8
+
9
+ import { supports } from './supports.js';
10
+
11
+ export async function urlPattern() {
12
+ if (supports.urlPattern) {
13
+ return globalThis.URLPattern;
14
+ }
15
+ const { default: polyfill } = await import('./polyfills/urlpattern.js');
16
+ return polyfill;
17
+ }
18
+
19
+ export async function navigation() {
20
+ if (supports.navigationAPI) {
21
+ return globalThis.navigation;
22
+ }
23
+ await import('./polyfills/navigation.js');
24
+ return globalThis.navigation;
25
+ }
26
+
27
+ export async function popover() {
28
+ if (supports.popoverAPI) return;
29
+ await import('./polyfills/popover.js');
30
+ }
31
+
32
+ export async function shadow(root = document) {
33
+ if (supports.declarativeShadowDOM) return;
34
+ const { apply } = await import('./polyfills/shadow.js');
35
+ apply(root);
36
+ }
37
+
38
+ export async function anchor(floating, anchorEl, options = {}) {
39
+ if (supports.anchorPositioning) {
40
+ // Native CSS Anchor positioning handles this; no script action needed.
41
+ return;
42
+ }
43
+ const { position } = await import('./polyfills/anchor.js');
44
+ position(floating, anchorEl, options);
45
+ }
46
+
47
+ export async function sanitizer() {
48
+ if (supports.sanitizerAPI && typeof globalThis.Sanitizer === 'function') {
49
+ try {
50
+ const s = new globalThis.Sanitizer();
51
+ if (typeof s.sanitize === 'function') {
52
+ return {
53
+ sanitizeToString(input) {
54
+ const temp = document.createElement('div');
55
+ temp.innerHTML = input;
56
+ const fragment = s.sanitize(temp);
57
+ const wrapper = document.createElement('div');
58
+ wrapper.appendChild(fragment);
59
+ return wrapper.innerHTML;
60
+ }
61
+ };
62
+ }
63
+ } catch {
64
+ // Fallback if construction fails
65
+ }
66
+ }
67
+ // Lightweight DOMPurify / Sanitizer standard fallback
68
+ return {
69
+ sanitizeToString(input) {
70
+ if (typeof DOMPurify !== 'undefined') {
71
+ return DOMPurify.sanitize(input);
72
+ }
73
+ // Fail-safe simple string sanitizer
74
+ const temp = document.createElement('div');
75
+ temp.textContent = input;
76
+ return temp.innerHTML;
77
+ }
78
+ };
79
+ }
80
+
81
+ export async function scheduler() {
82
+ if (supports.schedulerPostTask) {
83
+ return globalThis.scheduler;
84
+ }
85
+ await import('./polyfills/scheduler.js');
86
+ return globalThis.scheduler;
87
+ }
88
+
89
+ export async function yieldTask() {
90
+ const s = await scheduler();
91
+ return s.yield();
92
+ }
93
+
94
+ export default {
95
+ urlPattern,
96
+ navigation,
97
+ popover,
98
+ shadow,
99
+ anchor,
100
+ sanitizer,
101
+ scheduler,
102
+ yield: yieldTask
103
+ };
104
+
@@ -0,0 +1,42 @@
1
+ /**
2
+ * core/platform/index.js
3
+ *
4
+ * Public platform module entry.
5
+ * Re-exports supports detection registry and the lazy-load guard,
6
+ * while eagerly bootstrapping necessary global environment polyfills.
7
+ * Source: doc 18 §12, library2.md §Phase 1-A
8
+ */
9
+
10
+ import { supports, reset, typeGuard } from './supports.js';
11
+ import guard from './guard.js';
12
+
13
+ // Bootstrapping of critical global environment gaps
14
+ if (typeof window !== 'undefined') {
15
+ if (!supports.declarativeShadowDOM) {
16
+ import('./polyfills/shadow.js').catch(err => {
17
+ console.error('Failed to eagerly bootstrap shadow polyfill:', err);
18
+ });
19
+ }
20
+ if (!supports.popoverAPI) {
21
+ import('./polyfills/popover.js').catch(err => {
22
+ console.error('Failed to eagerly bootstrap popover polyfill:', err);
23
+ });
24
+ }
25
+ if (!supports.urlPattern) {
26
+ import('./polyfills/urlpattern.js').catch(err => {
27
+ console.error('Failed to eagerly bootstrap URLPattern polyfill:', err);
28
+ });
29
+ }
30
+ if (!supports.navigationAPI) {
31
+ import('./polyfills/navigation.js').catch(err => {
32
+ console.error('Failed to eagerly bootstrap Navigation polyfill:', err);
33
+ });
34
+ }
35
+ if (!supports.schedulerPostTask) {
36
+ import('./polyfills/scheduler.js').catch(err => {
37
+ console.error('Failed to eagerly bootstrap Scheduler polyfill:', err);
38
+ });
39
+ }
40
+ }
41
+
42
+ export { supports, reset, typeGuard, guard };
@@ -0,0 +1,119 @@
1
+ # Platform Missing Support
2
+
3
+ This document tracks remaining support, runtime gaps, type mismatches, and storage synchronization improvements for `src/core/platform`. Implemented behavior belongs in `usage.md` (or core documentation); unsupported, under-tested, or performance-gated behavior belongs here until it is built and verified.
4
+
5
+ ---
6
+
7
+ ## 0. Toolchain and Naming Rules
8
+
9
+ Heavy compilation, static analysis, and code generation belong in `tools/`, not in browser runtime modules.
10
+
11
+ Current toolchain platform support requirements:
12
+
13
+ - Statically verify polyfill execution branches and eliminate unused polyfills in production builds.
14
+ - Validate lazy-load wrapper targets at build time to prevent dynamic resolution failures.
15
+ - Detect unsupported browser APIs used in application code without corresponding `supports` check.
16
+
17
+ Naming rules for platform support:
18
+
19
+ - Prefer one-word files: `supports.js`, `guard.js`, `index.js`.
20
+ - Plural folders: `tests/`, `types/`, `polyfills/`.
21
+ - Single-word methods: `yield` (as `yieldTask`), `urlPattern`, `navigation`, `popover`, `shadow`, `anchor`, `sanitizer`, `scheduler`.
22
+ - Avoid compound names where single words carry full meaning in context.
23
+
24
+ ---
25
+
26
+ ## 1. Critical Runtime and Type Gaps
27
+
28
+ ### 1.1. Missing Type Declarations for Guards and Cache Reset
29
+
30
+ - **Status**: Type mismatch.
31
+ - **Files**:
32
+ - `types/core/platform/supports.d.ts`
33
+ - **Problem**:
34
+ The TypeScript definitions are severely limited. They only declare the `supports` object with a small fraction (8 out of 30+) of the actual lazy feature-detection flags. Furthermore, `guard` and `reset` are not typed or exported in the declaration file, despite being public exports in `src/core/platform/index.js`.
35
+ - **Expected Support**:
36
+ - Complete declarations for all 30+ feature detection keys on `supports`.
37
+ - Type definitions for `reset(key: string): void`.
38
+ - Type definitions for the `guard` object, including all feature-gate methods:
39
+ - `urlPattern(): Promise<typeof URLPattern>`
40
+ - `navigation(): Promise<Navigation>`
41
+ - `popover(): Promise<void>`
42
+ - `shadow(root?: ParentNode): Promise<void>`
43
+ - `anchor(floating: HTMLElement, anchorEl: HTMLElement, options?: object): Promise<void>`
44
+ - `sanitizer(): Promise<{ sanitizeToString(input: string): string }>`
45
+ - `scheduler(): Promise<Scheduler>`
46
+ - `yield(): Promise<void>`
47
+
48
+ ### 1.2. Missing Critical Storage Feature Flags
49
+
50
+ - **Status**: Runtime gap.
51
+ - **Files**:
52
+ - `src/core/platform/supports.js`
53
+ - **Problem**:
54
+ The `supports` detection registry is missing explicit flags for features utilized by the `storage` gateway, specifically:
55
+ - `compression`: Checks for `CompressionStream` and `DecompressionStream` (required for transparent record compression in `core/storage`).
56
+ - `storagePersistence`: Checks for `navigator.storage.persist` and `navigator.storage.persisted` (required for durable storage requests).
57
+ - **Expected Support**:
58
+ - Implement lazy `compression` detection checking `CompressionStream` and `DecompressionStream` in `globalThis`.
59
+ - Implement lazy `storagePersistence` detection checking `navigator.storage` properties.
60
+
61
+ ---
62
+
63
+ ## 2. Storage Integration & Synchronization Gaps
64
+
65
+ ### 2.1. Direct Browser API Access in Storage Gateway
66
+
67
+ - **Status**: Code smell & consistency gap.
68
+ - **Files**:
69
+ - `src/core/storage/index.js`
70
+ - `src/core/storage/quota.js`
71
+ - `src/core/storage/opfs.js`
72
+ - **Problem**:
73
+ The `storage` gateway accesses raw APIs like `CompressionStream`, `localStorage`, and `navigator.storage` directly without consulting the centralized `platform/supports` registry. This creates duplication and makes it impossible to mock feature support in unit tests via `reset()`.
74
+ - **Expected Support**:
75
+ - Refactor `src/core/storage/index.js` to import `supports` and use `supports.compression` and `supports.storagePersistence`.
76
+ - Refactor `src/core/storage/quota.js` to rely on `supports.storageManager` and `supports.storagePersistence` instead of doing in-line existence checks.
77
+ - Refactor `src/core/storage/opfs.js` to verify `supports.opfs` on the main thread prior to worker instantiation, preventing redundant worker start failure.
78
+
79
+ ---
80
+
81
+ ## 3. Test Coverage Gaps
82
+
83
+ ### 3.1. Unified Support Matrix & Reset Verification
84
+
85
+ - **Status**: Test gap.
86
+ - **Files**:
87
+ - `tests/core/platform/supports.test.js`
88
+ - **Needed Coverage**:
89
+ - Add assertions for new lazy flags: `compression` and `storagePersistence`.
90
+ - Verify that mock overrides via `reset()` correctly clear the lazy evaluation cache, permitting feature-detection simulation.
91
+
92
+ ### 3.2. Polyfill Dynamic Loading Fallback Verification
93
+
94
+ - **Status**: Test gap.
95
+ - **Files**:
96
+ - `tests/core/platform/guard.test.js`
97
+ - **Needed Coverage**:
98
+ - Mock supports to `false` for URLPattern, Navigation, and Scheduler.
99
+ - Call the guards and verify they successfully load the polyfills dynamically and expose functional fallback behaviors.
100
+
101
+ ---
102
+
103
+ ## 4. Suggested Implementation Order
104
+
105
+ 1. Implement `compression` and `storagePersistence` lazy detectors in `src/core/platform/supports.js`.
106
+ 2. Clean up direct global checks in `src/core/storage/index.js`, `src/core/storage/quota.js`, and `src/core/storage/opfs.js` to use `supports`.
107
+ 3. Update `types/core/platform/supports.d.ts` to fully type all `supports`, `guard`, and `reset` methods.
108
+ 4. Add verification tests in `tests/core/platform/supports.test.js` and `tests/core/platform/guard.test.js`.
109
+
110
+ ---
111
+
112
+ ## 5. Done Criteria
113
+
114
+ This missing-support list is complete when:
115
+
116
+ - `supports` exposes `compression` and `storagePersistence` capability flags.
117
+ - `core/storage` uses `supports` instead of direct global feature verification.
118
+ - `types/core/platform/supports.d.ts` is fully populated.
119
+ - Dynamic polyfill fallbacks are tested and pass successfully.