Skip to content

Add bun support#159

Open
aron-cf wants to merge 3 commits intocloudflare:mainfrom
aron-cf:bun-support
Open

Add bun support#159
aron-cf wants to merge 3 commits intocloudflare:mainfrom
aron-cf:bun-support

Conversation

@aron-cf
Copy link

@aron-cf aron-cf commented Mar 26, 2026

Add Bun ServerWebSocket support to capnweb.

Bun's server-side WebSocket uses callback-based handlers registered on Bun.serve() rather than the standard addEventListener interface. Passing a Bun ServerWebSocket to newWebSocketRpcSession() silently fails because the event listeners never fire. (#61)

A new BunWebSocketTransport implements the RpcTransport interface using the same resolver pattern as the existing WebSocket and MessagePort transports. It exposes dispatchMessage, dispatchClose, and dispatchError methods that Bun's handler callbacks invoke to feed messages into the transport.

Two convenience functions sit on top of the transport. newBunWebSocketRpcHandler() returns a complete handler object you can pass straight to Bun.serve(), wiring everything up automatically via ws.data. newBunWebSocketRpcSession() returns the stub and transport for users who need custom handler logic.

The readme now documents both approaches and notes that Bun is a supported runtime.

Quick start with the handler helper

import { RpcTarget, newBunWebSocketRpcHandler } from "capnweb";

class MyApi extends RpcTarget {
  greet(name: string) { return `Hello, ${name}!`; }
}

let rpcHandler = newBunWebSocketRpcHandler(() => new MyApi());

Bun.serve({
  fetch(req, server) {
    if (server.upgrade(req)) return;
    return new Response("Not Found", { status: 404 });
  },
  websocket: rpcHandler,
});

Escape hatch with manual wiring

import { newBunWebSocketRpcSession, RpcTarget } from "capnweb";

class MyApi extends RpcTarget {
  greet(name: string) { return `Hello, ${name}!`; }
}

Bun.serve({
  fetch(req, server) {
    let userId = authenticate(req);
    server.upgrade(req, { data: { userId } });
  },
  websocket: {
    open(ws) {
      let { stub, transport } = newBunWebSocketRpcSession(ws, new MyApi());
      ws.data.transport = transport;
    },
    message(ws, msg) { ws.data.transport.dispatchMessage(msg); },
    close(ws, code, reason) { ws.data.transport.dispatchClose(code, reason); },
    error(ws, err) { ws.data.transport.dispatchError(err); },
  },
});

Testing locally

Run the test suite with npm test. The new tests in __tests__/bun.test.ts exercise the transport, both convenience functions, and full round-trip calls through a loopback pair without needing a real Bun server.

Unit tests cover message queuing, close and error propagation, abort semantics, buffer-to-string conversion, and end-to-end calls including bidirectional callbacks and error forwarding. All existing tests continue to pass.

Closes #61

Bun's server-side WebSocket uses callback-based handlers registered
on `Bun.serve()` rather than the standard `addEventListener` interface
that capnweb's WebSocket transport relies on. This means passing a
Bun `ServerWebSocket` to `newWebSocketRpcSession()` errors due to the
missing interface.

This commit adds a new `BunWebSocketTransport` that implements the
`RpcTransport` interface directly, using the same resolver pattern
as the existing WebSocket and `MessagePort` transports. It exposes
`dispatchMessage`, `dispatchClose`, and `dispatchError` methods that
Bun's handler callbacks invoke to feed messages into the transport.

Two convenience functions sit on top of the transport.
`newBunWebSocketRpcSession()` returns the stub and transport for users
who need custom handler logic. `newBunWebSocketRpcHandler()` returns
a complete WebSocket handler object that can be passed directly to
Bun.serve(), wiring everything up automatically via `ws.data` so users
don't have to touch the transport at all.
@github-actions
Copy link
Contributor

github-actions bot commented Mar 26, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/capnweb@159

commit: 60f740e

@changeset-bot
Copy link

changeset-bot bot commented Mar 26, 2026

🦋 Changeset detected

Latest commit: 60f740e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
capnweb Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Document the new Bun WebSocket support added in the previous
commit. Add an HTTP server on Bun section showing both the
zero-wiring newBunWebSocketRpcHandler API and the lower-level
newBunWebSocketRpcSession escape hatch. Add Bun to the introductory
runtime list, note in the other runtimes section that Bun's
ServerWebSocket requires the dedicated API, and update the custom
transports section to list all four built-in transports.
@aron-cf
Copy link
Author

aron-cf commented Mar 26, 2026

I have read the CLA Document and I hereby sign the CLA

@aron-cf
Copy link
Author

aron-cf commented Mar 26, 2026

recheck

github-actions bot added a commit that referenced this pull request Mar 26, 2026
@kentonv
Copy link
Member

kentonv commented Mar 26, 2026

In package.json we currently have a special separate build for workerd that includes the workers-only stuff. Maybe we should have a separate build for bun, too? Avoid code bloat for other runtimes.

  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": {
        "workerd": "./dist/index-workers.js",
        "default": "./dist/index.js"
      },
      "require": {
        "workerd": "./dist/index-workers.cjs",
        "default": "./dist/index.cjs"
      }
    }
  },

// We throw flow-control test under Node only because it's testing straightforward
// JavaScript -- no need to run it on every runtime.
include: ['__tests__/index.test.ts', '__tests__/flow-control.test.ts'],
include: ['__tests__/index.test.ts', '__tests__/flow-control.test.ts', '__tests__/bun.test.ts'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused how the bun test passes on node, unless it's not actually testing integration with Bun's builtin APIs. If it isn't testing integration then I don't think it's a useful test.

Can we add Bun to the test matrix, and write a test that actually tests against Bun's built-in APIs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bun Support Missing

2 participants