@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,190 @@
1
+ /**
2
+ * src/core/storage/opfs.js
3
+ *
4
+ * Origin Private File System Façade.
5
+ * Offloads OPFS operations to an inline dedicated Web Worker to leverage
6
+ * high-performance synchronous file access handles (`createSyncAccessHandle`).
7
+ * Broadcasts cross-tab invalidations via standard BroadcastChannel.
8
+ *
9
+ * Source: doc 22 — Storage Architecture §4
10
+ */
11
+ import { supports } from '../platform/index.js';
12
+ import { uuid } from '../security/index.js';
13
+ const workerCode = `
14
+ self.onmessage = async (e) => {
15
+ const { id, op, key, value } = e.data;
16
+ try {
17
+ const root = await navigator.storage.getDirectory();
18
+
19
+ if (op === 'set') {
20
+ const fileHandle = await root.getFileHandle(key, { create: true });
21
+ const accessHandle = await fileHandle.createSyncAccessHandle();
22
+ const encoder = new TextEncoder();
23
+ const buffer = encoder.encode(JSON.stringify(value));
24
+
25
+ accessHandle.truncate(0);
26
+ accessHandle.write(buffer);
27
+ accessHandle.close();
28
+
29
+ self.postMessage({ id, success: true });
30
+ } else if (op === 'get') {
31
+ try {
32
+ const fileHandle = await root.getFileHandle(key);
33
+ const accessHandle = await fileHandle.createSyncAccessHandle();
34
+ const size = accessHandle.getSize();
35
+
36
+ if (size === 0) {
37
+ accessHandle.close();
38
+ self.postMessage({ id, success: true, value: null });
39
+ return;
40
+ }
41
+
42
+ const buffer = new Uint8Array(size);
43
+ accessHandle.read(buffer);
44
+ accessHandle.close();
45
+
46
+ const decoder = new TextDecoder();
47
+ const parsed = JSON.parse(decoder.decode(buffer));
48
+ self.postMessage({ id, success: true, value: parsed });
49
+ } catch (err) {
50
+ if (err.name === 'NotFoundError') {
51
+ self.postMessage({ id, success: true, value: null });
52
+ } else {
53
+ throw err;
54
+ }
55
+ }
56
+ } else if (op === 'delete') {
57
+ try {
58
+ await root.removeEntry(key);
59
+ self.postMessage({ id, success: true });
60
+ } catch (err) {
61
+ if (err.name === 'NotFoundError') {
62
+ self.postMessage({ id, success: true });
63
+ } else {
64
+ throw err;
65
+ }
66
+ }
67
+ } else if (op === 'clear') {
68
+ for await (const name of root.keys()) {
69
+ await root.removeEntry(name);
70
+ }
71
+ self.postMessage({ id, success: true });
72
+ } else if (op === 'list') {
73
+ const list = [];
74
+ for await (const name of root.keys()) {
75
+ list.push(name);
76
+ }
77
+ self.postMessage({ id, success: true, value: list });
78
+ }
79
+ } catch (err) {
80
+ self.postMessage({ id, success: false, error: err.message });
81
+ }
82
+ };
83
+ `;
84
+
85
+ class OpfsManager {
86
+ #worker = null;
87
+ #pending = new Map();
88
+ #channel = null;
89
+
90
+ constructor() {
91
+ if (typeof window !== 'undefined' && supports.opfs) {
92
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
93
+ this.#worker = new Worker(URL.createObjectURL(blob));
94
+ this.#channel = new BroadcastChannel('core:opfs-invalidation');
95
+
96
+ this.#worker.onmessage = (e) => {
97
+ const { id, success, value, error } = e.data;
98
+ const promise = this.#pending.get(id);
99
+ if (promise) {
100
+ this.#pending.delete(id);
101
+ if (success) {
102
+ promise.resolve(value);
103
+ } else {
104
+ promise.reject(new Error(error));
105
+ }
106
+ }
107
+ };
108
+ }
109
+ }
110
+
111
+ #lock(name, callback) {
112
+ if (typeof navigator !== 'undefined' && navigator.locks) {
113
+ return navigator.locks.request(name, callback);
114
+ }
115
+ return callback();
116
+ }
117
+
118
+ #send(op, key, value) {
119
+ if (!this.#worker) return Promise.reject(new Error('OPFS not supported in this context'));
120
+
121
+ const id = uuid();
122
+ return new Promise((resolve, reject) => {
123
+ this.#pending.set(id, { resolve, reject });
124
+ this.#worker.postMessage({ id, op, key, value });
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Reads a file entry from OPFS.
130
+ */
131
+ get(key) {
132
+ return this.#lock(`opfs:${key}`, () => this.#send('get', key));
133
+ }
134
+
135
+ /**
136
+ * Writes a file entry to OPFS and broadcasts an invalidation.
137
+ */
138
+ set(key, value) {
139
+ return this.#lock(`opfs:${key}`, () => this.#send('set', key, value).then(() => {
140
+ this.#channel?.postMessage({ op: 'set', key });
141
+ }));
142
+ }
143
+
144
+ /**
145
+ * Deletes a file entry from OPFS.
146
+ */
147
+ delete(key) {
148
+ return this.#lock(`opfs:${key}`, () => this.#send('delete', key).then(() => {
149
+ this.#channel?.postMessage({ op: 'delete', key });
150
+ }));
151
+ }
152
+
153
+ /**
154
+ * Clears all entries in OPFS.
155
+ */
156
+ clear() {
157
+ return this.#lock('opfs:global', () => this.#send('clear').then(() => {
158
+ this.#channel?.postMessage({ op: 'clear' });
159
+ }));
160
+ }
161
+
162
+ /**
163
+ * Lists all entry names in OPFS.
164
+ */
165
+ list() {
166
+ return this.#send('list');
167
+ }
168
+
169
+ /**
170
+ * Subscribes to write invalidations.
171
+ */
172
+ onChange(fn, signal) {
173
+ if (signal?.aborted) return () => {};
174
+
175
+ const listener = (event) => fn(event.data);
176
+ this.#channel?.addEventListener('message', listener);
177
+
178
+ const dispose = () => {
179
+ this.#channel?.removeEventListener('message', listener);
180
+ };
181
+
182
+ if (signal) {
183
+ signal.addEventListener('abort', dispose);
184
+ }
185
+
186
+ return dispose;
187
+ }
188
+ }
189
+
190
+ export const opfs = new OpfsManager();
@@ -0,0 +1,69 @@
1
+ # Storage Engine Architecture Plan
2
+
3
+ This document outlines the blueprint for the local-first Multi-Tier Storage Gateway within `core.storage`, mapping memory LRU, IndexedDB, Cache API, and Origin Private File System (OPFS) under a unified tiered facade.
4
+
5
+ ---
6
+
7
+ ## 1. Architectural Architecture & Requirements
8
+
9
+ We will structure `core.storage` to provide:
10
+ 1. **Tiered Storage Architecture:** Clean separation of data structures into Memory LRU (transient, near-instant), IndexedDB (default transactional storage), Cache API (Request/Response persistence), and OPFS (high-performance synchronous file systems).
11
+ 2. **Synchronous Storage Ban:** Prevent performance-degrading synchronous operations (like blocking localStorage reads) in user interaction loops to keep the Interaction to Next Paint (INP) score pristine.
12
+ 3. **Dedicated Web Worker Offloading:** Route heavy OPFS synchronous file operations (using `FileSystemSyncAccessHandle`) to a background Dedicated Web Worker to eliminate main thread contention.
13
+ 4. **Cross-Tab Invalidation:** Broadcast storage mutations across tabs in real-time utilizing a native `BroadcastChannel` named `core:opfs-invalidation` or namespace channels.
14
+ 5. **Quota Governance & Self-Healing:** Integrate the browser's native `StorageManager` to monitor space estimates, query persistence, request persistent storage (`navigator.storage.persist()`), and fire events on 80% quota usage limits.
15
+
16
+ ---
17
+
18
+ ## 2. Directory Layout & Core Components
19
+
20
+ Following `RULE[user_global]`, the storage components are flat and named using lowercase:
21
+
22
+ ```
23
+ src/core/storage/
24
+ ├── index.js # Unified Storage Gateway Entry (core.storage)
25
+ ├── idb.js # Promise-wrapped transactional IndexedDB client
26
+ ├── lru.js # Least-Recently-Used & WeakRef memory caching stores
27
+ ├── opfs.js # Dedicated Web Worker coordinator for OPFS access
28
+ ├── quota.js # StorageManager quota checks and persistence control
29
+ └── plan.md # This planning blueprint
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 3. Technical Blueprint & Multi-Tier Mapping
35
+
36
+ ### Tier Performance Matrix
37
+
38
+ | Tier | API Backend | Performance | Thread Scoped | Use Case |
39
+ |---|---|---|---|---|
40
+ | `memory` | Map / WeakRef | Sub-microsecond | Main / Worker | Ephemeral/transient cache |
41
+ | `idb` | IndexedDB | 1ms – 5ms | Main / Worker | Structured user/document databases |
42
+ | `cache` | Cache API | 2ms – 10ms | Main / Worker | Offline Request/Response caching |
43
+ | `opfs` | OPFS Access Handle | Sub-millisecond | Dedicated Worker | Large sequential writes, SQLite storage |
44
+
45
+ ### Unified Storage Gateway Contracts
46
+
47
+ ```javascript
48
+ // src/core/storage/index.js
49
+ export const storage = {
50
+ async get(key, tier = 'idb') { ... },
51
+ async set(key, value, tier = 'idb', ttl = null) { ... },
52
+ async delete(key, tier = 'idb') { ... },
53
+ async list(tier = 'idb') { ... },
54
+ async clear(tier = 'all') { ... }
55
+ };
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 4. Verification Plan
61
+
62
+ ### Automated Verification
63
+ - Verify database migrations run sequentially on database opening without skipping steps.
64
+ - Validate that WeakRef memory cache recovers objects when available and handles garbage collection gracefully under memory pressure.
65
+ - Confirm background Dedicated Worker file operations complete correctly without stalling the main thread.
66
+ - Assert that cross-tab signals are successfully broadcasted and received during file operations.
67
+
68
+ ### Quota Checks
69
+ - Validate that the quota manager detects when usage climbs past 80% and fires an eviction warning event.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * src/core/storage/quota.js
3
+ *
4
+ * Storage Quota Manager.
5
+ * Wraps browser StorageManager API, providing space estimations,
6
+ * persistence requests, and an active 80% usage eviction warning trigger.
7
+ *
8
+ * Source: doc 22 — Storage Architecture §6
9
+ */
10
+
11
+ import { supports } from '../platform/index.js';
12
+
13
+ export class QuotaManager {
14
+ #listeners = new Set();
15
+
16
+ /**
17
+ * Subscribes a handler to quota warning events.
18
+ */
19
+ onQuotaWarning(handler) {
20
+ this.#listeners.add(handler);
21
+ return () => {
22
+ this.#listeners.delete(handler);
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Fetches the current storage usage and quota estimates.
28
+ */
29
+ async estimate() {
30
+ if (supports.storageManager && navigator.storage?.estimate) {
31
+ return navigator.storage.estimate();
32
+ }
33
+ return { usage: 0, quota: 0 };
34
+ }
35
+
36
+ /**
37
+ * Requests the browser to allow persistent storage (avoiding auto-eviction).
38
+ */
39
+ async persist() {
40
+ if (supports.storagePersistence) {
41
+ return navigator.storage.persist();
42
+ }
43
+ return false;
44
+ }
45
+
46
+ /**
47
+ * Assesses usage against the threshold. Calls trigger hook if capacity > 80%.
48
+ */
49
+ async check(onEvictionWarning) {
50
+ const { usage, quota } = await this.estimate();
51
+ if (quota > 0 && usage / quota > 0.8) {
52
+ const data = { usage, quota };
53
+ if (typeof onEvictionWarning === 'function') {
54
+ onEvictionWarning(data);
55
+ }
56
+ for (const listener of this.#listeners) {
57
+ try {
58
+ listener(data);
59
+ } catch (err) {
60
+ console.error('Error invoking quota warning listener:', err);
61
+ }
62
+ }
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+ }
68
+
69
+ export const quota = new QuotaManager();
@@ -0,0 +1,226 @@
1
+ # Native Storage Usage Guide
2
+
3
+ The Native Storage layer provides a unified, multi-tiered browser storage gateway mapping memory (LRU), IndexedDB (IDB), Origin Private File System (OPFS), and Cache API storage tiers to a single consistent interface. It integrates transparent gzipped compression, write-ahead journaling for crash durability, multi-tab Web Locks serialization, database upgrade upgrade-blocking listeners, and proactive quota estimates.
4
+
5
+ Import from the storage entry point:
6
+
7
+ ```javascript
8
+ import { storage } from '@adukiorg/anza/storage';
9
+ ```
10
+
11
+ Or import individual adapters directly:
12
+
13
+ ```javascript
14
+ import { Database, LRUCache, WeakLRUCache } from '@adukiorg/anza/storage';
15
+ ```
16
+
17
+ ---
18
+
19
+ ## 1. Choosing an API
20
+
21
+ | Need | Tier / API | Characteristics |
22
+ |---|---|---|
23
+ | In-memory caching | `'memory'` | Synchronous, ephemeral, size-bounded LRU |
24
+ | General structured data | `'idb'` (default) | Asynchronous, persistent, transactional, compressed if >64KB |
25
+ | High-performance files | `'opfs'` | Dedicated worker, synchronous I/O access handles, Web Locked |
26
+ | HTTP request caching | `'cache'` | Response payloads, Cache API, custom headers, TTL support |
27
+ | Check space usage | `storage.estimate()` | Proactive quota assessment, persisted flag |
28
+ | Request persistence | `storage.persist()` | Prevents automatic browser-driven eviction |
29
+
30
+ ---
31
+
32
+ ## 2. Configuration
33
+
34
+ The default pool (`platform-db`, a 200-entry LRU, `platform-cache`) works out of
35
+ the box. Call `storage.configure(...)` once, before first use, to customize it.
36
+
37
+ ```javascript
38
+ storage.configure({
39
+ idb: { name: 'app-db', version: 2, migrations: [
40
+ (db) => db.createObjectStore('keyval'),
41
+ (db) => db.createObjectStore('users')
42
+ ] },
43
+ lru: { maxSize: 500 },
44
+ cache: { name: 'app-cache' }
45
+ });
46
+ ```
47
+
48
+ ## 3. Gateway Operations
49
+
50
+ The primary storage gateway exposes five main methods: `get`, `set`, `delete`, `list`, and `clear`.
51
+
52
+ ### Set
53
+
54
+ Writes an item to the designated tier. If tier is omitted, writes to `'idb'` (IndexedDB) and caches it in memory. The 2nd argument may be a tier string or an options object `{ tier, ttl }`.
55
+
56
+ ```javascript
57
+ // Simple key-value write (defaults to idb)
58
+ await storage.set('user:session', { token: 'abc' });
59
+
60
+ // Options object form (preferred when you need a TTL)
61
+ await storage.set('temp:config', { theme: 'dark' }, { tier: 'memory', ttl: 5000 });
62
+
63
+ // Tier string form
64
+ await storage.set('temp:config', { theme: 'dark' }, 'memory');
65
+
66
+ // TTL is honored across ALL tiers (memory, cache, idb, opfs)
67
+ await storage.set('draft', payload, { tier: 'idb', ttl: 60_000 });
68
+ await storage.set('scratch', blob, { tier: 'opfs', ttl: 60_000 });
69
+
70
+ // OPFS write (no expiry)
71
+ await storage.set('documents/resume.pdf', arrayBuffer, 'opfs');
72
+
73
+ // Cache API HTTP request caching with TTL
74
+ await storage.set('https://api.example.com/data', apiResponse, { tier: 'cache', ttl: 60000 });
75
+ ```
76
+
77
+ Expired idb/opfs entries are removed transparently on the next read.
78
+
79
+ ### Get
80
+
81
+ Retrieves an item from the designated tier. If tier is omitted, default reads check memory first, then fall back to IndexedDB.
82
+
83
+ ```javascript
84
+ // Default read (memory -> IndexedDB fallback)
85
+ const session = await storage.get('user:session');
86
+
87
+ // Tier-specific reads
88
+ const config = await storage.get('temp:config', 'memory');
89
+ const doc = await storage.get('documents/resume.pdf', 'opfs');
90
+ const response = await storage.get('https://api.example.com/data', 'cache');
91
+ ```
92
+
93
+ ### Delete
94
+
95
+ Removes a key from the storage pool.
96
+
97
+ ```javascript
98
+ await storage.delete('user:session');
99
+ await storage.delete('documents/resume.pdf', 'opfs');
100
+ ```
101
+
102
+ ### List
103
+
104
+ Lists keys stored in the requested tier.
105
+
106
+ ```javascript
107
+ const idbKeys = await storage.list('idb');
108
+ const opfsFiles = await storage.list('opfs');
109
+ ```
110
+
111
+ ### Clear
112
+
113
+ Wipes the storage pool for a specific tier or all.
114
+
115
+ ```javascript
116
+ // Wipe all tiers
117
+ await storage.clear('all');
118
+
119
+ // Wipe a specific tier
120
+ await storage.clear('opfs');
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 4. Multi-Store Transactions
126
+
127
+ For executing multi-store IndexedDB operations inside a single transactional block:
128
+
129
+ ```javascript
130
+ await storage.transaction(['keyval', 'users'], 'readwrite', (store, tx) => {
131
+ const keyvalStore = store('keyval');
132
+ const usersStore = store('users');
133
+
134
+ keyvalStore.put({ id: 1 }, 'lastActive');
135
+ usersStore.put({ id: 100, name: 'Alice' }, '100');
136
+ });
137
+ ```
138
+
139
+ > [!WARNING]
140
+ > Transactions auto-commit once control returns to the event loop. Avoid calling slow asynchronous operations (like `await fetch`) inside the transaction callback, as they cause transactions to commit prematurely.
141
+
142
+ ---
143
+
144
+ ## 5. Concurrency Coordination
145
+
146
+ ### Web Locks for OPFS
147
+ The Origin Private File System does not serialize cross-tab operations natively. The storage gateway automatically wraps all OPFS file reads and writes inside exclusive Web Locks:
148
+ - Reads/writes to a file named `report.json` acquire lock `opfs:report.json`.
149
+ - Global operations like `clear()` acquire lock `opfs:global`.
150
+
151
+ This prevents concurrent write collisions and lock errors across active tabs.
152
+
153
+ ---
154
+
155
+ ## 6. Upgrade Blocking Coordination
156
+
157
+ When performing schema upgrades, sibling tabs holding active connections can block migrations indefinitely:
158
+ - **Blocked Notification:** If an upgrade is blocked, the gateway dispatches a global `'storage:blocked'` window event.
159
+ - **Graceful Cleanup:** If another tab requests an upgrade, active connections listen for the `'versionchange'` event, immediately close themselves, and dispatch a global `'storage:versionchange'` window event.
160
+
161
+ ```javascript
162
+ window.addEventListener('storage:blocked', (e) => {
163
+ console.warn(`Database upgrade blocked: ${e.detail.name}`);
164
+ });
165
+
166
+ window.addEventListener('storage:versionchange', (e) => {
167
+ console.warn(`Database connection closed to allow upgrade in another tab: ${e.detail.name}`);
168
+ });
169
+ ```
170
+
171
+ ---
172
+
173
+ ## 7. Transparent Compression
174
+
175
+ To conserve quota and reduce serialization overhead, records exceeding the `compressionThreshold` (default: 64KB) are automatically gzipped using the browser's native `CompressionStream` API before writing to IndexedDB:
176
+
177
+ ```javascript
178
+ // Adjust the compression boundary if needed
179
+ storage.compressionThreshold = 32 * 1024; // 32KB
180
+
181
+ // Large dataset (>64KB) gets compressed automatically
182
+ await storage.set('large:dataset', largePayload, 'idb');
183
+
184
+ // Reads automatically decompress the record transparently
185
+ const dataset = await storage.get('large:dataset', 'idb');
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 8. Write Journaling
191
+
192
+ To protect transaction commits from unexpected tab crashes or power cuts, IndexedDB writes use a synchronous write-ahead journal stored in `localStorage`:
193
+ 1. The payload is written to `localStorage` under `storage:journal:${key}`.
194
+ 2. The IndexedDB write executes asynchronously.
195
+ 3. Upon completion, the journal entry is deleted from `localStorage`.
196
+ 4. On next boot, the system automatically checks for pending journals and replays them to prevent data loss.
197
+
198
+ ---
199
+
200
+ ## 9. Quota warnings
201
+
202
+ Subscribe to quota alerts to proactively warn users when browser storage utilization exceeds 80%:
203
+
204
+ ```javascript
205
+ const dispose = storage.onQuotaWarning(({ usage, quota }) => {
206
+ console.warn(`Storage space warning: ${(usage / quota * 100).toFixed(1)}% full.`);
207
+ });
208
+
209
+ // To unsubscribe
210
+ dispose();
211
+ ```
212
+
213
+ ---
214
+
215
+ ## 10. Advanced Queries
216
+
217
+ Query IndexedDB using indexes, ranges, cursors, direction and limits:
218
+
219
+ ```javascript
220
+ const records = await storage.query('users', {
221
+ index: 'byAge',
222
+ range: IDBKeyRange.bound(18, 30),
223
+ direction: 'prev',
224
+ limit: 20
225
+ });
226
+ ```
@@ -0,0 +1,50 @@
1
+ /**
2
+ * src/core/ui/base.js
3
+ *
4
+ * Web Component HTMLElement Base.
5
+ * Automatically initializes a unified lifecycle AbortController (`this.ctrl`)
6
+ * inside connectedCallback and aborts it in disconnectedCallback to secure
7
+ * all dynamic event and stream subscriptions against memory leaks.
8
+ *
9
+ * Source: doc 04 — Web Components §1, §6
10
+ */
11
+
12
+ export class BaseElement extends HTMLElement {
13
+ constructor() {
14
+ super();
15
+ this.ctrl = null;
16
+ }
17
+
18
+ /**
19
+ * Invoked when the element is appended to the DOM.
20
+ * Async mount rejections are caught to prevent unhandled promise rejections
21
+ * on raw BaseElement subclasses.
22
+ */
23
+ connectedCallback() {
24
+ this.ctrl = new AbortController();
25
+ Promise.resolve(this.mount()).catch((err) => {
26
+ console.error('[Native UI] mount failed:', err);
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Invoked when the element is detached from the DOM.
32
+ */
33
+ disconnectedCallback() {
34
+ this.unmount();
35
+ if (this.ctrl) {
36
+ this.ctrl.abort();
37
+ this.ctrl = null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Lifecycle mount hook. Overridden by child custom elements to bind listeners.
43
+ */
44
+ mount() {}
45
+
46
+ /**
47
+ * Lifecycle unmount hook. Overridden by child custom elements to perform cleanups.
48
+ */
49
+ unmount() {}
50
+ }
@@ -0,0 +1,82 @@
1
+ import { router } from '../../router/index.js';
2
+ import { element } from './element.js';
3
+
4
+ /**
5
+ * High-performance declarative routing container factory.
6
+ * Wraps ui.element with strict layout guard lifecycles and element-scoped view transitions.
7
+ */
8
+ export function container(tag, spec, base = import.meta.url) {
9
+ // Inject default contain: layout styling required for element-scoped View Transitions
10
+ const containerStyle = ':host { contain: layout; display: block; }';
11
+ spec.style = spec.style ? `${containerStyle}\n${spec.style}` : containerStyle;
12
+
13
+ // Intercept mount to strictly register the singleton layout container
14
+ const originalMount = spec.mount;
15
+ spec.mount = (ctx) => {
16
+ const el = ctx.el;
17
+ const name = el.getAttribute('name') || tag.toLowerCase();
18
+
19
+ // Singleton guard: Reject duplicate registration instantly
20
+ const existing = router.getContainer(name);
21
+ if (existing && existing !== el) {
22
+ throw new Error(`ContainerError: Singleton violation — '${name}' is already mounted. A second instance cannot register while the first is active.`);
23
+ }
24
+
25
+ router.registerContainer(name, el);
26
+ if (originalMount) originalMount(ctx);
27
+ };
28
+
29
+ // Intercept unmount to safely unregister the layout container
30
+ const originalUnmount = spec.unmount;
31
+ spec.unmount = (ctx) => {
32
+ const el = ctx.el;
33
+ const name = el.getAttribute('name') || tag.toLowerCase();
34
+ router.unregisterContainer(name, el);
35
+
36
+ if (originalUnmount) originalUnmount(ctx);
37
+ };
38
+
39
+ // Define the base element using the standard declarative factory
40
+ element(tag, spec, base);
41
+
42
+ // Dynamically inject the Delegated Swap Interface for route transitions
43
+ const ElementClass = customElements.get(tag);
44
+ if (ElementClass && !ElementClass.prototype.swapView) {
45
+ ElementClass.prototype.swapView = async function(newElement, options = {}) {
46
+ const { direction = 'push' } = options;
47
+
48
+ // Apply directional transition hint via CSS custom property
49
+ this.dataset.transitionDirection = direction;
50
+
51
+ const doSwap = () => {
52
+ this.replaceChildren(newElement);
53
+ delete this.dataset.transitionDirection;
54
+ };
55
+
56
+ // Strategy 1: Element-scoped transition (Chrome 147+, concurrent-safe)
57
+ if (typeof this.startViewTransition === 'function') {
58
+ try {
59
+ const vt = this.startViewTransition({ callback: doSwap });
60
+ await vt.ready;
61
+ } catch (err) {
62
+ if (err?.name !== 'AbortError') console.warn('[UI Container] Scoped VT aborted:', err);
63
+ }
64
+ return;
65
+ }
66
+
67
+ // Strategy 2: Document-scoped transition (Baseline Oct 2025)
68
+ if (typeof document.startViewTransition === 'function') {
69
+ try {
70
+ const vt = document.startViewTransition(doSwap);
71
+ await vt.ready;
72
+ } catch (err) {
73
+ if (err?.name !== 'AbortError') console.warn('[UI Container] Document VT aborted:', err);
74
+ }
75
+ return;
76
+ }
77
+
78
+ // Strategy 3: Synchronous direct swap
79
+ doSwap();
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Registers a custom element with a duplicate-registration safety guard.
3
+ */
4
+ export function define(tag, Class) {
5
+ if (typeof customElements !== 'undefined') {
6
+ if (customElements.get(tag)) {
7
+ console.warn(`Custom Element "${tag}" is already registered. Skipping duplicate load.`);
8
+ return;
9
+ }
10
+ customElements.define(tag, Class);
11
+ }
12
+ }