Skip to content

Add support for WireFormat + binaryBytes passthrough#133

Open
ashkalor wants to merge 1 commit intocloudflare:mainfrom
ashkalor:feat/wire-format
Open

Add support for WireFormat + binaryBytes passthrough#133
ashkalor wants to merge 1 commit intocloudflare:mainfrom
ashkalor:feat/wire-format

Conversation

@ashkalor
Copy link

@ashkalor ashkalor commented Feb 8, 2026

  • Introduces a WireFormat interface so that users can plug in binary formats (e.g. CBOR)
  • Widens all transports (RpcTransport, WebSocket, MessagePort, batch) from string to string | ArrayBuffer
  • Adds binaryBytes option to pass Uint8Array through raw instead of base64-encoding
  • Backwards compatible as jsonFormat is set as default.

My main motivation was to be able to directly pass messages without having to message -> base64 -> stringify - > parse -> decode -> message.
If we use a package like CBOR, message -> encode -> decode (which is very fast) -> message, skipping the whole base64 encode and decode. This basically allows someone in the userland to define their own interface that works with RPC

  import { type WireFormat, newWebSocketRpcSession } from "capnweb";
  import { encode, decode } from "cbor2";

  const cborFormat: WireFormat = {
    encode(value: unknown): ArrayBuffer {
      return encode(value);
    },
    decode(data: string | ArrayBuffer): unknown {
      if (typeof data === "string") {
        throw new TypeError("cborFormat expects binary data, got string");
      }
      return decode(new Uint8Array(data));
    },
  };

  // Both sides must use the same format
  let stub = newWebSocketRpcSession<MyApi>("wss://example.com/rpc", undefined, {
    format: cborFormat,
    binaryBytes: true,  // pass Uint8Array raw instead of base64-encoding
  });

  let result = await stub.processImage(new Uint8Array(pixelData));

I did some benchmarks for this

Wire Size

Scenario JSON CBOR Savings
Small RPC call (~40B) 31 B 22 B 29%
Medium object (~2KB) 1.3 KB 916 B 30%
Large binary (64KB) 85.4 KB 64.0 KB 25%
OccupancyGrid (1024x1024) 1.3 MB 1.0 MB 25%
PointCloud (50K pts, 800KB) 1.0 MB 781.4 KB 25%

Performance

Scenario Op JSON CBOR Winner
Small RPC (~40B) encode 125 ns 3.28 µs JSON 26x
decode 234 ns 2.33 µs JSON 10x
full RT 984 ns 6.41 µs JSON 7x
Medium obj (~2KB) encode 2.04 µs 193.56 µs JSON 95x
decode 4.82 µs 35.62 µs JSON 7x
full RT 11.40 µs 238.23 µs JSON 21x
Large binary (64KB) encode 9.59 µs 29.85 µs JSON 3x
decode 12.14 µs 2.48 µs CBOR 5x
full RT 44.04 µs 36.57 µs CBOR 1.2x
OccupancyGrid (1M) encode 152.82 µs 369.45 µs JSON 2x
decode 194.68 µs 10.13 µs CBOR 19x
full RT 783.11 µs 395.57 µs CBOR 2x
PointCloud (800KB) encode 121.17 µs 245.80 µs JSON 2x
decode 139.23 µs 6.45 µs CBOR 22x
full RT 554.20 µs 255.84 µs CBOR 2x

P.S: Huge fan of your work btw! Been following your work from capnproto days!

@changeset-bot
Copy link

changeset-bot bot commented Feb 8, 2026

⚠️ No Changeset found

Latest commit: b2beab7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 8, 2026

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

@ashkalor
Copy link
Author

ashkalor commented Feb 8, 2026

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

github-actions bot added a commit that referenced this pull request Feb 8, 2026
@ashkalor
Copy link
Author

Hey any updates on this? @kentonv If this approach is against the vision of this library, I'd be happy to make necessary changes to accomodate the vision. Please let me know.

@kentonv
Copy link
Member

kentonv commented Feb 12, 2026

Hi, sorry, this is in my inbox but it's a pretty busy inbox and I tend to handle the capnweb stuff in batches every couple weeks.

Looking briefly, I wonder if a different design would work with less code needed: Instead of giving the system a WireFormat hook, what if you could declare that your transport wants to send and receive messages in JS object format rather than string format. In this case the system would pass the pre-encoded objects to send() and expect already-decoded objects from receive(). The transport can then do whatever encoding it wants internally.

Actually I could imagine we have multiple levels of encoding:

  • string: JSON encoding applied already.
  • JSON-ready: JS object, but all contents are JSON-compatible.
  • JSON-ready + Uint8Array: JS object except bytes are kept in Uint8Array format, for encoders that support that.
  • Structured clone: JS object that is structured clonable. The system only applies encoding to RPC stubs. We could change the MessagePort transport to use this.

@ashkalor
Copy link
Author

That makes a lot of sense, so basically you want this to be handled in the transport layer. Are you comfortable with me adding Generic type support to RPC transport? Something like below

 // Core types in src/serialize.ts:
 export type EncodingLevel = "stringify" | "devalue" | "partial" | "passthrough";
 export type TransportData<L extends EncodingLevel> = L extends "stringify" ? string : object;

 // Generic RpcTransport in src/rpc.ts:
 export interface RpcTransport<L extends EncodingLevel = "stringify"> {
   readonly encodingLevel?: L;
   send(message: TransportData<L>): Promise<void>;
   receive(): Promise<TransportData<L>>;
   abort?(reason: any): void;
 }

Where
stringify -> json
devalue -> plain js object
partial -> Partial devalue + Uint8array
passthrough -> structured clone.

Also do you want me to close this pull request and create a new one for these changes? Or can I force push the new changes directly under the same branch?

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.

2 participants