Skip to content

Commit e89120e

Browse files
author
Developer
committed
docs: add Bun ServerWebSocket usage to README
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.
1 parent 50e46d7 commit e89120e

1 file changed

Lines changed: 70 additions & 1 deletion

File tree

README.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Cap'n Web is a spiritual sibling to [Cap'n Proto](https://capnproto.org) (and is
66
* That said, it integrates nicely with TypeScript.
77
* Also unlike Cap'n Proto, Cap'n Web's underlying serialization is human-readable. In fact, it's just JSON, with a little pre-/post-processing.
88
* It works over HTTP, WebSocket, and postMessage() out-of-the-box, with the ability to extend it to other transports easily.
9-
* It works in all major browsers, Cloudflare Workers, Node.js, and other modern JavaScript runtimes.
9+
* It works in all major browsers, Cloudflare Workers, Node.js, Bun, Deno, and other modern JavaScript runtimes.
1010
The whole thing compresses (minify+gzip) to under 10kB with no dependencies.
1111

1212
Cap'n Web is more expressive than almost every other RPC system, because it implements an object-capability RPC model. That means it:
@@ -630,6 +630,71 @@ Deno.serve(async (req) => {
630630
});
631631
```
632632

633+
### HTTP server on Bun
634+
635+
Bun's server-side WebSocket API uses [callback-based handlers](https://bun.sh/docs/runtime/http/websockets) instead of the standard `addEventListener` interface. Cap'n Web provides `newBunWebSocketRpcHandler()` which returns a handler object you can pass directly to `Bun.serve()`:
636+
637+
```ts
638+
import { RpcTarget, newBunWebSocketRpcHandler, newHttpBatchRpcResponse } from "capnweb";
639+
640+
class MyApiImpl extends RpcTarget implements MyApi {
641+
// ... define API, same as above ...
642+
}
643+
644+
// Create a WebSocket handler that manages RPC sessions automatically.
645+
// The callback is invoked once per connection to create a fresh API instance.
646+
let rpcHandler = newBunWebSocketRpcHandler(() => new MyApiImpl());
647+
648+
Bun.serve({
649+
async fetch(req, server) {
650+
let url = new URL(req.url);
651+
if (url.pathname === "/api") {
652+
// Upgrade WebSocket requests.
653+
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
654+
if (server.upgrade(req)) return;
655+
return new Response("WebSocket upgrade failed", { status: 500 });
656+
}
657+
658+
// Handle HTTP batch requests.
659+
let response = await newHttpBatchRpcResponse(req, new MyApiImpl());
660+
response.headers.set("Access-Control-Allow-Origin", "*");
661+
return response;
662+
}
663+
664+
return new Response("Not Found", { status: 404 });
665+
},
666+
667+
// Pass the handler directly — no manual wiring needed.
668+
websocket: rpcHandler,
669+
});
670+
```
671+
672+
If you need to attach custom data to connections or add your own logic to the WebSocket handlers, use `newBunWebSocketRpcSession()` instead, which gives you direct access to the transport:
673+
674+
```ts
675+
import { newBunWebSocketRpcSession, newHttpBatchRpcResponse, RpcTarget } from "capnweb";
676+
677+
class MyApiImpl extends RpcTarget implements MyApi {
678+
// ... define API, same as above ...
679+
}
680+
681+
Bun.serve({
682+
fetch(req, server) {
683+
let userId = authenticate(req);
684+
server.upgrade(req, { data: { userId } });
685+
},
686+
websocket: {
687+
open(ws) {
688+
let { stub, transport } = newBunWebSocketRpcSession(ws, new MyApiImpl());
689+
ws.data.transport = transport;
690+
},
691+
message(ws, msg) { ws.data.transport.dispatchMessage(msg); },
692+
close(ws, code, reason) { ws.data.transport.dispatchClose(code, reason); },
693+
error(ws, err) { ws.data.transport.dispatchError(err); },
694+
},
695+
});
696+
```
697+
633698
### HTTP server on other runtimes
634699

635700
Every runtime does HTTP handling and WebSockets a little differently, although most modern runtimes use the standard `Request` and `Response` types from the Fetch API, as well as the standard `WebSocket` API. You should be able to use these two functions (exported by `capnweb`) to implement both HTTP batch and WebSocket handling on all platforms:
@@ -654,6 +719,8 @@ function newWebSocketRpcSession(
654719
: Disposable;
655720
```
656721

722+
Note: Bun's `ServerWebSocket` does not implement the standard `WebSocket` `addEventListener` interface. If you are using Bun, use the dedicated `newBunWebSocketRpcHandler()` or `newBunWebSocketRpcSession()` described above.
723+
657724
### HTTP server using Hono
658725

659726
If your app is built on [Hono](https://hono.dev/) (on any runtime it supports), check out [`@hono/capnweb`](https://github.com/honojs/middleware/tree/main/packages/capnweb).
@@ -735,4 +802,6 @@ let stub: RemoteMainInterface = session.getRemoteMain();
735802
// Now we can call methods on the stub.
736803
```
737804

805+
Cap'n Web's built-in transports (WebSocket, MessagePort, HTTP batch, and Bun ServerWebSocket) are all implemented on top of this interface.
806+
738807
Note that sessions are entirely symmetric: neither side is defined as the "client" nor the "server". Each side can optionally expose a "main interface" to the other. In typical scenarios with a logical client and server, the server exposes a main interface but the client does not.

0 commit comments

Comments
 (0)