@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,69 @@
1
+ /**
2
+ * src/core/api/retry.js
3
+ *
4
+ * Implements exponential backoff with jitter for transient errors.
5
+ * Ensures the network layer is self-healing for temporary disconnects/server errors,
6
+ * while instantly failing client-side (4xx) errors.
7
+ *
8
+ * Source: doc 11 — Networking §8
9
+ */
10
+
11
+ import { PlatformError } from './fetch.js';
12
+
13
+ /**
14
+ * Retries an asynchronous operation with exponential backoff and full jitter.
15
+ */
16
+ export async function retry(operation, options = {}) {
17
+ const {
18
+ attempts = 3,
19
+ base = 100, // starting delay in ms
20
+ maxDelay = 3000, // maximum wait time in ms
21
+ signal
22
+ } = options;
23
+
24
+ let attempt = 0;
25
+
26
+ while (true) {
27
+ if (signal?.aborted) {
28
+ throw new PlatformError({
29
+ code: 'NETWORK_ERROR',
30
+ message: 'Network request aborted before retry attempt',
31
+ recoverable: false
32
+ });
33
+ }
34
+
35
+ try {
36
+ return await operation();
37
+ } catch (err) {
38
+ attempt++;
39
+
40
+ // Qualify transient failure: Network dropouts, timeouts, or server (5xx) issues
41
+ const isTransient = err.code === 'NETWORK_TIMEOUT' ||
42
+ err.code === 'NETWORK_ERROR' ||
43
+ (err.code === 'HTTP_ERROR' && err.context?.status >= 500);
44
+
45
+ if (attempt >= attempts || !isTransient) {
46
+ throw err;
47
+ }
48
+
49
+ // Exponential backoff with full jitter calculation
50
+ const tempDelay = base * Math.pow(2, attempt);
51
+ const delay = Math.random() * Math.min(maxDelay, tempDelay);
52
+
53
+ await new Promise((resolve, reject) => {
54
+ const timeoutId = setTimeout(resolve, delay);
55
+
56
+ if (signal) {
57
+ signal.addEventListener('abort', () => {
58
+ clearTimeout(timeoutId);
59
+ reject(new PlatformError({
60
+ code: 'NETWORK_ERROR',
61
+ message: 'Network request aborted during retry backoff',
62
+ recoverable: false
63
+ }));
64
+ });
65
+ }
66
+ });
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * src/core/api/stream.js
3
+ *
4
+ * Implements Streams API and progressive response handling.
5
+ * Parses NDJSON streams using native TransformStream pipelines, preserving backpressure.
6
+ * Useful for large data lists, logs, or live AI streaming responses.
7
+ *
8
+ * Source: doc 11 — Networking §11
9
+ */
10
+
11
+ /**
12
+ * Creates a reusable TransformStream to parse Newline-Delimited JSON (NDJSON).
13
+ */
14
+ export function createNDJSONTransform() {
15
+ let buffer = '';
16
+ return new TransformStream({
17
+ transform(chunk, controller) {
18
+ buffer += chunk;
19
+ const lines = buffer.split('\n');
20
+ buffer = lines.pop() || ''; // Keep trailing fragment in buffer
21
+
22
+ for (const line of lines) {
23
+ const trimmed = line.trim();
24
+ if (trimmed) {
25
+ try {
26
+ controller.enqueue(JSON.parse(trimmed));
27
+ } catch (err) {
28
+ controller.error(new Error(`Failed to parse NDJSON line: ${err.message}`));
29
+ }
30
+ }
31
+ }
32
+ },
33
+ flush(controller) {
34
+ const trimmed = buffer.trim();
35
+ if (trimmed) {
36
+ try {
37
+ controller.enqueue(JSON.parse(trimmed));
38
+ } catch (err) {
39
+ controller.error(new Error(`Failed to parse NDJSON line on flush: ${err.message}`));
40
+ }
41
+ }
42
+ }
43
+ });
44
+ }
45
+
46
+ import { events } from './events/index.js';
47
+
48
+ /**
49
+ * Initiates a streaming request and yields parsed JSON chunks as an AsyncIterable.
50
+ */
51
+ export async function* stream(url, opts = {}) {
52
+ const { signal, ...fetchOpts } = opts;
53
+
54
+ // Generate unique request ID to scope streaming telemetry events
55
+ const requestId = opts.requestId || (typeof crypto !== 'undefined' && crypto.randomUUID
56
+ ? crypto.randomUUID()
57
+ : Math.random().toString(36).slice(2) + Date.now().toString(36));
58
+
59
+ // Register request-specific temporary listeners
60
+ const disposes = [];
61
+ if (opts.on && typeof opts.on === 'object') {
62
+ for (const [event, handler] of Object.entries(opts.on)) {
63
+ if (typeof handler === 'function') {
64
+ const dispose = events.on(event, (e) => {
65
+ if (e.detail?.requestId === requestId) {
66
+ handler(e);
67
+ }
68
+ });
69
+ disposes.push(dispose);
70
+ }
71
+ }
72
+ }
73
+
74
+ let response;
75
+ try {
76
+ response = await fetch(url, { ...fetchOpts, signal });
77
+ } catch (err) {
78
+ events.emit('error', { error: err, requestId });
79
+ events.emit('failed', { error: err, requestId });
80
+ throw err;
81
+ }
82
+
83
+ if (!response.ok) {
84
+ const err = new Error(`Streaming failed: HTTP ${response.status} ${response.statusText}`);
85
+ events.emit('status:' + response.status, { response, requestId });
86
+ events.emit('failed', { response, requestId });
87
+ events.emit('error', { error: err, requestId });
88
+ throw err;
89
+ }
90
+
91
+ if (!response.body) {
92
+ const err = new Error('Streaming failed: Response body is not readable');
93
+ events.emit('error', { error: err, requestId });
94
+ throw err;
95
+ }
96
+
97
+ const reader = response.body
98
+ .pipeThrough(new TextDecoderStream())
99
+ .pipeThrough(createNDJSONTransform())
100
+ .getReader();
101
+
102
+ try {
103
+ while (true) {
104
+ let result;
105
+ try {
106
+ result = await reader.read();
107
+ } catch (err) {
108
+ events.emit('error', { error: err, requestId });
109
+ throw err;
110
+ }
111
+
112
+ const { value, done } = result;
113
+ if (done) break;
114
+
115
+ // Emit chunk telemetry event locally and globally
116
+ events.emit('chunk', { chunk: value, requestId });
117
+
118
+ yield value;
119
+ }
120
+ } finally {
121
+ reader.releaseLock();
122
+ // Guarantee auto-cleanup of streaming listeners
123
+ for (const dispose of disposes) {
124
+ dispose();
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * src/core/api/upload.js
3
+ *
4
+ * Handles file uploads with real-time progress events.
5
+ * Uses standard XMLHttpRequest to provide precise progress callbacks (opts.onProgress),
6
+ * bridging the standard fetch upload progress gap in Baseline browsers.
7
+ *
8
+ * Source: doc 11 — Networking §14
9
+ */
10
+
11
+ import { PlatformError } from './fetch.js';
12
+ import { events } from './events/index.js';
13
+
14
+ /**
15
+ * Uploads a file or binary payload with progress tracking and AbortSignal support.
16
+ */
17
+ export function upload(url, fileOrData, opts = {}) {
18
+ const {
19
+ method = 'POST',
20
+ headers = {},
21
+ onProgress,
22
+ signal
23
+ } = opts;
24
+
25
+ // Generate unique request ID to scope uploading telemetry events
26
+ const requestId = opts.requestId || (typeof crypto !== 'undefined' && crypto.randomUUID
27
+ ? crypto.randomUUID()
28
+ : Math.random().toString(36).slice(2) + Date.now().toString(36));
29
+
30
+ // Register request-specific temporary listeners
31
+ const disposes = [];
32
+ if (opts.on && typeof opts.on === 'object') {
33
+ for (const [event, handler] of Object.entries(opts.on)) {
34
+ if (typeof handler === 'function') {
35
+ const dispose = events.on(event, (e) => {
36
+ if (e.detail?.requestId === requestId) {
37
+ handler(e);
38
+ }
39
+ });
40
+ disposes.push(dispose);
41
+ }
42
+ }
43
+ }
44
+
45
+ const cleanup = () => {
46
+ for (const dispose of disposes) {
47
+ dispose();
48
+ }
49
+ };
50
+
51
+ return new Promise((resolve, reject) => {
52
+ const xhr = new XMLHttpRequest();
53
+ xhr.open(method, url, true);
54
+
55
+ // Apply headers
56
+ for (const [key, value] of Object.entries(headers)) {
57
+ xhr.setRequestHeader(key, value);
58
+ }
59
+
60
+ // Attach upload progress listeners
61
+ if (xhr.upload) {
62
+ xhr.upload.onprogress = (event) => {
63
+ if (event.lengthComputable) {
64
+ const progressPayload = {
65
+ loaded: event.loaded,
66
+ total: event.total,
67
+ percentage: Math.round((event.loaded / event.total) * 100)
68
+ };
69
+
70
+ // Trigger legacy onProgress callback if defined
71
+ if (onProgress) {
72
+ onProgress(progressPayload);
73
+ }
74
+
75
+ // Emit progress telemetry event
76
+ events.emit('progress', { ...progressPayload, requestId });
77
+ }
78
+ };
79
+ }
80
+
81
+ // Handle abort triggers
82
+ if (signal) {
83
+ if (signal.aborted) {
84
+ xhr.abort();
85
+ cleanup();
86
+ return reject(
87
+ new PlatformError({
88
+ code: 'NETWORK_ERROR',
89
+ message: 'Upload aborted',
90
+ recoverable: false
91
+ })
92
+ );
93
+ }
94
+ signal.addEventListener('abort', () => {
95
+ xhr.abort();
96
+ cleanup();
97
+ reject(
98
+ new PlatformError({
99
+ code: 'NETWORK_ERROR',
100
+ message: 'Upload aborted',
101
+ recoverable: false
102
+ })
103
+ );
104
+ });
105
+ }
106
+
107
+ xhr.onload = () => {
108
+ if (xhr.status >= 200 && xhr.status < 300) {
109
+ let responseData = xhr.responseText;
110
+ try {
111
+ responseData = JSON.parse(responseData);
112
+ } catch {
113
+ // Keep raw text if not JSON format
114
+ }
115
+
116
+ // Mock a simple Response object for matching response event payloads
117
+ const mockResponse = {
118
+ status: xhr.status,
119
+ ok: true,
120
+ headers: new Headers({ 'Content-Type': xhr.getResponseHeader('Content-Type') || 'text/plain' })
121
+ };
122
+ events.emit(`status:${xhr.status}`, { response: mockResponse, requestId });
123
+
124
+ cleanup();
125
+ resolve(responseData);
126
+ } else {
127
+ const err = new PlatformError({
128
+ code: 'HTTP_ERROR',
129
+ message: `Upload failed with status code ${xhr.status}`,
130
+ context: { url, status: xhr.status },
131
+ recoverable: xhr.status >= 500
132
+ });
133
+
134
+ const mockResponse = {
135
+ status: xhr.status,
136
+ ok: false,
137
+ headers: new Headers({ 'Content-Type': xhr.getResponseHeader('Content-Type') || 'text/plain' })
138
+ };
139
+ events.emit(`status:${xhr.status}`, { response: mockResponse, requestId });
140
+ events.emit('failed', { response: mockResponse, requestId });
141
+ events.emit('error', { error: err, requestId });
142
+
143
+ cleanup();
144
+ reject(err);
145
+ }
146
+ };
147
+
148
+ xhr.onerror = (err) => {
149
+ const errorObj = new PlatformError({
150
+ code: 'NETWORK_ERROR',
151
+ message: 'Upload network connection failed',
152
+ cause: err,
153
+ context: { url },
154
+ recoverable: true
155
+ });
156
+ events.emit('failed', { error: errorObj, requestId });
157
+ events.emit('error', { error: errorObj, requestId });
158
+
159
+ cleanup();
160
+ reject(errorObj);
161
+ };
162
+
163
+ xhr.ontimeout = () => {
164
+ const errorObj = new PlatformError({
165
+ code: 'NETWORK_TIMEOUT',
166
+ message: 'Upload request timed out',
167
+ context: { url },
168
+ recoverable: true
169
+ });
170
+ events.emit('timeout', { error: errorObj, requestId });
171
+ events.emit('failed', { error: errorObj, requestId });
172
+ events.emit('error', { error: errorObj, requestId });
173
+
174
+ cleanup();
175
+ reject(errorObj);
176
+ };
177
+
178
+ xhr.send(fileOrData);
179
+ });
180
+ }
@@ -0,0 +1,206 @@
1
+ # Networking & Caching Layer Usage Guide (`core.api`)
2
+
3
+ The `core.api` networking layer is a baseline-native, local-first HTTP client designed for high performance, self-healing retries, dynamic prefix routing, telemetry-driven event hooks, and fine-grained TTL caching.
4
+
5
+ ---
6
+
7
+ ## 1. Quick Start
8
+
9
+ Import the client directly from the ESM entry point:
10
+
11
+ ```javascript
12
+ import { api } from '@adukiorg/anza/api';
13
+
14
+ // Simple GET request (automatically parses JSON)
15
+ const profile = await api.get('/user/profile');
16
+ console.log(profile.name);
17
+
18
+ // Simple POST request with body serialization
19
+ const result = await api.post('/posts', { title: 'Native ESM rules' });
20
+ ```
21
+
22
+ ---
23
+
24
+ ## 2. Dynamic Outbound Prefix Resolution
25
+
26
+ Prefixes let you map short names or sub-paths to base URLs exactly once at application bootstrap. This keeps your request endpoints clean and isolates environment configuration.
27
+
28
+ ### Registering Prefixes
29
+
30
+ ```javascript
31
+ import { api } from '@adukiorg/anza/api';
32
+
33
+ // Add configuration at startup
34
+ api.prefix.add('auth', 'https://auth.example.com');
35
+ api.prefix.add('default', 'https://api.example.com'); // acts as root fallback
36
+ ```
37
+
38
+ ### Automatic Path Resolution
39
+
40
+ The client automatically detects prefixes and safely concatenates paths without duplicating slashes:
41
+
42
+ ```javascript
43
+ // Resolves to: https://auth.example.com/login
44
+ const token = await api.post('auth/login', { username, password });
45
+
46
+ // Resolves to root fallback: https://api.example.com/user/profile
47
+ const profile = await api.get('/user/profile');
48
+
49
+ // Fully qualified URLs are left unchanged
50
+ const data = await api.get('https://other-domain.org/data');
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 3. Fine-Grained TTL Caching
56
+
57
+ Caching in `core.api` defaults to a **zero-cache policy** (network-only) unless an explicit expiry/TTL is provided. This prevents dynamic state mutations from serving stale data by default.
58
+
59
+ ### Caching with TTL
60
+
61
+ To cache successful GET responses, pass `expiry` or `ttl` (in milliseconds) inside request options:
62
+
63
+ ```javascript
64
+ // Cache is checked first. On miss, network is called and cache is populated for 60 seconds
65
+ const products = await api.get('/products', { expiry: 60000 });
66
+ ```
67
+
68
+ ### Cache Purging & Glob Invalidation
69
+
70
+ The cache manager supports granular deletions, whole store purges, or namespace-level invalidation utilizing standard glob patterns:
71
+
72
+ ```javascript
73
+ // Purge the entire API cache store
74
+ await api.cache.clear();
75
+
76
+ // Evict a single exact URL
77
+ await api.cache.delete('https://api.example.com/products');
78
+
79
+ // Glob Purging: Evict all endpoints starting with or containing /user/
80
+ await api.cache.delete('*/user/*');
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 4. Telemetry Events & Network Monitoring
86
+
87
+ The networking layer streams real-time connection events, allowing you to attach global hooks, security checks, and toast alerts based on HTTP statuses, content types, or failures.
88
+
89
+ ### Standard Telemetry Events
90
+
91
+ | Event Name | Trigger |
92
+ | --- | --- |
93
+ | `'error'` | Any network connection failure, request timeout, or server error |
94
+ | `'failed'` | Any non-ok response status (>= 400) or aborted connections |
95
+ | `'timeout'` | Requests exceeding their defined timeout limit |
96
+ | `'status:401'` | Specific HTTP 401 Unauthorized responses |
97
+ | `'status:500'` | Specific HTTP 500 Internal Server Error responses |
98
+ | `'status:xxx'` | Any specific HTTP status code (replace `xxx` with the desired code) |
99
+ | `'type:text'` | Successful responses carrying `text/plain` Content-Type |
100
+ | `'type:json'` | Successful responses carrying `application/json` Content-Type |
101
+ | `'type:stream'` | Successful responses carrying `text/event-stream` Content-Type |
102
+
103
+ ### Request-Scoped Event Listeners (Automatic Cleanup)
104
+
105
+ If you only care about events triggered by a **specific network request**, you can define listeners directly in the request options using the `on` object. The API client scopes these listeners to this single request and **automatically cleans them up** as soon as the request terminates (succeeds or fails). No manual disposer calls are needed:
106
+
107
+ ```javascript
108
+ await api.get('/user/profile', {
109
+ on: {
110
+ // Fired only if this specific request returns a 401
111
+ 'status:401': () => {
112
+ redirectToLogin();
113
+ },
114
+ // Fired only if this specific request fails or times out
115
+ 'failed': (event) => {
116
+ console.error('Request failed:', event.detail.error);
117
+ }
118
+ }
119
+ });
120
+ ```
121
+
122
+ ### Global Listeners & Manual Disposers
123
+
124
+ For broad application state hooks (such as global loading spinners or error overlays), subscribe to events globally. The `api.on` method returns a synchronous disposer function to stop listening:
125
+
126
+ ```javascript
127
+ // Register a global listener and receive a clean unsubscription function
128
+ const offUnauthorized = api.on('status:401', (event) => {
129
+ redirectToLogin();
130
+ });
131
+
132
+ // To stop listening manually, simply invoke the returned function:
133
+ offUnauthorized();
134
+ ```
135
+
136
+ ### Advanced Lifecycle-Gated Cleaning (with AbortSignal)
137
+
138
+ If you are developing custom elements or reactive components, you can pass an `AbortSignal` as the third parameter to `api.on`. The networking engine will listen to the signal and **automatically clean up the listener** when the component unmounts or aborts, eliminating boilerplate disposer calls:
139
+
140
+ ```javascript
141
+ // Example inside a reactive Custom Element mount hook:
142
+ mount({ el, ctrl }) {
143
+ // Pass the element's mount AbortController signal
144
+ api.on('status:401', () => {
145
+ redirectToLogin();
146
+ }, ctrl.signal);
147
+
148
+ // Fully automated: detaches completely when the component unmounts!
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## 5. Streaming & Progress Uploads
155
+
156
+ ### Response Streams (NDJSON)
157
+
158
+ For live logs or AI streaming responses, the client parses newline-delimited JSON streams natively preserving backpressure. It fully supports request-scoped `on` listeners (e.g. `chunk`, `error`, `failed`) and broadcasts them to the global telemetry bus with automatic unsubscriptions:
159
+
160
+ ```javascript
161
+ // Stream lines as an AsyncIterable and hook event listeners directly
162
+ const eventStream = api.stream('https://api.example.com/logs/stream', {
163
+ on: {
164
+ // Triggers progressively for each enqueued JSON chunk
165
+ chunk: (event) => {
166
+ console.log('Progressive chunk:', event.detail.chunk);
167
+ },
168
+ // Triggers if the connection drops mid-stream
169
+ error: (event) => {
170
+ console.error('Stream dropped:', event.detail.error);
171
+ }
172
+ }
173
+ });
174
+
175
+ for await (const chunk of eventStream) {
176
+ // Yields parsed JSON chunks symmetrically
177
+ }
178
+ ```
179
+
180
+ ### Tracking Upload Progress
181
+
182
+ Standard `fetch` uploads do not support progress callbacks in Baseline browsers. The client bridges this gap utilizing a highly precise native XMLHttpRequest gateway. It fully supports request-scoped `on` listeners—enabling clean tracking of upload `progress`, HTTP `status`, success, or `error` events:
183
+
184
+ ```javascript
185
+ const fileData = new FormData();
186
+ fileData.append('file', rawFileBlob);
187
+
188
+ const response = await api.upload('https://api.example.com/upload', fileData, {
189
+ method: 'POST',
190
+ on: {
191
+ // Fired progressively during transmission
192
+ progress: (event) => {
193
+ const { loaded, total, percentage } = event.detail;
194
+ console.log(`Uploaded: ${loaded} / ${total} (${percentage}%)`);
195
+ },
196
+ // Fired on successful completion
197
+ 'status:200': (event) => {
198
+ console.log('Upload completed successfully!');
199
+ },
200
+ // Fired on connection errors
201
+ error: (event) => {
202
+ console.error('Upload failed:', event.detail.error);
203
+ }
204
+ }
205
+ });
206
+ ```
@@ -0,0 +1,38 @@
1
+ /**
2
+ * src/core/events/bus.js
3
+ *
4
+ * Global Event Bus.
5
+ * Extends the native EventTarget for engine-optimized listener dispatch.
6
+ * Preserves backward-compatible .on() and .emit() wrapper methods while
7
+ * exposing the full native addEventListener / dispatchEvent interface.
8
+ *
9
+ * Source: doc 10 — Event Architecture §9
10
+ */
11
+
12
+ export class EventBus extends EventTarget {
13
+ /**
14
+ * Subscribes to a global event.
15
+ * If a signal is provided, the subscription is automatically cleaned up when the signal aborts.
16
+ * Returns a disposer function for manual cleanup.
17
+ */
18
+ on(type, fn, signal) {
19
+ if (signal?.aborted) return () => {};
20
+
21
+ this.addEventListener(type, fn, { signal });
22
+
23
+ const dispose = () => {
24
+ this.removeEventListener(type, fn);
25
+ };
26
+
27
+ return dispose;
28
+ }
29
+
30
+ /**
31
+ * Dispatches a global custom event with a detail payload.
32
+ */
33
+ emit(type, detail) {
34
+ this.dispatchEvent(new CustomEvent(type, { detail }));
35
+ }
36
+ }
37
+
38
+ export const bus = new EventBus();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * src/core/events/delegate.js
3
+ *
4
+ * High-performance event delegation with fast-path selector matching.
5
+ * Handles dynamic delegation of events traversing Shadow DOM boundaries
6
+ * by evaluating event.composedPath(), caching matcher evaluations in a WeakMap.
7
+ *
8
+ * Source: doc 10 — Event Architecture §6
9
+ */
10
+
11
+ // Dual-layered WeakMap cache to prevent redundant CSS selector evaluations
12
+ const matchesCache = new WeakMap();
13
+
14
+ /**
15
+ * Checks if an element matches a selector using cached query evaluations.
16
+ */
17
+ function matchesSelector(element, selector) {
18
+ if (!element || typeof element.matches !== 'function') {
19
+ return false;
20
+ }
21
+
22
+ let selectorMap = matchesCache.get(element);
23
+ if (!selectorMap) {
24
+ selectorMap = new Map();
25
+ matchesCache.set(element, selectorMap);
26
+ }
27
+
28
+ if (selectorMap.has(selector)) {
29
+ return selectorMap.get(selector);
30
+ }
31
+
32
+ const result = element.matches(selector);
33
+ selectorMap.set(selector, result);
34
+ return result;
35
+ }
36
+
37
+ /**
38
+ * Attaches a delegated event listener to an ancestor root element.
39
+ *
40
+ * @param {EventTarget} root - The root container element.
41
+ * @param {string} selector - The query selector to match dynamic descendants.
42
+ * @param {string} type - The event name to intercept.
43
+ * @param {Function} handler - The listener callback, with `this` bound to the matched element.
44
+ * @param {Object} [options={}] - Standard addEventListener options.
45
+ * @returns {Function} A disposer function that unregisters the delegation hook.
46
+ */
47
+ export function delegate(root, selector, type, handler, options = {}) {
48
+ const signal = options.signal;
49
+ if (signal?.aborted) return () => {};
50
+
51
+ const listener = (event) => {
52
+ // composedPath() contains the full bubble chain, traversing through nested shadow trees
53
+ const path = event.composedPath();
54
+
55
+ for (const target of path) {
56
+ if (target === root) break;
57
+ if (matchesSelector(target, selector)) {
58
+ // Invoke handler with matched element bound to `this`
59
+ handler.call(target, event, target);
60
+ break;
61
+ }
62
+ }
63
+ };
64
+
65
+ root.addEventListener(type, listener, options);
66
+
67
+ const dispose = () => {
68
+ root.removeEventListener(type, listener, options);
69
+ if (signal) {
70
+ signal.removeEventListener('abort', dispose);
71
+ }
72
+ };
73
+
74
+ if (signal) {
75
+ signal.addEventListener('abort', dispose, { once: true });
76
+ }
77
+
78
+ return dispose;
79
+ }