uniworker 1.0.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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/src/index.ts +203 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Whimsical, Inc.
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# UniWorker
|
|
2
|
+
|
|
3
|
+
A lightweight, type-safe library for communicating with [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) via proxied function calls.
|
|
4
|
+
|
|
5
|
+
UniWorker lets you call functions inside a Web Worker as if they were local, abstracting away `postMessage` / `addEventListener` boilerplate entirely. It's inspired by [Comlink](https://github.com/GoogleChromeLabs/comlink), while being simpler, more limited in scope, and significantly faster.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Fully typed** — proxy methods preserve the original function signatures
|
|
10
|
+
- **Three calling modes** — fire-and-forget, awaited, and proxy-creating
|
|
11
|
+
- **Transferable support** — transfer `ArrayBuffer`, `OffscreenCanvas`, `ImageBitmap`, etc.
|
|
12
|
+
- **Mock proxy** — run the same code synchronously on the main thread for testing or fallback
|
|
13
|
+
- **Zero dependencies**
|
|
14
|
+
- **~1 KB** minified + gzipped
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install uniworker
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Define worker code (`my-worker.ts`)
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { expose } from "uniworker";
|
|
28
|
+
|
|
29
|
+
expose((name: string) => {
|
|
30
|
+
// The initializer function runs once when init() is called.
|
|
31
|
+
// It returns an object whose methods become available on the proxy.
|
|
32
|
+
return {
|
|
33
|
+
greet(greeting: string) {
|
|
34
|
+
return `${greeting}, ${name}!`;
|
|
35
|
+
},
|
|
36
|
+
add(a: number, b: number) {
|
|
37
|
+
return a + b;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Use from the main thread (`main.ts`)
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { init, WorkerProxy } from "uniworker";
|
|
47
|
+
|
|
48
|
+
// Type describing the initializer function (matches the expose() argument)
|
|
49
|
+
type MyWorkerInit = (name: string) => {
|
|
50
|
+
greet(greeting: string): string;
|
|
51
|
+
add(a: number, b: number): number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const worker = new Worker(new URL("./my-worker.ts", import.meta.url), {
|
|
55
|
+
type: "module",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const proxy = await init<MyWorkerInit>({ worker }, "World");
|
|
59
|
+
|
|
60
|
+
// Fire-and-forget (returns void, does not wait)
|
|
61
|
+
proxy.greet("Hello");
|
|
62
|
+
|
|
63
|
+
// Await the return value
|
|
64
|
+
const message = await proxy.await.greet("Hello");
|
|
65
|
+
console.log(message); // "Hello, World!"
|
|
66
|
+
|
|
67
|
+
const sum = await proxy.await.add(2, 3);
|
|
68
|
+
console.log(sum); // 5
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
### `expose(initializer)`
|
|
74
|
+
|
|
75
|
+
Call inside a Web Worker script. `initializer` is a function that receives arguments passed from `init()` and returns an object whose methods are exposed to the main thread.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
expose((canvas: OffscreenCanvas) => {
|
|
79
|
+
const ctx = canvas.getContext("2d")!;
|
|
80
|
+
return {
|
|
81
|
+
drawRect(x: number, y: number, w: number, h: number) {
|
|
82
|
+
ctx.fillRect(x, y, w, h);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `init<T>(options, ...args): Promise<WorkerProxy<ReturnType<T>>>`
|
|
89
|
+
|
|
90
|
+
Call on the main thread to initialize the worker and get a typed proxy.
|
|
91
|
+
|
|
92
|
+
**Options:**
|
|
93
|
+
| Option | Type | Description |
|
|
94
|
+
|----------|------|-------------|
|
|
95
|
+
| `worker` | `Worker` | The Web Worker instance |
|
|
96
|
+
| `transfer` | `Transfer` | Optional array of transferable objects to send with the init call |
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const canvas = document.createElement("canvas");
|
|
100
|
+
const offscreen = canvas.transferControlToOffscreen();
|
|
101
|
+
|
|
102
|
+
const proxy = await init<MyWorkerInit>(
|
|
103
|
+
{ worker, transfer: [offscreen] },
|
|
104
|
+
offscreen
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `WorkerProxy<T>`
|
|
109
|
+
|
|
110
|
+
The proxy object returned by `init()`. It provides three ways to call each exposed method:
|
|
111
|
+
|
|
112
|
+
| Mode | Syntax | Returns | Description |
|
|
113
|
+
|------|--------|---------|-------------|
|
|
114
|
+
| Fire-and-forget | `proxy.method(args)` | `void` | Fastest. Sends message, doesn't wait for a result. |
|
|
115
|
+
| Awaited | `proxy.await.method(args)` | `Promise<T>` | Sends message and resolves with the return value. |
|
|
116
|
+
| Create proxy | `proxy.createProxy.method(args)` | `Promise<WorkerProxy<T>>` | Like `await`, but the return value is itself wrapped in a new proxy. Useful for factory functions that return objects with their own methods. |
|
|
117
|
+
|
|
118
|
+
#### Transferring data
|
|
119
|
+
|
|
120
|
+
Use `.transfer()` before calling a method to transfer ownership of objects:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
proxy.transfer([imageBitmap]).drawImage(imageBitmap);
|
|
124
|
+
|
|
125
|
+
// Also works with await
|
|
126
|
+
const result = await proxy.transfer([buffer]).await.process(buffer);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `createMockProxy<T>(obj): WorkerProxy<T>`
|
|
130
|
+
|
|
131
|
+
Creates a synchronous mock proxy that wraps a plain object with the same interface as `WorkerProxy`. Useful for:
|
|
132
|
+
|
|
133
|
+
- **Testing** without spinning up real workers
|
|
134
|
+
- **Fallback** when Web Workers are unavailable
|
|
135
|
+
- **Isomorphic code** that should work with or without a worker
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { createMockProxy, WorkerProxy } from "uniworker";
|
|
139
|
+
|
|
140
|
+
const impl = {
|
|
141
|
+
greet(name: string) {
|
|
142
|
+
return `Hello, ${name}!`;
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const proxy = createMockProxy(impl);
|
|
147
|
+
|
|
148
|
+
// Same interface as worker proxy
|
|
149
|
+
proxy.greet("World"); // fire-and-forget (sync)
|
|
150
|
+
const msg = await proxy.await.greet("World"); // "Hello, World!"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## How It Works
|
|
154
|
+
|
|
155
|
+
1. **`expose()`** registers the initializer and listens for messages in the worker.
|
|
156
|
+
2. **`init()`** sends the init arguments to the worker, which runs the initializer and returns a mapping of method names to internal IDs.
|
|
157
|
+
3. The main-thread proxy translates method calls into `postMessage` calls using these IDs.
|
|
158
|
+
4. For `await` calls, a return handler is registered and resolved when the worker posts back the result.
|
|
159
|
+
|
|
160
|
+
The protocol is a simple positional array format (`[fnID, returnID, proxy, ...args]`), avoiding serialization overhead of structured objects.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
[MIT](./LICENSE) © Whimsical, Inc.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type Transfer = (OffscreenCanvas | Transferable)[];
|
|
2
|
+
type Fn = (...args: any) => any;
|
|
3
|
+
type Promisify<T> = T extends Promise<unknown> ? T : Promise<T>;
|
|
4
|
+
type PromisifyFn<T extends Fn> = (...args: Parameters<T>) => Promisify<ReturnType<T>>;
|
|
5
|
+
type AsyncProperty<T> = T extends Fn ? PromisifyFn<T> : undefined;
|
|
6
|
+
type Async<T> = {
|
|
7
|
+
[P in keyof T]: AsyncProperty<T[P]>;
|
|
8
|
+
};
|
|
9
|
+
type Voidify<T> = T extends Fn ? (...args: Parameters<T>) => void : void;
|
|
10
|
+
type NoReturn<T> = {
|
|
11
|
+
[P in keyof T]: Voidify<T[P]>;
|
|
12
|
+
};
|
|
13
|
+
type InitFn = (...args: any) => object;
|
|
14
|
+
type WorkerInstance<T> = T;
|
|
15
|
+
type WorkerProxyAsync<T> = Async<WorkerInstance<T>>;
|
|
16
|
+
type WorkerProxySendOnly<T> = NoReturn<WorkerInstance<T>>;
|
|
17
|
+
type CreateProxyPromisifyInitFn<T extends InitFn> = (...args: Parameters<T>) => Promisify<WorkerProxy<ReturnType<T>>>;
|
|
18
|
+
type CreateProxyAsyncProperty<T> = T extends InitFn ? CreateProxyPromisifyInitFn<T> : undefined;
|
|
19
|
+
type CreateProxyAsync<T> = {
|
|
20
|
+
[P in keyof T]: CreateProxyAsyncProperty<T[P]>;
|
|
21
|
+
};
|
|
22
|
+
type CreateProxy<T> = CreateProxyAsync<WorkerInstance<T>>;
|
|
23
|
+
type WorkerProxyWithAwaitAndCreateProxy<T> = WorkerProxySendOnly<T> & {
|
|
24
|
+
await: WorkerProxyAsync<T>;
|
|
25
|
+
createProxy: CreateProxy<T>;
|
|
26
|
+
};
|
|
27
|
+
export type WorkerProxy<T extends object> = WorkerProxyWithAwaitAndCreateProxy<T> & {
|
|
28
|
+
transfer(transfers: (OffscreenCanvas | Transferable)[]): WorkerProxyWithAwaitAndCreateProxy<T>;
|
|
29
|
+
};
|
|
30
|
+
export declare function expose(workerInitializer: (...args: any[]) => any): void;
|
|
31
|
+
export declare function init<T extends InitFn>({ worker, transfer, }: {
|
|
32
|
+
worker: Worker;
|
|
33
|
+
transfer?: Transfer;
|
|
34
|
+
}, ...args: Parameters<T>): Promise<WorkerProxy<ReturnType<T>>>;
|
|
35
|
+
export declare function createMockProxy<T extends {
|
|
36
|
+
[k: string]: any;
|
|
37
|
+
}>(obj: T): WorkerProxy<T>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const InitWorkerID = 0;
|
|
2
|
+
export function expose(workerInitializer) {
|
|
3
|
+
let nextFnID = InitWorkerID + 1;
|
|
4
|
+
const fns = new Map();
|
|
5
|
+
function registerFn(fn) {
|
|
6
|
+
const id = nextFnID++;
|
|
7
|
+
fns.set(id, fn);
|
|
8
|
+
return id;
|
|
9
|
+
}
|
|
10
|
+
function createProxyMappings(obj) {
|
|
11
|
+
return Object.keys(obj)
|
|
12
|
+
.filter((key) => typeof obj[key] === "function")
|
|
13
|
+
.map((key) => {
|
|
14
|
+
const fn = obj[key];
|
|
15
|
+
const id = registerFn(fn);
|
|
16
|
+
return [key, id];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
fns.set(InitWorkerID, workerInitializer);
|
|
20
|
+
addEventListener("message", (ev) => {
|
|
21
|
+
if (!ev || !ev.data) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const [id, returnID, proxy, ...args] = ev.data;
|
|
25
|
+
const fn = fns.get(id);
|
|
26
|
+
if (fn == null) {
|
|
27
|
+
console.warn("Function is not registered:", ...ev.data);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const returnValue = fn(...args);
|
|
31
|
+
if (returnID != null) {
|
|
32
|
+
// If the return value is a promise, resolve it before posting the op return. For non promise values
|
|
33
|
+
// resolve will be a synchronous operation.
|
|
34
|
+
Promise.resolve(returnValue).then((returnValue) => {
|
|
35
|
+
if (proxy) {
|
|
36
|
+
returnValue = createProxyMappings(returnValue);
|
|
37
|
+
}
|
|
38
|
+
const returnMessage = [returnID, returnValue];
|
|
39
|
+
postMessage(returnMessage);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function createProxy(mappings, adapter) {
|
|
45
|
+
const client = {
|
|
46
|
+
await: {},
|
|
47
|
+
createProxy: {},
|
|
48
|
+
};
|
|
49
|
+
function registerFn([fnName, fnID]) {
|
|
50
|
+
client[fnName] = (...args) => adapter.callWorkerFn(fnID, args);
|
|
51
|
+
client.await[fnName] = (...args) => adapter.callWorkerFnAwait(fnID, false, args);
|
|
52
|
+
client.createProxy[fnName] = (...args) => adapter.callWorkerFnAwait(fnID, true, args);
|
|
53
|
+
}
|
|
54
|
+
mappings.forEach(registerFn);
|
|
55
|
+
client.transfer = (transfer) => {
|
|
56
|
+
adapter.setCurrentTransfer(transfer);
|
|
57
|
+
return client;
|
|
58
|
+
};
|
|
59
|
+
return client;
|
|
60
|
+
}
|
|
61
|
+
export async function init({ worker, transfer, }, ...args) {
|
|
62
|
+
let nextReturnID = 0;
|
|
63
|
+
const returnHandlers = new Map();
|
|
64
|
+
let adapter = new (class {
|
|
65
|
+
constructor() {
|
|
66
|
+
// Initialize with transfer to be used for init function:
|
|
67
|
+
this.currentTransfer = transfer || [];
|
|
68
|
+
}
|
|
69
|
+
setCurrentTransfer(transfer) {
|
|
70
|
+
this.currentTransfer.length = 0;
|
|
71
|
+
if (transfer) {
|
|
72
|
+
this.currentTransfer.push(...transfer);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
callWorkerFn(id, args) {
|
|
76
|
+
const message = [id, null, false, ...args];
|
|
77
|
+
worker.postMessage(message, this.currentTransfer);
|
|
78
|
+
this.setCurrentTransfer(null);
|
|
79
|
+
}
|
|
80
|
+
callWorkerFnAwait(id, proxy, args) {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const returnID = nextReturnID++;
|
|
83
|
+
returnHandlers.set(returnID, (returnValue) => {
|
|
84
|
+
if (proxy) {
|
|
85
|
+
returnValue = createProxy(returnValue, this);
|
|
86
|
+
}
|
|
87
|
+
resolve(returnValue);
|
|
88
|
+
});
|
|
89
|
+
const message = [id, returnID, proxy, ...args];
|
|
90
|
+
worker.postMessage(message, this.currentTransfer);
|
|
91
|
+
this.setCurrentTransfer(null);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
worker.addEventListener("message", (ev) => {
|
|
96
|
+
if (!ev || !ev.data || !Array.isArray(ev.data)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const [returnID, returnValue] = ev.data;
|
|
100
|
+
const returnHandler = returnHandlers.get(returnID);
|
|
101
|
+
if (returnHandler == null) {
|
|
102
|
+
console.warn("Return handler is not registered for:", ...ev.data);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
returnHandlers.delete(returnID);
|
|
106
|
+
returnHandler(returnValue);
|
|
107
|
+
});
|
|
108
|
+
return await adapter.callWorkerFnAwait(InitWorkerID, true, args);
|
|
109
|
+
}
|
|
110
|
+
export function createMockProxy(obj) {
|
|
111
|
+
const proxy = new (class {
|
|
112
|
+
constructor() {
|
|
113
|
+
this.await = {};
|
|
114
|
+
this.createProxy = {};
|
|
115
|
+
}
|
|
116
|
+
transfer(_) {
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
Object.keys(obj)
|
|
121
|
+
.filter((key) => typeof obj[key] === "function")
|
|
122
|
+
.forEach((key) => {
|
|
123
|
+
proxy[key] = obj[key];
|
|
124
|
+
proxy.await[key] = function (...args) {
|
|
125
|
+
return Promise.resolve(obj[key](...args));
|
|
126
|
+
};
|
|
127
|
+
proxy.createProxy[key] = function (...args) {
|
|
128
|
+
return Promise.resolve(createMockProxy(obj[key](...args)));
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
return proxy;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uniworker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, type-safe library for communicating with Web Workers via proxied function calls",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"web-worker",
|
|
29
|
+
"worker",
|
|
30
|
+
"proxy",
|
|
31
|
+
"typed",
|
|
32
|
+
"comlink",
|
|
33
|
+
"postmessage",
|
|
34
|
+
"transferable",
|
|
35
|
+
"offscreencanvas"
|
|
36
|
+
],
|
|
37
|
+
"author": "Whimsical, Inc.",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/WhimsicalCode/uniworker.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/WhimsicalCode/uniworker#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/WhimsicalCode/uniworker/issues"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.7.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export type Transfer = (OffscreenCanvas | Transferable)[];
|
|
2
|
+
|
|
3
|
+
type Fn = (...args: any) => any;
|
|
4
|
+
type Promisify<T> = T extends Promise<unknown> ? T : Promise<T>;
|
|
5
|
+
type PromisifyFn<T extends Fn> = (...args: Parameters<T>) => Promisify<ReturnType<T>>;
|
|
6
|
+
type AsyncProperty<T> = T extends Fn ? PromisifyFn<T> : undefined;
|
|
7
|
+
type Async<T> = { [P in keyof T]: AsyncProperty<T[P]> };
|
|
8
|
+
type Voidify<T> = T extends Fn ? (...args: Parameters<T>) => void : void;
|
|
9
|
+
type NoReturn<T> = { [P in keyof T]: Voidify<T[P]> };
|
|
10
|
+
|
|
11
|
+
type InitFn = (...args: any) => object;
|
|
12
|
+
|
|
13
|
+
type WorkerInstance<T> = T;
|
|
14
|
+
type WorkerProxyAsync<T> = Async<WorkerInstance<T>>;
|
|
15
|
+
type WorkerProxySendOnly<T> = NoReturn<WorkerInstance<T>>;
|
|
16
|
+
|
|
17
|
+
type CreateProxyPromisifyInitFn<T extends InitFn> = (...args: Parameters<T>) => Promisify<WorkerProxy<ReturnType<T>>>;
|
|
18
|
+
type CreateProxyAsyncProperty<T> = T extends InitFn ? CreateProxyPromisifyInitFn<T> : undefined;
|
|
19
|
+
type CreateProxyAsync<T> = { [P in keyof T]: CreateProxyAsyncProperty<T[P]> };
|
|
20
|
+
type CreateProxy<T> = CreateProxyAsync<WorkerInstance<T>>;
|
|
21
|
+
|
|
22
|
+
type WorkerProxyWithAwaitAndCreateProxy<T> = WorkerProxySendOnly<T> & {
|
|
23
|
+
await: WorkerProxyAsync<T>;
|
|
24
|
+
createProxy: CreateProxy<T>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type WorkerProxy<T extends object> = WorkerProxyWithAwaitAndCreateProxy<T> & {
|
|
28
|
+
transfer(transfers: (OffscreenCanvas | Transferable)[]): WorkerProxyWithAwaitAndCreateProxy<T>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type FnID = number;
|
|
32
|
+
type FnReturnID = number;
|
|
33
|
+
type FnCallMessage = [id: FnID, returnID: FnReturnID | null, proxy: boolean, ...args: any[]];
|
|
34
|
+
type FnReturnMessage = [id: FnReturnID, value: any];
|
|
35
|
+
type FnReturnHandler = (value: any) => void;
|
|
36
|
+
type FnName = string;
|
|
37
|
+
type ProxyMappings = [FnName, FnID][];
|
|
38
|
+
|
|
39
|
+
const InitWorkerID: FnID = 0;
|
|
40
|
+
|
|
41
|
+
export function expose(workerInitializer: (...args: any[]) => any) {
|
|
42
|
+
let nextFnID = InitWorkerID + 1;
|
|
43
|
+
const fns = new Map<FnID, Fn>();
|
|
44
|
+
|
|
45
|
+
function registerFn(fn: Fn) {
|
|
46
|
+
const id = nextFnID++;
|
|
47
|
+
fns.set(id, fn);
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createProxyMappings(obj: { [k: string]: any }): ProxyMappings {
|
|
52
|
+
return Object.keys(obj)
|
|
53
|
+
.filter((key) => typeof obj[key] === "function")
|
|
54
|
+
.map((key) => {
|
|
55
|
+
const fn: Fn = obj[key];
|
|
56
|
+
const id = registerFn(fn);
|
|
57
|
+
return [key, id];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fns.set(InitWorkerID, workerInitializer);
|
|
62
|
+
|
|
63
|
+
addEventListener("message", (ev: MessageEvent) => {
|
|
64
|
+
if (!ev || !ev.data) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const [id, returnID, proxy, ...args] = ev.data as FnCallMessage;
|
|
68
|
+
const fn = fns.get(id);
|
|
69
|
+
if (fn == null) {
|
|
70
|
+
console.warn("Function is not registered:", ...ev.data);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const returnValue = fn(...args);
|
|
74
|
+
if (returnID != null) {
|
|
75
|
+
// If the return value is a promise, resolve it before posting the op return. For non promise values
|
|
76
|
+
// resolve will be a synchronous operation.
|
|
77
|
+
Promise.resolve(returnValue).then((returnValue) => {
|
|
78
|
+
if (proxy) {
|
|
79
|
+
returnValue = createProxyMappings(returnValue);
|
|
80
|
+
}
|
|
81
|
+
const returnMessage: FnReturnMessage = [returnID, returnValue];
|
|
82
|
+
postMessage(returnMessage);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface WorkerAdapter {
|
|
89
|
+
setCurrentTransfer(transfer: Transfer | null): void;
|
|
90
|
+
|
|
91
|
+
callWorkerFn(id: FnID, args: any[]): void;
|
|
92
|
+
|
|
93
|
+
callWorkerFnAwait(id: FnID, proxy: boolean, args: any[]): Promise<any>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createProxy(mappings: ProxyMappings, adapter: WorkerAdapter) {
|
|
97
|
+
const client: { [k: string]: any } = {
|
|
98
|
+
await: {},
|
|
99
|
+
createProxy: {},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function registerFn([fnName, fnID]: [FnName, FnID]) {
|
|
103
|
+
client[fnName] = (...args: any[]) => adapter.callWorkerFn(fnID, args);
|
|
104
|
+
client.await[fnName] = (...args: any[]) => adapter.callWorkerFnAwait(fnID, false, args);
|
|
105
|
+
client.createProxy[fnName] = (...args: any[]) => adapter.callWorkerFnAwait(fnID, true, args);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
mappings.forEach(registerFn);
|
|
109
|
+
|
|
110
|
+
client.transfer = (transfer: Transfer) => {
|
|
111
|
+
adapter.setCurrentTransfer(transfer);
|
|
112
|
+
return client;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return client;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function init<T extends InitFn>(
|
|
119
|
+
{
|
|
120
|
+
worker,
|
|
121
|
+
transfer,
|
|
122
|
+
}: {
|
|
123
|
+
worker: Worker;
|
|
124
|
+
transfer?: Transfer;
|
|
125
|
+
},
|
|
126
|
+
...args: Parameters<T>
|
|
127
|
+
): Promise<WorkerProxy<ReturnType<T>>> {
|
|
128
|
+
let nextReturnID = 0;
|
|
129
|
+
const returnHandlers = new Map<FnReturnID, FnReturnHandler>();
|
|
130
|
+
|
|
131
|
+
let adapter: WorkerAdapter = new (class {
|
|
132
|
+
// Initialize with transfer to be used for init function:
|
|
133
|
+
currentTransfer = transfer || [];
|
|
134
|
+
|
|
135
|
+
setCurrentTransfer(transfer: Transfer | null): void {
|
|
136
|
+
this.currentTransfer.length = 0;
|
|
137
|
+
if (transfer) {
|
|
138
|
+
this.currentTransfer.push(...transfer);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
callWorkerFn(id: FnID, args: any[]): void {
|
|
143
|
+
const message: FnCallMessage = [id, null, false, ...args];
|
|
144
|
+
worker.postMessage(message, this.currentTransfer);
|
|
145
|
+
this.setCurrentTransfer(null);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
callWorkerFnAwait(id: FnID, proxy: boolean, args: any[]): Promise<any> {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
const returnID = nextReturnID++;
|
|
151
|
+
returnHandlers.set(returnID, (returnValue) => {
|
|
152
|
+
if (proxy) {
|
|
153
|
+
returnValue = createProxy(returnValue, this);
|
|
154
|
+
}
|
|
155
|
+
resolve(returnValue);
|
|
156
|
+
});
|
|
157
|
+
const message: FnCallMessage = [id, returnID, proxy, ...args];
|
|
158
|
+
worker.postMessage(message, this.currentTransfer);
|
|
159
|
+
this.setCurrentTransfer(null);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
|
|
164
|
+
worker.addEventListener("message", (ev: MessageEvent) => {
|
|
165
|
+
if (!ev || !ev.data || !Array.isArray(ev.data)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const [returnID, returnValue] = ev.data as FnReturnMessage;
|
|
169
|
+
const returnHandler = returnHandlers.get(returnID);
|
|
170
|
+
if (returnHandler == null) {
|
|
171
|
+
console.warn("Return handler is not registered for:", ...ev.data);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
returnHandlers.delete(returnID);
|
|
175
|
+
returnHandler(returnValue);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return await adapter.callWorkerFnAwait(InitWorkerID, true, args);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function createMockProxy<T extends { [k: string]: any }>(obj: T): WorkerProxy<T> {
|
|
182
|
+
const proxy: { [k: string]: any } = new (class {
|
|
183
|
+
transfer(_: any) {
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
await: { [k: string]: any } = {};
|
|
187
|
+
createProxy: { [k: string]: any } = {};
|
|
188
|
+
})();
|
|
189
|
+
|
|
190
|
+
Object.keys(obj)
|
|
191
|
+
.filter((key) => typeof obj[key] === "function")
|
|
192
|
+
.forEach((key) => {
|
|
193
|
+
proxy[key] = obj[key];
|
|
194
|
+
proxy.await[key] = function (...args: any[]) {
|
|
195
|
+
return Promise.resolve(obj[key](...args));
|
|
196
|
+
};
|
|
197
|
+
proxy.createProxy[key] = function (...args: any[]) {
|
|
198
|
+
return Promise.resolve(createMockProxy(obj[key](...args)));
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return proxy as WorkerProxy<T>;
|
|
203
|
+
}
|