@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,381 @@
1
+ # Workers Plan
2
+
3
+ This document defines the improvement plan for `core.workers`. It follows the notes in `docs/notes/`, especially the worker, runtime, performance, memory, security, offline, storage, transport, and module-system notes.
4
+
5
+ The goal is a native browser concurrency layer that keeps the main thread free, owns every worker lifecycle, makes cross-context messaging explicit, and cleans up after itself.
6
+
7
+ ---
8
+
9
+ ## 1. Scope
10
+
11
+ `core.workers` covers these files:
12
+
13
+ ```
14
+ src/core/workers/
15
+ ├── index.js
16
+ ├── dedicated.js
17
+ ├── pool.js
18
+ ├── shared.js
19
+ ├── broadcast.js
20
+ ├── locks.js
21
+ ├── offscreen.js
22
+ └── plan.md
23
+ ```
24
+
25
+ Related files:
26
+
27
+ ```
28
+ types/core/workers/index.d.ts
29
+ tests/core/workers/
30
+ docs/core/workers/index.md
31
+ ```
32
+
33
+ ---
34
+
35
+ ## 2. Rules
36
+
37
+ All implementation work must follow the naming rules:
38
+
39
+ - Prefer one-word names.
40
+ - Keep file names lowercase and one word.
41
+ - Let folder scope carry the domain.
42
+ - Use short verbs for methods: `run`, `send`, `close`, `stop`, `open`.
43
+ - Use nouns for values: `task`, `payload`, `port`, `signal`, `timer`, `worker`.
44
+ - Avoid repeated qualifiers such as `workerWorker`, `channelName`, `taskItem`, or `scriptUrl` when `worker`, `name`, `item`, or `script` is clear.
45
+ - Keep public compatibility aliases only when removing them would break users.
46
+
47
+ Target public surface:
48
+
49
+ ```javascript
50
+ workers.run(script, task, options)
51
+ workers.shared(script, name)
52
+ workers.lock(name, fn, options)
53
+ workers.broadcast(name, payload)
54
+ workers.subscribe(name, fn, signal)
55
+ workers.offscreen(canvas, script, options)
56
+ workers.close(script)
57
+ workers.clear()
58
+ ```
59
+
60
+ Compatibility exports can remain during the transition:
61
+
62
+ ```javascript
63
+ DedicatedWorker
64
+ WorkerPool
65
+ SharedConnection
66
+ OffscreenHandle
67
+ ```
68
+
69
+ Internally, prefer shorter class aliases:
70
+
71
+ ```javascript
72
+ Dedicated
73
+ Pool
74
+ Shared
75
+ Offscreen
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 3. Current Gaps
81
+
82
+ ### `dedicated.js`
83
+
84
+ - `run` has no `AbortSignal` handling.
85
+ - `run` has no timeout support.
86
+ - Ports are not closed on abort or timeout.
87
+ - Worker `error` and `messageerror` events are not routed into pending requests.
88
+ - A terminated worker can still accept calls until the browser rejects them.
89
+ - Transfer ownership is supported, but there is no plain message contract check.
90
+
91
+ ### `pool.js`
92
+
93
+ - `TaskOptions.signal` and `TaskOptions.timeout` exist in types but are not enforced.
94
+ - Idle workers do not expire after a quiet period.
95
+ - Pools are not shut down on `pagehide`.
96
+ - Crashed tasks are always rejected instead of allowing idempotent requeue.
97
+ - Priority sorting is simple and can starve background work.
98
+ - Queue items are not cancellable before execution.
99
+ - Pool size is not clamped by a configurable maximum.
100
+
101
+ ### `shared.js`
102
+
103
+ - The type file exposes `postMessage`, `onMessage`, and `close`, but the runtime exposes `send` and `subscribe` without `close`.
104
+ - Fallback behavior does not fully match `MessagePort`.
105
+ - Fallback listener errors are not isolated.
106
+ - There is no explicit reconnect or closed state.
107
+ - There is no cross-tab fallback strategy using `BroadcastChannel` plus `Web Locks`.
108
+
109
+ ### `broadcast.js`
110
+
111
+ - Abort cleanup removes the listener from the channel, but does not remove the abort listener.
112
+ - `messageerror` is not handled.
113
+ - Temporary one-shot channels close immediately after `postMessage`; this should be tested across browsers.
114
+ - There is no `close(name)` or `clear()` control for long-lived consumers.
115
+
116
+ ### `locks.js`
117
+
118
+ - Passing both `signal` and `timeout` does not create a combined signal.
119
+ - `ifAvailable` and `steal` are not exposed.
120
+ - Fallback mode runs the callback immediately and does not provide same-tab mutual exclusion.
121
+ - Lock names need a documented convention.
122
+ - Timeout errors should preserve the original abort reason when present.
123
+
124
+ ### `offscreen.js`
125
+
126
+ - Transfer has no `ready` acknowledgement from the worker.
127
+ - Resize, device pixel ratio, and visibility changes are not forwarded.
128
+ - Worker errors are logged but not surfaced to callers.
129
+ - There is no `send` method for render commands.
130
+ - `terminate` is available, but the public facade does not expose a clear close contract.
131
+
132
+ ### `index.js`
133
+
134
+ - Pool instances are cached forever.
135
+ - There is no `close(script)` for one pool.
136
+ - There is no `clear()` for all pools.
137
+ - The facade does not expose dedicated worker creation directly.
138
+ - Docs, tests, and types are out of sync with runtime behavior.
139
+
140
+ ---
141
+
142
+ ## 4. Architecture
143
+
144
+ The module should enforce these principles from the notes:
145
+
146
+ 1. The main thread is for UI and coordination.
147
+ 2. Dedicated workers own task computation.
148
+ 3. Shared workers own persistent same-origin coordination.
149
+ 4. Service workers remain in `core.offline` and `src/sw`, but `core.workers` should provide safe message patterns where needed.
150
+ 5. Broadcast channels inform other contexts that something happened.
151
+ 6. Web Locks prevent other contexts from doing something concurrently.
152
+ 7. OffscreenCanvas moves render loops away from the main thread.
153
+ 8. Every request-response path uses `MessageChannel`.
154
+ 9. Large binary payloads use transferables.
155
+ 10. `SharedArrayBuffer` is optional and only enabled when `crossOriginIsolated` is true.
156
+
157
+ ---
158
+
159
+ ## 5. Message Contract
160
+
161
+ Worker requests should use plain data only:
162
+
163
+ ```javascript
164
+ {
165
+ task,
166
+ payload,
167
+ meta
168
+ }
169
+ ```
170
+
171
+ Worker responses should be consistent:
172
+
173
+ ```javascript
174
+ {
175
+ ok,
176
+ value,
177
+ error
178
+ }
179
+ ```
180
+
181
+ Rules:
182
+
183
+ - `ok: true` resolves with `value`.
184
+ - `ok: false` rejects with `error`.
185
+ - `error` must be serializable.
186
+ - Functions, DOM nodes, class instances, symbols, and weak references must not cross the worker boundary.
187
+ - Transferables should be passed explicitly.
188
+ - Transfer ownership must be documented because the sender loses access after transfer.
189
+
190
+ ---
191
+
192
+ ## 6. Implementation Phases
193
+
194
+ ### Phase 1: Contract
195
+
196
+ - [ ] Define the request and response shape in `dedicated.js`.
197
+ - [ ] Accept `signal`, `timeout`, `transferables`, and `meta` in one `options` object.
198
+ - [ ] Keep the existing positional `run(task, payload, transferables)` path as a compatibility bridge.
199
+ - [ ] Normalize internal names: `script`, `name`, `item`, `payload`, `port`, `timer`.
200
+ - [ ] Update `types/core/workers/index.d.ts` to match runtime behavior.
201
+
202
+ ### Phase 2: Dedicated
203
+
204
+ - [ ] Add abort support before and during a request.
205
+ - [ ] Add timeout support.
206
+ - [ ] Close both message ports on success, error, abort, timeout, and deserialization failure.
207
+ - [ ] Track pending requests so worker-level `error` can reject them.
208
+ - [ ] Add `closed` state after `terminate`.
209
+ - [ ] Add tests for success, worker error, message error, abort, timeout, and transferables.
210
+
211
+ ### Phase 3: Pool
212
+
213
+ - [ ] Enforce `signal` and `timeout` while a task is queued.
214
+ - [ ] Remove queued tasks when aborted before execution.
215
+ - [ ] Add an idle timer that terminates workers after inactivity.
216
+ - [ ] Add `pagehide` cleanup for all pools.
217
+ - [ ] Add a maximum size option.
218
+ - [ ] Add fair priority scheduling so background work cannot starve forever.
219
+ - [ ] Add an `idempotent` option for safe requeue after worker crash.
220
+ - [ ] Add tests for cancellation, idle cleanup, crash recovery, and fairness.
221
+
222
+ ### Phase 4: Shared
223
+
224
+ - [ ] Align runtime with types: `send`, `subscribe`, and `close`.
225
+ - [ ] Add aliases only if needed: `postMessage` to `send`, `onMessage` to `subscribe`.
226
+ - [ ] Track `connected` and `closed` states.
227
+ - [ ] Make fallback behavior match the same subscribe/send contract.
228
+ - [ ] Isolate listener errors in both normal and fallback modes.
229
+ - [ ] Add optional reconnect.
230
+ - [ ] Document when to use SharedWorker instead of BroadcastChannel.
231
+ - [ ] Add tests for native mode, fallback mode, close, and listener cleanup.
232
+
233
+ ### Phase 5: Broadcast
234
+
235
+ - [ ] Remove abort listeners during manual disposal.
236
+ - [ ] Add `close(name)` for one channel.
237
+ - [ ] Add `clear()` for all channels.
238
+ - [ ] Add `messageerror` handling.
239
+ - [ ] Confirm one-shot broadcast delivery before closing the temporary channel.
240
+ - [ ] Add tests for subscribe, abort cleanup, disposal, close, clear, and message errors.
241
+
242
+ ### Phase 6: Locks
243
+
244
+ - [ ] Support `mode`, `signal`, `timeout`, `ifAvailable`, and `steal`.
245
+ - [ ] Combine caller abort and timeout into one signal.
246
+ - [ ] Preserve abort reasons where possible.
247
+ - [ ] Add same-tab fallback queuing when Web Locks are missing.
248
+ - [ ] Document lock name conventions:
249
+
250
+ ```text
251
+ idb:store
252
+ opfs:file
253
+ auth:refresh
254
+ sync:leader
255
+ cache:name
256
+ ```
257
+
258
+ - [ ] Add tests for shared mode, exclusive mode, timeout, abort, try-lock, and fallback ordering.
259
+
260
+ ### Phase 7: Offscreen
261
+
262
+ - [ ] Add a ready handshake after transfer.
263
+ - [ ] Add `send(payload, transferables)` for render commands.
264
+ - [ ] Forward resize and device pixel ratio updates.
265
+ - [ ] Surface worker errors to callers.
266
+ - [ ] Add `close()` as the primary lifecycle verb and keep `terminate()` as an alias.
267
+ - [ ] Add tests for unsupported environments, transfer success, ready, send, resize, error, and close.
268
+
269
+ ### Phase 8: Facade
270
+
271
+ - [ ] Add `workers.close(script)` to terminate one pool.
272
+ - [ ] Add `workers.clear()` to terminate all pools and close broadcasts.
273
+ - [ ] Consider `workers.dedicated(script)` for direct dedicated worker creation.
274
+ - [ ] Keep `workers.run` as the main task-pool entry.
275
+ - [ ] Keep exports stable until a major release can remove compatibility names.
276
+
277
+ ### Phase 9: Docs
278
+
279
+ - [ ] Update `docs/core/workers/index.md`.
280
+ - [ ] Add worker script examples for request-response handling.
281
+ - [ ] Document transferables and sender ownership loss.
282
+ - [ ] Document when to use each primitive:
283
+
284
+ | Need | Use |
285
+ |---|---|
286
+ | CPU task | `workers.run` |
287
+ | One tab-owned worker | `DedicatedWorker` |
288
+ | Shared socket or rate limit | `workers.shared` |
289
+ | Cross-tab event | `workers.broadcast` |
290
+ | Cross-tab mutex | `workers.lock` |
291
+ | Canvas render loop | `workers.offscreen` |
292
+
293
+ ### Phase 10: Verification
294
+
295
+ - [ ] Run `npm test -- --files tests/core/workers/*.test.js` if supported by the runner.
296
+ - [ ] Run the full worker test group.
297
+ - [ ] Run existing storage and offline tests because they depend on worker patterns.
298
+ - [ ] Confirm declarations compile for `types/core/workers/index.d.ts`.
299
+ - [ ] Add browser checks for SharedWorker, BroadcastChannel, Web Locks, and OffscreenCanvas.
300
+
301
+ ---
302
+
303
+ ## 7. Test Matrix
304
+
305
+ | File | Coverage |
306
+ |---|---|
307
+ | `dedicated.test.js` | run, abort, timeout, error, messageerror, transfer |
308
+ | `pool.test.js` | priority, fairness, abort, timeout, idle, crash, clear |
309
+ | `shared.test.js` | connect, send, subscribe, fallback, close |
310
+ | `broadcast.test.js` | broadcast, subscribe, dispose, abort, close, clear |
311
+ | `locks.test.js` | exclusive, shared, timeout, abort, ifAvailable, fallback |
312
+ | `offscreen.test.js` | support, transfer, ready, resize, send, close |
313
+ | `index.test.js` | facade, pool reuse, close, clear |
314
+
315
+ ---
316
+
317
+ ## 8. Runtime Checks
318
+
319
+ Every feature should gate itself:
320
+
321
+ ```javascript
322
+ const has = {
323
+ worker: typeof Worker !== 'undefined',
324
+ shared: typeof SharedWorker !== 'undefined',
325
+ channel: typeof BroadcastChannel !== 'undefined',
326
+ locks: typeof navigator !== 'undefined' && !!navigator.locks,
327
+ offscreen: typeof OffscreenCanvas !== 'undefined',
328
+ isolated: typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated
329
+ };
330
+ ```
331
+
332
+ Rules:
333
+
334
+ - Missing Worker support should reject with a clear error.
335
+ - Missing SharedWorker support should fall back to a compatible local worker where possible.
336
+ - Missing BroadcastChannel support should degrade silently only for optional notifications.
337
+ - Missing Web Locks should use same-tab fallback locking.
338
+ - Missing OffscreenCanvas should return a handle that reports unsupported state.
339
+ - SharedArrayBuffer paths must require `isolated`.
340
+
341
+ ---
342
+
343
+ ## 9. Security
344
+
345
+ - Never pass DOM nodes, functions, or class instances to workers.
346
+ - Validate task names before dispatch if a worker script supports multiple tasks.
347
+ - Treat worker messages as untrusted input.
348
+ - Keep worker scripts as module workers.
349
+ - Avoid `importScripts`.
350
+ - Do not enable SharedArrayBuffer paths unless `crossOriginIsolated` is true.
351
+ - Route unhandled worker errors to the application error pipeline when one exists.
352
+
353
+ ---
354
+
355
+ ## 10. Performance
356
+
357
+ - Use transferables for large `ArrayBuffer`, `ImageBitmap`, `OffscreenCanvas`, streams, `AudioData`, and `VideoFrame`.
358
+ - Avoid cloning large payloads.
359
+ - Keep pool creation lazy.
360
+ - Reserve at least one logical core for the main thread.
361
+ - Reclaim idle workers.
362
+ - Prefer `pagehide` for shutdown hooks.
363
+ - Avoid unbounded queues.
364
+ - Add observable queue length and active count for diagnostics.
365
+
366
+ ---
367
+
368
+ ## 11. Done
369
+
370
+ The worker module is complete when:
371
+
372
+ - Public API, docs, tests, and declarations agree.
373
+ - Every subscription and port has deterministic cleanup.
374
+ - Abort and timeout work consistently across dedicated, pool, shared, broadcast, lock, and offscreen flows.
375
+ - Pool workers are lazy, bounded, cancellable, and reclaimed.
376
+ - Shared worker fallback is predictable.
377
+ - Broadcast channels close when unused.
378
+ - Locks coordinate same-origin work and have a same-tab fallback.
379
+ - OffscreenCanvas has a ready, send, resize, and close lifecycle.
380
+ - Worker errors are visible to callers.
381
+ - The main thread stays responsible for UI while heavy work moves to workers.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * src/core/workers/pool.js
3
+ *
4
+ * Dedicated Worker Pool.
5
+ * Bounded by a configurable max (default: hardwareConcurrency - 1, min 2).
6
+ * Priority queue: user-blocking > user-visible > background with anti-starvation
7
+ * counter so background work cannot wait forever.
8
+ *
9
+ * Source: plan.md Phase 3
10
+ */
11
+
12
+ import { Dedicated } from './dedicated.js';
13
+
14
+ // Numeric priority levels
15
+ const LEVEL = { 'user-blocking': 2, 'user-visible': 1, background: 0 };
16
+
17
+ // Starvation prevention: a background task gets promoted after this many
18
+ // higher-priority tasks have run ahead of it.
19
+ const STARVATION_LIMIT = 20;
20
+
21
+ export class Pool {
22
+ #script;
23
+ #max;
24
+ #idle;
25
+ #workers = [];
26
+ #queue = [];
27
+ #inited = false;
28
+ #closed = false;
29
+ #idleMs;
30
+
31
+ /**
32
+ * @param {string} script - Worker script URL
33
+ * @param {object} [opts]
34
+ * @param {number} [opts.size] - Initial pool size
35
+ * @param {number} [opts.max] - Maximum pool size (≥ size)
36
+ * @param {number} [opts.idle] - Idle timeout in ms before a worker is reclaimed (0 = never)
37
+ */
38
+ constructor(script, opts = {}) {
39
+ const cores = navigator.hardwareConcurrency || 2;
40
+ const defaultSize = Math.max(2, cores - 1);
41
+
42
+ this.#script = script;
43
+ this.#max = Math.max(opts.max ?? opts.size ?? defaultSize, 1);
44
+ this.#idle = opts.idle ?? 30_000; // 30 s default
45
+ this.#inited = false;
46
+
47
+ // Shutdown on page hide — covers both BFCache restores and actual unloads
48
+ addEventListener('pagehide', () => this.terminate(), { once: true });
49
+ }
50
+
51
+ get size() { return this.#workers.length; }
52
+ get pending() { return this.#queue.length; }
53
+
54
+ #spawn() {
55
+ const w = {
56
+ instance: new Dedicated(this.#script),
57
+ busy: false,
58
+ timer: null
59
+ };
60
+ this.#workers.push(w);
61
+ return w;
62
+ }
63
+
64
+ #init() {
65
+ if (this.#inited) return;
66
+ this.#inited = true;
67
+ // Spawn at least one worker eagerly; the rest are lazy
68
+ this.#spawn();
69
+ }
70
+
71
+ /**
72
+ * Queues a task with priority scheduling.
73
+ *
74
+ * Options (TaskOptions):
75
+ * payload – structured-clone data
76
+ * transferables – Transferable[]
77
+ * signal – AbortSignal
78
+ * timeout – ms to cancel while queued or running
79
+ * priority – 'user-blocking' | 'user-visible' | 'background'
80
+ * idempotent – if true, requeue on worker crash
81
+ * meta – arbitrary metadata for the worker
82
+ */
83
+ run(task, options = {}) {
84
+ if (this.#closed) {
85
+ return Promise.reject(new Error(`Pool "${this.#script}" is closed`));
86
+ }
87
+
88
+ const {
89
+ payload = null,
90
+ transferables = [],
91
+ signal,
92
+ timeout,
93
+ priority: priorityStr = 'user-visible',
94
+ idempotent = false,
95
+ meta
96
+ } = options;
97
+
98
+ const level = LEVEL[priorityStr] ?? 1;
99
+
100
+ // Already-aborted signals are rejected immediately
101
+ if (signal?.aborted) {
102
+ return Promise.reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
103
+ }
104
+
105
+ this.#init();
106
+
107
+ return new Promise((resolve, reject) => {
108
+ // Combine caller signal + optional timeout while task is queued OR running
109
+ let controller = null;
110
+ let timer = null;
111
+
112
+ if (signal || timeout) {
113
+ controller = new AbortController();
114
+
115
+ if (signal) {
116
+ signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
117
+ }
118
+ if (timeout) {
119
+ timer = setTimeout(
120
+ () => controller.abort(new Error(`Pool task "${task}" timed out after ${timeout}ms`)),
121
+ timeout
122
+ );
123
+ }
124
+ }
125
+
126
+ const combined = controller?.signal;
127
+
128
+ const item = {
129
+ task,
130
+ payload,
131
+ transferables,
132
+ signal: combined,
133
+ meta,
134
+ idempotent,
135
+ level,
136
+ waited: 0, // starvation counter
137
+ resolve: (v) => { if (timer) clearTimeout(timer); resolve(v); },
138
+ reject: (e) => { if (timer) clearTimeout(timer); reject(e); },
139
+ cancelled: false
140
+ };
141
+
142
+ // Cancel while still queued
143
+ if (combined) {
144
+ combined.addEventListener('abort', () => {
145
+ item.cancelled = true;
146
+ item.reject(combined.reason ?? new DOMException('Aborted', 'AbortError'));
147
+ }, { once: true });
148
+ }
149
+
150
+ this.#enqueue(item);
151
+ this.#next();
152
+ });
153
+ }
154
+
155
+ #enqueue(item) {
156
+ this.#queue.push(item);
157
+ // Sort ascending so pop() returns highest priority.
158
+ // Secondary: lower waited = lower index = pop'd later (oldest high-priority runs first).
159
+ this.#queue.sort((a, b) => {
160
+ const ea = this.#effective(a);
161
+ const eb = this.#effective(b);
162
+ return ea - eb; // ascending; pop() gives highest
163
+ });
164
+ }
165
+
166
+ /** Effective priority with starvation compensation */
167
+ #effective(item) {
168
+ const boost = Math.floor(item.waited / STARVATION_LIMIT);
169
+ return Math.min(item.level + boost, 2);
170
+ }
171
+
172
+ #next() {
173
+ if (this.#queue.length === 0) return;
174
+
175
+ // Find an idle worker
176
+ let slot = this.#workers.find((w) => !w.busy && !w.instance.closed);
177
+
178
+ // Spawn a new one if under max
179
+ if (!slot && this.#workers.length < this.#max) {
180
+ slot = this.#spawn();
181
+ }
182
+
183
+ if (!slot) return;
184
+
185
+ // Increment waited counter for items that will stay in queue
186
+ for (const item of this.#queue) item.waited++;
187
+
188
+ // Dequeue the highest-priority item (last after sort)
189
+ const item = this.#queue.pop();
190
+
191
+ // Skip if already cancelled while queued
192
+ if (item.cancelled) {
193
+ this.#next(); // try the next item
194
+ return;
195
+ }
196
+
197
+ this.#cancelIdleTimer(slot);
198
+ slot.busy = true;
199
+
200
+ slot.instance
201
+ .run(item.task, {
202
+ payload: item.payload,
203
+ transferables: item.transferables,
204
+ signal: item.signal,
205
+ meta: item.meta
206
+ })
207
+ .then(item.resolve)
208
+ .catch((err) => {
209
+ if (item.idempotent && !item.signal?.aborted && !slot.instance.closed) {
210
+ // Safe to requeue for one retry
211
+ item.waited = 0;
212
+ this.#enqueue(item);
213
+ } else {
214
+ item.reject(err);
215
+ }
216
+
217
+ // Recycle crashed worker
218
+ if (slot.instance.closed || err?.message?.includes('terminated')) {
219
+ try { slot.instance.terminate(); } catch { /* already gone */ }
220
+ slot.instance = new Dedicated(this.#script);
221
+ }
222
+ })
223
+ .finally(() => {
224
+ slot.busy = false;
225
+ this.#startIdleTimer(slot);
226
+ this.#next();
227
+ });
228
+ }
229
+
230
+ #startIdleTimer(slot) {
231
+ if (!this.#idle || this.#workers.length <= 1) return;
232
+ slot.timer = setTimeout(() => {
233
+ if (!slot.busy) {
234
+ slot.instance.terminate();
235
+ this.#workers = this.#workers.filter((w) => w !== slot);
236
+ }
237
+ }, this.#idle);
238
+ }
239
+
240
+ #cancelIdleTimer(slot) {
241
+ if (slot.timer) {
242
+ clearTimeout(slot.timer);
243
+ slot.timer = null;
244
+ }
245
+ }
246
+
247
+ /** Terminates all workers and clears the queue. */
248
+ terminate() {
249
+ if (this.#closed) return;
250
+ this.#closed = true;
251
+
252
+ const reason = new Error(`Pool "${this.#script}" terminated`);
253
+ for (const item of this.#queue) {
254
+ if (!item.cancelled) item.reject(reason);
255
+ }
256
+ this.#queue = [];
257
+
258
+ for (const w of this.#workers) {
259
+ this.#cancelIdleTimer(w);
260
+ try { w.instance.terminate(); } catch { /* already gone */ }
261
+ }
262
+ this.#workers = [];
263
+ }
264
+ }
265
+
266
+ // Compatibility alias
267
+ export { Pool as WorkerPool };