@atolljs/core 0.1.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 (44) hide show
  1. package/README.md +110 -0
  2. package/dist/index.js +19 -0
  3. package/dist/index10.js +186 -0
  4. package/dist/index11.js +52 -0
  5. package/dist/index12.js +66 -0
  6. package/dist/index13.js +27 -0
  7. package/dist/index14.js +63 -0
  8. package/dist/index15.js +50 -0
  9. package/dist/index16.js +46 -0
  10. package/dist/index17.js +42 -0
  11. package/dist/index18.js +61 -0
  12. package/dist/index19.js +56 -0
  13. package/dist/index2.js +10 -0
  14. package/dist/index20.js +59 -0
  15. package/dist/index3.js +120 -0
  16. package/dist/index4.js +40 -0
  17. package/dist/index5.js +481 -0
  18. package/dist/index6.js +23 -0
  19. package/dist/index7.js +10 -0
  20. package/dist/index8.js +37 -0
  21. package/dist/index9.js +22 -0
  22. package/package.json +68 -0
  23. package/src/contract/listSchema.ts +211 -0
  24. package/src/contract/msgpackCodec.ts +9 -0
  25. package/src/contract/msgpackrCodec.ts +16 -0
  26. package/src/contract/mz.ts +48 -0
  27. package/src/contract/sharedMemory.ts +909 -0
  28. package/src/contract/types.ts +123 -0
  29. package/src/index.ts +114 -0
  30. package/src/log.ts +69 -0
  31. package/src/observable.ts +151 -0
  32. package/src/pool/errors.ts +31 -0
  33. package/src/pool/memory.ts +68 -0
  34. package/src/pool/workerClient.ts +181 -0
  35. package/src/pool/workerPool.ts +438 -0
  36. package/src/reactive.ts +161 -0
  37. package/src/service.ts +283 -0
  38. package/src/shared/sharedWorkerClient.ts +142 -0
  39. package/src/shared/sharedWorkerHost.ts +104 -0
  40. package/src/task.ts +99 -0
  41. package/src/testing/inProcessWorker.ts +127 -0
  42. package/src/worker/defineWorker.ts +135 -0
  43. package/src/worker/registry.ts +42 -0
  44. package/src/worker/workerBootstrap.ts +56 -0
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # AtollJS
2
+
3
+ [![CI](https://github.com/jwhenry3/atolljs/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/jwhenry3/atolljs/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/jwhenry3/atolljs/graph/badge.svg?branch=master)](https://codecov.io/gh/jwhenry3/atolljs)
5
+
6
+ Typed shared-memory worker pools for TypeScript — deterministic `SharedArrayBuffer`
7
+ layouts, first-class task methods, and cross-thread reactive state.
8
+
9
+ ## Packages
10
+
11
+ | Package | What it is |
12
+ |---|---|
13
+ | `@atolljs/core` | Core SDK — `defineWorker`/`connectWorker` typed worker clients over `WorkerPool`, shared-memory contracts, `watch`/`observe`, codecs |
14
+ | `@atolljs/react` | React hooks — `useObservable`, `useSharedValue`, `useTask` |
15
+ | `@atolljs/vue` | Vue composables — `useObservable`, `useSharedValue`, `useTask` |
16
+ | `@atolljs/solidjs` | Solid primitives — `createObservable`, `createSharedValue`, `createTask` |
17
+ | `@atolljs/svelte` | Svelte 5 rune bindings — `observableValue`, `sharedValue`, `taskState` |
18
+ | `@atolljs/angular` | Angular signals — `observableSignal`, `sharedValue`, `taskState` |
19
+ | `@atolljs/nextjs` | Next.js client-component bindings (React re-export) |
20
+ | `@atolljs/node` | `node:worker_threads` runtime adapter |
21
+ | `@atolljs/nestjs` | NestJS module/decorators for worker pools |
22
+ | `@atolljs/islands` | worker-side React reconciler + proxy DOM islands — opt-in DOM rendering |
23
+ | `@atolljs/react-island` | React shell components for islands — `<Island/>`, `islandComponent`, `lazyIsland` |
24
+ | `@atolljs/vue-island` | Vue shell (`useIsland`, `<AtollIsland>`) + Vue worker renderer |
25
+ | `@atolljs/svelte-island` | Svelte shell (`use:island`, `createIslandState`) + Svelte 5 worker renderer |
26
+ | `@atolljs/solid-island` | Solid shell (`createIsland`, `Island`) + `solid-js/universal` worker renderer |
27
+ | `@atolljs/angular-island` | Angular shell (`<atoll-island>`, `[atollIsland]`) + `Renderer2` worker renderer |
28
+
29
+ Framework bindings are published independently — install only the one you use:
30
+
31
+ ```bash
32
+ npm install @atolljs/core @atolljs/react
33
+ ```
34
+
35
+ ## Documentation
36
+
37
+ - **`docs/`** — in-repo markdown documentation covering internals, contracts,
38
+ islands, and per-framework bindings. Start at [`docs/README.md`](docs/README.md)
39
+ (agents: `AGENTS.md` points here before extensive work).
40
+ - **`docs-consumer/`** — the consumer-facing package-usage site; deploys to
41
+ GitHub Pages on every push to `master`:
42
+ [jwhenry3.github.io/atolljs](https://jwhenry3.github.io/atolljs/) (`/consumer/`).
43
+
44
+ ## Releasing
45
+
46
+ Create a GitHub Release tagged `v<semver>` — `.github/workflows/publish.yml`
47
+ runs the full test suite, builds the core `dist`, stamps every publishable
48
+ package at the tag's version (lockstep; `packages/incidents` stays private),
49
+ and **stages** each to npm with provenance. Staged versions aren't
50
+ installable until a maintainer approves them — `npm stage list` /
51
+ `npm stage approve <stage-id>` (2FA at approval, not in CI), or the Staged
52
+ Packages tab on npmjs.com. Requires a granular `NPM_TOKEN` repo secret.
53
+ Preview the plan locally: `node scripts/publish.mjs v0.1.0 --dry-run`.
54
+
55
+ Staging needs each package to already exist on the registry, so the first
56
+ release is a manual bootstrap — from the repo root:
57
+
58
+ ```bash
59
+ npm login # once
60
+ npm ci && npx vite build && npx tsc -p tsconfig.build.json
61
+ node scripts/publish.mjs 0.1.0 --direct # prompts for 2FA per package
62
+ ```
63
+
64
+ Once all nine packages exist, release-driven `npm stage publish` works for
65
+ every subsequent version.
66
+
67
+ ### Trusted publishing (OIDC)
68
+
69
+ Prefer OIDC over the `NPM_TOKEN` secret — no long-lived credential, and a
70
+ trust relationship can be **stage-only** so the workflow can't direct-publish
71
+ even if compromised. Configure per package (needs the package to exist on
72
+ npm, and npm CLI ≥ 11.10):
73
+
74
+ ```bash
75
+ for p in atoll atoll-node atoll-react atoll-angular atoll-nestjs atoll-nextjs atoll-solidjs atoll-svelte atoll-vue; do
76
+ npm trust github "@atolljs/$p" --repo jwhenry3/atolljs --file publish.yml --allow-stage-publish -y
77
+ sleep 2
78
+ done
79
+ ```
80
+
81
+ Omit `--allow-publish` — stage-only. First call prompts for 2FA; choose
82
+ "skip for 5 minutes" and the loop finishes hands-free. Verify with
83
+ `npm trust list @atolljs/core`. Once all nine show the relationship,
84
+ delete the `NODE_AUTH_TOKEN` env line in `publish.yml` (npm only uses OIDC
85
+ when no token is present) — the secret can be revoked after.
86
+
87
+ ## Layout
88
+
89
+ ```
90
+ src/ core SDK — contract/ (shared protocol), pool/, worker/
91
+ packages/<fw>/ independently publishable framework bindings
92
+ packages/incidents/ demo domain package (contract + worker + pool)
93
+ examples/<fw>/ per-framework demo apps
94
+ docs/ in-repo markdown documentation (internals + bindings)
95
+ docs-consumer/ consumer docs site
96
+ ```
97
+
98
+ ## Scripts
99
+
100
+ ```bash
101
+ npm test # vitest — all suites (sdk + packages + example e2e)
102
+ npm run build # typecheck + lib build
103
+ npm run dev:all # launch every example dev server
104
+ npm run build:pages # consumer docs + examples → dist-pages (GitHub Pages artifact)
105
+ ```
106
+
107
+ Worker demos require cross-origin isolation (COOP/COEP) — the dev servers set it;
108
+ GitHub Pages cannot send headers, so the Pages artifact ships `coi-sw.js`, a
109
+ service worker that injects them (first visit reloads once; islands fall back to
110
+ their poll transport where isolation still isn't available).
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ import { msgpackrCodec as e } from "./index2.js";
2
+ import { listSchema as t } from "./index3.js";
3
+ import { getLogLevel as n, log as r, scoped as i, setLogLevel as a, setLogSink as o } from "./index4.js";
4
+ import { defineSharedMemory as s, field as c, getDefinedSharedMemoryCount as l, jsonCodec as u, registerConnectorFactory as d } from "./index5.js";
5
+ import { mz as f } from "./index6.js";
6
+ import { msgpackCodec as p } from "./index7.js";
7
+ import { MemoryManager as m } from "./index8.js";
8
+ import { PoolQueueFullError as h, TaskAbortedError as g, TaskTimeoutError as _, WorkerCrashedError as v } from "./index9.js";
9
+ import { WorkerPool as y } from "./index10.js";
10
+ import { connectWorker as b, workerClient as x } from "./index11.js";
11
+ import { connectSharedWorker as S } from "./index12.js";
12
+ import { TaskRegistry as C } from "./index13.js";
13
+ import { sharedWorkerHost as w } from "./index14.js";
14
+ import { createClient as T, defineService as E, implementService as D, rpc as O, serviceMethod as k } from "./index15.js";
15
+ import { RESERVED_CLIENT_KEYS as A, defineWorker as j } from "./index17.js";
16
+ import { reactive as M, shallowEqual as N, watch as P } from "./index18.js";
17
+ import { observe as F } from "./index19.js";
18
+ import { defineTask as I, isAsyncTask as L, toTask as R } from "./index20.js";
19
+ export { m as MemoryManager, h as PoolQueueFullError, A as RESERVED_CLIENT_KEYS, g as TaskAbortedError, C as TaskRegistry, _ as TaskTimeoutError, v as WorkerCrashedError, y as WorkerPool, S as connectSharedWorker, b as connectWorker, T as createClient, E as defineService, s as defineSharedMemory, I as defineTask, j as defineWorker, c as field, l as getDefinedSharedMemoryCount, n as getLogLevel, D as implementService, L as isAsyncTask, u as jsonCodec, t as listSchema, r as log, p as msgpackCodec, e as msgpackrCodec, f as mz, F as observe, M as reactive, d as registerConnectorFactory, O as rpc, i as scoped, k as serviceMethod, a as setLogLevel, o as setLogSink, N as shallowEqual, w as sharedWorkerHost, R as toTask, P as watch, x as workerClient };
@@ -0,0 +1,186 @@
1
+ import { fmtBytes as e, scoped as t } from "./index4.js";
2
+ import { MemoryManager as n } from "./index8.js";
3
+ import { PoolQueueFullError as r, TaskAbortedError as i, TaskTimeoutError as a, WorkerCrashedError as o } from "./index9.js";
4
+ //#region src/pool/workerPool.ts
5
+ var s = t("pool"), c = t("task"), l = () => ({
6
+ count: 0,
7
+ mean: 0,
8
+ max: 0,
9
+ add(e) {
10
+ this.count++, this.mean += (e - this.mean) / this.count, e > this.max && (this.max = e);
11
+ }
12
+ }), u = class {
13
+ slots = [];
14
+ pending = /* @__PURE__ */ new Map();
15
+ nextMessageId = 0;
16
+ queue = [];
17
+ memoryManager;
18
+ workerUrl;
19
+ spawn;
20
+ concurrency;
21
+ maxQueue;
22
+ respawn;
23
+ taskTimeout;
24
+ closed = !1;
25
+ drainWaiters = [];
26
+ statsAgg = {
27
+ completed: 0,
28
+ failed: 0,
29
+ aborted: 0,
30
+ waitMs: l(),
31
+ runMs: l()
32
+ };
33
+ sharedMemory;
34
+ constructor(t) {
35
+ if (t.sharedMemory && (typeof SharedArrayBuffer > "u" || typeof crossOriginIsolated < "u" && !crossOriginIsolated)) throw Error("WorkerPool requires SharedArrayBuffer in a cross-origin isolated context — serve the page with COOP/COEP headers.");
36
+ if (!t.workerUrl && !t.createWorker) throw Error("WorkerPool requires either `workerUrl` or `createWorker` in its config.");
37
+ this.workerUrl = t.workerUrl, this.spawn = t.createWorker ?? (() => new Worker(this.workerUrl, { type: "module" })), this.concurrency = t.concurrency ?? 1, this.maxQueue = t.maxQueue ?? Infinity, this.respawn = t.respawn ?? !0, this.taskTimeout = t.taskTimeout, t.sharedMemory && (this.sharedMemory = t.sharedMemory, this.memoryManager = new n(t.memory), this.memoryManager.ensureCapacity(this.sharedMemory.totalBytes), this.sharedMemory.bind(this.memoryManager.getBuffer()));
38
+ let r = t.poolSize ?? "auto", i = r === "auto" ? navigator.hardwareConcurrency ?? 4 : r;
39
+ s.info(this.sharedMemory ? `spawning ${i} worker(s) with ${e(this.sharedMemory.totalBytes)} shared buffer` : `spawning ${i} worker(s), message-only`, { workerUrl: t.workerUrl ? String(t.workerUrl) : "createWorker()" }), s.debug("tip: setLogLevel('trace') for per-task detail");
40
+ for (let e = 0; e < i; e++) this.addWorker(this.spawn());
41
+ let a = t.tasks ?? {};
42
+ for (let [e, t] of Object.entries(a)) {
43
+ if (e in this) {
44
+ s.warn(`task "${e}" collides with a WorkerPool member — not installed`);
45
+ continue;
46
+ }
47
+ this[e] = (...e) => this.runTask(t, ...e);
48
+ }
49
+ Object.keys(a).length > 0 && s.info(`task methods: ${Object.keys(a).join(", ")}`);
50
+ }
51
+ addWorker(e) {
52
+ let t = {
53
+ worker: e,
54
+ inFlight: 0
55
+ };
56
+ return e.addEventListener("message", (e) => {
57
+ this.handleReply(t, e.data);
58
+ }), e.addEventListener("error", (e) => {
59
+ this.handleWorkerError(t, e);
60
+ }), this.memoryManager ? e.postMessage({
61
+ type: "INIT_MEMORY",
62
+ memory: this.memoryManager.memory
63
+ }) : e.postMessage({ type: "INIT" }), this.slots.push(t), s.debug(`worker spawned, ${this.memoryManager ? "INIT_MEMORY" : "INIT"} sent`), t;
64
+ }
65
+ handleReply(e, t) {
66
+ let n = t.messageId === void 0 ? void 0 : this.pending.get(t.messageId);
67
+ n && (this.pending.delete(t.messageId), e.inFlight--, this.detach(n), n.done || (n.done = !0, this.statsAgg.runMs.add(performance.now() - (n.sentAt ?? n.enqueuedAt)), t.success ? (this.statsAgg.completed++, c.debug(`← ${n.contract.taskId} ok`), n.resolve(t.result)) : (this.statsAgg.failed++, c.warn(`← ${n.contract.taskId} failed: ${t.error}`), n.reject(Error(t.error)))), this.drainQueue(), this.checkDrained());
68
+ }
69
+ handleWorkerError(e, t) {
70
+ if (this.closed) return;
71
+ let n = t, r = n?.message ?? (n?.error ? String(n.error) : "worker error");
72
+ s.warn(`worker error: ${r}`);
73
+ for (let [t, n] of [...this.pending]) if (n.slot === e) {
74
+ if (this.pending.delete(t), this.detach(n), n.done) continue;
75
+ n.done = !0, this.statsAgg.failed++, n.reject(new o(r));
76
+ }
77
+ let i = this.slots.indexOf(e);
78
+ if (i >= 0 && this.slots.splice(i, 1), e.worker.terminate(), this.respawn) s.info("respawning crashed worker"), this.addWorker(this.spawn());
79
+ else if (this.slots.length === 0) for (let e of this.queue.splice(0)) e.done = !0, this.detach(e), this.statsAgg.failed++, e.reject(new o(r));
80
+ this.drainQueue(), this.checkDrained();
81
+ }
82
+ pickWorker() {
83
+ let e;
84
+ for (let t of this.slots) t.inFlight >= this.concurrency || (!e || t.inFlight < e.inFlight) && (e = t);
85
+ return e;
86
+ }
87
+ drainQueue() {
88
+ let e;
89
+ for (; this.queue.length > 0 && (e = this.pickWorker());) {
90
+ let t = this.queue.shift();
91
+ t.done || this.send(e, t);
92
+ }
93
+ }
94
+ send(e, t) {
95
+ let n = this.nextMessageId++;
96
+ t.messageId = n, t.slot = e, t.sentAt = performance.now(), this.statsAgg.waitMs.add(performance.now() - t.enqueuedAt), e.inFlight++, this.pending.set(n, t), c.debug(`→ ${t.contract.taskId}`, {
97
+ worker: this.slots.indexOf(e),
98
+ args: t.args
99
+ }), e.worker.postMessage({
100
+ type: "EXECUTE_TASK",
101
+ messageId: n,
102
+ taskId: t.contract.taskId,
103
+ args: t.args
104
+ });
105
+ }
106
+ detach(e) {
107
+ e.timer && clearTimeout(e.timer), e.signal && e.onSignalAbort && e.signal.removeEventListener("abort", e.onSignalAbort);
108
+ }
109
+ cancel(e, t) {
110
+ if (e.done) return;
111
+ let n = this.queue.indexOf(e);
112
+ n >= 0 && this.queue.splice(n, 1), e.done = !0, this.detach(e), this.statsAgg.aborted++, e.reject(t), this.drainQueue(), this.checkDrained();
113
+ }
114
+ checkDrained() {
115
+ if (this.queue.length === 0 && this.pending.size === 0) {
116
+ let e = this.drainWaiters.splice(0);
117
+ for (let t of e) t();
118
+ }
119
+ }
120
+ dispatch(e, t, n) {
121
+ return this.closed || this.slots.length === 0 ? Promise.reject(new o("WorkerPool has been terminated — no workers available.")) : n?.signal?.aborted ? Promise.reject(new i()) : new Promise((o, s) => {
122
+ let c = {
123
+ contract: e,
124
+ args: t,
125
+ enqueuedAt: performance.now(),
126
+ resolve: o,
127
+ reject: s,
128
+ done: !1,
129
+ signal: n?.signal
130
+ };
131
+ n?.signal && (c.onSignalAbort = () => this.cancel(c, new i()), n.signal.addEventListener("abort", c.onSignalAbort, { once: !0 }));
132
+ let l = n?.timeout ?? this.taskTimeout;
133
+ l !== void 0 && (c.timer = setTimeout(() => this.cancel(c, new a()), l));
134
+ let u = this.pickWorker();
135
+ if (u) this.send(u, c);
136
+ else if (this.queue.length >= this.maxQueue) {
137
+ c.done = !0, this.detach(c), s(new r());
138
+ return;
139
+ } else this.queue.push(c);
140
+ });
141
+ }
142
+ runTask(e, ...t) {
143
+ return this.dispatch(e, t);
144
+ }
145
+ stats() {
146
+ return {
147
+ workers: this.slots.length,
148
+ idle: this.slots.filter((e) => e.inFlight === 0).length,
149
+ inFlight: this.pending.size,
150
+ queued: this.queue.length,
151
+ completed: this.statsAgg.completed,
152
+ failed: this.statsAgg.failed,
153
+ aborted: this.statsAgg.aborted,
154
+ waitMs: {
155
+ count: this.statsAgg.waitMs.count,
156
+ mean: this.statsAgg.waitMs.mean,
157
+ max: this.statsAgg.waitMs.max
158
+ },
159
+ runMs: {
160
+ count: this.statsAgg.runMs.count,
161
+ mean: this.statsAgg.runMs.mean,
162
+ max: this.statsAgg.runMs.max
163
+ }
164
+ };
165
+ }
166
+ close() {
167
+ return this.queue.length === 0 && this.pending.size === 0 ? (this.terminate(), Promise.resolve()) : new Promise((e) => {
168
+ this.drainWaiters.push(() => {
169
+ this.terminate(), e();
170
+ });
171
+ });
172
+ }
173
+ terminate() {
174
+ this.closed = !0, s.info(`terminating ${this.slots.length} worker(s)`);
175
+ let e = new o("pool terminated");
176
+ for (let [, t] of this.pending) this.detach(t), !t.done && (t.done = !0, this.statsAgg.failed++, t.reject(e));
177
+ this.pending.clear();
178
+ for (let t of this.queue.splice(0)) t.done = !0, this.detach(t), this.statsAgg.failed++, t.reject(e);
179
+ for (let e of this.slots) e.worker.terminate();
180
+ this.slots = [], this.checkDrained();
181
+ }
182
+ };
183
+ //#endregion
184
+ export { u as WorkerPool };
185
+
186
+ //# sourceMappingURL=index10.js.map
@@ -0,0 +1,52 @@
1
+ import { WorkerPool as e } from "./index10.js";
2
+ //#region src/pool/workerClient.ts
3
+ var t = (e, n, r, i) => {
4
+ let a = /* @__PURE__ */ new Map();
5
+ return new Proxy(() => {}, {
6
+ get(o, s) {
7
+ if (s === "then" || typeof s == "symbol") return;
8
+ if (s === "with") return (i) => t(e, n, r, i);
9
+ if (e.length === 0 && r && s in r) return Reflect.get(r, s);
10
+ let c = a.get(s);
11
+ return c || (c = t([...e, s], n, r, i), a.set(s, c)), c;
12
+ },
13
+ apply(t, r, a) {
14
+ let o = n(), s = { taskId: e.join(".") };
15
+ return o.dispatch ? o.dispatch(s, a, i) : o.runTask(s, ...a);
16
+ }
17
+ });
18
+ };
19
+ function n(e) {
20
+ let n;
21
+ return t([], typeof e == "function" ? () => n ??= e() : () => e);
22
+ }
23
+ function r(n) {
24
+ let r = null, i = () => r ??= new e({
25
+ sharedMemory: n.sharedMemory,
26
+ createWorker: typeof n.worker == "function" ? n.worker : () => new Worker(n.worker, { type: "module" }),
27
+ poolSize: n.poolSize,
28
+ memory: n.memory,
29
+ concurrency: n.concurrency,
30
+ maxQueue: n.maxQueue,
31
+ respawn: n.respawn,
32
+ taskTimeout: n.taskTimeout
33
+ });
34
+ return n.lazy === !1 && i(), t([], i, {
35
+ start: () => {
36
+ i();
37
+ },
38
+ terminate: () => {
39
+ r?.terminate(), r = null;
40
+ },
41
+ get pool() {
42
+ return r;
43
+ },
44
+ get sharedMemory() {
45
+ return n.sharedMemory;
46
+ }
47
+ });
48
+ }
49
+ //#endregion
50
+ export { r as connectWorker, n as workerClient };
51
+
52
+ //# sourceMappingURL=index11.js.map
@@ -0,0 +1,66 @@
1
+ import { scoped as e } from "./index4.js";
2
+ //#region src/shared/sharedWorkerClient.ts
3
+ var t = e("shared-client"), n = e("task"), r = class {
4
+ clientIndex;
5
+ sharedMemory;
6
+ port;
7
+ constructor(e, n, r) {
8
+ this.clientIndex = n, this.port = e, this.sharedMemory = r.sharedMemory;
9
+ let i = r.tasks ?? {};
10
+ for (let [e, n] of Object.entries(i)) {
11
+ if (e in this) {
12
+ t.warn(`task "${e}" collides with a SharedWorkerClient member — not installed`);
13
+ continue;
14
+ }
15
+ this[e] = (...e) => this.runTask(n, ...e);
16
+ }
17
+ Object.keys(i).length > 0 && t.info(`task methods: ${Object.keys(i).join(", ")}`);
18
+ }
19
+ runTask(e, ...t) {
20
+ return new Promise((r, i) => {
21
+ let a = Math.random().toString(36).substring(2), o = performance.now();
22
+ n.debug(`→ ${e.taskId}`, { args: t });
23
+ let s = (t) => {
24
+ let c = t.data;
25
+ if (c.messageId === a) {
26
+ this.port.removeEventListener("message", s);
27
+ let t = (performance.now() - o).toFixed(1);
28
+ c.success ? (n.debug(`← ${e.taskId} ok`, { ms: +t }), r(c.result)) : (n.warn(`← ${e.taskId} failed: ${c.error}`, { ms: +t }), i(Error(c.error)));
29
+ }
30
+ };
31
+ this.port.addEventListener("message", s), this.port.postMessage({
32
+ type: "EXECUTE_TASK",
33
+ messageId: a,
34
+ taskId: e.taskId,
35
+ args: t
36
+ });
37
+ });
38
+ }
39
+ disconnect() {
40
+ try {
41
+ this.port.postMessage({ type: "SHARED_DISCONNECT" });
42
+ } finally {
43
+ this.port.close();
44
+ }
45
+ }
46
+ };
47
+ async function i(e) {
48
+ if (typeof SharedArrayBuffer > "u" || typeof crossOriginIsolated < "u" && !crossOriginIsolated) throw Error("connectSharedWorker requires SharedArrayBuffer in a cross-origin isolated context — serve the page with COOP/COEP headers.");
49
+ if (!e.port && !e.workerUrl && !e.createWorker) throw Error("connectSharedWorker requires `workerUrl`, `createWorker`, or `port` in its config.");
50
+ let n = e.port ?? (e.createWorker ? e.createWorker().port : new SharedWorker(e.workerUrl, { type: "module" }).port);
51
+ return n.start(), await new Promise((i, a) => {
52
+ let o = setTimeout(() => a(/* @__PURE__ */ Error("connectSharedWorker: timed out waiting for the shared worker — is the host module calling sharedWorkerHost()?")), e.connectTimeoutMs ?? 1e4), s = (a) => {
53
+ let c = a.data;
54
+ c?.type === "SHARED_MEMORY" && (n.removeEventListener("message", s), clearTimeout(o), e.sharedMemory.bind(c.buffer), t.info(`connected as client #${c.clientIndex}`, { bytes: c.buffer.byteLength }), i(new r(n, c.clientIndex, e)));
55
+ };
56
+ n.addEventListener("message", s), n.postMessage({
57
+ type: "SHARED_CONNECT",
58
+ memoryBytes: e.sharedMemory.totalBytes,
59
+ memory: e.memory
60
+ });
61
+ });
62
+ }
63
+ //#endregion
64
+ export { i as connectSharedWorker };
65
+
66
+ //# sourceMappingURL=index12.js.map
@@ -0,0 +1,27 @@
1
+ import { scoped as e } from "./index4.js";
2
+ //#region src/worker/registry.ts
3
+ var t = e("registry"), n = class {
4
+ static tasks = /* @__PURE__ */ new Map();
5
+ static register(e, n) {
6
+ this.tasks.set(e.taskId, {
7
+ contract: e,
8
+ handler: n
9
+ }), t.debug(`registered "${e.taskId}"`, {
10
+ args: e.argsSchema ? "validated" : "unvalidated",
11
+ result: e.resultSchema ? "validated" : "unvalidated"
12
+ });
13
+ }
14
+ static getContract(e) {
15
+ return this.tasks.get(e)?.contract;
16
+ }
17
+ static async execute(e, ...t) {
18
+ let n = this.tasks.get(e);
19
+ if (!n) throw Error(`Task handler not found for id: ${e}`);
20
+ let { contract: r, handler: i } = n, a = await i(...r.argsSchema ? r.argsSchema.parse(t) : t);
21
+ return r.resultSchema ? r.resultSchema.parse(a) : a;
22
+ }
23
+ };
24
+ //#endregion
25
+ export { n as TaskRegistry };
26
+
27
+ //# sourceMappingURL=index13.js.map
@@ -0,0 +1,63 @@
1
+ import { fmtBytes as e, scoped as t } from "./index4.js";
2
+ import { bindSharedMemories as n } from "./index5.js";
3
+ import { MemoryManager as r } from "./index8.js";
4
+ import { TaskRegistry as i } from "./index13.js";
5
+ //#region src/shared/sharedWorkerHost.ts
6
+ var a = t("shared-worker"), o = null, s = 0;
7
+ function c(t, i) {
8
+ if (o) return o.buffer.byteLength < t && a.warn(`client requested ${e(t)} but buffer is ${e(o.buffer.byteLength)} — first client's capacity wins`), o;
9
+ let s = new r(i);
10
+ return s.ensureCapacity(t), o = s.memory, n(s.getBuffer()), a.info(`shared buffer allocated (${e(o.buffer.byteLength)})`), o;
11
+ }
12
+ function l(e) {
13
+ let t = ++s;
14
+ a.info(`client #${t} connected`), e.onmessage = async (n) => {
15
+ let r = n.data;
16
+ if (r?.type === "SHARED_CONNECT") {
17
+ let n = c(r.memoryBytes ?? 0, r.memory);
18
+ e.postMessage({
19
+ type: "SHARED_MEMORY",
20
+ buffer: n.buffer,
21
+ clientIndex: t
22
+ });
23
+ return;
24
+ }
25
+ if (r?.type === "EXECUTE_TASK") {
26
+ let { messageId: n, taskId: s, args: c } = r;
27
+ if (!o) {
28
+ e.postMessage({
29
+ messageId: n,
30
+ success: !1,
31
+ error: "Shared memory not initialized in worker."
32
+ });
33
+ return;
34
+ }
35
+ let l = performance.now();
36
+ try {
37
+ let r = await i.execute(s, ...c || []);
38
+ a.debug(`task ${s} → ${(performance.now() - l).toFixed(1)}ms (client #${t})`), e.postMessage({
39
+ messageId: n,
40
+ success: !0,
41
+ result: r
42
+ });
43
+ } catch (t) {
44
+ a.warn(`task ${s} failed: ${t.message || String(t)}`), e.postMessage({
45
+ messageId: n,
46
+ success: !1,
47
+ error: t.message || String(t)
48
+ });
49
+ }
50
+ return;
51
+ }
52
+ r?.type === "SHARED_DISCONNECT" && (e.close(), a.info(`client #${t} disconnected`));
53
+ }, e.start();
54
+ }
55
+ function u() {
56
+ self.onconnect = (e) => {
57
+ l(e.ports[0]);
58
+ };
59
+ }
60
+ //#endregion
61
+ export { l as attachSharedPort, u as sharedWorkerHost };
62
+
63
+ //# sourceMappingURL=index14.js.map
@@ -0,0 +1,50 @@
1
+ import { scoped as e } from "./index4.js";
2
+ import { TaskRegistry as t } from "./index13.js";
3
+ //#region src/service.ts
4
+ var n = e("service");
5
+ function r(e = {}) {
6
+ return e;
7
+ }
8
+ function i(e) {
9
+ return e;
10
+ }
11
+ function a(e, t) {
12
+ let n = {};
13
+ for (let [r, i] of Object.entries(t)) {
14
+ let t = "def" in i ? i.def : i;
15
+ n[r] = {
16
+ taskId: t.taskId ?? `${e}.${r}`,
17
+ ...t.dataType ? { dataType: t.dataType } : {},
18
+ ...t.argsSchema ? { argsSchema: t.argsSchema } : {},
19
+ ...t.resultSchema ? { resultSchema: t.resultSchema } : {}
20
+ };
21
+ }
22
+ return {
23
+ name: e,
24
+ tasks: n
25
+ };
26
+ }
27
+ function o(e, r) {
28
+ let i = Object.keys(e.tasks).filter((e) => {
29
+ let t = r[e];
30
+ return typeof t != "function" && typeof t?.run != "function";
31
+ });
32
+ if (i.length > 0) throw Error(`implementService("${e.name}"): missing handlers for ${i.join(", ")}`);
33
+ for (let [n, i] of Object.entries(e.tasks)) {
34
+ let e = r[n];
35
+ t.register(i, typeof e == "function" ? e.bind(r) : e.run.bind(e));
36
+ }
37
+ return n.info(`service "${e.name}" implemented — ${Object.keys(e.tasks).join(", ")}`), {
38
+ service: e,
39
+ handlers: r
40
+ };
41
+ }
42
+ function s(e, t) {
43
+ let n = {};
44
+ for (let [r, i] of Object.entries(e.tasks)) n[r] = (...e) => t.runTask(i, ...e);
45
+ return n;
46
+ }
47
+ //#endregion
48
+ export { s as createClient, a as defineService, o as implementService, r as rpc, i as serviceMethod };
49
+
50
+ //# sourceMappingURL=index15.js.map
@@ -0,0 +1,46 @@
1
+ import { fmtBytes as e, scoped as t } from "./index4.js";
2
+ import { bindSharedMemories as n, getDefinedSharedMemoryCount as r } from "./index5.js";
3
+ import { TaskRegistry as i } from "./index13.js";
4
+ //#region src/worker/workerBootstrap.ts
5
+ var a = t("worker"), o = !1;
6
+ typeof self > "u" ? a.warn("workerBootstrap: no worker global found — skipping message wiring") : self.onmessage = async (t) => {
7
+ let s = t.data;
8
+ if (s.type === "INIT") {
9
+ o = !0, r() > 0 && a.warn("pool sent INIT without memory, but shared-memory contracts are defined — field access will throw until bound"), a.info("initialized (message-only pool — no shared memory)");
10
+ return;
11
+ }
12
+ if (s.type === "INIT_MEMORY") {
13
+ let t = s.memory;
14
+ n(t.buffer), o = !0, a.info(`shared memory initialized (${e(t.buffer.byteLength)})`);
15
+ return;
16
+ }
17
+ if (s.type === "EXECUTE_TASK") {
18
+ let { messageId: e, taskId: t, args: n } = s;
19
+ if (!o) {
20
+ a.warn(`rejected ${t} — shared memory not initialized`), self.postMessage({
21
+ messageId: e,
22
+ success: !1,
23
+ error: "Shared memory not initialized in worker."
24
+ });
25
+ return;
26
+ }
27
+ let r = performance.now();
28
+ try {
29
+ let o = await i.execute(t, ...n || []);
30
+ a.debug(`task ${t} → ${(performance.now() - r).toFixed(1)}ms`), self.postMessage({
31
+ messageId: e,
32
+ success: !0,
33
+ result: o
34
+ });
35
+ } catch (n) {
36
+ a.warn(`task ${t} failed: ${n.message || String(n)}`), self.postMessage({
37
+ messageId: e,
38
+ success: !1,
39
+ error: n.message || String(n)
40
+ });
41
+ }
42
+ }
43
+ };
44
+ //#endregion
45
+
46
+ //# sourceMappingURL=index16.js.map
@@ -0,0 +1,42 @@
1
+ import { scoped as e } from "./index4.js";
2
+ import { TaskRegistry as t } from "./index13.js";
3
+ import "./index16.js";
4
+ //#region src/worker/defineWorker.ts
5
+ var n = e("worker"), r = [
6
+ "start",
7
+ "terminate",
8
+ "pool",
9
+ "sharedMemory",
10
+ "then",
11
+ "with"
12
+ ], i = (e) => typeof e == "object" && !!e && "run" in e, a = new Set(r);
13
+ function o(e) {
14
+ let r = e.methods ?? {}, o = e.services ?? {}, s = 0, c = (e, n, r, o) => {
15
+ if (a.has(n)) throw Error(`defineWorker: "${n}" is reserved for the client API — rename the method`);
16
+ if (i(r)) {
17
+ let { def: n } = r;
18
+ t.register({
19
+ taskId: n.taskId ?? e,
20
+ ...n.dataType ? { dataType: n.dataType } : {},
21
+ ...n.argsSchema ? { argsSchema: n.argsSchema } : {},
22
+ ...n.resultSchema ? { resultSchema: n.resultSchema } : {}
23
+ }, r.run.bind(r));
24
+ } else t.register({ taskId: e }, r.bind(o));
25
+ s++;
26
+ };
27
+ for (let [e, t] of Object.entries(r)) c(e, e, t, r);
28
+ for (let [e, t] of Object.entries(o)) {
29
+ if (a.has(e)) throw Error(`defineWorker: service name "${e}" is reserved for the client API — rename it`);
30
+ for (let [n, r] of Object.entries(t)) c(`${e}.${n}`, n, r, t);
31
+ }
32
+ n.info(`worker defined — ${s} method(s)`);
33
+ let l = {
34
+ methods: r,
35
+ services: o
36
+ };
37
+ return e.sharedMemory && (l.sharedMemory = e.sharedMemory), l;
38
+ }
39
+ //#endregion
40
+ export { r as RESERVED_CLIENT_KEYS, o as defineWorker };
41
+
42
+ //# sourceMappingURL=index17.js.map