npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sigx/actors-tcp

v0.3.0

Published

Framed TCP transport for @sigx/actors — multiplexed host-to-host connections

Downloads

67

Readme

@sigx/actors-tcp

Framed TCP transport for @sigx/actors: one multiplexed, framed connection per peer instead of one HTTP connection per in-flight request.

pnpm add @sigx/actors-tcp
import { cluster, httpTransport } from '@sigx/actors/cluster';
import { tcpTransport } from '@sigx/actors-tcp';

cluster({
    providers,
    advertise: 'http://10.0.4.7:7311',
    secret: process.env.HOST_SECRET,
    // A CHAIN: TCP wherever the peer advertises it, HTTP everywhere else.
    // That is what makes a rolling deploy of this transport possible.
    transport: [tcpTransport({ port: 11111 }), httpTransport()]
});

Why you would use it

On Node, this is the recommended transport. Measured against a tuned HTTP baseline (pool bounded to the concurrency) at concurrency 64 — benchmarks/BASELINES.md:

| | connections per peer | ops/s | p99 | |---|---:|---:|---:| | tuned HTTP | 64 | 14 287 | 9.6 ms | | TCP | 1 | 69 768 | 1.58 ms |

Two separate wins, and they are worth different amounts:

Socket count — real at any network. HTTP's pool sizes to concurrency × peers (measured at two connections per in-flight request, so ~12 600 per host at c=64 across 99 peers). That is file descriptors, kernel buffers, conntrack entries and a connection burst on every peer restart. One connection per peer does not change with RTT.

Throughput — real, but mostly a loopback effect. The 4.9× is a software ratio: ~70 µs per call versus ~14 µs. On a LAN with a 200–1000 µs round trip that difference is worth roughly 1.1×, not 4.9×. Take the socket property as the reason to choose this; treat the throughput as a bonus that shrinks the further apart your hosts are.

An earlier version of this README said this transport was "not about latency". That was wrong. Per-call HMAC really is worth only 1.19× over a socket — but Node's HTTP stack is a separate and much larger cost, and a framed protocol on a persistent socket skips it. The Tier-2 rig caught it.

HTTP remains the default, and must: @sigx/actors/cluster stays zero-dep and WinterCG-clean so Cloudflare Workers keep working, and HTTP is the only transport that runs everywhere. With a bounded pool it is a perfectly reasonable choice.

Deploying it

HostDescriptor.addresses carries a tcp entry per host, so a mixed cluster is expressible and the rollout is safe:

  1. Deploy with transport: [tcpTransport(), httpTransport()] everywhere.
  2. Hosts that have the new build advertise tcp and use it with each other; hosts that do not are still reached over HTTP.
  3. Once every host advertises tcp, drop httpTransport() from the chain if you want the internal HTTP mount gone entirely.

Step 3 is optional and has a consequence worth knowing: with no HTTP transport in the chain there is no internal /_sigx/host mount at all — a smaller attack surface, but nothing to curl. The public actor wire is unaffected.

Options

| option | default | | |---|---|---| | port | 0 | Listen port. 0 binds an ephemeral port, which is then what gets advertised. | | host | all interfaces | Bind address. | | advertiseHost | 127.0.0.1 | Host peers should dial. Set this on a multi-homed box. | | maxFrameBytes | 8 MiB | Frames larger than this are refused before any payload is buffered. | | credit | 32 | Stream chunks a consumer accepts before it must extend credit. | | keepAliveMs | 15 000 | Idle PING interval. 0 disables. | | handshakeTimeoutMs | 10 000 | How long an accepted connection may go without naming itself before it is closed. 0 disables. | | maxPendingInbound | 256 | Most un-handshaken connections held at once; further ones are closed on accept. 0 disables. |

This transport belongs on a private network. It binds all interfaces unless you set host, and it speaks no TLS — the cluster HMAC authenticates the peer, not the link. Put it on a pod network, a VPC or an mTLS-terminated mesh, never on a public interface. The last two options above bound what an unauthenticated connection can cost you before it says who it is; they are not a substitute for not being reachable.

Both are validated at construction: a negative or non-finite value throws rather than quietly disabling the bound it was meant to set.

How it works

Frames are @sigx/actors/cluster/frames — a 12-byte big-endian header (length, type, flags, status, corrId) then a JSON payload through the same wire codec the HTTP transport uses, so registered type handlers and the prototype-pollution reviver apply identically.

Four things are load-bearing:

  • Cancellation is a frame, not a socket close. With many streams multiplexed on one connection, closing it would cancel everything. A CANCEL frame targets one stream, and on receipt the callee both aborts and calls generator.return() — an async generator parked at yield never runs its finally from a signal alone.
  • Backpressure is applied at the generator. Credit is checked before next() is pulled, so a slow consumer stops the producer rather than filling a buffer behind it.
  • A dropped connection fails its in-flight calls as unreachable, and never retries them. The placement already evicts, refreshes and re-resolves; silently re-sending a non-idempotent actor method to a host that may no longer own the actor would be a correctness bug.
  • Simultaneous dial is settled without an extra round trip: the lexicographically smaller hostId is the designated dialer and its outbound connection wins, so exactly one survives.

Authentication is the same per-call HMAC the HTTP transport uses, over the same shared secret. Transport encryption is an operator concern — run mTLS or a private network between hosts.

Conformance

This package runs the shared transport conformance suite (@sigx/actors/cluster/testing), which was written against httpTransport() before this transport existed — so it describes the contract rather than this implementation's habits. All 18 cases pass, including the two link-hygiene cases that HTTP skips because it holds no connections.

Requirements

Node (or Bun/Deno) — it imports node:net. Not WinterCG-clean, which is precisely why it is a separate package: @sigx/actors/cluster stays zero-dep so Cloudflare Workers keep working, and HTTP stays the default.