Skip to content

Commit c6da20f

Browse files
committed
Initial version of @reactodia/worker-proxy library
0 parents  commit c6da20f

14 files changed

Lines changed: 3959 additions & 0 deletions

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# http://editorconfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
charset = utf-8
8+
insert_final_newline = true
9+
10+
[*.{js,jsx,ts,tsx}]
11+
indent_style = space
12+
indent_size = 4
13+
14+
[*.json]
15+
indent_style = space
16+
indent_size = 4

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/node_modules
2+
/dist
3+
/*.sh
4+
/*.bat
5+
/*.tgz
6+
.vscode/
7+
8+
*.log

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
All notable changes to the project will be documented in this document.
3+
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
5+
6+
## [0.1.0]
7+
- Initial release with:
8+
* Ref-counted worker proxy: `refCountedWorker()` function and `RefCountedWorker` interface.
9+
* Worker proxy protocol: `connectWorker()` function and `WorkerConstructor`, `WorkerObject`, `WorkerCall`, `WorkerCallSuccess`, `WorkerCallError` types.
10+
11+
[0.1.0]: https://github.com/reactodia/worker-proxy/commits/v0.1.0

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Alexey Morozov
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Reactodia Worker Proxy [![npm version](https://badge.fury.io/js/@reactodia%2Fworker-proxy.svg)](https://badge.fury.io/js/@reactodia%2Fworker-proxy)
2+
3+
`@reactodia/worker-proxy` is a TypeScript/JavaScript library for the browser that makes it easy to run background tasks with dedicated [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker) with automatic connection lifecycle and transparently mapped worker logic via [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects.
4+
5+
## Installation
6+
7+
Install with:
8+
```sh
9+
npm install --save @reactodia/worker-proxy
10+
```
11+
12+
## Quick example
13+
14+
`calc.worker.ts` module:
15+
```ts
16+
import { connectWorker } from '@reactodia/worker-proxy/protocol';
17+
18+
// Define a class with methods which return Promise
19+
// with results to the worker caller side:
20+
class Calculator {
21+
constructor(options: {
22+
precision: number;
23+
}) { ... }
24+
25+
add(a: number, b: number): Promise<number> {
26+
...
27+
}
28+
}
29+
30+
// Setup communication protocol with the worker caller
31+
connectWorker(Calculator);
32+
```
33+
34+
`using-calc.ts` module:
35+
```ts
36+
import { refCountedWorker } from '@reactodia/worker-proxy';
37+
import type { Calculator } from './calc.worker.ts';
38+
39+
// Create a ref-counted worker definition
40+
const calcWorker = refCountedWorker<typeof Calculator>(
41+
() => new Worker(
42+
new URL('./calc.worker.js', import.meta.url)
43+
),
44+
[{ calcPrecision: 2 }]
45+
);
46+
47+
// Get a proxy and connect to the worker
48+
const calculator = calcWorker.acquire();
49+
// Call a worker method
50+
const sum1 = await calculator.add(2, 3);
51+
const sum2 = await calculator.add(10, 20);
52+
// Release the worker when its no longer needed
53+
calcWorker.release();
54+
```
55+
56+
### Using Web Workers from a React component
57+
58+
Although the library does not expose a built-in hook to use workers from a React component to avoid dependencies, it should be trivial to define a simple hook like this:
59+
60+
```ts
61+
import type { RefCountedWorker } from '@reactodia/worker-proxy';
62+
63+
function useWorker<T>(worker: RefCountedWorker<T>): T {
64+
React.useEffect(() => {
65+
worker.acquire();
66+
return () => worker.release();
67+
}, [worker]);
68+
return worker.getProxy();
69+
}
70+
```
71+
72+
Then it can be used in a component this way:
73+
74+
```ts
75+
import { refCountedWorker } from '@reactodia/worker-proxy';
76+
import type { Calculator } from './calc.worker.ts';
77+
78+
const CalcWorker = refCountedWorker<typeof Calculator>(
79+
() => new Worker(
80+
new URL('./calc.worker.js', import.meta.url)
81+
),
82+
[{ calcPrecision: 2 }]
83+
);
84+
85+
function MyComponent() {
86+
const calculator = useWorker(CalcWorker);
87+
...
88+
}
89+
```
90+
91+
## API
92+
93+
The library has the following exports:
94+
95+
### Main entry point `@reactodia/worker-proxy`
96+
97+
#### `refCountedWorker<T>(factory, constructorArgs)` function
98+
99+
Creates a ref-counted [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) definition which automatically manages the lifecycle of a worker (create, connect, disconnect) and exposes it behind a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object implementing the same interface as the mapped worker.
100+
101+
Parameters:
102+
* `factory`: `() => Worker` - callback to construct a Worker instance on demand
103+
* `constructorArgs`: `ConstructorParameters<T>` - constructor arguments to initialize the worker with its connected class constructor before any other calls
104+
105+
Returns: `RefCountedWorker<WorkerObject<InstanceType<T>>>`
106+
107+
#### `RefCountedWorker<T>` interface
108+
109+
```ts
110+
export interface RefCountedWorker<T> {
111+
/**
112+
* Returns an lifecycle state of the connected Worker instance.
113+
*/
114+
getState(): 'disconnected' | 'blocked' | 'ready' | 'connecting' | 'connected';
115+
/**
116+
* Returns a cached Proxy instance to transparently call the worker logic.
117+
*
118+
* Important: calls to the proxy will be resolved only after `acquire()`
119+
* method have been called at least once.
120+
*/
121+
getProxy(): T;
122+
/**
123+
* Increments a ref-count and connects to the worker if needed.
124+
*/
125+
acquire(): T;
126+
/**
127+
* Decrements a ref-count and disconnects from the worker if ref-count is zero.
128+
*/
129+
release(): void;
130+
}
131+
```
132+
133+
### Protocol entry point `@reactodia/worker-proxy/protocol`
134+
135+
#### `connectWorker(factory: WorkerConstructor)` function
136+
137+
Establishes a specific connection protocol between the callee (worker) and external caller (which created a worker via `new Worker(...)` constructor).
138+
139+
The protocol assumes the worker exposes an RPC-like interface via a `class` where every public method returns a `Promise`. This interface is transparently mapped from the caller to the worker via messages.
140+
141+
The communication protocol is defined in terms of messages to be sent with `postMessage()` and received with `onmessage` from withing the worker.
142+
The worker expect the first message to be a `WorkerCall` with `method === "constructor"` to create an instance of the connected class.
143+
144+
Here are the exported definitions for the message types:
145+
146+
```ts
147+
export interface WorkerCall {
148+
readonly type: 'call';
149+
readonly id: number;
150+
readonly method: string;
151+
readonly args: readonly unknown[];
152+
}
153+
154+
export interface WorkerCallSuccess {
155+
readonly type: 'success';
156+
readonly id: number;
157+
readonly result: unknown;
158+
}
159+
160+
export interface WorkerCallError {
161+
readonly type: 'error';
162+
readonly id: number;
163+
readonly error: unknown;
164+
}
165+
```
166+
167+
## License
168+
169+
The library is distributed under MIT license, see [LICENSE](./LICENSE).

0 commit comments

Comments
 (0)