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

@swarmmachina/swm-core

v2.0.2

Published

High-performance HTTP/WebSocket server built on swm-uws with an opt-in node:http backend

Readme

@swarmmachina/swm-core

License: MPL 2.0 Node.js Version dependencies stability

A high-performance HTTP/WebSocket server built on the @swarmmachina/swm-uws native binding, with a bundled node:http backend available as an explicit fallback.

Features

  • uWS by default - Native HTTP/WebSocket transport on the primary path
  • Opt-in Node backend - Pure node:http + RFC 6455 fallback via backend: 'node'
  • HTTP + WebSocket - Both protocols in a single server instance
  • Context pooling - Minimizes garbage collection overhead
  • Graceful shutdown - Cleanly closes active connections
  • Streaming support - Efficient handling of large payloads
  • Auto Content-Type detection - Automatically sets headers based on response type
  • Modern ES modules - Native ESM support (Node.js 22 and 24)

Installation

# Install the package and its swm-uws runtime dependency
npm install @swarmmachina/swm-core

Runtime requirements

The default backend depends on the native @swarmmachina/swm-uws addon, which ships platform-specific prebuilds:

  • Node.js 22 or 24 — other majors are rejected by the package engine constraint.
  • Linux x64 with glibc — use a bookworm/slim image rather than Alpine/musl.
  • Windows x64 and macOS arm64/x64 are supported.
  • Linux ARM64, Windows ARM64 and musl are not supported. Use backend: 'node' on unsupported hosts.
  • TLS and permessage-deflate are disabled in the native binding; terminate TLS before the application.

Backends

The server runs on a selectable transport backend, chosen with the backend option:

| backend | Transport | Status | | ----------------- | ----------- | ------------------------------------------------------------ | | 'uws' (default) | swm-uws | Primary native engine. HTTP + WebSocket. Highest throughput. | | 'node' | node:http | Experimental opt-in fallback. No permessage-deflate. |

// Default: @swarmmachina/swm-uws
const server = new Server({ port: 6000, router })

// Explicit fallback: bundled node:http backend
const fallback = new Server({ backend: 'node', port: 6000, router })

The experimental, opt-in 'node' backend is bundled with this package; no second package is needed. It serves the full HTTP and WebSocket API on node:http plus a pure-JS RFC 6455 implementation. It does not implement the permessage-deflate compression extension. WebSocket conformance is checked with the Autobahn TestSuite (npm run test:autobahn, requires docker).

Because the package installs @swarmmachina/swm-uws as a runtime dependency, a missing or platform-incompatible native addon is treated as an installation error when the default backend starts. backend: 'node' remains an explicit runtime fallback.

Native binding regression gate

The default runtime is @swarmmachina/[email protected]. The regression gate runs the same swm-core HTTP and WebSocket paths against the dev-only [email protected] reference, changing only the native binding.

npm run test:e2e:bindings
npm run bench:bindings
npm run bench:bindings:deep

The performance command runs four measured iterations in balanced AB/BA order after a two-second warmup. HTTP uses 100 connections, six seconds and pipelining 10; WebSocket echo uses 50 connections, six seconds and 64-byte messages. The report includes throughput, HTTP p97.5/p99 and WebSocket p95/p99 latency, ELU, RSS, heap and errors, and writes its JSON artifact to benchmark/profiles/binding-compare/summary.json. Override the parameters with the BINDING_BENCH_* environment variables defined in benchmark/binding-compare.js.

Use bench:bindings:deep when validating a release or investigating a noisy result. It runs six balanced AB/BA pairs across HTTP concurrency/pipelining, headers, request bodies, and closed/open-loop WebSocket workloads. Raw binding servers are measured alongside the same paths through swm-core, which separates native binding cost from framework overhead. Its report uses paired throughput and latency deltas with an interquartile range and writes artifacts to benchmark/profiles/binding-deep/. Override duration, warmup, runs, and sampling with the DEEP_BINDING_* variables in benchmark/binding-deep-compare.js.

To advance the runtime binding and its upstream reference together, run:

npm run deps:update:bindings -- 0.4.1 v20.69.0
npm run test:e2e:bindings

The updater changes both dependency pins, regenerates package-lock.json, and updates the version references in this README. Commit the generated lockfile; do not edit the two pins independently.

By default, swm-core enables the measured beginWrite, collectBody, and requestPause capabilities. Set SWM_UWS_NATIVE_FAST_PATHS=0 to disable every capability-gated path without changing the backend, or pass a comma-separated allowlist. SWM_UWS_NATIVE_FAST_PATHS=all also enables experimental request snapshot and response batching; those two paths remain opt-in until they meet the performance gate.

Quick Start

Basic HTTP Server

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  router: (ctx) => {
    return { message: 'Hello World' }
  }
})

await server.listen()
console.log('Server listening on port 3000')

HTTP Server with Routing (Traditional API)

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  router: async (ctx) => {
    // Simple routing
    if (ctx.url() === '/' && ctx.method() === 'get') {
      return { message: 'Welcome to the API' }
    }

    if (ctx.url() === '/users' && ctx.method() === 'get') {
      return { users: await getUsers() }
    }

    if (ctx.url() === '/users' && ctx.method() === 'post') {
      const data = await ctx.json()
      return await createUser(data)
    }

    // 404 Not Found
    ctx.status(404)
    return { error: 'Not found' }
  },
  onHttpError: (ctx, error) => {
    console.error('HTTP Error:', error)
  }
})

await server.listen()

HTTP Server with Declarative Routing (routes API)

The routes API provides method-specific routing, URL parameters and wildcard matching on both backends. With the default uWS backend, routes are registered directly with uWebSockets.js; the Node backend uses the bundled JavaScript router.

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  routes: [
    {
      method: 'get',
      path: '/',
      handler: () => ({ message: 'Welcome to the API' })
    },
    {
      method: 'get',
      path: '/users',
      handler: async () => ({ users: await getUsers() })
    },
    {
      method: 'get',
      path: '/users/:id',
      handler: (ctx) => {
        const id = ctx.param('id') // or ctx.param(0)
        return getUserById(id)
      }
    },
    {
      method: 'post',
      path: '/users',
      handler: async (ctx) => {
        const data = await ctx.json()
        return await createUser(data)
      }
    },
    {
      method: 'put',
      path: '/users/:id',
      handler: async (ctx) => {
        const id = ctx.param('id')
        const data = await ctx.json()
        return await updateUser(id, data)
      }
    },
    {
      method: 'delete',
      path: '/users/:id',
      handler: (ctx) => {
        const id = ctx.param('id')
        return deleteUser(id)
      }
    }
  ],
  onHttpError: (ctx, error) => {
    console.error('HTTP Error:', error)
  }
})

await server.listen()

Benefits of Declarative Routing:

  • Direct uWS registration - On the default uWS backend, routes are registered with the native engine
  • Node backend support - The same route definitions are matched by the bundled JavaScript router
  • URL Parameters - Built-in support for :param syntax
  • Cleaner Code - Declarative route definitions
  • Method-specific - Automatic HTTP method routing
  • Wildcard catch-all - A { method: 'any', path: '/*' } route matches anything not matched by a more specific route (useful as a 404 handler or static-file fallback). Specific routes always win over /*.

WebSocket Server

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  router: (ctx) => {
    return { message: 'HTTP endpoint' }
  },
  ws: {
    enabled: true,
    wsIdleTimeoutSec: 30,
    onUpgrade: (meta) => ({
      isAllowed: true,
      userData: { ip: meta.ip() }
    }),
    onOpen: (ctx) => {
      console.log('Client connected:', ctx.data.ip)
      ctx.send('Welcome!')
    },
    onMessage: (ctx, message, isBinary) => {
      const text = Buffer.from(message).toString()
      console.log('Received:', text)
      ctx.send(`Echo: ${text}`)
    },
    onClose: (ctx, code, message) => {
      console.log('Client disconnected:', ctx.data.ip)
    },
    onError: (ctx, error) => {
      console.error('WebSocket error:', error)
    }
  }
})

await server.listen()

API Documentation

Server Constructor

new Server(options)

Options:

| Option | Type | Default | Description | | --------------- | ---------- | -------------- | ------------------------------------------------------- | | router | Function | one required | Route handler function (ctx) => any (traditional API) | | routes | Array | one required | Array of route definitions (declarative routing API) | | onHttpError | Function | () => {} | Request error handler (ctx, error) => void | | onServerError | Function | () => {} | Node backend post-listen transport error handler | | port | Number | 6000 | Server port (1-65535) | | maxBodySize | Number | 1 | Max request body size in MB (1-64) | | ws | Object | null | WebSocket configuration (see below) | | backend | String | 'uws' | 'uws' or the experimental opt-in 'node' fallback |

Note: You must provide either router or routes, but not both.

Route Definition (for routes array):

| Property | Type | Description | | ------------ | ------------------ | ------------------------------------------------------------------------------------------------------ | | method | String | HTTP method: 'get', 'post', 'put', 'delete'/'del', 'patch', 'options', 'head', 'any' | | path | String | URL path pattern. Supports :param segments and a /* wildcard catch-all | | handler | Function | Handler function (ctx) => any \| Promise<any> | | preHandler | Function/Array | Optional. One function or an array, run before handler (see Route preHandlers) |

WebSocket Options (ws object):

| Option | Type | Default | Description | | -------------------- | ---------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | enabled | Boolean | false | Enable WebSocket support. If not set and at least one ws handler is provided, WS will be enabled automatically. | | wsIdleTimeoutSec | Number | 15 | Idle timeout in seconds (min: 5). On the Node backend this also bounds partial/fragmented message assembly. | | wsUpgradeTimeoutMs | Number | 10000 | Node backend deadline for an asynchronous onUpgrade decision (100-300000 ms). | | onOpen | Function | (ctx) => {} | Called when client connects. | | onMessage | Function | (ctx, message, isBinary) => {} | Called when message received. | | onClose | Function | (ctx, code, message) => {} | Called when client disconnects. | | onDrain | Function | (ctx) => {} | Called when socket is writable again. | | onError | Function | (ctx, error) => {} | Called on WebSocket error. | | onUpgrade | Function | (meta) => ({isAllowed: true, userData?, protocol?}) | Validate WebSocket upgrade. protocol, when returned, must exactly match one token from meta.getHeader('sec-websocket-protocol'). Call metadata getters synchronously before any await; the underlying uWS request is only valid for the synchronous callback. | | onSubscription | Function | (ctx, topic, newCount, oldCount) => {} | Called on topic subscription change. | | connectionKey | Function | undefined | Opt-in. (ctx) => string \| number \| null. Derive a stable key (e.g. a user id) so the connection can be addressed via server.sendTo(). Computed once in onOpen; return nullish to skip. Unset = no registry (zero overhead). |

When a client requests WebSocket subprotocols, onUpgrade must explicitly return the selected token as protocol. The value must be one of the requested tokens; raw client input is never reflected automatically.

Server Methods

server.listen()

Start the server and begin accepting connections.

await server.listen()

server.shutdown([timeout])

Gracefully shutdown the server. Waits for active connections to finish.

server.shutdown(10000) // 10 second timeout

server.close()

Forcefully close the server immediately.

server.close()

server.publish(topic, message, [isBinary])

Publish message to all WebSocket clients subscribed to a topic.

server.publish('news', 'Breaking news!', false)

Returns: boolean - Success status

server.getSubscribersCount(topic)

Get number of subscribers for a topic.

const count = server.getSubscribersCount('news')

Returns: number - Subscriber count

server.sendTo(key, message, [isBinary])

Send a message directly to the single connection registered under key (the value returned from ws.connectionKey). For 1:1 messaging where topic pub/sub would be overkill. Requires ws.connectionKey to be configured.

server.sendTo('user-42', 'private message')

Returns: boolean - true if a live connection was found and the backend did not report the message as dropped; false when the key is unknown or the backpressure limit was exceeded.

Keys are matched with strict Map identity: 42 and '42' are different keys. Pick one type for connectionKey return values and sendTo() arguments.

server.hasConnection(key) / server.getConnection(key) / server.connectionCount

Inspect the connection registry. hasConnection returns a boolean; getConnection returns the backend-specific raw WebSocket handle (or undefined); connectionCount is the number of registered connections. Only the methods declared by the package's RawWebSocket type are portable across backends. Additional uWS methods are available only when backend: 'uws' is selected.

if (server.hasConnection('user-42')) {
  /* ... */
}
const raw = server.getConnection('user-42') // backend-specific handle or undefined
console.log(server.connectionCount)

HttpContext API

The ctx object passed to the router function:

Properties

| Property | Type | Description | | ------------- | --------- | ------------------------------ | | ctx.replied | Boolean | Whether response has been sent | | ctx.aborted | Boolean | Whether request was aborted |

Methods

ctx.method()

Get request lowercased method.

const method = ctx.method()

Returns: string

ctx.url()

Get request url.

const url = ctx.url()

Returns: string

ctx.ip()

Get client IP address.

const ip = ctx.ip()

Returns: string

ctx.query(name)

Get query parameter value.

const page = ctx.query('page') // ?page=1

Returns: string | undefined - undefined when the parameter is absent

ctx.fullQuery()

Get full raw query string.

const q = ctx.fullQuery() // page=1&limit=20

Returns: string

ctx.param(indexOrName)

Get URL parameter by index or name (for pattern matching in the routes API).

// By index
const id = ctx.param(0) // First parameter

// By name (routes API only)
const id = ctx.param('id') // /users/:id

// Multiple parameters
const userId = ctx.param('userId') // /users/:userId/posts/:postId
const postId = ctx.param('postId')

Returns: string | undefined - undefined when the parameter is absent

ctx.header(name)

Get request header value.

const auth = ctx.header('authorization')

Returns: string

ctx.contentLength()

Get a valid non-negative Content-Length value. Returns null when the header is absent or invalid.

const length = ctx.contentLength()

Returns: number | null

ctx.body([maxSize])

Read request body as Buffer.

const buffer = await ctx.body()
const buffer = await ctx.body(5 * 1024 * 1024) // 5MB limit

Returns: Promise<Buffer>

ctx.buffer([maxSize])

Alias for ctx.body([maxSize]).

const buffer = await ctx.buffer()

Returns: Promise<Buffer>

ctx.json([maxSize])

Parse request body as JSON.

const data = await ctx.json()

Returns: Promise<any>

ctx.text([maxSize])

Read request body as text.

const text = await ctx.text()

Returns: Promise<string>

ctx.status(code)

Set response status code. Returns context for chaining.

ctx.status(201).send({ created: true })

Returns: HttpContext

ctx.setHeader(key, value)

Set or replace a staged response header. Header names are case-insensitive. Repeated setHeader() calls replace previously staged values for the same header. Null or undefined values are silently ignored.

ctx.setHeader('x-header-any', 'string-value').status(201).send({ created: true })

Returns: HttpContext

ctx.appendHeader(key, value)

Append another staged response header line without replacing existing values. Useful for repeated headers such as Set-Cookie. Null or undefined values are silently ignored.

ctx.appendHeader('set-cookie', 'access=...; Path=/; HttpOnly')
ctx.appendHeader('set-cookie', 'refresh=...; Path=/refresh; HttpOnly')

Returns: HttpContext

ctx.setHeaders(headers)

Set multiple response headers at once. Equivalent to calling setHeader() for each key. Header values may be strings or arrays of strings. A block returned by prepareHeaders() is also accepted.

ctx.setHeaders({
  'x-request-id': '123',
  'cache-control': 'no-cache',
  'set-cookie': ['a=1; Path=/', 'b=2; Path=/refresh']
})
ctx.flushHeaders([headers])

Flush all staged headers (and optionally stage additional ones) to the underlying response. Called automatically by reply() and startStreaming() — only needed for advanced use cases.

ctx.flushHeaders({ 'x-extra': 'value' })
ctx.send(data)

Send response with automatic content-type detection.

ctx.send({ message: 'OK' }) // application/json
ctx.send('Hello') // text/plain
ctx.send(Buffer.from('data')) // application/octet-stream
ctx.send(null) // 204 No Content

Supported types: Objects and arrays (JSON), strings and other primitive values (text), Buffer/ArrayBuffer/typed-array views (binary), and nullish values (204 No Content).

ctx.sendJson(data, [status])

Send a JSON response with explicit status code. Defaults to 200.

ctx.sendJson({ users: [] })
ctx.sendJson({ error: 'Not found' }, 404)
ctx.sendText(text, [status])

Send a plain text response with explicit status code. Defaults to 200.

ctx.sendText('OK')
ctx.sendText('Created', 201)
ctx.sendBuffer(buffer, [status])

Send a binary response with explicit status code. Defaults to 200.

ctx.sendBuffer(Buffer.from('data'))
ctx.sendBuffer(imageBuffer, 201)
ctx.sendError(error)

Send an error response. If error.status is a finite number, uses that as the HTTP status with error.message as the body. Otherwise responds with 500 Internal Server Error.

ctx.sendError(new Error('Something broke'))

// With custom status
const err = new Error('Not found')
err.status = 404
ctx.sendError(err)
ctx.reply(status, headers, body)

Send response with full control over status, headers, and body. Header values may be strings or arrays of strings. Array values are written as separate header lines.

ctx.reply(
  200,
  {
    'content-type': 'application/json',
    'set-cookie': ['a=1; Path=/', 'b=2; Path=/refresh']
  },
  '{"ok":true}'
)
ctx.stream(readable, [status], [headers])

Stream a readable stream to the response.

import fs from 'fs'

const stream = fs.createReadStream('./large-file.mp4')
await ctx.stream(stream, 200, { 'content-type': 'video/mp4' })

Returns: Promise<void>

ctx.startStreaming([status], [headers])

Start streaming response manually (for advanced use cases). Header values may be strings or arrays of strings.

ctx.startStreaming(200, {
  'content-type': 'text/plain',
  'set-cookie': ['a=1; Path=/', 'b=2; Path=/refresh']
})
ctx.write(chunk)

Write chunk to streaming response.

const ok = ctx.write('chunk of data')
if (!ok) {
  // backpressure, pause writing
}

Returns: boolean - false if backpressure detected

ctx.end([chunk])

End streaming response.

ctx.end('final chunk')
ctx.onWritable(callback)

Register callback to be called when the response stream becomes writable again (for backpressure handling). The callback receives the current write offset.

ctx.onWritable((offset) => {
  // Socket is writable again, can resume writing
  // offset is the current write offset
})

Returns: void

ctx.tryEnd(chunk, totalSize)

Try to finish a streaming response whose total byte size is known. totalSize is required and is passed to the backend's tryEnd implementation.

const finalChunk = Buffer.from('final chunk')
const totalSize = ctx.getWriteOffset() + finalChunk.byteLength
const [ok, done] = ctx.tryEnd(finalChunk, totalSize)
if (done) {
  // Response is complete
}

Returns: [boolean, boolean] - [ok, done] where ok indicates success and done indicates completion

ctx.getWriteOffset()

Get the current write offset (useful for tryEnd and backpressure handling).

const offset = ctx.getWriteOffset()

Returns: number - Current write offset

WSContext API

The ctx object passed to WebSocket handlers:

Properties

| Property | Type | Description | | ---------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ctx.data | Object | User data from onUpgrade return value (userData field) | | ctx.ws | RawWebSocket | Backend-specific raw WebSocket handle. Identity-stable for the connection; only methods in the exported RawWebSocket type are portable across backends (see Context lifetime & pooling) | | ctx.key | string/number | Key this connection is registered under (from connectionKey), or null. Read-only. |

Methods

ctx.send(data, [isBinary])

Send message to this client.

ctx.send('Hello client!')
ctx.send(Buffer.from([1, 2, 3]), true) // binary

Returns: number — backend-neutral send status mirroring uWS: 1 success, 0 backpressure (queued behind backpressure), 2 dropped (not sent — backpressure limit exceeded). Check it to react to backpressure.

ctx.end([code], [reason])

Close this WebSocket connection.

ctx.end(1000, 'Goodbye')
ctx.subscribe(topic)

Subscribe this client to a topic.

ctx.subscribe('news')

Returns: boolean - Success status

ctx.unsubscribe(topic)

Unsubscribe this client from a topic.

ctx.unsubscribe('news')

Returns: boolean - Success status

ctx.publish(topic, message, [isBinary])

Publish message to all subscribers of a topic.

ctx.publish('news', 'Breaking news!')

Returns: boolean - Success status

ctx.decode(message)

Decode a received binary message to a UTF-8 string — an opt-in convenience for the common Buffer.from(message).toString(). Not called automatically, so handlers that work with raw bytes pay nothing.

onMessage: (ctx, message) => {
  const text = ctx.decode(message)
}

Returns: string

Call it synchronously inside the handler: uWS neuters the message ArrayBuffer at the first await/return, so decoding it later throws. Unlike ctx, the message argument does not survive await — decode first, then await.

WSContext lifetime & pooling

HttpContext is pooled and reused across requests to minimize GC overhead; WSContext is allocated fresh per connection and never reused — that is what guarantees the fail-loud behavior below. Do not reason about WSContext by analogy to HttpContext.

| Context | Allocated per… | Valid for… | Safe to retain? | | ------------- | ------------------------- | ----------------------------- | -------------------------------------------- | | HttpContext | request (pooled, reused) | a single request/response | No — released when the response is finalized | | WSContext | connection (never reused) | the whole connection lifetime | Yes, for the connection; not past onClose |

  • One instance per connection. The same WSContext is passed to every callback of a given connection (onOpen, onMessage, onClose, onDrain, onSubscription). Instances are not shared between connections, so ctx never silently switches sockets — you can hold it across an await and use it as a stable per-connection identity.
  • Don't use it past onClose. After close the instance is cleared, so a retained reference fails loudly (ws is null) instead of acting on a stale socket. Don't stash ctx in a structure that outlives the connection.
  • Address other connections by key, not by ctx. To reach a different connection (from another handler, a timer, an HTTP route), set connectionKey and use server.sendTo(), or keep the raw ctx.ws handle yourself. See Direct messaging between connections.

Examples

REST API with Error Handling

import Server from '@swarmmachina/swm-core'

const users = new Map()

const server = new Server({
  port: 3000,
  router: async (ctx) => {
    try {
      // GET /users
      if (ctx.url() === '/users' && ctx.method() === 'get') {
        return Array.from(users.values())
      }

      // GET /users/:id
      if (ctx.url().startsWith('/users/') && ctx.method() === 'get') {
        const id = ctx.url().split('/')[2]
        const user = users.get(id)

        if (!user) {
          return ctx.status(404).send({ error: 'User not found' })
        }

        return user
      }

      // POST /users
      if (ctx.url() === '/users' && ctx.method() === 'post') {
        const data = await ctx.json()

        if (!data.name || !data.email) {
          return ctx.status(400).send({ error: 'Missing required fields' })
        }

        const user = { id: Date.now().toString(), ...data }
        users.set(user.id, user)

        return ctx.status(201).send(user)
      }

      // 404
      return ctx.status(404).send({ error: 'Not found' })
    } catch (error) {
      console.error('Route error:', error)
      return ctx.status(500).send({ error: 'Internal server error' })
    }
  },
  onHttpError: (ctx, error) => {
    console.error(`HTTP Error [${ctx.method()} ${ctx.url()}]:`, error)
  }
})

await server.listen()
console.log('REST API running on http://localhost:3000')

File Upload

import Server from '@swarmmachina/swm-core'
import fs from 'fs/promises'

const server = new Server({
  port: 3000,
  maxBodySize: 10, // 10 MB
  router: async (ctx) => {
    if (ctx.url() === '/upload' && ctx.method() === 'post') {
      const filename = ctx.query('filename') || 'upload.bin'
      const body = await ctx.body()

      await fs.writeFile(`./uploads/${filename}`, body)

      return ctx.status(201).send({
        success: true,
        filename,
        size: body.length
      })
    }

    return ctx.status(404).send({ error: 'Not found' })
  }
})

await server.listen()

File Streaming

import Server from '@swarmmachina/swm-core'
import fs from 'fs'

const server = new Server({
  port: 3000,
  router: async (ctx) => {
    if (ctx.url() === '/download' && ctx.method() === 'get') {
      const filename = ctx.query('file')

      if (!filename) {
        return ctx.status(400).send({ error: 'Missing file parameter' })
      }

      const stream = fs.createReadStream(`./files/${filename}`)

      await ctx.stream(stream, 200, {
        'content-type': 'application/octet-stream',
        'content-disposition': `attachment; filename="${filename}"`
      })

      return
    }

    return ctx.status(404).send({ error: 'Not found' })
  }
})

await server.listen()

WebSocket Chat Room

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  router: (ctx) => {
    return { message: 'WebSocket chat server' }
  },
  ws: {
    enabled: true,
    onUpgrade: (meta) => ({
      isAllowed: true,
      userData: { username: meta.getQuery('username') || 'Anonymous' }
    }),
    onOpen: (ctx) => {
      console.log('User joined:', ctx.data.username)
      ctx.subscribe('chat')
      ctx.publish(
        'chat',
        JSON.stringify({
          type: 'join',
          user: ctx.data.username
        })
      )
    },
    onMessage: (ctx, message, isBinary) => {
      const text = Buffer.from(message).toString()

      // Broadcast to all clients in the chat room
      ctx.publish(
        'chat',
        JSON.stringify({
          type: 'message',
          user: ctx.data.username,
          text
        })
      )
    },
    onClose: (ctx, code, message) => {
      console.log('User left:', ctx.data.username)
      ctx.publish(
        'chat',
        JSON.stringify({
          type: 'leave',
          user: ctx.data.username
        })
      )
    }
  }
})

await server.listen()
console.log('Chat server running on ws://localhost:3000')

WebSocket with Authentication

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  router: (ctx) => ({ ok: true }),
  ws: {
    enabled: true,
    onUpgrade: async (meta) => {
      // Validate token from query or header
      const token = meta.getQuery('token') || meta.getHeader('authorization')

      if (!token) {
        return { isAllowed: false }
      }

      try {
        const user = await validateToken(token)

        return {
          isAllowed: true,
          userData: { userId: user.id, username: user.name }
        }
      } catch (error) {
        return { isAllowed: false }
      }
    },
    onOpen: (ctx) => {
      console.log('Authenticated user:', ctx.data.username)
      ctx.send(`Welcome, ${ctx.data.username}!`)
    },
    onMessage: (ctx, message, isBinary) => {
      const text = Buffer.from(message).toString()
      console.log(`[${ctx.data.username}]:`, text)
    }
  }
})

await server.listen()

Direct messaging between connections

To send a message to a specific other connection (1:1), topic pub/sub is overkill. Set connectionKey to give each connection a stable address, then use server.sendTo(key, …). The registry is maintained automatically (populated in onOpen, cleaned in onClose) and only exists when connectionKey is set — nothing happens on the message hot path.

import Server from '@swarmmachina/swm-core'

const server = new Server({
  port: 3000,
  router: (ctx) => ({ ok: true }),
  ws: {
    enabled: true,
    onUpgrade: (meta) => ({
      isAllowed: true,
      userData: { userId: meta.getQuery('userId') }
    }),
    // Address each connection by its user id.
    connectionKey: (ctx) => ctx.data.userId,
    onMessage: (ctx, message) => {
      const { to, text } = JSON.parse(ctx.decode(message))

      // Direct 1:1 message to another connection.
      server.sendTo(to, JSON.stringify({ from: ctx.key, text }))
    }
  }
})

await server.listen()

Notes:

  • The key must come from verified identity (a session, a signed token checked in onUpgrade) — never from an attacker-controlled value like a raw query parameter. With last-write-wins semantics, anyone who can claim an arbitrary key can silently take over that address and receive its 1:1 messages. The example above trusts userId only for brevity.
  • If two live connections yield the same key, the newer one wins; the older socket closing will not evict the newer entry, and the displaced connection's ctx.key is reset to null.
  • Keys are compared with strict Map identity — 42 and '42' are different addresses; stick to one type.
  • Need backend-specific low-level control? Use server.getConnection(key) and narrow the handle for the selected backend. Methods beyond RawWebSocket (such as uWS getBufferedAmount()) are not portable to the Node backend.
  • Prefer managing your own Map instead? Store ctx.ws (identity-stable for the connection) rather than ctx, and delete it in onClose.

For 1-to-many fan-out (rooms, channels, broadcasts) prefer topic pub/sub (ctx.subscribe / ctx.publish / server.publish) instead of addressing connections individually.

Advanced Usage

Graceful Shutdown

const server = new Server({
  /* ... */
})
await server.listen()

// Handle shutdown signals
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully...')
  server.shutdown(10000) // 10 second timeout
})

process.on('SIGINT', () => {
  console.log('SIGINT received, shutting down gracefully...')
  server.shutdown(10000)
})

Route preHandlers

A route may declare a preHandler — one function or an array — run before its handler (auth, logging, validation).

const requireAuth = (ctx) => {
  if (ctx.header('authorization') !== 'Bearer secret') {
    ctx.status(401).send('Unauthorized') // replying short-circuits the chain
  }
}

const server = new Server({
  routes: [
    {
      method: 'get',
      path: '/admin',
      preHandler: requireAuth,
      handler: () => ({ ok: true })
    }
  ]
})
  • Run in order, sync or async (awaited); replying (ctx.replied) stops the chain.
  • Composed once at registration — zero per-request cost for routes without one.
  • Declarative routes API only (not the router function).

Custom Response Headers

const server = new Server({
  router: (ctx) => {
    // Set custom headers
    ctx.setHeader('custom-header', 'value')
    return ctx.reply(
      200,
      {
        'content-type': 'application/json',
        'x-custom-header': 'value',
        'cache-control': 'no-cache'
      },
      JSON.stringify({ ok: true })
    )
  }
})

For headers reused across requests, validate and compile them once with prepareHeaders(). The returned opaque block is immutable and can be passed to reply(), setHeaders(), streaming methods, or flushHeaders(). Dynamic plain objects and individual header setters continue to validate every value.

import Server, { prepareHeaders } from '@swarmmachina/swm-core'

const responseHeaders = prepareHeaders({
  'content-type': 'application/json',
  'cache-control': 'no-store',
  'set-cookie': ['access=...; Path=/; HttpOnly', 'refresh=...; Path=/refresh; HttpOnly']
})

const server = new Server({
  router: (ctx) => ctx.reply(200, responseHeaders, JSON.stringify({ ok: true }))
})

prepareHeaders() copies all values and rejects CR or LF before creating the trusted block, so later mutation of the source object cannot change responses.

CORS

cors(options) stages CORS headers and replies to preflight (OPTIONS) requests. Call it at the top of a handler; it returns true when it handled the preflight.

import Server, { cors } from '@swarmmachina/swm-core'

const applyCors = cors({
  origin: 'https://app.example', // default '*'
  credentials: true, // default false
  maxAge: 600 // optional, preflight cache seconds
})

const server = new Server({
  routes: [
    {
      method: 'any',
      path: '/*',
      handler: (ctx) => {
        if (applyCors(ctx)) {
          return // preflight handled (204)
        }

        return { ok: true }
      }
    }
  ]
})

Options: origin (default '*'), methods, allowedHeaders, credentials, maxAge. A non-'*' origin appends Vary: Origin; credentials requires an explicit origin.

Serving Static Files

serveStatic(root, options) returns a handler for a wildcard /* route (specific routes still take precedence). It guards against path traversal, sets Content-Type by extension, and caches file contents in memory.

import Server, { serveStatic } from '@swarmmachina/swm-core'

const server = new Server({
  routes: [
    { method: 'get', path: '/api/health', handler: () => ({ ok: true }) },
    {
      method: 'get',
      path: '/*',
      handler: serveStatic('./public', {
        spa: true, // fall back to index.html for unmatched paths (default false)
        maxAge: 3600 // optional Cache-Control: public, max-age=<seconds>
      })
    }
  ]
})

Options: spa (fall back to index), index (default 'index.html'), cache (default true; set false in dev to pick up edits), cacheLimit (max cached files, default 128), maxAge (Cache-Control seconds). Misses return 404, traversal 403, non-GET/HEAD 405.

Backpressure Handling

const server = new Server({
  router: async (ctx) => {
    if (ctx.url() === '/stream') {
      ctx.startStreaming(200, { 'content-type': 'text/plain' })

      for (let i = 0; i < 1000; i++) {
        const ok = ctx.write(`Chunk ${i}\n`)

        if (!ok) {
          // Handle backpressure
          await new Promise((resolve) => {
            ctx.onWritable((offset) => {
              resolve(offset)
            })
          })
        }
      }

      ctx.end()
    }
  }
})

Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

Regression profiling (CI)

npm run profile:ci runs the regression-profiling suites (HTTP, body-parser and WebSocket), records CPU profiles and memory peaks, and fails on a guard breach. In CI it runs only on push to master and workflow_dispatch (see .github/workflows/ci.yml) and uploads the regression-profiles artifact. Thresholds live in benchmark/baselines/*.json.

GitHub's shared runners are noisy — throughput can swing 30–40% between runs — so absolute thresholds there only catch large regressions. Running the profiling job on a quiet self-hosted runner removes that noise and lets the thresholds be tightened enough to catch small regressions.

Self-hosted runner

The gated regression-profile, compare-http, compare-ws and autobahn jobs use self-hosted runners. The regular test job stays on ubuntu-latest.

Public-repository note. The profiling and comparison jobs are gated to push/workflow_dispatch, while Autobahn is workflow_dispatch only, so fork pull requests never reach a self-hosted machine — they only run test on ubuntu-latest. Anything merged to master still executes profiling and comparison jobs on the runner, so treat the host as exposed: dedicated unprivileged user, firewalled, no production secrets, ephemeral runner.

1. Register the runner (repo Settings → Actions → Runners → New self-hosted runner). The connection is outbound-only — no inbound ports, works behind NAT:

./config.sh --url https://github.com/<owner>/<repo> --token <RUNNER_TOKEN> --labels bench --ephemeral
sudo ./svc.sh install <user>
sudo ./svc.sh start

2. Harden the host.

  • Run the runner as a dedicated unprivileged user, never root.
  • Keep the host firewalled; allow only outbound HTTPS to GitHub.
  • Do not expose repository or production secrets to the runner.
  • --ephemeral de-registers the runner after each job, limiting persistence.

3. Tune for low noise (otherwise the variance returns):

sudo cpupower frequency-set -g performance

Keep the host idle during a run; optionally pin the runner to dedicated cores.

4. Point the job at the runner. In .github/workflows/ci.yml, set the regression-profile job to runs-on: [self-hosted, Linux, bench].

5. Recalibrate. Throughput and memory numbers differ per machine, so recalibrate benchmark/baselines/*.json on the self-hosted runner: collect a few green runs, then set each guard just past the observed noise floor (throughput min ≈ observed low × 0.9; latency and memory max ≈ observed high × 1.15).

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

Licensed under the MPL-2.0 License.

Copyright © 2025 SwarmMachina Team

See LICENSE file for details.