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

partyserver

v0.5.6

Published

Build real-time applications powered by [Durable Objects](https://developers.cloudflare.com/durable-objects/), inspired by [PartyKit](https://www.partykit.io/).

Readme

PartyServer

Build real-time applications powered by Durable Objects, inspired by PartyKit.

Installation

npm install partyserver

Why Use PartyServer?

PartyServer enhances Durable Objects with the following features:

  • Simple "room"-based routing
  • Lifecycle hooks for connections and requests
  • A unified API for managing hibernated and non-hibernated Durable Objects
  • Easy broadcasting to all or selected connections in a server

How is it different from PartyKit?

  • Decouples the idea of a URL from the server name. This is useful when you want to associate a server with some other identifier like a session ID, etc. You can still use routePartykitRequest() to get PartyKit-style route matching.
  • Doesn't include bindings for other services like AI, static assets, etc. Instead, use wrangler's built-in support for those services.
  • Doesn't have PartyKit's auto-inferred declaration for Durable Object bindings and migrations, so you have to manually specify these in wrangler.jsonc. We may add this in the future.

Usage

Here's an example of how to use PartyServer:

Write your code:

// index.ts

import { routePartykitRequest, Server } from "partyserver";

// Define your Server
export class MyServer extends Server {
  onConnect(connection) {
    console.log("Connected", connection.id, "to server", this.name);
  }

  onMessage(connection, message) {
    console.log("Message from", connection.id, ":", message);
    // Send the message to every other connection
    this.broadcast(message, [connection.id]);
  }
}

export default {
  // Set up your fetch handler to use configured Servers
  async fetch(request: Request, env: Env): Promise<Response> {
    return (
      (await routePartykitRequest(request, env)) ||
      new Response("Not Found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;

And configure your wrangler.jsonc:

{
  "name": "my-partyserver-app",
  "main": "index.ts",
  "durable_objects": {
    "bindings": [
      {
        "name": "MyServer",
        "class_name": "MyServer"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1", // Should be unique for each entry
      "new_sqlite_classes": ["MyServer"]
    }
  ]
}

You can connect to the server from a client (like a browser or a mobile app) using partysocket or any other WebSocket client.

import { PartySocket } from "partysocket";

const socket = new PartySocket({
  host: "https://my-partyserver-app.threepointone.workers.dev", // optional, defaults to window.location.host,
  party: "my-server", // the server name. if you use routePartykitRequest, it automatically uses the kebab-cased version of the binding name (MyServer -> my-server)
  room: "my-room"
});

See the /fixtures folder for more specific examples.

Customizing Server

Server is a class that extends DurableObject. You can override the following methods on Server to add custom behavior:

Lifecycle Hooks

These methods can be optionally async:

  • onStart(): Called when the server starts for the first time or after waking up from hibernation. You can use this to load data from storage and perform other initialization, such as retrieving data or configuration from other services or databases.

  • onConnect(connection, context) - Called when a new websocket connection is established. You can use this to set up any connection-specific state or perform other initialization. Receives a reference to the connecting Connection, and a ConnectionContext that provides information about the initial connection request.

  • onMessage(connection, message) - Called when a message is received on a connection.

  • onClose(connection, code, reason, wasClean) - Called when a connection is closed by a client. By the time onClose is called, the connection is already closed and can no longer receive messages.

  • onError(connection, error) - Called when an error occurs on a connection.

  • onRequest(request): Response - Called when a request is made to the server. This is useful for handling HTTP requests in addition to WebSocket connections.

  • onAlarm() - Called when an alarm is triggered. You can set an alarm by calling this.ctx.storage.setAlarm(Date). Read more about Durable Objects alarms here.

  • getConnectionTags(connection, context): string[] - You can set additional metadata on connections by returning them from getConnectionTags(), and then filter connections based on the tag with this.getConnections.

Additional Methods

  • broadcast(message, exclude = []) - Send a message to all connections, optionally excluding connection ids listed in the second array parameter.

  • getConnections(tags = []) - Get all currently connected WebSocket connections, optionally filtered by tags set by getConnectionTags(). Returns an iterable list of Connection.

  • getConnection(id) - Get a connection by its ID.

Properties

  • .name - (readonly) the server's name. Resolves from the underlying Durable Object's ctx.id.name, populated whenever the DO is addressed via idFromName() / getByName() — which is the supported way to address PartyServer DOs. Available inside every entry point including the constructor, onStart(), onAlarm(), and hibernating websocket handlers. PartyServer also persists this name to a small __ps_name fallback record during initialization so that alarm handlers firing on stale on-disk alarm records (scheduled by older workerd versions that didn't persist name) can still recover the name. Legacy DOs bootstrapped via setName() use the same fallback.

    DOs addressed via idFromString() or newUniqueId() without a setName() bootstrap are not supported and .name will throw.

  • .ctx - the context object for the Durable Object, containing references to storage

  • .env - the environment object for the Durable Object, defined by bindings and other configuration in your wrangler.jsonc configuration file.

Durable Object methods

  • fetch(request) - PartyServer overrides the fetch method to add the lifecycle methods to the server instance. In most cases you don't have to implement this mehod yourself. If you do (for example, to add request handling before any lifecycle methods are called), make sure to call super.fetch(request) as appropriate to ensure the lifecycle methods are called.

  • alarm() - You should not implement/override this yourself, use onAlarm() instead. This method is called whenever an alarm is triggered. This is the only way to run code after the server has been evicted. Read more about alarms here.

  • Do not implement any of these methods on your server class: webSocketMessage /webSocketClose / webSocketError / alarm. We override them to call the lifecycle methods in hibernation mode.

Connection Properties

A connection is a standard WebSocket with the following additional properties:

  • id - A unique ID for the connection
  • tags - An array of tags assigned to the connection (TODO)
  • server - The server name the connection is in
  • state - Arbitrary state data (up to 2KB) that can be set on the connection using connection.setState()

Hibernation

You can enable hibernation by setting a static options property on your Server class. This allows the server to hibernate when not in use and wake up when a new connection is established. All lifecycle hooks will be called as expected when the server wakes up.

export class MyServer extends Server {
  static options = {
    hibernate: true
  };
  // ...
}

Utility Methods

  • getServerByName(namespace, name, {locationHint, jurisdiction, routingRetry}): Promise<DurableObjectStub> - Create a new Server with a specific name. Optionally pass a locationHint to specify the location, jurisdiction to specify the jurisdiction, or routingRetry: false to disable retries for transient Durable Object infrastructure errors.

  • routePartykitRequest(request, env, {locationHint, jurisdiction, prefix = 'parties', routingRetry, onBeforeConnect, onBeforeRequest}): Promise<Response | null> - Match a request to a server. Takes a URL of the form /${prefix}/:server/:name and matches it with any namespace named :server (case insensitive) and a server named :name. Optionally pass a locationHint to specify the location, jurisdiction to specify the jurisdiction, or routingRetry: false to disable retries for transient Durable Object infrastructure errors.

    • routingRetry - Retry options for transient Durable Object infrastructure errors thrown while routing to the target Durable Object. Enabled by default with 3 attempts and jittered exponential backoff. Only errors with retryable === true are retried, and errors with overloaded === true are never retried.
    • onBeforeConnect(request, {party: string, name: string}): Request | Response | void - A function that can modify the request for a websocket connection before it's passed to the server.
      • If it returns a Request, it will be used instead of the original request. Use this to modify headers or other request properties before passing it to the partyserver instance / durable object.
      • If it returns a Response, it will be returned instead of the original request. Use this to return a custom response, such as a 404 or 403.
      • If it returns undefined or null, the request will be passed to the server as normal.
    • onBeforeRequest - A function that can modify the request before it's passed to the server. This is exactly the same as onBeforeConnect, but for HTTP requests.

Using PartyServer with Durable Object Facets

Durable Object Facets let you spawn a child DO from inside a parent's isolate via ctx.facets.get(name, factory). This is useful for frameworks (e.g. Cloudflare Agents sub-agents) that want isolated SQLite storage per facet without exposing a separate Durable Object namespace.

Facets that extend Server work, but you must pass an explicit id in FacetStartupOptions. Otherwise the facet inherits the parent DO's ctx.id (including ctx.id.name), and this.name on the facet will return the parent's name — almost never what you want.

import { Server } from "partyserver";

export class FacetChild extends Server {
  // `this.name` here will report `facetName` (NOT the parent's name)
  // because we passed an explicit `id` at spawn time below.
  onStart() {
    console.log("facet started:", this.name);
  }
}

export class ParentServer extends Server {
  async fetch(request: Request) {
    const facetName = "child-foo";

    // Recommended: construct the id via `ctx.exports[BoundDOClass]`,
    // which is also a `DurableObjectNamespace`. Any bound DO class
    // works — the id is opaque + a name; nothing routes through the
    // namespace at runtime for facets.
    const id = this.ctx.exports.ParentServer.idFromName(facetName);

    const facet = this.ctx.facets.get(facetName, () => ({
      class: this.ctx.exports.FacetChild,
      id // <-- the critical bit
    }));

    return facet.fetch(request);
  }
}

Without the explicit id, the facet's ctx.id.name is the parent's name, this.name returns the parent's name, and setName(facetName) throws (it would mismatch ctx.id.name). With the explicit id, the facet has its own native ctx.id.name, no setName() is needed, and cold wakes recover the name automatically (the factory re-runs and idFromName(facetName) is deterministic). PartyServer may also persist that native name as an alarm fallback for older compatibility dates.

Plain strings (id: "child-foo") are NOT a substitute for idFromName(facetName) — workerd treats a string id as idFromString-like and the resulting facet has no ctx.id.name, so this.name will throw.

Comparison to Erlang/Elixir

"Wait", I hear you say, "this looks a lot like Erlang/Elixir's actor model!" And you'd be right! Durable Objects are inspired by the actor model/GenServer and aims to provide a similar experience for developers building applications on Cloudflare Workers. It's implemented fully in the infrastructure layer, so you don't have to maintain your own infrastructure.

Instead of spawning "actors", you create servers that handle connections and messages. This is because Durable Objects are designed to be long-lived and stateful, so it makes sense to model them as servers that can handle multiple connections.

It differs in a specific way: There's no "terminate" handler, because a Durable Object can "shut down" / get evicted at any time, and we can't reliably call a "terminate" handler. Instead, you can set an alarm to run some cleanup code at some point in the future.