@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,169 @@
1
+ /**
2
+ * src/core/workers/index.js
3
+ *
4
+ * Public workers entry point.
5
+ * Aggregates dedicated, pooled, shared, broadcast, locking, and offscreen
6
+ * primitives into a single unified public facade.
7
+ *
8
+ * Public API:
9
+ * workers.run(script, task, opts) – pool task
10
+ * workers.dedicated(script) – raw dedicated worker
11
+ * workers.shared(script, name) – shared worker connection
12
+ * workers.lock(name, fn, opts) – web lock
13
+ * workers.broadcast(name, payload) – fire-and-forget broadcast
14
+ * workers.subscribe(name, fn, signal) – channel subscription
15
+ * workers.offscreen(canvas, script, opts) – offscreen canvas
16
+ * workers.close(script) – terminate one pool
17
+ * workers.clear() – terminate all pools + clear broadcasts
18
+ *
19
+ * Feature gates:
20
+ * has.worker, has.shared, has.channel, has.locks, has.offscreen, has.isolated
21
+ *
22
+ * Source: plan.md Phase 8
23
+ */
24
+
25
+ import { Dedicated, DedicatedWorker } from './dedicated.js';
26
+ import { Pool, WorkerPool } from './pool.js';
27
+ import { Shared, SharedConnection } from './shared.js';
28
+ import { broadcast } from './broadcast.js';
29
+ import { lock } from './locks.js';
30
+ import { offscreen, Offscreen, OffscreenHandle } from './offscreen.js';
31
+
32
+ /** Runtime feature detection */
33
+ export const has = {
34
+ worker: typeof Worker !== 'undefined',
35
+ shared: typeof SharedWorker !== 'undefined',
36
+ channel: typeof BroadcastChannel !== 'undefined',
37
+ locks: typeof navigator !== 'undefined' && !!navigator.locks,
38
+ offscreen: typeof OffscreenCanvas !== 'undefined',
39
+ isolated: typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated
40
+ };
41
+
42
+ // Pool registry — one pool per script URL
43
+ const pools = new Map();
44
+
45
+ export const workers = {
46
+ /**
47
+ * Runs a prioritized CPU-bound task in a lazily allocated dedicated worker pool.
48
+ *
49
+ * @param {string} script - Worker script URL
50
+ * @param {string} task - Task name
51
+ * @param {object} [opts] - TaskOptions: payload, priority, signal, timeout, transferables, meta
52
+ */
53
+ run(script, task, opts = {}) {
54
+ if (!has.worker) {
55
+ return Promise.reject(new Error('Web Workers are not available in this environment'));
56
+ }
57
+ if (!pools.has(script)) {
58
+ pools.set(script, new Pool(script, opts));
59
+ }
60
+ return pools.get(script).run(task, opts);
61
+ },
62
+
63
+ /**
64
+ * Creates a raw Dedicated worker (not pooled).
65
+ * Use this when you need a single long-lived worker with direct control.
66
+ *
67
+ * @param {string} script
68
+ * @returns {Dedicated}
69
+ */
70
+ dedicated(script) {
71
+ if (!has.worker) {
72
+ throw new Error('Web Workers are not available in this environment');
73
+ }
74
+ return new Dedicated(script);
75
+ },
76
+
77
+ /**
78
+ * Opens a connection to a SharedWorker (or dedicated fallback).
79
+ *
80
+ * @param {string} script
81
+ * @param {string} [name]
82
+ * @returns {Shared}
83
+ */
84
+ shared(script, name) {
85
+ const connection = new Shared(script, name);
86
+ connection.connect();
87
+ return connection;
88
+ },
89
+
90
+ /**
91
+ * Acquires a named Web Lock and executes `fn` within it.
92
+ * See locks.js for lock name conventions and option details.
93
+ */
94
+ lock,
95
+
96
+ /**
97
+ * Sends a message to a named BroadcastChannel.
98
+ *
99
+ * @param {string} name
100
+ * @param {any} payload
101
+ */
102
+ broadcast(name, payload) {
103
+ if (!has.channel) return; // degrade silently — optional cross-tab notification
104
+ broadcast.broadcast(name, payload);
105
+ },
106
+
107
+ /**
108
+ * Subscribes to messages on a named BroadcastChannel.
109
+ * Returns a dispose function.
110
+ *
111
+ * @param {string} name
112
+ * @param {Function} fn
113
+ * @param {AbortSignal} [signal]
114
+ */
115
+ subscribe(name, fn, signal) {
116
+ if (!has.channel) return () => {};
117
+ return broadcast.subscribe(name, fn, signal);
118
+ },
119
+
120
+ /**
121
+ * Transfers an HTMLCanvasElement to a worker for off-main-thread rendering.
122
+ * Returns a Promise that resolves to an Offscreen handle after the ready
123
+ * handshake completes.
124
+ *
125
+ * @param {HTMLCanvasElement} canvas
126
+ * @param {string} script
127
+ * @param {object} [opts]
128
+ */
129
+ offscreen,
130
+
131
+ /**
132
+ * Terminates the pool for one script URL and removes it from the registry.
133
+ *
134
+ * @param {string} script
135
+ */
136
+ close(script) {
137
+ const pool = pools.get(script);
138
+ if (pool) {
139
+ pool.terminate();
140
+ pools.delete(script);
141
+ }
142
+ },
143
+
144
+ /**
145
+ * Terminates all pools and clears all broadcast channels.
146
+ */
147
+ clear() {
148
+ for (const [script, pool] of pools) {
149
+ pool.terminate();
150
+ pools.delete(script);
151
+ }
152
+ broadcast.clear();
153
+ }
154
+ };
155
+
156
+ export {
157
+ Dedicated,
158
+ Pool,
159
+ Shared,
160
+ Offscreen,
161
+ broadcast,
162
+ lock,
163
+ offscreen,
164
+ // Compatibility aliases
165
+ DedicatedWorker,
166
+ WorkerPool,
167
+ SharedConnection,
168
+ OffscreenHandle
169
+ };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * src/core/workers/locks.js
3
+ *
4
+ * Web Locks API Facade.
5
+ * Provides exclusive or shared operational execution blocks with AbortSignal
6
+ * and timeout controls. Combines caller signal + timeout into a single signal.
7
+ * Exposes ifAvailable and steal modes. Falls back to a same-tab queue when
8
+ * the Web Locks API is unavailable.
9
+ *
10
+ * Lock name conventions:
11
+ * idb:<store> – IndexedDB store access
12
+ * opfs:<file> – Origin Private File System file
13
+ * auth:<op> – Authentication operation (e.g. auth:refresh)
14
+ * sync:<role> – Background sync (e.g. sync:leader)
15
+ * cache:<name> – Cache invalidation (e.g. cache:avatar)
16
+ *
17
+ * Source: plan.md Phase 6
18
+ */
19
+
20
+ // Same-tab fallback queue when Web Locks API is absent.
21
+ // Maps lock name → Promise chain (exclusive, ordered).
22
+ const fallbackQueue = new Map();
23
+
24
+ /**
25
+ * Acquires a named lock and executes `fn` within it.
26
+ *
27
+ * @param {string} name Lock name (see conventions above)
28
+ * @param {Function} fn Async callback to execute while holding the lock
29
+ * @param {object} [options]
30
+ * @param {AbortSignal} [options.signal] External abort signal
31
+ * @param {'exclusive'|'shared'} [options.mode='exclusive']
32
+ * @param {number} [options.timeout] ms before acquisition times out
33
+ * @param {boolean} [options.ifAvailable] Acquire only if lock is immediately available
34
+ * @param {boolean} [options.steal] Forcibly pre-empt any existing holder
35
+ * @returns {Promise<any>}
36
+ */
37
+ export async function lock(name, fn, options = {}) {
38
+ const { signal, mode = 'exclusive', timeout, ifAvailable = false, steal = false } = options;
39
+
40
+ // -- Same-tab fallback --
41
+ if (typeof navigator === 'undefined' || !navigator.locks) {
42
+ return _fallback(name, fn, { signal, timeout, ifAvailable });
43
+ }
44
+
45
+ // -- Combine signal + timeout --
46
+ let controller = null;
47
+ let timer = null;
48
+
49
+ if (signal || timeout) {
50
+ controller = new AbortController();
51
+
52
+ if (signal) {
53
+ if (signal.aborted) {
54
+ controller.abort(signal.reason);
55
+ } else {
56
+ signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
57
+ }
58
+ }
59
+
60
+ if (timeout) {
61
+ timer = setTimeout(() => {
62
+ const reason = new Error(`Lock "${name}" acquisition timed out after ${timeout}ms`);
63
+ reason.name = 'TimeoutError';
64
+ controller.abort(reason);
65
+ }, timeout);
66
+ }
67
+ }
68
+
69
+ const active = controller?.signal ?? undefined;
70
+
71
+ const requestOpts = { mode, signal: active };
72
+ if (ifAvailable) requestOpts.ifAvailable = true;
73
+ if (steal) requestOpts.steal = true;
74
+
75
+ try {
76
+ return await navigator.locks.request(name, requestOpts, async (acquired) => {
77
+ // ifAvailable returns null when the lock is not immediately grantable
78
+ if (acquired === null) {
79
+ throw new DOMException(`Lock "${name}" is not available`, 'NotAllowedError');
80
+ }
81
+ return await fn();
82
+ });
83
+ } catch (err) {
84
+ // Rethrow AbortError with the original abort reason when present
85
+ if (err.name === 'AbortError' && active?.aborted) {
86
+ throw active.reason ?? err;
87
+ }
88
+ throw err;
89
+ } finally {
90
+ if (timer) clearTimeout(timer);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Same-tab mutual exclusion fallback (no Web Locks API).
96
+ * Uses a promise chain to serialise callbacks for each lock name.
97
+ * Does not support shared mode (all locks are exclusive in fallback).
98
+ */
99
+ async function _fallback(name, fn, { signal, timeout, ifAvailable } = {}) {
100
+ if (signal?.aborted) {
101
+ throw signal.reason ?? new DOMException('Aborted', 'AbortError');
102
+ }
103
+
104
+ if (ifAvailable) {
105
+ // In fallback mode we cannot check availability — always treat as unavailable
106
+ // when there is already an active chain for this name.
107
+ if (fallbackQueue.has(name)) {
108
+ throw new DOMException(`Lock "${name}" is not available (fallback)`, 'NotAllowedError');
109
+ }
110
+ }
111
+
112
+ const prior = fallbackQueue.get(name) ?? Promise.resolve();
113
+
114
+ let resolveChain;
115
+ const link = new Promise((res) => { resolveChain = res; });
116
+ fallbackQueue.set(name, prior.then(() => link));
117
+
118
+ let timer = null;
119
+ let controller = null;
120
+
121
+ if (signal || timeout) {
122
+ controller = new AbortController();
123
+ if (signal) {
124
+ signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
125
+ }
126
+ if (timeout) {
127
+ timer = setTimeout(() => {
128
+ const r = new Error(`Lock "${name}" acquisition timed out after ${timeout}ms`);
129
+ r.name = 'TimeoutError';
130
+ controller.abort(r);
131
+ }, timeout);
132
+ }
133
+ }
134
+
135
+ const combined = controller?.signal;
136
+
137
+ try {
138
+ // Wait for the chain, but bail early if aborted while queued
139
+ await Promise.race([
140
+ prior,
141
+ ...(combined
142
+ ? [new Promise((_, rej) => combined.addEventListener('abort', () => rej(combined.reason), { once: true }))]
143
+ : [])
144
+ ]);
145
+
146
+ if (combined?.aborted) {
147
+ throw combined.reason ?? new DOMException('Aborted', 'AbortError');
148
+ }
149
+
150
+ return await fn();
151
+ } finally {
152
+ if (timer) clearTimeout(timer);
153
+ resolveChain();
154
+ // Clean up the map when the chain is idle
155
+ const remaining = fallbackQueue.get(name);
156
+ if (remaining === prior.then(() => link)) {
157
+ fallbackQueue.delete(name);
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * src/core/workers/offscreen.js
3
+ *
4
+ * OffscreenCanvas Transfer Lifecycle.
5
+ * Hands canvas rendering to a background Worker. The worker must respond with
6
+ * { ok: true } on its port after receiving the canvas to signal readiness.
7
+ *
8
+ * Lifecycle:
9
+ * open() – transfer canvas + wait for ready acknowledgement
10
+ * send() – dispatch render commands
11
+ * resize() – forward new dimensions and device pixel ratio
12
+ * close() – terminate the worker (terminate() is an alias)
13
+ *
14
+ * Source: plan.md Phase 7
15
+ */
16
+
17
+ export class Offscreen {
18
+ #canvas;
19
+ #script;
20
+ #worker = null;
21
+ #port = null;
22
+ #ready = false;
23
+ #closed = false;
24
+ #onError;
25
+ #onResize;
26
+
27
+ /**
28
+ * @param {HTMLCanvasElement} canvas
29
+ * @param {string} script Worker script URL
30
+ * @param {object} [opts]
31
+ * @param {Function} [opts.onError] Called when the worker reports an error
32
+ * @param {Function} [opts.onResize] Called after a resize message is forwarded
33
+ */
34
+ constructor(canvas, script, opts = {}) {
35
+ this.#canvas = canvas;
36
+ this.#script = script;
37
+ this.#onError = opts.onError ?? null;
38
+ this.#onResize = opts.onResize ?? null;
39
+ }
40
+
41
+ get ready() { return this.#ready; }
42
+ get closed() { return this.#closed; }
43
+
44
+ /**
45
+ * Transfers canvas control to the worker and waits for the ready handshake.
46
+ *
47
+ * The worker must send `{ ok: true }` on the received port after it has
48
+ * initialised its rendering context.
49
+ *
50
+ * @returns {Promise<this>}
51
+ */
52
+ open() {
53
+ if (this.#closed) {
54
+ return Promise.reject(new Error('OffscreenCanvas handle is closed'));
55
+ }
56
+ if (this.#ready) {
57
+ return Promise.resolve(this);
58
+ }
59
+
60
+ if (!this.#supports()) {
61
+ return Promise.reject(new Error('OffscreenCanvas is not supported in this environment'));
62
+ }
63
+
64
+ return new Promise((resolve, reject) => {
65
+ try {
66
+ const offscreen = this.#canvas.transferControlToOffscreen();
67
+ this.#worker = new Worker(this.#script, { type: 'module' });
68
+
69
+ this.#worker.addEventListener('error', (e) => {
70
+ const err = new Error(e.message ?? 'Worker error');
71
+ if (!this.#ready) {
72
+ reject(err);
73
+ }
74
+ this.#onError?.(err);
75
+ });
76
+
77
+ // MessageChannel for the ready handshake
78
+ const channel = new MessageChannel();
79
+ this.#port = channel.port1;
80
+
81
+ this.#port.onmessage = (e) => {
82
+ if (!this.#ready && e.data?.ok) {
83
+ this.#ready = true;
84
+ resolve(this);
85
+ }
86
+ };
87
+
88
+ this.#worker.postMessage(
89
+ { canvas: offscreen, port: channel.port2 },
90
+ [offscreen, channel.port2]
91
+ );
92
+ } catch (err) {
93
+ reject(err);
94
+ }
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Sends a render command to the worker.
100
+ *
101
+ * @param {any} payload
102
+ * @param {Transferable[]} [transferables]
103
+ */
104
+ send(payload, transferables = []) {
105
+ if (this.#closed) throw new Error('OffscreenCanvas handle is closed');
106
+ if (!this.#ready) throw new Error('OffscreenCanvas handle is not ready — call open() first');
107
+ this.#port.postMessage(payload, transferables);
108
+ }
109
+
110
+ /**
111
+ * Forwards updated dimensions and device pixel ratio to the worker.
112
+ *
113
+ * @param {object} [opts]
114
+ * @param {number} [opts.width] New CSS width in pixels (defaults to canvas.clientWidth)
115
+ * @param {number} [opts.height] New CSS height in pixels (defaults to canvas.clientHeight)
116
+ * @param {number} [opts.dpr] Device pixel ratio (defaults to devicePixelRatio)
117
+ */
118
+ resize(opts = {}) {
119
+ const width = opts.width ?? this.#canvas.clientWidth;
120
+ const height = opts.height ?? this.#canvas.clientHeight;
121
+ const dpr = opts.dpr ?? (typeof devicePixelRatio !== 'undefined' ? devicePixelRatio : 1);
122
+ this.send({ type: 'resize', width, height, dpr });
123
+ this.#onResize?.({ width, height, dpr });
124
+ }
125
+
126
+ /**
127
+ * Terminates the worker and releases the handle.
128
+ * `terminate()` is kept as an alias for back-compat.
129
+ */
130
+ close() {
131
+ if (this.#closed) return;
132
+ this.#closed = true;
133
+ this.#ready = false;
134
+ try { this.#port?.close(); } catch { /* best-effort */ }
135
+ try { this.#worker?.terminate(); } catch { /* best-effort */ }
136
+ this.#port = null;
137
+ this.#worker = null;
138
+ }
139
+
140
+ terminate() { return this.close(); }
141
+
142
+ #supports() {
143
+ return (
144
+ typeof OffscreenCanvas !== 'undefined' &&
145
+ typeof this.#canvas.transferControlToOffscreen === 'function'
146
+ );
147
+ }
148
+ }
149
+
150
+ // Compatibility alias
151
+ export { Offscreen as OffscreenHandle };
152
+
153
+ /**
154
+ * Convenience factory — creates an Offscreen handle, transfers the canvas,
155
+ * and returns the instance (ready state established after the returned
156
+ * promise resolves).
157
+ *
158
+ * @param {HTMLCanvasElement} canvas
159
+ * @param {string} script
160
+ * @param {object} [opts]
161
+ * @returns {Promise<Offscreen>}
162
+ */
163
+ export function offscreen(canvas, script, opts = {}) {
164
+ const handle = new Offscreen(canvas, script, opts);
165
+ return handle.open();
166
+ }