Actors/Packages/WebSocket
@sigx/actors-ws · Preview

WebSocket#

The same frames as TCP, over one port — the one your proxy already forwards.

v0.1.0 MIT

Installation#

Terminal
pnpm add @sigx/actors-ws ws
TypeScript
import { createServer } from 'node:http';
import { defineActorApp } from '@sigx/actors/host';
import { createAppHandler } from '@sigx/actors/node';
import { cluster, httpTransport } from '@sigx/actors/cluster';
import { wsTransport } from '@sigx/actors-ws';

const ws = wsTransport({
    advertiseUrl: () => `ws://10.0.4.7:7311/_sigx/host-ws`,
});

const app = defineActorApp({ actors, storage }).use(
    cluster({ providers, advertise: 'http://10.0.4.7:7311', secret, transport: [ws, httpTransport()] }),
);

const server = createServer(createAppHandler(app));
await ws.attach(server);          // the upgrade a route cannot express
await new Promise<void>((r) => server.listen(7311, r));
await app.start();

Why this and not TCP#

Raw TCP is the better wire where you control the network. WebSocket earns its place in the cases TCP cannot reach:

  • One port. Frames ride the HTTP listener the host already has — no second port in a security group, a Service, or a firewall rule.
  • Through proxies and load balancers. They forward WebSocket; they will generally not forward an arbitrary TCP protocol.
  • Dialable from WinterCG runtimes, since the client half is the standard WebSocket. node:net can never be.

The trade is a small framing tax over raw TCP, and a ws peer dependency for the Node server half.

Measured#

At concurrency 64 against a tuned HTTP baseline:

Connections per peerops/sp99bytes/call
tuned HTTP6414,2879.6 ms640
WebSocket163,4951.58 ms236

Read the two wins differently: the socket count holds at any RTT, while the 4.4× throughput is a software ratio a real network largely absorbs — ~70µs versus ~16µs per call, against a LAN round trip of 200–1000µs.

HTTP remains the default and must, so Workers keep working.

Why attach() exists#

ActorRoute.handle returns a Response and cannot express a Node WebSocket upgrade — that needs the raw socket. So this is the one piece the plugin's route seam cannot carry, and the package deliberately does not own your server: you hand it yours.

Next steps#