@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,373 @@
1
+ /**
2
+ * src/core/storage/index.js
3
+ *
4
+ * Unified Storage Gateway.
5
+ * Integrates LRU caching, Cache API, OPFS, and IndexedDB under a unified,
6
+ * tiered public storage surface.
7
+ *
8
+ * Source: doc 22 — Storage Architecture §1, §3
9
+ */
10
+
11
+ import { Database } from './idb.js';
12
+ import { opfs } from './opfs.js';
13
+ import { CacheStorage } from './cache.js';
14
+ import { LRUCache, WeakLRUCache } from './lru.js';
15
+ import { quota } from './quota.js';
16
+ import { supports } from '../platform/index.js';
17
+
18
+ // Default migration: the core key-value store.
19
+ const defaultMigrations = [(db) => db.createObjectStore('keyval')];
20
+
21
+ // Initialize default storage pool instances (reassignable via storage.configure).
22
+ let idb = new Database('platform-db', 1, defaultMigrations);
23
+ let cache = new CacheStorage('platform-cache');
24
+ let lru = new LRUCache(200);
25
+
26
+ /**
27
+ * Normalizes the 3rd argument of get/set/delete: either a tier string or an
28
+ * options object `{ tier, ttl }`.
29
+ */
30
+ function resolve(tierOrOptions, ttl = null) {
31
+ if (tierOrOptions && typeof tierOrOptions === 'object') {
32
+ return { tier: tierOrOptions.tier ?? 'idb', ttl: tierOrOptions.ttl ?? null };
33
+ }
34
+ return { tier: tierOrOptions ?? 'idb', ttl };
35
+ }
36
+
37
+ // Expiry envelope so idb/opfs honor TTL like memory/cache do.
38
+ function wrapExpiry(value, ttl) {
39
+ if (!ttl) return value;
40
+ return { __exp: Date.now() + ttl, __v: value };
41
+ }
42
+
43
+ function unwrapExpiry(value) {
44
+ if (value && typeof value === 'object' && typeof value.__exp === 'number') {
45
+ if (Date.now() > value.__exp) return { expired: true, value: null };
46
+ return { expired: false, value: value.__v };
47
+ }
48
+ return { expired: false, value };
49
+ }
50
+
51
+ // Helper functions for transparent compression
52
+ async function compress(value) {
53
+ const isObject = typeof value === 'object' && value !== null;
54
+ const stringVal = isObject ? JSON.stringify(value) : String(value);
55
+ const bytes = new TextEncoder().encode(stringVal);
56
+ const stream = new ReadableStream({
57
+ start(controller) {
58
+ controller.enqueue(bytes);
59
+ controller.close();
60
+ }
61
+ }).pipeThrough(new CompressionStream('gzip'));
62
+ const response = new Response(stream);
63
+ const buffer = await response.arrayBuffer();
64
+ return {
65
+ _compressed: true,
66
+ _type: isObject ? 'json' : 'string',
67
+ data: buffer
68
+ };
69
+ }
70
+
71
+ async function decompress(envelope) {
72
+ const stream = new ReadableStream({
73
+ start(controller) {
74
+ controller.enqueue(new Uint8Array(envelope.data));
75
+ controller.close();
76
+ }
77
+ }).pipeThrough(new DecompressionStream('gzip'));
78
+ const response = new Response(stream);
79
+ const text = await response.text();
80
+ return envelope._type === 'json' ? JSON.parse(text) : text;
81
+ }
82
+
83
+ // Replay write journal automatically on startup
84
+ async function replayJournal() {
85
+ if (typeof localStorage === 'undefined') return;
86
+ const keysToRemove = [];
87
+ for (let i = 0; i < localStorage.length; i++) {
88
+ const k = localStorage.key(i);
89
+ if (k && k.startsWith('storage:journal:')) {
90
+ try {
91
+ const raw = localStorage.getItem(k);
92
+ if (raw) {
93
+ const { key, value } = JSON.parse(raw);
94
+ console.log(`Replaying journal write for key: ${key}`);
95
+ await storage.set(key, value, 'idb');
96
+ }
97
+ } catch (err) {
98
+ console.error(`Failed to replay journal entry ${k}:`, err);
99
+ }
100
+ keysToRemove.push(k);
101
+ }
102
+ }
103
+ for (const k of keysToRemove) {
104
+ localStorage.removeItem(k);
105
+ }
106
+ }
107
+
108
+ if (typeof window !== 'undefined') {
109
+ Promise.resolve().then(() => replayJournal().catch(console.error));
110
+ }
111
+
112
+ export const storage = {
113
+ compressionThreshold: 64 * 1024,
114
+
115
+ /**
116
+ * Reconfigures the storage pool. Call before first use.
117
+ *
118
+ * storage.configure({
119
+ * idb: { name, version, migrations },
120
+ * lru: { maxSize },
121
+ * cache: { name }
122
+ * });
123
+ */
124
+ configure({ idb: idbOpts, lru: lruOpts, cache: cacheOpts } = {}) {
125
+ if (idbOpts) {
126
+ idb = new Database(
127
+ idbOpts.name ?? 'platform-db',
128
+ idbOpts.version ?? 1,
129
+ idbOpts.migrations ?? defaultMigrations
130
+ );
131
+ }
132
+ if (lruOpts) {
133
+ lru = new LRUCache(lruOpts.maxSize ?? 200);
134
+ }
135
+ if (cacheOpts) {
136
+ cache = new CacheStorage(cacheOpts.name ?? 'platform-cache');
137
+ }
138
+ return this;
139
+ },
140
+
141
+ /**
142
+ * Retrieves an item from the requested tier.
143
+ * The 2nd argument may be a tier string or `{ tier }`.
144
+ */
145
+ async get(key, tierOrOptions = 'idb') {
146
+ const { tier } = resolve(tierOrOptions);
147
+
148
+ if (tier === 'memory') {
149
+ return lru.get(key);
150
+ }
151
+
152
+ if (tier === 'opfs') {
153
+ const stored = await opfs.get(key);
154
+ const ex = unwrapExpiry(stored);
155
+ if (ex.expired) {
156
+ await opfs.delete(key);
157
+ return null;
158
+ }
159
+ return ex.value;
160
+ }
161
+
162
+ if (tier === 'cache') {
163
+ const res = await cache.get(key);
164
+ if (!res) return null;
165
+ try {
166
+ return await res.json();
167
+ } catch {
168
+ return await res.text();
169
+ }
170
+ }
171
+
172
+ // Default 'idb' read with LRU memory caching
173
+ const cached = lru.get(key);
174
+ if (cached !== null) return cached;
175
+
176
+ let value = await idb.get('keyval', key);
177
+ if (value !== null) {
178
+ const ex = unwrapExpiry(value);
179
+ if (ex.expired) {
180
+ await idb.delete('keyval', key);
181
+ return null;
182
+ }
183
+ value = ex.value;
184
+
185
+ if (value && value._compressed && value.data) {
186
+ try {
187
+ value = await decompress(value);
188
+ } catch (err) {
189
+ console.error('Decompression failed:', err);
190
+ }
191
+ }
192
+ lru.set(key, value);
193
+ }
194
+ return value;
195
+ },
196
+
197
+ /**
198
+ * Saves an item to the requested tier.
199
+ * The 2nd argument may be a tier string or `{ tier, ttl }`. TTL is honored across all tiers.
200
+ */
201
+ async set(key, value, tierOrOptions = 'idb') {
202
+ const { tier, ttl } = resolve(tierOrOptions);
203
+
204
+ // Quota proactive check before write
205
+ await quota.check();
206
+
207
+ if (tier === 'memory') {
208
+ lru.set(key, value, ttl);
209
+ return;
210
+ }
211
+
212
+ if (tier === 'opfs') {
213
+ await opfs.set(key, wrapExpiry(value, ttl));
214
+ return;
215
+ }
216
+
217
+ if (tier === 'cache') {
218
+ const response = new Response(
219
+ typeof value === 'object' ? JSON.stringify(value) : String(value),
220
+ {
221
+ headers: {
222
+ 'content-type': typeof value === 'object' ? 'application/json' : 'text/plain'
223
+ }
224
+ }
225
+ );
226
+ await cache.set(key, response, ttl);
227
+ return;
228
+ }
229
+
230
+ // Default: Write to IDB and cache in LRU
231
+ const journalKey = `storage:journal:${key}`;
232
+ if (typeof localStorage !== 'undefined') {
233
+ try {
234
+ localStorage.setItem(journalKey, JSON.stringify({ key, value }));
235
+ } catch (err) {
236
+ console.warn('Failed to write journal:', err);
237
+ }
238
+ }
239
+
240
+ try {
241
+ let finalValue = value;
242
+ if (supports.compression) {
243
+ const serialized = typeof value === 'object' ? JSON.stringify(value) : String(value);
244
+ if (serialized.length > this.compressionThreshold) {
245
+ try {
246
+ finalValue = await compress(value);
247
+ } catch (err) {
248
+ console.error('Compression failed:', err);
249
+ }
250
+ }
251
+ }
252
+
253
+ await idb.set('keyval', key, wrapExpiry(finalValue, ttl));
254
+ lru.set(key, value, ttl);
255
+ } finally {
256
+ if (typeof localStorage !== 'undefined') {
257
+ localStorage.removeItem(journalKey);
258
+ }
259
+ }
260
+ },
261
+
262
+ /**
263
+ * Deletes an item from the storage tiers.
264
+ */
265
+ async delete(key, tierOrOptions = 'idb') {
266
+ const { tier } = resolve(tierOrOptions);
267
+ lru.delete(key);
268
+
269
+ if (tier === 'opfs') {
270
+ await opfs.delete(key);
271
+ return;
272
+ }
273
+
274
+ if (tier === 'cache') {
275
+ await cache.delete(key);
276
+ return;
277
+ }
278
+
279
+ await idb.delete('keyval', key);
280
+ },
281
+
282
+ /**
283
+ * Performs advanced index/cursor queries on the IDB store.
284
+ */
285
+ async query(storeName, queryOpts) {
286
+ return idb.query(storeName, queryOpts);
287
+ },
288
+
289
+ /**
290
+ * Run a callback inside a multi-store transaction context.
291
+ */
292
+ transaction(storeNames, mode, fn) {
293
+ return idb.transaction(storeNames, mode, fn);
294
+ },
295
+
296
+ /**
297
+ * Lists keys available in the store.
298
+ */
299
+ async list(tier = 'idb') {
300
+ if (tier === 'opfs') {
301
+ return opfs.list();
302
+ }
303
+
304
+ if (tier === 'cache') {
305
+ const c = await cache.open();
306
+ const keys = await c.keys();
307
+ return keys.map((req) => req.url);
308
+ }
309
+
310
+ return idb.keys('keyval');
311
+ },
312
+
313
+ /**
314
+ * Clears the storage pool for a specific tier or all.
315
+ */
316
+ async clear(tier = 'all') {
317
+ lru.clear();
318
+
319
+ if (tier === 'all' || tier === 'opfs') {
320
+ await opfs.clear();
321
+ }
322
+
323
+ if (tier === 'all' || tier === 'cache') {
324
+ await cache.clear();
325
+ }
326
+
327
+ if (tier === 'all' || tier === 'idb') {
328
+ await idb.clear('keyval');
329
+ }
330
+ },
331
+
332
+ /**
333
+ * Retrieves storage quotas.
334
+ */
335
+ async estimate() {
336
+ const est = await quota.estimate();
337
+ const persisted = await this.persisted();
338
+ return {
339
+ quota: est.quota,
340
+ usage: est.usage,
341
+ persisted
342
+ };
343
+ },
344
+
345
+ /**
346
+ * Requests storage persistence.
347
+ */
348
+ persist() {
349
+ return quota.persist();
350
+ },
351
+
352
+ /**
353
+ * Checks if storage is persisted.
354
+ */
355
+ async persisted() {
356
+ if (typeof navigator !== 'undefined' && navigator.storage?.persisted) {
357
+ return navigator.storage.persisted();
358
+ }
359
+ return false;
360
+ },
361
+
362
+ /**
363
+ * Registers a callback for storage quota warning events.
364
+ */
365
+ onQuotaWarning(handler) {
366
+ return quota.onQuotaWarning(handler);
367
+ }
368
+ };
369
+
370
+
371
+ // Named class exports — lets consumers instantiate adapters directly:
372
+ // import { Database, LRUCache } from '@adukiorg/anza/storage';
373
+ export { Database, LRUCache, WeakLRUCache };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * src/core/storage/lru.js
3
+ *
4
+ * Bounded Least-Recently-Used (LRU) Cache.
5
+ * Provides Map-based LRU and WeakRef-based GC-eligible bounded caches
6
+ * supporting custom TTL values.
7
+ *
8
+ * Source: doc 22 — Storage Architecture §2
9
+ */
10
+
11
+ export class LRUCache {
12
+ constructor(maxSize = 100) {
13
+ this.maxSize = maxSize;
14
+ this.cache = new Map();
15
+ }
16
+
17
+ get(key) {
18
+ if (!this.cache.has(key)) return null;
19
+
20
+ const entry = this.cache.get(key);
21
+ if (entry.expires && Date.now() > entry.expires) {
22
+ this.cache.delete(key);
23
+ return null;
24
+ }
25
+
26
+ // Refresh access position by moving key to the end
27
+ this.cache.delete(key);
28
+ this.cache.set(key, entry);
29
+ return entry.value;
30
+ }
31
+
32
+ set(key, value, ttlMs) {
33
+ if (this.cache.has(key)) {
34
+ this.cache.delete(key);
35
+ } else if (this.cache.size >= this.maxSize) {
36
+ // Map keys iterator guarantees insertion-order; first item is least recently used
37
+ const oldest = this.cache.keys().next().value;
38
+ this.cache.delete(oldest);
39
+ }
40
+
41
+ const expires = ttlMs ? Date.now() + ttlMs : null;
42
+ this.cache.set(key, { value, expires });
43
+ }
44
+
45
+ delete(key) {
46
+ return this.cache.delete(key);
47
+ }
48
+
49
+ clear() {
50
+ this.cache.clear();
51
+ }
52
+ }
53
+
54
+ /**
55
+ * WeakRef-based bounded LRU cache.
56
+ * Yields elements to the Garbage Collector if memory pressure requires.
57
+ */
58
+ export class WeakLRUCache {
59
+ constructor(maxSize = 100) {
60
+ this.maxSize = maxSize;
61
+ this.cache = new Map();
62
+ }
63
+
64
+ get(key) {
65
+ if (!this.cache.has(key)) return null;
66
+
67
+ const entry = this.cache.get(key);
68
+ const value = entry.ref.deref();
69
+
70
+ // Clean up if GC collected the reference or TTL expired
71
+ if (value === undefined || (entry.expires && Date.now() > entry.expires)) {
72
+ this.cache.delete(key);
73
+ return null;
74
+ }
75
+
76
+ this.cache.delete(key);
77
+ this.cache.set(key, entry);
78
+ return value;
79
+ }
80
+
81
+ set(key, value, ttlMs) {
82
+ if (typeof value !== 'object' && typeof value !== 'function') {
83
+ throw new TypeError('WeakLRUCache values must be objects or functions');
84
+ }
85
+
86
+ if (this.cache.has(key)) {
87
+ this.cache.delete(key);
88
+ } else if (this.cache.size >= this.maxSize) {
89
+ const oldest = this.cache.keys().next().value;
90
+ this.cache.delete(oldest);
91
+ }
92
+
93
+ const expires = ttlMs ? Date.now() + ttlMs : null;
94
+ this.cache.set(key, {
95
+ ref: new WeakRef(value),
96
+ expires
97
+ });
98
+ }
99
+
100
+ delete(key) {
101
+ return this.cache.delete(key);
102
+ }
103
+
104
+ clear() {
105
+ this.cache.clear();
106
+ }
107
+ }
@@ -0,0 +1,165 @@
1
+ # Storage Missing Support
2
+
3
+ This document tracks remaining support, runtime gaps, concurrency constraints, serialization enhancements, and performance optimization work for `src/core/storage`. 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 storage support requirements:
12
+
13
+ - Analyze storage tier utilization and statically compile Dedicated Web Worker scripts to avoid runtime ObjectURL blobs.
14
+ - Detect synchronous storage access patterns (like `localStorage` or `sessionStorage` usage) outside designated bootstrap paths.
15
+ - Statically validate migration schema progression profiles.
16
+
17
+ Naming rules for storage support:
18
+
19
+ - Prefer one-word files: `idb.js`, `lru.js`, `opfs.js`, `quota.js`, `cache.js`, `index.js`.
20
+ - Plural folders: `tests/`, `migrations/`.
21
+ - Single-word methods: `get`, `set`, `delete`, `query`, `list`, `clear`, `estimate`, `persist`, `persisted`, `transaction`.
22
+ - Single-word events: `warning`, `quota`, `blocked`.
23
+ - Avoid compound names where single words carry full meaning in context.
24
+
25
+ ---
26
+
27
+ ## 1. Critical Runtime and Type Gaps
28
+
29
+ ### 1.1. Missing Unified Gateway Methods
30
+
31
+ - **Status**: Runtime gap.
32
+ - **Files**:
33
+ - `src/core/storage/index.js`
34
+ - **Problem**:
35
+ The Storage Architecture specification (§15) defines `storage.transaction(stores, mode, fn)`, `storage.persisted()`, and `storage.onQuotaWarning(handler)` as part of the Unified Storage Gateway interface. Currently, these methods are not implemented on the exported `storage` facade.
36
+ - **Expected Support**:
37
+ - Implement `storage.transaction(stores, mode, fn)` to execute multi-store operations inside a single transactional block.
38
+ - Implement `storage.persisted()` wrapping `navigator.storage.persisted()`.
39
+ - Implement `storage.onQuotaWarning(handler)` to subscribe to quota limit checks.
40
+
41
+ ### 1.2. Storage Type Mismatches
42
+
43
+ - **Status**: Type mismatch.
44
+ - **Files**:
45
+ - `types/core/storage/index.d.ts`
46
+ - **Problem**:
47
+ The type declaration file does not align with the public specifications. It is missing declarations for `persisted()`, `transaction()`, and the proper returned structure from `estimate()` (including the `persisted` flag).
48
+ - **Expected Support**:
49
+ - Align `types/core/storage/index.d.ts` to expose the true interface.
50
+
51
+ ---
52
+
53
+ ## 2. Concurrency & Durability Gaps
54
+
55
+ ### 2.1. Web Locks Coordination for OPFS
56
+
57
+ - **Status**: Critical concurrency gap.
58
+ - **Files**:
59
+ - `src/core/storage/opfs.js`
60
+ - **Problem**:
61
+ The OPFS manager does not coordinate file access across different tabs. Concurrent reads and writes to the same virtual file will throw errors or lead to raw binary data corruption.
62
+ - **Expected Support**:
63
+ - Wrap OPFS operations (`get`, `set`, `delete`) in an exclusive Web Lock (`workers.lock` or `navigator.locks.request`) scoped to the filename to serialize cross-tab operations.
64
+
65
+ ### 2.2. IndexedDB Upgrade Blocking Coordination
66
+
67
+ - **Status**: Runtime gap.
68
+ - **Files**:
69
+ - `src/core/storage/idb.js`
70
+ - **Problem**:
71
+ If a user has multiple tabs open, an IndexedDB database schema upgrade (triggered by opening with a higher version) is blocked indefinitely because the old tabs hold active connections. Currently, the database class does not listen to `onblocked` or `onversionchange` events.
72
+ - **Expected Support**:
73
+ - Handle the `blocked` event on the database open request, dispatching an event to warn the system of connection locks.
74
+ - Listen to the `versionchange` event on the active database connection. If triggered, gracefully close the database immediately so that other tabs can proceed with schema upgrades.
75
+
76
+ ### 2.3. Write Journal for Durability
77
+
78
+ - **Status**: Planned but missing.
79
+ - **Files**:
80
+ - `src/core/storage/index.js`
81
+ - **Problem**:
82
+ Transactions can fail to write to disk due to abrupt tab crashes or sudden power losses, leaving memory and disk states inconsistent.
83
+ - **Expected Support**:
84
+ - Introduce a write journal using crash-durable `localStorage` to buffer pending keys before committing them to IndexedDB.
85
+ - Verify and replay uncommitted journal operations automatically during client boot.
86
+
87
+ ---
88
+
89
+ ## 3. Serialization & Performance Gaps
90
+
91
+ ### 3.1. Transparent Compression for Large Records
92
+
93
+ - **Status**: Performance enhancement.
94
+ - **Files**:
95
+ - `src/core/storage/index.js`
96
+ - **Problem**:
97
+ Writing large objects (>64KB) directly to IndexedDB consumes excess quota, increases serialization/deserialization delay, and degrades storage bandwidth.
98
+ - **Expected Support**:
99
+ - Implement transparent compression using the browser's native Compression Streams API (`new CompressionStream('gzip')`) when writing values exceeding 64KB.
100
+ - Inject a metadata envelope (`{ value, compressed: true }`) to identify compressed payloads and decompress them automatically on read.
101
+
102
+ ### 3.2. Worker Script Optimization
103
+
104
+ - **Status**: Optimization.
105
+ - **Files**:
106
+ - `src/core/storage/opfs.js`
107
+ - **Problem**:
108
+ The OPFS manager instantiates its Dedicated Worker using a Blob URL wrapper on an inline string (`URL.createObjectURL(new Blob(...))`). This slows down initial load, blocks minification, and triggers Content Security Policy (CSP) security warnings.
109
+ - **Expected Support**:
110
+ - Offload worker script extraction to the build-time compiler (`tools/`) to generate a static asset or optimize string injection.
111
+
112
+ ---
113
+
114
+ ## 4. Test Coverage Gaps
115
+
116
+ ### 4.1. OPFS Integration Tests
117
+
118
+ - **Status**: Missing coverage.
119
+ - **Files**:
120
+ - `tests/core/storage/opfs.test.js` [NEW]
121
+ - **Needed Coverage**:
122
+ - Dedicated Worker execution offloading.
123
+ - Parallel reads/writes and verification of data persistence.
124
+ - Cross-tab change invalidation alerts over BroadcastChannel.
125
+
126
+ ### 4.2. Cache API and TTL Verification
127
+
128
+ - **Status**: Missing coverage.
129
+ - **Files**:
130
+ - `tests/core/storage/cache.test.js` [NEW]
131
+ - **Needed Coverage**:
132
+ - Cache matches and correct header insertion for `x-expires-at`.
133
+ - Automatic eviction of expired items.
134
+
135
+ ### 4.3. Quota Warning and Eviction Tests
136
+
137
+ - **Status**: Missing coverage.
138
+ - **Files**:
139
+ - `tests/core/storage/quota.test.js` [NEW]
140
+ - **Needed Coverage**:
141
+ - Mocking quota estimate queries.
142
+ - Verification that warnings fire when threshold usage exceeds 80%.
143
+
144
+ ---
145
+
146
+ ## 5. Suggested Implementation Order
147
+
148
+ 1. Align `types/core/storage/index.d.ts` with the full unified storage API.
149
+ 2. Add comprehensive tests for `opfs.js`, `cache.js`, and `quota.js`.
150
+ 3. Implement Web Locks for OPFS file synchronization.
151
+ 4. Implement database blocking and connection version upgrade handlers.
152
+ 5. Implement transparent Compression Streams for large values (>64KB).
153
+ 6. Implement write journaling for transactional durability.
154
+
155
+ ---
156
+
157
+ ## 6. Done Criteria
158
+
159
+ This missing-support list is complete when:
160
+
161
+ - TypeScript declarations map the complete unified storage API.
162
+ - OPFS, Cache TTL, and Quota warning components have complete integration tests.
163
+ - Web Locks synchronize multi-tab file writes.
164
+ - DB upgrades are not blocked by connections in sibling tabs.
165
+ - Large records (>64KB) are compressed transparently using native streams.