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

@rvncom/socketio-bun-engine

v1.1.5

Published

Engine.IO server implementation for Bun runtime

Readme

@rvncom/socketio-bun-engine

CI npm version npm downloads license

Engine.IO server implementation for the Bun runtime. Provides native WebSocket and HTTP long-polling transports for Socket.IO.

Fork of @socket.io/bun-engine with bug fixes, improved API, and active maintenance.

Installation

bun add @rvncom/socketio-bun-engine

Usage

import { Server as Engine } from "@rvncom/socketio-bun-engine";
import { Server } from "socket.io";

const engine = new Engine({
  path: "/socket.io/",
});

const io = new Server();
io.bind(engine);

io.on("connection", (socket) => {
  // ...
});

export default {
  port: 3000,
  ...engine.handler(),
};

You can also use engine.handleRequest() directly for custom routing:

Bun.serve({
  port: 3000,

  fetch(req, server) {
    const url = new URL(req.url);

    if (url.pathname === "/health") {
      return new Response(
        JSON.stringify({ status: "ok", connections: engine.clientsCount }),
        {
          headers: { "Content-Type": "application/json" },
        },
      );
    }

    return engine.handleRequest(req, server);
  },

  websocket: engine.handler().websocket,
});

Options

path

Default: /engine.io/

The path to handle on the server side. Must match the client configuration.

pingTimeout

Default: 20000

Milliseconds without a pong packet before considering the connection closed.

pingInterval

Default: 25000

Milliseconds between ping packets sent by the server.

upgradeTimeout

Default: 10000

Milliseconds before an uncompleted transport upgrade is cancelled.

maxHttpBufferSize

Default: 1e6 (1 MB)

Maximum message size in bytes before closing the session.

pollingTimeout

Default: 60000 (60 seconds)

Milliseconds before a pending polling request times out. If the client doesn't send a new poll within this window, the transport is closed.

maxClients

Default: 0 (unlimited)

Maximum number of concurrent clients. New connections are rejected with HTTP 503 when the limit is reached.

maxHandshakesPerSecond

Default: 0 (unlimited)

Maximum number of new handshakes allowed per second (global). Excess handshakes receive HTTP 429 with Retry-After: 1. Protects against connection flood attacks.

const engine = new Engine({
  maxHandshakesPerSecond: 100, // max 100 new connections/sec
});

maxHandshakesPerIp

Default: 0 (unlimited)

Maximum number of new handshakes per second from a single IP address. Excess receive HTTP 429. Combine with maxHandshakesPerSecond for layered protection — a single noisy IP can't exhaust the global budget for legitimate clients.

const engine = new Engine({
  maxHandshakesPerSecond: 1000, // global
  maxHandshakesPerIp: 20, // per-IP
});

backpressureThreshold

Default: 1048576 (1 MB)

WebSocket send buffer threshold in bytes. When getBufferedAmount() exceeds this value, writes are paused automatically and resumed when the buffer drains. Set to 0 to disable.

rateLimit

Per-socket message rate limiting. Disabled by default.

const engine = new Engine({
  rateLimit: {
    maxMessages: 100, // max messages per window
    windowMs: 1000, // window duration in ms
  },
});

engine.on("connection", (socket) => {
  socket.on("rateLimited", () => {
    console.log(`Socket ${socket.id} rate limited`);
  });
});

perMessageDeflate

Default: false

Enable WebSocket per-message deflate compression (RFC 7692). Pass true for defaults or a Bun.WebSocketPerMessageDeflateOptions object for fine-grained control. Provides 50-70% bandwidth savings for text-heavy payloads.

const engine = new Engine({
  perMessageDeflate: true,
});

enableMetrics

Default: false

Controls whether per-message byte counting (bytesReceived, bytesSent, avgRtt, upgrades) is active from the start. When false, these metrics activate lazily on first server.metrics access. Connection and disconnection counters are always tracked regardless of this option.

const engine = new Engine({
  enableMetrics: true, // attach byte-counting listeners immediately
});

degradationThreshold

Default: 0 (disabled)

Fraction (0–1) of maxClients at which graceful degradation activates. Requires maxClients > 0. When active:

  • New polling connections are rejected (WebSocket only, returns 503)
  • New connections get doubled pingInterval to reduce heartbeat overhead
const engine = new Engine({
  maxClients: 10000,
  degradationThreshold: 0.8, // degrade at 8000+ clients
});

engine.on("degradation", ({ active, clients }) => {
  console.log(`Degradation ${active ? "ON" : "OFF"} at ${clients} clients`);
});

allowRequest

A function that receives the handshake/upgrade request and can reject it:

const engine = new Engine({
  allowRequest: (req, server) => {
    return Promise.reject("not allowed");
  },
});

cors

Cross-Origin Resource Sharing options:

const engine = new Engine({
  cors: {
    origin: ["https://example.com"],
    allowedHeaders: ["my-header"],
    credentials: true,
  },
});

editHandshakeHeaders

Edit response headers for the handshake request:

const engine = new Engine({
  editHandshakeHeaders: (responseHeaders, req, server) => {
    responseHeaders.set("set-cookie", "sid=1234");
  },
});

editResponseHeaders

Edit response headers for all requests:

const engine = new Engine({
  editResponseHeaders: (responseHeaders, req, server) => {
    responseHeaders.set("my-header", "abcd");
  },
});

Metrics

Built-in server metrics with zero dependencies. Per-message byte counting is lazy by default — counters activate on first server.metrics access (or immediately with enableMetrics: true). Connection/disconnection counters are always active.

const snapshot = engine.metrics;
// {
//   connections: 150,        // total opened (cumulative)
//   disconnections: 12,      // total closed
//   activeConnections: 138,  // currently connected
//   upgrades: 130,           // polling → websocket
//   bytesReceived: 524288,
//   bytesSent: 1048576,
//   errors: 2,
//   avgRtt: 14,              // average round-trip time (ms, max 1000 samples)
//   pollingCount: 8,         // currently connected polling transports
//   websocketCount: 130      // currently connected websocket transports
// }

Per-socket RTT is also available:

engine.on("connection", (socket) => {
  socket.on("heartbeat", () => {
    console.log(`RTT: ${socket.rtt}ms`);
  });
});

API

server.clientsCount

Number of currently connected clients.

server.metrics

Returns a MetricsSnapshot object with server-wide counters.

server.sockets

Iterator over all connected Socket instances.

server.getSocket(id)

Look up a specific socket by session ID.

server.use(middleware)

Registers a middleware function run on each handshake before allowRequest. Middlewares are invoked in registration order. Call next() to continue, or next(err) to reject the handshake with HTTP 403.

engine.use((req, server, next) => {
  const token = new URL(req.url).searchParams.get("token");
  if (!token) return next(new Error("missing token"));
  // synchronous or async work, then continue
  next();
});

engine.use(async (req, server, next) => {
  await checkAuth(req);
  next();
});

socket.remoteAddress

Resolved client IP address (string) from Bun.Server.requestIP(). May be undefined if Bun can't resolve it (some proxy setups).

server.broadcast(data)

Sends a message to all connected sockets. The packet is encoded once and sent as pre-encoded data to WebSocket transports (zero-copy). Polling transports fall back to the normal path.

server.broadcastExcept(excludeId, data)

Sends a message to all connected sockets except the one with the given id. Same zero-copy optimization as broadcast().

server.degraded

Returns true if the server is currently in degraded mode.

server.shutdown(opts?)

Gracefully shuts down the server. Stops accepting new connections (returns 503), sends close to all existing clients, and resolves when all are disconnected or after the timeout.

await engine.shutdown({ timeout: 10000 }); // default: 10s

engine.on("shutdown", () => {
  console.log("Server shut down");
});

Options:

  • timeout (default: 10000): Maximum time in milliseconds to wait for clients to disconnect before force-closing.

server.draining

Returns true after shutdown() has been called.

server.close()

Returns a Promise<void> that resolves when all clients have disconnected.

socket.bytesSent / socket.bytesReceived

Cumulative byte counters for this socket's message traffic. Counts payload bytes only (excludes protocol framing).

socket.messagesSent / socket.messagesReceived

Cumulative message counters for this socket.

socket.connectedAt

Timestamp (Date.now()) of when the socket was created. Useful for computing session duration:

engine.on("connection", (socket) => {
  socket.on("close", () => {
    const duration = Date.now() - socket.connectedAt;
    console.log(
      `Socket ${socket.id}: ${socket.messagesSent} sent, ${socket.bytesReceived} bytes recv, ${duration}ms`,
    );
  });
});

Debugging

Enable debug logs with the NODE_DEBUG environment variable:

# All engine.io logs
NODE_DEBUG=engine.io bun run server.ts

# Specific subsystems
NODE_DEBUG=engine.io:socket bun run server.ts      # Socket logs only
NODE_DEBUG=engine.io:websocket bun run server.ts   # WebSocket logs only
NODE_DEBUG=engine.io:polling bun run server.ts     # Polling logs only

# Multiple subsystems
NODE_DEBUG=engine.io:socket,engine.io:websocket bun run server.ts

Requirements

  • Bun >= 1.0.0
  • TypeScript >= 5.9.2 (peer dependency)

Benchmarks

Benchmarked on GitHub Actions (ubuntu-latest), v1.1.4 vs @socket.io/bun-engine. Full report.

| Metric | vs upstream | @rvncom | @socket.io | |--------|------------|---------|------------| | Throughput | 1.1x faster | 246,305 msg/s | 215,517 msg/s | | Connections | 5% slower | 874 conn/s | 924 conn/s | | Latency (p95) | ~same | 1.6 ms | 1.7 ms |

License

MIT