@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,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
|
+
}
|