npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@whimsical-code/uniworker

v1.0.0

Published

A lightweight, type-safe library for communicating with Web Workers via proxied function calls

Readme

UniWorker

A lightweight, type-safe library for communicating with Web Workers via proxied function calls.

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, while being simpler, more limited in scope, and significantly faster.

Features

  • Fully typed — proxy methods preserve the original function signatures
  • Three calling modes — fire-and-forget, awaited, and proxy-creating
  • Transferable support — transfer ArrayBuffer, OffscreenCanvas, ImageBitmap, etc.
  • Mock proxy — run the same code synchronously on the main thread for testing or fallback
  • Zero dependencies
  • ~1 KB minified + gzipped

Install

npm install uniworker

Quick Start

1. Define worker code (my-worker.ts)

import { expose } from "uniworker";

expose((name: string) => {
  // The initializer function runs once when init() is called.
  // It returns an object whose methods become available on the proxy.
  return {
    greet(greeting: string) {
      return `${greeting}, ${name}!`;
    },
    add(a: number, b: number) {
      return a + b;
    },
  };
});

2. Use from the main thread (main.ts)

import { init, WorkerProxy } from "uniworker";

// Type describing the initializer function (matches the expose() argument)
type MyWorkerInit = (name: string) => {
  greet(greeting: string): string;
  add(a: number, b: number): number;
};

const worker = new Worker(new URL("./my-worker.ts", import.meta.url), {
  type: "module",
});

const proxy = await init<MyWorkerInit>({ worker }, "World");

// Fire-and-forget (returns void, does not wait)
proxy.greet("Hello");

// Await the return value
const message = await proxy.await.greet("Hello");
console.log(message); // "Hello, World!"

const sum = await proxy.await.add(2, 3);
console.log(sum); // 5

API

expose(initializer)

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.

expose((canvas: OffscreenCanvas) => {
  const ctx = canvas.getContext("2d")!;
  return {
    drawRect(x: number, y: number, w: number, h: number) {
      ctx.fillRect(x, y, w, h);
    },
  };
});

init<T>(options, ...args): Promise<WorkerProxy<ReturnType<T>>>

Call on the main thread to initialize the worker and get a typed proxy.

Options: | Option | Type | Description | |----------|------|-------------| | worker | Worker | The Web Worker instance | | transfer | Transfer | Optional array of transferable objects to send with the init call |

const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();

const proxy = await init<MyWorkerInit>(
  { worker, transfer: [offscreen] },
  offscreen
);

WorkerProxy<T>

The proxy object returned by init(). It provides three ways to call each exposed method:

| Mode | Syntax | Returns | Description | |------|--------|---------|-------------| | Fire-and-forget | proxy.method(args) | void | Fastest. Sends message, doesn't wait for a result. | | Awaited | proxy.await.method(args) | Promise<T> | Sends message and resolves with the return value. | | 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. |

Transferring data

Use .transfer() before calling a method to transfer ownership of objects:

proxy.transfer([imageBitmap]).drawImage(imageBitmap);

// Also works with await
const result = await proxy.transfer([buffer]).await.process(buffer);

createMockProxy<T>(obj): WorkerProxy<T>

Creates a synchronous mock proxy that wraps a plain object with the same interface as WorkerProxy. Useful for:

  • Testing without spinning up real workers
  • Fallback when Web Workers are unavailable
  • Isomorphic code that should work with or without a worker
import { createMockProxy, WorkerProxy } from "uniworker";

const impl = {
  greet(name: string) {
    return `Hello, ${name}!`;
  },
};

const proxy = createMockProxy(impl);

// Same interface as worker proxy
proxy.greet("World");                          // fire-and-forget (sync)
const msg = await proxy.await.greet("World");  // "Hello, World!"

How It Works

  1. expose() registers the initializer and listens for messages in the worker.
  2. init() sends the init arguments to the worker, which runs the initializer and returns a mapping of method names to internal IDs.
  3. The main-thread proxy translates method calls into postMessage calls using these IDs.
  4. For await calls, a return handler is registered and resolved when the worker posts back the result.

The protocol is a simple positional array format ([fnID, returnID, proxy, ...args]), avoiding serialization overhead of structured objects.

License

MIT © Whimsical, Inc.