@hybrid-compute/worker 0.0.1

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.
@@ -0,0 +1,70 @@
1
+ {
2
+ "git": false,
3
+ "github": {
4
+ "release": true,
5
+ "tokenRef": "GH_TOKEN"
6
+ },
7
+ "npm": {
8
+ "publish": true,
9
+ "skipChecks": true
10
+ },
11
+ "hooks": {
12
+ "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
13
+ },
14
+ "plugins": {
15
+ "@release-it/bumper": {
16
+ "out": {
17
+ "file": "package.json",
18
+ "path": ["dependencies.@hybrid-compute/core"]
19
+ }
20
+ },
21
+ "@release-it/conventional-changelog": {
22
+ "header": "# Changelog",
23
+ "preset": {
24
+ "name": "conventionalcommits",
25
+ "types": [
26
+ {
27
+ "type": "chore",
28
+ "section": "Tasks"
29
+ },
30
+ {
31
+ "type": "docs",
32
+ "section": "Documentation"
33
+ },
34
+ {
35
+ "type": "feat",
36
+ "section": "Feature"
37
+ },
38
+ {
39
+ "type": "fix",
40
+ "section": "Bug"
41
+ },
42
+ {
43
+ "type": "perf",
44
+ "section": "Performance change"
45
+ },
46
+ {
47
+ "type": "refactor",
48
+ "section": "Refactoring"
49
+ },
50
+ {
51
+ "type": "release",
52
+ "section": "Create a release commit",
53
+ "hidden": true
54
+ },
55
+ {
56
+ "type": "style",
57
+ "section": "Markup, white-space, formatting, missing semi-colons...",
58
+ "hidden": true
59
+ },
60
+ {
61
+ "type": "test",
62
+ "section": "Adding missing tests",
63
+ "hidden": true
64
+ }
65
+ ]
66
+ },
67
+ "infile": "CHANGELOG.md"
68
+ }
69
+ }
70
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1 (2025-06-01)
4
+
5
+ ### Tasks
6
+
7
+ * 🤖 Add deps ([0a08767](https://github.com/phun-ky/hybrid-compute/commit/0a08767abed76c91ea902363890bb8f99e44a1da))
8
+ * 🤖 Adjustments before release ([ee23ac6](https://github.com/phun-ky/hybrid-compute/commit/ee23ac627f7d04c3c2b4fdf9b2dc1f826fa21593))
9
+ * 🤖 More adjustments ([f9dfee8](https://github.com/phun-ky/hybrid-compute/commit/f9dfee8fb9eb2e1cb104fca9ea24903a69d16f26))
10
+ * 🤖 Preparing for first release, with setup ([13f8f03](https://github.com/phun-ky/hybrid-compute/commit/13f8f03def7941c79b3695d0ba2409c6f9ba0896))
11
+ * 🤖 Remove lerna ([f4ba79e](https://github.com/phun-ky/hybrid-compute/commit/f4ba79ed10bb66271859cdfb905b8e4a8979784e))
12
+ * 🤖 Update references, keywords and description + badges ([bc0b1ac](https://github.com/phun-ky/hybrid-compute/commit/bc0b1ac537bc9610ade89fc001132776d096fd55))
13
+ * 🤖 Update workflows ([7f9d6b5](https://github.com/phun-ky/hybrid-compute/commit/7f9d6b55f7180bb0179fc9159d7453395e2ca1af))
14
+ * preparing for release ([d07d107](https://github.com/phun-ky/hybrid-compute/commit/d07d10777a200a755965cef115106430805fe70b))
15
+
16
+ ### Documentation
17
+
18
+ * ✏️ Add documentation ([81e6fa1](https://github.com/phun-ky/hybrid-compute/commit/81e6fa189a3fb974e89c03a846d030118fdedbe4))
19
+ * ✏️ Regenerate documentation ([487d54f](https://github.com/phun-ky/hybrid-compute/commit/487d54ffb40abc03315e04fd2e5c1434fc3e9b27))
20
+ * ✏️ Update documentation ([1039082](https://github.com/phun-ky/hybrid-compute/commit/10390822d4a087a7a26b7a25ce25cb9099dbce94))
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @hybrid-compute/worker
2
+
3
+ [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
4
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](http://makeapullrequest.com)
5
+ [![SemVer 2.0](https://img.shields.io/badge/SemVer-2.0-green.svg)](http://semver.org/spec/v2.0.0.html)
6
+ ![npm version](https://img.shields.io/npm/v/@hybrid-compute/core)
7
+ ![issues](https://img.shields.io/github/issues/phun-ky/hybrid-compute)
8
+ ![license](https://img.shields.io/npm/l/@hybrid-compute/core)
9
+ ![size](https://img.shields.io/bundlephobia/min/@hybrid-compute/core)
10
+ ![npm](https://img.shields.io/npm/dm/%40hybrid-compute/core)
11
+ ![GitHub Repo stars](https://img.shields.io/github/stars/phun-ky/hybrid-compute)
12
+ [![codecov](https://codecov.io/gh/phun-ky/hybrid-compute/graph/badge.svg?token=VA91DL7ZLZ)](https://codecov.io/gh/phun-ky/hybrid-compute)
13
+ [![build](https://github.com/phun-ky/hybrid-compute/actions/workflows/check.yml/badge.svg)](https://github.com/phun-ky/hybrid-compute/actions/workflows/check.yml)
14
+
15
+ Part of the [`@hybrid-compute`](https://github.com/phun-ky/hybrid-compute)
16
+ monorepo.
17
+
18
+ > See the [main README](https://github.com/phun-ky/hybrid-compute#readme) for
19
+ > full project overview, usage examples, architecture, and contribution
20
+ > guidelines.
21
+
22
+ ## API Docs
23
+
24
+ [ThreadedCompute API Documentation](https://github.com/phun-ky/hybrid-compute/blob/main/docs/api/worker/src/classes/ThreadedCompute.md)
25
+
26
+ ## 📦 Package Info
27
+
28
+ This package provides:
29
+
30
+ - A compute backend that runs tasks in a dedicated Web Worker
31
+ - Asynchronous task messaging via `postMessage`
32
+ - Useful for offloading CPU-intensive work from the main thread
33
+
34
+ ## Usage
35
+
36
+ ```bash
37
+ npm install @hybrid-compute/worker
38
+ ```
39
+
40
+ ```ts
41
+ import { createThreadedCompute } from '@hybrid-compute/worker';
42
+
43
+ const threaded = createThreadedCompute(
44
+ new URL('./worker.js', import.meta.url),
45
+ ['add']
46
+ );
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Contributing
52
+
53
+ Want to contribute? Please read the
54
+ [CONTRIBUTING.md](https://github.com/phun-ky/hybrid-compute/blob/main/CONTRIBUTING.md)
55
+ and
56
+ [CODE_OF_CONDUCT.md](https://github.com/phun-ky/hybrid-compute/blob/main/CODE_OF_CONDUCT.md)
57
+
58
+ ## License
59
+
60
+ This project is licensed under the MIT License - see the
61
+ [LICENSE](https://github.com/phun-ky/hybrid-compute/blob/main/LICENSE) file for
62
+ details.
63
+
64
+ ## Sponsor me
65
+
66
+ I'm an Open Source evangelist, creating stuff that does not exist yet to help
67
+ get rid of secondary activities and to enhance systems already in place, be it
68
+ documentation, tools or web sites.
69
+
70
+ The sponsorship is an unique opportunity to alleviate more hours for me to
71
+ maintain my projects, create new ones and contribute to the large community
72
+ we're all part of :)
73
+
74
+ [Support me on GitHub Sponsors](https://github.com/sponsors/phun-ky).
75
+
76
+ p.s. **Ukraine is still under brutal Russian invasion. A lot of Ukrainian people
77
+ are hurt, without shelter and need help**. You can help in various ways, for
78
+ instance, directly helping refugees, spreading awareness, putting pressure on
79
+ your local government or companies. You can also support Ukraine by donating
80
+ e.g. to [Red Cross](https://www.icrc.org/en/donate/ukraine),
81
+ [Ukraine humanitarian organisation](https://savelife.in.ua/en/donate-en/#donate-army-card-weekly)
82
+ or
83
+ [donate Ambulances for Ukraine](https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone).
@@ -0,0 +1,3 @@
1
+ import SharedConfig from '../../eslint.config.mjs';
2
+
3
+ export default SharedConfig;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@hybrid-compute/worker",
3
+ "version": "0.0.1",
4
+ "description": "Threaded compute backend for executing tasks in Web Workers.",
5
+ "keywords": [
6
+ "web-worker",
7
+ "worker",
8
+ "thread",
9
+ "multithreaded",
10
+ "compute",
11
+ "offload",
12
+ "async",
13
+ "frontend",
14
+ "task"
15
+ ],
16
+ "homepage": "https://phun-ky.net/projects/hybrid-compute",
17
+ "bugs": {
18
+ "url": "https://github.com/phun-ky/hybrid-compute/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/phun-ky/hybrid-compute.git"
23
+ },
24
+ "funding": "https://github.com/phun-ky/hybrid-compute?sponsor=1",
25
+ "license": "MIT",
26
+ "author": "Alexander Vassbotn Røyne-Helgesen <alexander@phun-ky.net>",
27
+ "type": "module",
28
+ "exports": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "scripts": {
31
+ "release": "release-it",
32
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
33
+ "test": "tsx --test **/__tests__/**/*.spec.ts",
34
+ "pretest:ci": "rm -rf coverage && mkdir -p coverage",
35
+ "test:ci": "glob -c \"node --import tsx --test --no-warnings --experimental-test-coverage --test-reporter=cobertura --test-reporter-destination=coverage/cobertura-coverage.xml --test-reporter=spec --test-reporter-destination=stdout\" \"**/__tests__/**/*.spec.ts\""
36
+ },
37
+ "dependencies": {
38
+ "@hybrid-compute/core": "0.0.1"
39
+ },
40
+ "devDependencies": {
41
+ "eslint": "^9.27.0",
42
+ "eslint-config-phun-ky": "^1.0.3",
43
+ "prettier": "^3.5.3",
44
+ "typescript": "^5.8.3"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.0.0",
48
+ "npm": ">=10.8.2"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }
@@ -0,0 +1,139 @@
1
+ import test, { describe } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ createThreadedCompute,
5
+ ThreadedCompute,
6
+ WorkerResultMessageInterface,
7
+ WorkerTaskMessageInterface
8
+ } from '..';
9
+
10
+ describe('ThreadedCompute', () => {
11
+ test('canRun returns true for registered task', () => {
12
+ const OriginalWorker = globalThis.Worker;
13
+
14
+ class MockWorker {
15
+ public onmessage: ((event: any) => void) | null = null;
16
+ constructor(
17
+ public path: string,
18
+ public options: object
19
+ ) {}
20
+ postMessage() {}
21
+ }
22
+
23
+ // @ts-expect-error allow override
24
+ globalThis.Worker = MockWorker;
25
+
26
+ try {
27
+ const threaded = new ThreadedCompute('mock-worker.js', ['foo', 'bar']);
28
+ assert.equal(threaded.canRun('foo'), true);
29
+ assert.equal(threaded.canRun('baz'), false);
30
+ } finally {
31
+ globalThis.Worker = OriginalWorker;
32
+ }
33
+ });
34
+
35
+ test('runTask resolves with worker result', async () => {
36
+ const OriginalWorker = globalThis.Worker;
37
+
38
+ class MockWorker {
39
+ public onmessage:
40
+ | ((event: MessageEvent<WorkerResultMessageInterface>) => void)
41
+ | null = null;
42
+ public received: WorkerTaskMessageInterface | null = null;
43
+
44
+ constructor(
45
+ public path: string,
46
+ public options: object
47
+ ) {}
48
+
49
+ postMessage(msg: WorkerTaskMessageInterface) {
50
+ this.received = msg;
51
+
52
+ setTimeout(() => {
53
+ this.onmessage?.({
54
+ data: {
55
+ id: msg.id,
56
+ result: `result-${msg.input}`
57
+ }
58
+ } as MessageEvent<WorkerResultMessageInterface>);
59
+ }, 10);
60
+ }
61
+ }
62
+
63
+ // @ts-expect-error override
64
+ globalThis.Worker = MockWorker;
65
+
66
+ try {
67
+ const compute = new ThreadedCompute('mock-worker.js', ['echo']);
68
+ const result = await compute.runTask<string, string>('echo', 'ping');
69
+ assert.equal(result, 'result-ping');
70
+ } finally {
71
+ globalThis.Worker = OriginalWorker;
72
+ }
73
+ });
74
+
75
+ test('runTask rejects with worker error', async () => {
76
+ const OriginalWorker = globalThis.Worker;
77
+
78
+ class MockWorker {
79
+ public onmessage:
80
+ | ((event: MessageEvent<WorkerResultMessageInterface>) => void)
81
+ | null = null;
82
+
83
+ constructor(
84
+ public path: string,
85
+ public options: object
86
+ ) {}
87
+
88
+ postMessage(msg: WorkerTaskMessageInterface) {
89
+ setTimeout(() => {
90
+ this.onmessage?.({
91
+ data: {
92
+ id: msg.id,
93
+ error: 'Something went wrong'
94
+ }
95
+ } as MessageEvent<WorkerResultMessageInterface>);
96
+ }, 10);
97
+ }
98
+ }
99
+
100
+ // @ts-expect-error override
101
+ globalThis.Worker = MockWorker;
102
+
103
+ try {
104
+ const compute = new ThreadedCompute('mock-worker.js', ['fail']);
105
+ await assert.rejects(
106
+ () => compute.runTask('fail', 42),
107
+ /Something went wrong/
108
+ );
109
+ } finally {
110
+ globalThis.Worker = OriginalWorker;
111
+ }
112
+ });
113
+ });
114
+ describe('createThreadedCompute', () => {
115
+ test('returns instance of ThreadedCompute', () => {
116
+ const OriginalWorker = globalThis.Worker;
117
+
118
+ class MockWorker {
119
+ public onmessage:
120
+ | ((event: MessageEvent<WorkerResultMessageInterface>) => void)
121
+ | null = null;
122
+ constructor(
123
+ public path: string,
124
+ public options: object
125
+ ) {}
126
+ postMessage() {}
127
+ }
128
+
129
+ // @ts-expect-error override
130
+ globalThis.Worker = MockWorker;
131
+
132
+ try {
133
+ const instance = createThreadedCompute('mock-worker.js', ['foo']);
134
+ assert.ok(instance instanceof ThreadedCompute);
135
+ } finally {
136
+ globalThis.Worker = OriginalWorker;
137
+ }
138
+ });
139
+ });
package/src/index.ts ADDED
@@ -0,0 +1,118 @@
1
+ import { ComputeBackendInterface } from '@hybrid-compute/core';
2
+
3
+ import {
4
+ WorkerResultMessageInterface,
5
+ WorkerTaskMessageInterface
6
+ } from './types.js';
7
+
8
+ export * from './types.js';
9
+
10
+ /**
11
+ * `ThreadedCompute` is a compute backend that runs tasks in a dedicated Web Worker.
12
+ *
13
+ * It manages communication with the worker via `postMessage`, assigns unique task
14
+ * request IDs, and resolves results asynchronously. Tasks are registered by name
15
+ * during construction and mapped internally for availability checks.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const threaded = new ThreadedCompute(new URL('./worker.js', import.meta.url), ['add']);
20
+ *
21
+ * const result = await threaded.runTask<number, number>('add', 5);
22
+ * console.log(result); // Output from worker
23
+ * ```
24
+ *
25
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Worker
26
+ */
27
+ export class ThreadedCompute implements ComputeBackendInterface {
28
+ private worker: Worker;
29
+ private taskMap = new Set<string>();
30
+
31
+ // Map of task response IDs to success callbacks
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ private callbacks = new Map<number, (value: any) => void>();
34
+
35
+ // Map of task response IDs to error callbacks
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ private errors = new Map<number, (err: any) => void>();
38
+
39
+ private nextId = 1;
40
+
41
+ /**
42
+ * Constructs a new `ThreadedCompute` backend with a specified worker and list of available task names.
43
+ *
44
+ * @param workerPath - Path to the worker script (typically a URL via `import.meta.url`).
45
+ * @param taskNames - A list of task identifiers this worker is capable of executing.
46
+ */
47
+ constructor(workerPath: string, taskNames: string[]) {
48
+ this.worker = new Worker(workerPath, { type: 'module' });
49
+ this.taskMap = new Set(taskNames);
50
+
51
+ this.worker.onmessage = (e: MessageEvent<WorkerResultMessageInterface>) => {
52
+ const { id, result, error } = e.data;
53
+
54
+ if (error) this.errors.get(id)?.(error);
55
+ else this.callbacks.get(id)?.(result);
56
+
57
+ this.callbacks.delete(id);
58
+ this.errors.delete(id);
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Checks whether the current worker is registered to handle a given task.
64
+ *
65
+ * @param taskName - The name of the task to check.
66
+ * @returns `true` if the task is supported, otherwise `false`.
67
+ */
68
+ canRun(taskName: string): boolean {
69
+ return this.taskMap.has(taskName);
70
+ }
71
+
72
+ /**
73
+ * Runs a task inside the Web Worker.
74
+ *
75
+ * The task is assigned a unique ID and sent via `postMessage`. The result or error
76
+ * is resolved asynchronously when the worker responds.
77
+ *
78
+ * @typeParam Input - The input type for the task.
79
+ * @typeParam Output - The output type returned by the task.
80
+ *
81
+ * @param taskName - The name of the task to execute.
82
+ * @param input - The input payload for the task.
83
+ * @returns A Promise resolving to the task output.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const result = await threaded.runTask('square', 9); // 81
88
+ * ```
89
+ */
90
+ runTask<Input, Output>(taskName: string, input: Input): Promise<Output> {
91
+ return new Promise<Output>((resolve, reject) => {
92
+ const id = this.nextId++;
93
+
94
+ this.callbacks.set(id, resolve);
95
+ this.errors.set(id, reject);
96
+
97
+ const message: WorkerTaskMessageInterface = { task: taskName, input, id };
98
+
99
+ this.worker.postMessage(message);
100
+ });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Factory function to create a new `ThreadedCompute` backend.
106
+ *
107
+ * @param workerPath - The module worker file path (e.g. `new URL('./worker.js', import.meta.url)`).
108
+ * @param tasks - A list of supported task names this worker can execute.
109
+ * @returns A new `ThreadedCompute` instance.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const backend = createThreadedCompute(new URL('./worker.js', import.meta.url), ['resizeImage']);
114
+ * ```
115
+ */
116
+ export function createThreadedCompute(workerPath: string, tasks: string[]) {
117
+ return new ThreadedCompute(workerPath, tasks);
118
+ }
package/src/types.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The message format sent from the main thread to a Web Worker when executing a task.
3
+ *
4
+ * This message includes a task name, its input payload, and a unique ID used to match
5
+ * the response from the worker.
6
+ *
7
+ * @property task - The name of the task to be executed in the worker.
8
+ * @property input - The input data for the task. This can be any JSON-serializable value.
9
+ * @property id - A unique identifier for correlating the response message.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const message: WorkerTaskMessageInterface = {
14
+ * task: 'double',
15
+ * input: 21,
16
+ * id: 1
17
+ * };
18
+ * worker.postMessage(message);
19
+ * ```
20
+ */
21
+ export interface WorkerTaskMessageInterface {
22
+ task: string;
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ input: any;
25
+ id: number;
26
+ }
27
+
28
+ /**
29
+ * The message format sent from a Web Worker back to the main thread upon task completion.
30
+ *
31
+ * This message includes the result of the task execution or an error message if the task failed.
32
+ *
33
+ * @property id - The unique identifier that matches a previously sent task message.
34
+ * @property result - The result of the task execution (if successful).
35
+ * @property error - An optional error message if the task failed.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const response: WorkerResultMessageInterface = {
40
+ * id: 1,
41
+ * result: 42
42
+ * };
43
+ * postMessage(response);
44
+ * ```
45
+ *
46
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
47
+ */
48
+ export interface WorkerResultMessageInterface {
49
+ id: number;
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ result: any;
52
+ error?: string;
53
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "composite": true,
7
+ "declaration": true,
8
+ "declarationMap": true
9
+ },
10
+ "include": ["src"],
11
+ "references": [{ "path": "../core" }]
12
+ }