Skip to content

Add bun support#159

Merged
kentonv merged 6 commits into
cloudflare:mainfrom
aron-cf:bun-support
Apr 20, 2026
Merged

Add bun support#159
kentonv merged 6 commits into
cloudflare:mainfrom
aron-cf:bun-support

Conversation

@aron-cf

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

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

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

@pkg-pr-new

pkg-pr-new Bot commented Mar 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 0ed0f7f

@changeset-bot

changeset-bot Bot commented Mar 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0ed0f7f

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

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

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

@aron-cf

aron-cf commented Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

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

@aron-cf

aron-cf commented Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

recheck

github-actions Bot added a commit that referenced this pull request Mar 26, 2026
@kentonv

kentonv commented Mar 26, 2026

Copy link
Copy Markdown
Member

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"
      }
    }
  },

Comment thread vitest.config.ts Outdated
@aron-cf

aron-cf commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

@kentonv thanks for the review.

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.

Yeah that makes sense. I've done that.

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.

Yeh, that's fair. It was testing against the interface of the BunWebSocket server. I agree an integration test is more useful here. I've updated the tests to run against an actual Bun webserver.

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

I've not created a test matrix to keep things simple, but happy to do so if you'd prefer. Now we now have a npm run test:bun script to run the integration tests against the Bun web server and a new npm run test:ci to run both vitest and bun on CI. I've added a note to the README to cover this.

Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread src/bun.ts Outdated
Comment thread src/index-bun.ts Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
aron-cf added 2 commits April 10, 2026 12:02
Bun uses callback-based WebSocket handlers registered on `Bun.serve()`
rather than the standard `addEventListener` interface. Passing a Bun
ServerWebSocket to `newWebSocketRpcSession()` errors because the
`addEventListener` method does not exist (cloudflare#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 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 Bun-specific code ships in a separate entry point (index-bun.ts)
mapped via a "bun" conditional export in package.json, following the
same pattern as the existing workerd build. Other runtimes never load
this code.

The test suite in __tests__/bun.test.ts runs under Bun native test
runner against real `Bun.serve()` instances with actual WebSocket
connections. Run with `npm run test:bun` or `npm run test:ci`.

Closes cloudflare#61
Adds the oven-sh/setup-bun action so the Bun runtime is available in
the Playwright container. The test step now runs `test:ci` which chains
Vitest and Bun tests in sequence.
aron-cf added 2 commits April 10, 2026 12:48
Adds an "HTTP server on Bun" section to the readme showing both the
handler helper and the manual wiring escape hatch. Notes that the Bun
helpers ship via a conditional export and are never loaded by other
runtimes.

Adds a development section documenting the test scripts: npm test for
vitest only, npm run test:bun for the Bun suite, and npm run test:ci
for both together.
Previously, "bun" and "workerd" were nested inside "import"/"require",
which meant TypeScript always fell through to the single top-level
"types" entry and picked up index.d.ts regardless of the active
condition.

Each platform block is now a top-level condition with its own "types",
"import", and "require" keys:

- "bun" resolves to index-bun.d.ts, which exposes BunWebSocketTransport,
  BunServerWebSocket, and the Bun-specific session helpers
- "workerd" shares the same declarations as the default but keeps a
  distinct runtime (index-workers.js) that imports cloudflare:workers
  and registers it for native RPC interop
- The bare "types"/"import"/"require" fallback covers Node.js and
  every other environment
aron-cf added a commit to cloudflare/sandbox-sdk that referenced this pull request Apr 16, 2026
The published capnweb@0.6.1 does not include Bun-specific APIs
(BunWebSocketTransport, newBunWebSocketRpcSession). Vendor the
transport code from cloudflare/capnweb#159 into a standalone file
so the container can use the released npm package instead of a
pkg.pr.new pre-release URL.

The vendored file should be removed once capnweb ships a release
with the bun conditional export.
aron-cf added a commit to cloudflare/sandbox-sdk that referenced this pull request Apr 20, 2026
The published capnweb@0.6.1 does not include Bun-specific APIs
(BunWebSocketTransport, newBunWebSocketRpcSession). Vendor the
transport code from cloudflare/capnweb#159 into a standalone file
so the container can use the released npm package instead of a
pkg.pr.new pre-release URL.

The vendored file should be removed once capnweb ships a release
with the bun conditional export.
Remove example of custom Bun websocket transport
@kentonv kentonv merged commit 7cb9132 into cloudflare:main Apr 20, 2026
4 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 20, 2026
aron-cf added a commit to cloudflare/sandbox-sdk that referenced this pull request Apr 24, 2026
The published capnweb@0.6.1 does not include Bun-specific APIs
(BunWebSocketTransport, newBunWebSocketRpcSession). Vendor the
transport code from cloudflare/capnweb#159 into a standalone file
so the container can use the released npm package instead of a
pkg.pr.new pre-release URL.

The vendored file should be removed once capnweb ships a release
with the bun conditional export.
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