@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.
- package/CHANGELOG.md +137 -0
- package/README.md +215 -0
- package/bin/anza.js +63 -0
- package/bin/create.js +150 -0
- package/importmap.json +72 -0
- package/package.json +100 -0
- package/src/core/animations/index.js +55 -0
- package/src/core/animations/play.js +111 -0
- package/src/core/animations/registry.js +54 -0
- package/src/core/animations/scroll.js +50 -0
- package/src/core/animations/tokens.js +58 -0
- package/src/core/animations/usage.md +301 -0
- package/src/core/animations/waapi.js +86 -0
- package/src/core/api/cache.js +120 -0
- package/src/core/api/caches/glob.js +24 -0
- package/src/core/api/caches/index.js +118 -0
- package/src/core/api/events/index.js +75 -0
- package/src/core/api/fetch.js +99 -0
- package/src/core/api/index.js +158 -0
- package/src/core/api/pipeline.js +98 -0
- package/src/core/api/plan.md +209 -0
- package/src/core/api/prefixes/index.js +66 -0
- package/src/core/api/retry.js +69 -0
- package/src/core/api/stream.js +127 -0
- package/src/core/api/upload.js +180 -0
- package/src/core/api/usage.md +206 -0
- package/src/core/events/bus.js +38 -0
- package/src/core/events/delegate.js +79 -0
- package/src/core/events/index.js +26 -0
- package/src/core/events/listen.js +50 -0
- package/src/core/events/missing.md +103 -0
- package/src/core/events/once.js +49 -0
- package/src/core/events/plan.md +177 -0
- package/src/core/events/types/index.js +34 -0
- package/src/core/events/usage.md +107 -0
- package/src/core/offline/bridge.js +51 -0
- package/src/core/offline/clock.js +100 -0
- package/src/core/offline/connectivity.js +116 -0
- package/src/core/offline/index.js +41 -0
- package/src/core/offline/missing.md +89 -0
- package/src/core/offline/plan.md +143 -0
- package/src/core/offline/queue.js +168 -0
- package/src/core/offline/state.js +18 -0
- package/src/core/offline/sync.js +106 -0
- package/src/core/offline/usage.md +273 -0
- package/src/core/platform/guard.js +104 -0
- package/src/core/platform/index.js +42 -0
- package/src/core/platform/missing.md +119 -0
- package/src/core/platform/platform.d.ts +88 -0
- package/src/core/platform/polyfills/anchor.js +79 -0
- package/src/core/platform/polyfills/navigation.js +142 -0
- package/src/core/platform/polyfills/popover.js +142 -0
- package/src/core/platform/polyfills/scheduler.js +194 -0
- package/src/core/platform/polyfills/shadow.js +35 -0
- package/src/core/platform/polyfills/urlpattern.js +119 -0
- package/src/core/platform/supports.js +186 -0
- package/src/core/platform/usage.md +287 -0
- package/src/core/router/cache.js +95 -0
- package/src/core/router/container.js +146 -0
- package/src/core/router/handler.js +52 -0
- package/src/core/router/history.js +120 -0
- package/src/core/router/index.js +158 -0
- package/src/core/router/intercept.js +376 -0
- package/src/core/router/match.js +145 -0
- package/src/core/router/missing.md +716 -0
- package/src/core/router/outlet.js +139 -0
- package/src/core/router/plan.md +370 -0
- package/src/core/router/sync/index.js +16 -0
- package/src/core/router/sync/tab.js +115 -0
- package/src/core/router/sync/transport.js +139 -0
- package/src/core/router/transitions.js +59 -0
- package/src/core/router/usage.md +773 -0
- package/src/core/security/crypto.js +159 -0
- package/src/core/security/index.js +49 -0
- package/src/core/security/missing.md +97 -0
- package/src/core/security/permissions.js +64 -0
- package/src/core/security/sanitize.js +100 -0
- package/src/core/security/usage.md +283 -0
- package/src/core/state/derived.js +117 -0
- package/src/core/state/index.js +23 -0
- package/src/core/state/missing.md +165 -0
- package/src/core/state/persist.js +284 -0
- package/src/core/state/store.js +308 -0
- package/src/core/state/sync.js +46 -0
- package/src/core/state/usage.md +440 -0
- package/src/core/storage/cache.js +83 -0
- package/src/core/storage/idb.js +196 -0
- package/src/core/storage/index.js +373 -0
- package/src/core/storage/lru.js +107 -0
- package/src/core/storage/missing.md +165 -0
- package/src/core/storage/opfs.js +190 -0
- package/src/core/storage/plan.md +69 -0
- package/src/core/storage/quota.js +69 -0
- package/src/core/storage/usage.md +226 -0
- package/src/core/ui/base.js +50 -0
- package/src/core/ui/define/container.js +82 -0
- package/src/core/ui/define/define.js +12 -0
- package/src/core/ui/define/element.js +390 -0
- package/src/core/ui/define/index.js +9 -0
- package/src/core/ui/define/orchestrator.js +105 -0
- package/src/core/ui/define/proxy.js +644 -0
- package/src/core/ui/define/state.js +6 -0
- package/src/core/ui/define/utils.js +134 -0
- package/src/core/ui/implementation.md +170 -0
- package/src/core/ui/index.js +41 -0
- package/src/core/ui/observe.js +117 -0
- package/src/core/ui/plan.md +510 -0
- package/src/core/ui/schedule.js +60 -0
- package/src/core/ui/template.js +37 -0
- package/src/core/ui/transitions.js +37 -0
- package/src/core/ui/ui.types.md +890 -0
- package/src/core/ui/usage.md +1124 -0
- package/src/core/ui/watch.md +346 -0
- package/src/core/workers/broadcast.js +138 -0
- package/src/core/workers/dedicated.js +153 -0
- package/src/core/workers/index.js +169 -0
- package/src/core/workers/locks.js +160 -0
- package/src/core/workers/offscreen.js +166 -0
- package/src/core/workers/plan.md +381 -0
- package/src/core/workers/pool.js +267 -0
- package/src/core/workers/shared.js +137 -0
- package/src/core/workers/usage.md +622 -0
- package/src/elements/base.js +12 -0
- package/src/elements/data/card/index.html +9 -0
- package/src/elements/data/card/index.js +19 -0
- package/src/elements/data/card/index.tags.json +1 -0
- package/src/elements/data/card/style.css +46 -0
- package/src/elements/data/chart/index.html +1 -0
- package/src/elements/data/chart/index.js +143 -0
- package/src/elements/data/chart/index.tags.json +1 -0
- package/src/elements/data/chart/style.css +13 -0
- package/src/elements/data/list/index.html +3 -0
- package/src/elements/data/list/index.js +19 -0
- package/src/elements/data/list/index.tags.json +1 -0
- package/src/elements/data/list/style.css +39 -0
- package/src/elements/data/stat/index.html +9 -0
- package/src/elements/data/stat/index.js +19 -0
- package/src/elements/data/stat/index.tags.json +1 -0
- package/src/elements/data/stat/style.css +50 -0
- package/src/elements/data/table/index.html +1 -0
- package/src/elements/data/table/index.js +16 -0
- package/src/elements/data/table/index.tags.json +1 -0
- package/src/elements/data/table/style.css +50 -0
- package/src/elements/feedback/alert/index.html +11 -0
- package/src/elements/feedback/alert/index.js +28 -0
- package/src/elements/feedback/alert/index.tags.json +1 -0
- package/src/elements/feedback/alert/style.css +75 -0
- package/src/elements/feedback/empty/index.html +13 -0
- package/src/elements/feedback/empty/index.js +34 -0
- package/src/elements/feedback/empty/index.tags.json +1 -0
- package/src/elements/feedback/empty/style.css +45 -0
- package/src/elements/feedback/progress/index.html +7 -0
- package/src/elements/feedback/progress/index.js +46 -0
- package/src/elements/feedback/progress/index.tags.json +1 -0
- package/src/elements/feedback/progress/style.css +36 -0
- package/src/elements/feedback/skeleton/index.html +1 -0
- package/src/elements/feedback/skeleton/index.js +78 -0
- package/src/elements/feedback/skeleton/index.tags.json +1 -0
- package/src/elements/feedback/skeleton/style.css +28 -0
- package/src/elements/feedback/toast/index.html +3 -0
- package/src/elements/feedback/toast/index.js +65 -0
- package/src/elements/feedback/toast/index.tags.json +1 -0
- package/src/elements/feedback/toast/style.css +36 -0
- package/src/elements/forms/checkbox/index.html +7 -0
- package/src/elements/forms/checkbox/index.js +104 -0
- package/src/elements/forms/checkbox/index.tags.json +1 -0
- package/src/elements/forms/checkbox/style.css +86 -0
- package/src/elements/forms/field/index.html +13 -0
- package/src/elements/forms/field/index.js +42 -0
- package/src/elements/forms/field/index.tags.json +1 -0
- package/src/elements/forms/field/style.css +42 -0
- package/src/elements/forms/form/index.html +3 -0
- package/src/elements/forms/form/index.js +122 -0
- package/src/elements/forms/form/index.tags.json +1 -0
- package/src/elements/forms/form/style.css +11 -0
- package/src/elements/forms/input/index.html +4 -0
- package/src/elements/forms/input/index.js +103 -0
- package/src/elements/forms/input/index.tags.json +1 -0
- package/src/elements/forms/input/style.css +39 -0
- package/src/elements/forms/radio/index.html +4 -0
- package/src/elements/forms/radio/index.js +109 -0
- package/src/elements/forms/radio/index.tags.json +1 -0
- package/src/elements/forms/radio/style.css +65 -0
- package/src/elements/forms/select/index.html +9 -0
- package/src/elements/forms/select/index.js +114 -0
- package/src/elements/forms/select/index.tags.json +1 -0
- package/src/elements/forms/select/style.css +95 -0
- package/src/elements/forms/textarea/index.html +4 -0
- package/src/elements/forms/textarea/index.js +115 -0
- package/src/elements/forms/textarea/index.tags.json +1 -0
- package/src/elements/forms/textarea/style.css +46 -0
- package/src/elements/forms/toggle/index.html +4 -0
- package/src/elements/forms/toggle/index.js +89 -0
- package/src/elements/forms/toggle/index.tags.json +1 -0
- package/src/elements/forms/toggle/style.css +63 -0
- package/src/elements/forms/upload/index.html +13 -0
- package/src/elements/forms/upload/index.js +120 -0
- package/src/elements/forms/upload/index.tags.json +1 -0
- package/src/elements/forms/upload/style.css +61 -0
- package/src/elements/index.js +71 -0
- package/src/elements/layout/app/index.html +7 -0
- package/src/elements/layout/app/index.js +16 -0
- package/src/elements/layout/app/index.tags.json +1 -0
- package/src/elements/layout/app/style.css +41 -0
- package/src/elements/layout/grid/index.html +3 -0
- package/src/elements/layout/grid/index.js +41 -0
- package/src/elements/layout/grid/index.tags.json +1 -0
- package/src/elements/layout/grid/style.css +12 -0
- package/src/elements/layout/header/index.html +8 -0
- package/src/elements/layout/header/index.js +16 -0
- package/src/elements/layout/header/index.tags.json +1 -0
- package/src/elements/layout/header/style.css +28 -0
- package/src/elements/layout/scroll/index.html +3 -0
- package/src/elements/layout/scroll/index.js +19 -0
- package/src/elements/layout/scroll/index.tags.json +1 -0
- package/src/elements/layout/scroll/style.css +24 -0
- package/src/elements/layout/sidebar/index.html +3 -0
- package/src/elements/layout/sidebar/index.js +24 -0
- package/src/elements/layout/sidebar/index.tags.json +1 -0
- package/src/elements/layout/sidebar/style.css +30 -0
- package/src/elements/layout/split/index.html +3 -0
- package/src/elements/layout/split/index.js +18 -0
- package/src/elements/layout/split/index.tags.json +1 -0
- package/src/elements/layout/split/style.css +28 -0
- package/src/elements/layout/stack/index.html +3 -0
- package/src/elements/layout/stack/index.js +31 -0
- package/src/elements/layout/stack/index.tags.json +1 -0
- package/src/elements/layout/stack/style.css +15 -0
- package/src/elements/layout/surface/index.html +3 -0
- package/src/elements/layout/surface/index.js +19 -0
- package/src/elements/layout/surface/index.tags.json +1 -0
- package/src/elements/layout/surface/style.css +29 -0
- package/src/elements/navigation/breadcrumb/index.html +5 -0
- package/src/elements/navigation/breadcrumb/index.js +16 -0
- package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
- package/src/elements/navigation/breadcrumb/style.css +36 -0
- package/src/elements/navigation/nav/index.html +3 -0
- package/src/elements/navigation/nav/index.js +24 -0
- package/src/elements/navigation/nav/index.tags.json +1 -0
- package/src/elements/navigation/nav/style.css +38 -0
- package/src/elements/navigation/pagination/index.html +3 -0
- package/src/elements/navigation/pagination/index.js +94 -0
- package/src/elements/navigation/pagination/index.tags.json +1 -0
- package/src/elements/navigation/pagination/style.css +39 -0
- package/src/elements/navigation/steps/index.html +6 -0
- package/src/elements/navigation/steps/index.js +64 -0
- package/src/elements/navigation/steps/index.tags.json +1 -0
- package/src/elements/navigation/steps/style.css +78 -0
- package/src/elements/navigation/tabs/index.html +6 -0
- package/src/elements/navigation/tabs/index.js +132 -0
- package/src/elements/navigation/tabs/index.tags.json +1 -0
- package/src/elements/navigation/tabs/style.css +52 -0
- package/src/elements/overlay/dialog/index.html +5 -0
- package/src/elements/overlay/dialog/index.js +57 -0
- package/src/elements/overlay/dialog/index.tags.json +1 -0
- package/src/elements/overlay/dialog/style.css +31 -0
- package/src/elements/overlay/drawer/index.html +3 -0
- package/src/elements/overlay/drawer/index.js +56 -0
- package/src/elements/overlay/drawer/index.tags.json +1 -0
- package/src/elements/overlay/drawer/style.css +48 -0
- package/src/elements/overlay/menu/index.html +3 -0
- package/src/elements/overlay/menu/index.js +107 -0
- package/src/elements/overlay/menu/index.tags.json +1 -0
- package/src/elements/overlay/menu/style.css +43 -0
- package/src/elements/overlay/popover/index.html +3 -0
- package/src/elements/overlay/popover/index.js +44 -0
- package/src/elements/overlay/popover/index.tags.json +1 -0
- package/src/elements/overlay/popover/style.css +21 -0
- package/src/elements/overlay/sheet/index.html +8 -0
- package/src/elements/overlay/sheet/index.js +105 -0
- package/src/elements/overlay/sheet/index.tags.json +1 -0
- package/src/elements/overlay/sheet/style.css +64 -0
- package/src/elements/overlay/tooltip/index.html +6 -0
- package/src/elements/overlay/tooltip/index.js +16 -0
- package/src/elements/overlay/tooltip/index.tags.json +1 -0
- package/src/elements/overlay/tooltip/style.css +41 -0
- package/src/elements/primitives/avatar/index.html +2 -0
- package/src/elements/primitives/avatar/index.js +79 -0
- package/src/elements/primitives/avatar/index.tags.json +1 -0
- package/src/elements/primitives/avatar/style.css +36 -0
- package/src/elements/primitives/badge/index.html +3 -0
- package/src/elements/primitives/badge/index.js +20 -0
- package/src/elements/primitives/badge/index.tags.json +1 -0
- package/src/elements/primitives/badge/style.css +67 -0
- package/src/elements/primitives/button/index.html +3 -0
- package/src/elements/primitives/button/index.js +61 -0
- package/src/elements/primitives/button/index.tags.json +1 -0
- package/src/elements/primitives/button/style.css +66 -0
- package/src/elements/primitives/divider/index.html +1 -0
- package/src/elements/primitives/divider/index.js +43 -0
- package/src/elements/primitives/divider/index.tags.json +1 -0
- package/src/elements/primitives/divider/style.css +39 -0
- package/src/elements/primitives/icon/index.html +3 -0
- package/src/elements/primitives/icon/index.js +66 -0
- package/src/elements/primitives/icon/index.tags.json +1 -0
- package/src/elements/primitives/icon/style.css +20 -0
- package/src/elements/primitives/link/index.html +3 -0
- package/src/elements/primitives/link/index.js +129 -0
- package/src/elements/primitives/link/index.tags.json +1 -0
- package/src/elements/primitives/link/style.css +40 -0
- package/src/elements/primitives/spinner/index.html +1 -0
- package/src/elements/primitives/spinner/index.js +62 -0
- package/src/elements/primitives/spinner/index.tags.json +1 -0
- package/src/elements/primitives/spinner/style.css +20 -0
- package/src/elements/primitives/text/index.html +1 -0
- package/src/elements/primitives/text/index.js +79 -0
- package/src/elements/primitives/text/index.tags.json +1 -0
- package/src/elements/primitives/text/style.css +25 -0
- package/src/index.js +23 -0
- package/src/styles/base.css +66 -0
- package/src/styles/index.css +10 -0
- package/src/styles/layers.css +9 -0
- package/src/styles/reset.css +66 -0
- package/src/sw/activate.js +51 -0
- package/src/sw/expire.js +47 -0
- package/src/sw/index.js +28 -0
- package/src/sw/install.js +35 -0
- package/src/sw/push.js +58 -0
- package/src/sw/queue.js +60 -0
- package/src/sw/routes.js +71 -0
- package/src/sw/strategies.js +247 -0
- package/src/sw/sync.js +80 -0
- package/src/tokens/index.css +26 -0
- package/src/tokens/primitives/colors.css +54 -0
- package/src/tokens/primitives/motion.css +34 -0
- package/src/tokens/primitives/radius.css +16 -0
- package/src/tokens/primitives/shadow.css +34 -0
- package/src/tokens/primitives/spacing.css +27 -0
- package/src/tokens/primitives/typography.css +46 -0
- package/src/tokens/primitives/zindex.css +18 -0
- package/src/tokens/registered/colors.css +133 -0
- package/src/tokens/registered/dimensions.css +31 -0
- package/src/tokens/semantic/components.css +125 -0
- package/src/tokens/semantic/contrast.css +33 -0
- package/src/tokens/semantic/dark.css +61 -0
- package/src/tokens/semantic/light.css +64 -0
- package/types/core/animations/index.d.ts +52 -0
- package/types/core/api/index.d.ts +68 -0
- package/types/core/events/index.d.ts +50 -0
- package/types/core/offline/index.d.ts +68 -0
- package/types/core/platform/index.d.ts +60 -0
- package/types/core/router/index.d.ts +203 -0
- package/types/core/security/index.d.ts +33 -0
- package/types/core/state/index.d.ts +68 -0
- package/types/core/storage/index.d.ts +40 -0
- package/types/core/ui/index.d.ts +446 -0
- package/types/core/workers/index.d.ts +221 -0
- package/types/elements/index.d.ts +150 -0
- 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 };
|