@mr.python/promise-worker-ts
v1.0.0
Published
Type-safe Promise-based Web Worker communication with TypeScript, supporting tagged messages and transferable objects
Downloads
235
Maintainers
Readme
promise-worker-ts 🚀
Type-safe Promise-based communication with Web Workers in TypeScript.
Features ✨
- 🔒 Fully Type-Safe: Complete end-to-end type safety for Web Worker communication
- 🏷️ Tagged Messages: Support for multiple message types in a single worker
- 🔄 Promise-Based: Clean, modern async/await syntax
- 📦 Zero Dependencies: Lightweight and framework-agnostic
- 🛡️ TypeScript First: Built with TypeScript for the best developer experience
- 📤 Transferable Objects: Full support for transferring ownership of objects between threads
Installation 📦
npm install promise-worker-ts
# or
yarn add promise-worker-ts
# or
pnpm add promise-worker-tsQuick Start 🚀
Basic Usage
- Define your shared types (e.g.,
types.shared.ts):
import type { PromiseWorker } from "promise-worker-ts";
export type DoubleNumber = PromiseWorker<number, number>;- Set up your worker (
worker.ts):
import { listen } from "promise-worker-ts";
import type { DoubleNumber } from "./types.shared";
listen<DoubleNumber>((num) => num * 2);- Use in your main thread:
import { send } from "promise-worker-ts";
import type { DoubleNumber } from "./types.shared";
const worker = new Worker(/* ... */);
const result = await send<DoubleNumber>(worker, 21); // result = 42Tagged Messages
When you need multiple message types in a single worker:
- Define tagged types:
import type { PromiseWorkerTagged } from "promise-worker-ts";
export type AddNumbers = PromiseWorkerTagged<"add", [number, number], number>;
export type MultiplyNumbers = PromiseWorkerTagged<
"multiply",
[number, number],
number
>;- Set up worker handlers:
import { listen } from "promise-worker-ts";
import type { AddNumbers, MultiplyNumbers } from "./types.shared";
listen<AddNumbers>("add", ([a, b]) => a + b);
listen<MultiplyNumbers>("multiply", ([a, b]) => a * b);- Use in main thread:
import { send } from "promise-worker-ts";
import type { AddNumbers, MultiplyNumbers } from "./types.shared";
const worker = new Worker("worker.ts");
const sum = await send<AddNumbers>("add", worker, [5, 3]); // 8
const product = await send<MultiplyNumbers>("multiply", worker, [5, 3]); // 15API Reference 📖
Types
These types are technically objects, but they're convenience types for being able to define once and use in both workers and main thread.
PromiseWorker<Input, Output, Error = unknown>
Basic type for single-purpose workers
Input: Type of data sent to workerOutput: Type of data received from workerError: Optional error type
PromiseWorkerTagged<Tag, Input, Output, Error = unknown>
Type for tagged messages in multi-purpose workers
Tag: Literal string type for message identificationInput: Type of data sent to workerOutput: Type of data received from workerError: Optional error type
Functions
send<T>(worker: Worker, input: Input): Promise<Output>
Send message to untagged worker
send<T>(tag: string, worker: Worker, input: Input): Promise<Output>
Send message to tagged worker
listen<T>(handler: (input: Input) => Output | Promise<Output>): void
Listen for untagged messages
listen<T>(tag: string, handler: (input: Input) => Output | Promise<Output>): void
Listen for tagged messages
Reverse direction (worker → main)
sendToMain<T>(input: Input, transfer?: Transferable[]): Promise<Output>sendToMain<T>(tag: string, input: Input, transfer?: Transferable[]): Promise<Output>listenMain<T>(worker: Worker, handler: (input: Input, transfer: Transferable[]) => Output | Promise<Output>): () => voidlistenMain<T>(tag: string, worker: Worker, handler: (input: Input, transfer: Transferable[]) => Output | Promise<Output>): () => void
These mirror send/listen but in the opposite direction: call from the worker, handle on the main thread, with full support for tagging and transferables.
Advanced Usage 🔧
Error Handling
type DivideNumbers = PromiseWorkerTagged<
"divide", // Tag
[number, number], // Input
number, // Output
"Division by zero" // Error
>;
// In worker
listen<DivideNumbers>("divide", ([a, b]) => {
if (b === 0) throw "Division by zero";
return a / b;
});
// In main thread
try {
const result = await send<DivideNumbers>("divide", worker, [10, 0]);
} catch (error) {
console.error(error); // 'Division by zero'
}Transferable Objects
// Send with transferable objects
const arrayBuffer = new ArrayBuffer(1024);
await send<MyType>(worker, arrayBuffer, [arrayBuffer]);
// Send back transferable objects
listen<MyType>((arrayBuffer, transferables) => {
const newArrayBuffer = new ArrayBuffer(1024);
// Add to transfer queue and return
transferables.push(newArrayBuffer);
return newArrayBuffer;
});For listening, you can pass a callback function.
Todo
- [x] Write tests for transferable objects
- [ ] Write more robust test for passing back transfer objects
- [x] Make it so workers can initiate promises to their parent threads
Worker → Main Example
- Define the types (shared):
import type { PromiseWorkerTagged } from "promise-worker-ts";
export type RevCallSquare = PromiseWorkerTagged<"rev.call.square", number, number>;
export type RevTriggerSquare = PromiseWorkerTagged<
"rev.trigger.square",
number,
number
>;- In worker:
import { listen, sendToMain } from "promise-worker-ts";
import type { RevCallSquare, RevTriggerSquare } from "./types.shared";
listen<RevTriggerSquare>("rev.trigger.square", async (n) => {
return await sendToMain<RevCallSquare>("rev.call.square", n);
});- In main:
import { listenMain, send } from "promise-worker-ts";
import type { RevCallSquare, RevTriggerSquare } from "./types.shared";
const worker = new Worker("reverse.worker.ts");
const stop = listenMain<RevCallSquare>("rev.call.square", worker, (n, transfer) => n * 2);
const result = await send<RevTriggerSquare>("rev.trigger.square", worker, 5);
// result === 10
stop();Contributing 🤝
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License 📄
This project is licensed under the MIT License - see the LICENSE file for details.
Support 💖
If you find this project helpful, please consider:
- Starring the repository ⭐
- Reporting issues 🐛
- Contributing improvements 🛠️
- Sharing with others 🌟
Credits 👏
Created and maintained by Peter Batory-Bernardin
