@hybrid-compute/core 0.0.2

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,64 @@
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/conventional-changelog": {
16
+ "header": "# Changelog",
17
+ "preset": {
18
+ "name": "conventionalcommits",
19
+ "types": [
20
+ {
21
+ "type": "chore",
22
+ "section": "Tasks"
23
+ },
24
+ {
25
+ "type": "docs",
26
+ "section": "Documentation"
27
+ },
28
+ {
29
+ "type": "feat",
30
+ "section": "Feature"
31
+ },
32
+ {
33
+ "type": "fix",
34
+ "section": "Bug"
35
+ },
36
+ {
37
+ "type": "perf",
38
+ "section": "Performance change"
39
+ },
40
+ {
41
+ "type": "refactor",
42
+ "section": "Refactoring"
43
+ },
44
+ {
45
+ "type": "release",
46
+ "section": "Create a release commit",
47
+ "hidden": true
48
+ },
49
+ {
50
+ "type": "style",
51
+ "section": "Markup, white-space, formatting, missing semi-colons...",
52
+ "hidden": true
53
+ },
54
+ {
55
+ "type": "test",
56
+ "section": "Adding missing tests",
57
+ "hidden": true
58
+ }
59
+ ]
60
+ },
61
+ "infile": "CHANGELOG.md"
62
+ }
63
+ }
64
+ }
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @hybrid-compute/core
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
+ [HybridCompute Core API Documentation](https://github.com/phun-ky/hybrid-compute/blob/main/docs/api/core/src/classes/HybridCompute.md)
25
+
26
+ ## 📦 Package Info
27
+
28
+ This package provides:
29
+
30
+ - The core orchestrator `HybridCompute` class
31
+ - Strategy-based task routing (`local`, `worker`, `remote`, or `auto`)
32
+ - Unified API for compute execution
33
+
34
+ ## Usage
35
+
36
+ ```bash
37
+ npm install @hybrid-compute/core
38
+ ```
39
+
40
+ ```ts
41
+ import { HybridCompute } from '@hybrid-compute/core';
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Contributing
47
+
48
+ Want to contribute? Please read the
49
+ [CONTRIBUTING.md](https://github.com/phun-ky/hybrid-compute/blob/main/CONTRIBUTING.md)
50
+ and
51
+ [CODE_OF_CONDUCT.md](https://github.com/phun-ky/hybrid-compute/blob/main/CODE_OF_CONDUCT.md)
52
+
53
+ ## License
54
+
55
+ This project is licensed under the MIT License - see the
56
+ [LICENSE](https://github.com/phun-ky/hybrid-compute/blob/main/LICENSE) file for
57
+ details.
58
+
59
+ ## Sponsor me
60
+
61
+ I'm an Open Source evangelist, creating stuff that does not exist yet to help
62
+ get rid of secondary activities and to enhance systems already in place, be it
63
+ documentation, tools or web sites.
64
+
65
+ The sponsorship is an unique opportunity to alleviate more hours for me to
66
+ maintain my projects, create new ones and contribute to the large community
67
+ we're all part of :)
68
+
69
+ [Support me on GitHub Sponsors](https://github.com/sponsors/phun-ky).
70
+
71
+ p.s. **Ukraine is still under brutal Russian invasion. A lot of Ukrainian people
72
+ are hurt, without shelter and need help**. You can help in various ways, for
73
+ instance, directly helping refugees, spreading awareness, putting pressure on
74
+ your local government or companies. You can also support Ukraine by donating
75
+ e.g. to [Red Cross](https://www.icrc.org/en/donate/ukraine),
76
+ [Ukraine humanitarian organisation](https://savelife.in.ua/en/donate-en/#donate-army-card-weekly)
77
+ or
78
+ [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,51 @@
1
+ {
2
+ "name": "@hybrid-compute/core",
3
+ "version": "0.0.2",
4
+ "description": "Core orchestrator for dispatching compute tasks to local, threaded, or remote backends.",
5
+ "keywords": [
6
+ "compute",
7
+ "task-dispatch",
8
+ "core",
9
+ "strategy",
10
+ "hybrid",
11
+ "orchestrator",
12
+ "execution",
13
+ "modular",
14
+ "framework"
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
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
32
+ "release": "release-it",
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
+ "devDependencies": {
38
+ "@types/node": "^22.15.24",
39
+ "eslint": "^9.27.0",
40
+ "eslint-config-phun-ky": "^1.0.3",
41
+ "prettier": "^3.5.3",
42
+ "typescript": "^5.8.3"
43
+ },
44
+ "engines": {
45
+ "node": ">=22.0.0",
46
+ "npm": ">=10.8.2"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }
@@ -0,0 +1,139 @@
1
+ import test, { describe } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import {
4
+ ComputeBackendInterface,
5
+ createHybridCompute,
6
+ HybridCompute
7
+ } from '..';
8
+
9
+ // Mocks
10
+ const mockTaskName = 'double';
11
+ const mockInput = 21;
12
+ const mockOutput = 42;
13
+
14
+ function createMockBackend(): ComputeBackendInterface {
15
+ return {
16
+ canRun: (taskName) => taskName === 'double',
17
+ runTask: async <Input, Output>(
18
+ taskName: string,
19
+ input: Input
20
+ ): Promise<Output> => {
21
+ // mock response — force-cast for test purposes
22
+ return 42 as unknown as Output;
23
+ }
24
+ };
25
+ }
26
+
27
+ function createNonRunnableMockBackend(): ComputeBackendInterface {
28
+ return {
29
+ canRun: () => false,
30
+ runTask: async <Input, Output>() => {
31
+ throw new Error('Should not be called');
32
+ }
33
+ };
34
+ }
35
+
36
+ describe('HybridCompute', () => {
37
+ test('runs task using local strategy', async () => {
38
+ const compute = new HybridCompute({ local: createMockBackend() });
39
+
40
+ const result = await compute.runTask(mockTaskName, mockInput, 'local');
41
+ assert.strictEqual(result, mockOutput);
42
+ });
43
+
44
+ test('runs task using worker strategy', async () => {
45
+ const compute = new HybridCompute({ worker: createMockBackend() });
46
+
47
+ const result = await compute.runTask(mockTaskName, mockInput, 'worker');
48
+ assert.strictEqual(result, mockOutput);
49
+ });
50
+
51
+ test('runs task using remote strategy', async () => {
52
+ const compute = new HybridCompute({ remote: createMockBackend() });
53
+
54
+ const result = await compute.runTask(mockTaskName, mockInput, 'remote');
55
+ assert.strictEqual(result, mockOutput);
56
+ });
57
+ test('uses auto strategy with priority: worker > local > remote', async () => {
58
+ const callOrder: string[] = [];
59
+
60
+ const worker: ComputeBackendInterface = {
61
+ canRun: () => (callOrder.push('worker'), true),
62
+ runTask: async <Input, Output>(
63
+ taskName: string,
64
+ input: Input
65
+ ): Promise<Output> => {
66
+ callOrder.push('runWorker');
67
+ return mockOutput as unknown as Output;
68
+ }
69
+ };
70
+
71
+ const local: ComputeBackendInterface = {
72
+ canRun: () => (callOrder.push('local'), true),
73
+ runTask: async <Input, Output>(
74
+ taskName: string,
75
+ input: Input
76
+ ): Promise<Output> => {
77
+ callOrder.push('runLocal');
78
+ return mockOutput as unknown as Output;
79
+ }
80
+ };
81
+
82
+ const remote: ComputeBackendInterface = {
83
+ canRun: () => (callOrder.push('remote'), true),
84
+ runTask: async <Input, Output>(
85
+ taskName: string,
86
+ input: Input
87
+ ): Promise<Output> => {
88
+ callOrder.push('runRemote');
89
+ return mockOutput as unknown as Output;
90
+ }
91
+ };
92
+
93
+ const compute = new HybridCompute({ worker, local, remote });
94
+ const result = await compute.runTask(mockTaskName, mockInput, 'auto');
95
+
96
+ assert.strictEqual(result, mockOutput);
97
+ assert.deepStrictEqual(callOrder, ['worker', 'runWorker']);
98
+ });
99
+
100
+ test('falls back in auto strategy if earlier backends cannot run', async () => {
101
+ const compute = new HybridCompute({
102
+ worker: createMockBackend(),
103
+ local: createMockBackend(),
104
+ remote: createMockBackend()
105
+ });
106
+
107
+ const result = await compute.runTask(mockTaskName, mockInput, 'auto');
108
+ assert.strictEqual(result, mockOutput);
109
+ });
110
+
111
+ test('throws if no backend matches the strategy', async () => {
112
+ const compute = new HybridCompute({});
113
+
114
+ await assert.rejects(
115
+ () => compute.runTask(mockTaskName, mockInput, 'local'),
116
+ /No backend available/
117
+ );
118
+ });
119
+
120
+ test('throws if no backend can run in auto strategy', async () => {
121
+ const compute = new HybridCompute({
122
+ worker: createNonRunnableMockBackend(),
123
+ local: createNonRunnableMockBackend(),
124
+ remote: createNonRunnableMockBackend()
125
+ });
126
+
127
+ await assert.rejects(
128
+ () => compute.runTask(mockTaskName, mockInput, 'auto'),
129
+ /No backend available/
130
+ );
131
+ });
132
+ });
133
+
134
+ describe('createHybridCompute', () => {
135
+ test('returns HybridCompute instance', () => {
136
+ const instance = createHybridCompute({ local: createMockBackend() });
137
+ assert.ok(instance instanceof HybridCompute);
138
+ });
139
+ });
package/src/index.ts ADDED
@@ -0,0 +1,103 @@
1
+ export * from './types.js';
2
+
3
+ import {
4
+ ExecutionStrategyType,
5
+ HybridComputeOptionsInterface
6
+ } from './types.js';
7
+
8
+ /**
9
+ * The HybridCompute class acts as an orchestrator to delegate compute tasks
10
+ * across different backends (local, worker, or remote) using a flexible strategy.
11
+ *
12
+ * It supports four strategies:
13
+ * - `'local'`: Forces execution on the local (main thread) backend.
14
+ * - `'worker'`: Forces execution on a threaded/WebWorker backend.
15
+ * - `'remote'`: Forces execution on a remote/server backend.
16
+ * - `'auto'`: Automatically selects the first available backend that supports the task.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const compute = new HybridCompute({
21
+ * local: createLocalCompute(),
22
+ * worker: createThreadedCompute(workerPath, ['double']),
23
+ * remote: createRemoteCompute({ transport: 'fetch', endpoint: '/api/compute' })
24
+ * });
25
+ *
26
+ * const result = await compute.runTask<number, number>('double', 21, 'auto');
27
+ * console.log(result); // 42
28
+ * ```
29
+ */
30
+ export class HybridCompute {
31
+ private backends: HybridComputeOptionsInterface;
32
+
33
+ /**
34
+ * Creates a new HybridCompute orchestrator.
35
+ *
36
+ * @param backends - An object containing optional local, worker, and remote backends.
37
+ */
38
+ constructor(backends: HybridComputeOptionsInterface) {
39
+ this.backends = backends;
40
+ }
41
+
42
+ /**
43
+ * Runs a task using the specified execution strategy.
44
+ *
45
+ * If `strategy` is `'auto'`, it will try the worker, then local, then remote backend in order of priority.
46
+ *
47
+ * @typeParam Input - The type of the input expected by the task.
48
+ * @typeParam Output - The expected output type returned by the task.
49
+ *
50
+ * @param taskName - The name of the task to execute.
51
+ * @param input - The input payload for the task.
52
+ * @param strategy - The execution strategy to use. Defaults to `'auto'`.
53
+ *
54
+ * @returns A Promise resolving to the task's output.
55
+ *
56
+ * @throws If no backend is available or able to run the task.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const output = await compute.runTask('greetUser', { name: 'Alice' }, 'worker');
61
+ * ```
62
+ */
63
+ async runTask<Input, Output>(
64
+ taskName: string,
65
+ input: Input,
66
+ strategy: ExecutionStrategyType = 'auto'
67
+ ): Promise<Output> {
68
+ if (strategy === 'local' && this.backends.local) {
69
+ return this.backends.local.runTask(taskName, input);
70
+ } else if (strategy === 'worker' && this.backends.worker) {
71
+ return this.backends.worker.runTask(taskName, input);
72
+ } else if (strategy === 'remote' && this.backends.remote) {
73
+ return this.backends.remote.runTask(taskName, input);
74
+ } else if (strategy === 'auto') {
75
+ if (this.backends.worker?.canRun(taskName)) {
76
+ return this.backends.worker.runTask(taskName, input);
77
+ } else if (this.backends.local?.canRun(taskName)) {
78
+ return this.backends.local.runTask(taskName, input);
79
+ } else if (this.backends.remote?.canRun(taskName)) {
80
+ return this.backends.remote.runTask(taskName, input);
81
+ }
82
+ }
83
+
84
+ throw new Error(
85
+ `No backend available for task '${taskName}' using strategy '${strategy}'`
86
+ );
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Factory function for creating a HybridCompute instance.
92
+ *
93
+ * @param backends - Configuration options specifying available backends.
94
+ * @returns A new HybridCompute orchestrator.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const hybrid = createHybridCompute({ local: createLocalCompute() });
99
+ * ```
100
+ */
101
+ export function createHybridCompute(backends: HybridComputeOptionsInterface) {
102
+ return new HybridCompute(backends);
103
+ }
package/src/types.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The strategy used to determine which compute backend to use.
3
+ *
4
+ * - `auto`: Automatically select the best backend available.
5
+ * - `local`: Run tasks directly on the main thread.
6
+ * - `worker`: Offload tasks to WebWorker or thread-based compute.
7
+ * - `remote`: Offload tasks to a remote server or service.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const strategy: ExecutionStrategyType = 'worker';
12
+ * ```
13
+ */
14
+ export type ExecutionStrategyType = 'auto' | 'local' | 'worker' | 'remote';
15
+
16
+ /**
17
+ * Describes a unit of work that can be executed by a compute backend.
18
+ *
19
+ * @template Input The input type expected by the task.
20
+ * @template Output The output type returned by the task.
21
+ *
22
+ * @property name - A unique name used to identify the task.
23
+ * @property run - A function that takes input and returns a Promise of the output.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const task: ComputeTaskInterface<number, number> = {
28
+ * name: 'double',
29
+ * run: async (x) => x * 2
30
+ * };
31
+ * ```
32
+ */
33
+ export interface ComputeTaskInterface<Input = unknown, Output = unknown> {
34
+ name: string;
35
+ run(input: Input): Promise<Output>;
36
+ }
37
+
38
+ /**
39
+ * Represents a backend capable of executing registered compute tasks.
40
+ *
41
+ * Each backend must be able to:
42
+ * - Determine if it can run a given task (`canRun`)
43
+ * - Execute a named task with input and return a Promise of output (`runTask`)
44
+ *
45
+ * @template Input The input type for task execution.
46
+ * @template Output The output type for task execution.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const backend: ComputeBackendInterface = {
51
+ * canRun: (taskName) => taskName === 'double',
52
+ * runTask: async (taskName, input) => {
53
+ * if (taskName === 'double') return (input as number) * 2;
54
+ * throw new Error('Unknown task');
55
+ * }
56
+ * };
57
+ * ```
58
+ */
59
+ export interface ComputeBackendInterface {
60
+ canRun(taskName: string): boolean;
61
+ runTask<Input, Output>(taskName: string, input: Input): Promise<Output>;
62
+ }
63
+
64
+ /**
65
+ * Configuration options for initializing the HybridCompute orchestrator.
66
+ *
67
+ * Backends are optional, and can be combined or omitted based on the environment or needs.
68
+ *
69
+ * @property local - A local synchronous compute backend (main thread).
70
+ * @property worker - A background-thread compute backend (e.g. WebWorker).
71
+ * @property remote - A server-side or cloud compute backend.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const options: HybridComputeOptionsInterface = {
76
+ * local: createLocalCompute(),
77
+ * remote: createRemoteCompute({ ... })
78
+ * };
79
+ * ```
80
+ *
81
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
82
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
83
+ */
84
+ export interface HybridComputeOptionsInterface {
85
+ local?: ComputeBackendInterface;
86
+ worker?: ComputeBackendInterface;
87
+ remote?: ComputeBackendInterface;
88
+ }
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": []
12
+ }