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

@waits/lively-server

v0.0.1

Published

Real-time collaboration server — WebSocket rooms, presence, and cursor relay

Readme

@waits/lively-server

Real-time collaboration server — WebSocket rooms, presence, and cursor relay.

Quick Start

import { LivelyServer } from "@waits/lively-server";

const server = new LivelyServer({ port: 1999 });
await server.start();
// Clients connect to ws://localhost:1999/rooms/{roomId}

Auth

Provide a custom AuthHandler to authenticate WebSocket upgrades. If auth is configured, query-param userId/displayName are ignored.

const server = new LivelyServer({
  auth: {
    async authenticate(req) {
      const url = new URL(req.url!, `http://${req.headers.host}`);
      const token = url.searchParams.get("token");
      const user = await verifyToken(token);
      if (!user) return null; // rejects with 401
      return { userId: user.id, displayName: user.name };
    },
  },
});

Without auth, clients pass identity via query params:

ws://localhost:1999/rooms/my-room?userId=alice&displayName=Alice

Message Handling

All messages must follow a { type: string, ...payload } envelope. The server handles cursor:update automatically — everything else is relayed to peers and passed to onMessage.

const server = new LivelyServer({
  onMessage: async (roomId, senderId, message) => {
    console.log(`[${roomId}] ${senderId}:`, message);
    // Persist to database, trigger side effects, etc.
  },
});

Room Events

const server = new LivelyServer({
  onJoin: (roomId, user) => {
    console.log(`${user.displayName} joined ${roomId}`);
  },
  onLeave: (roomId, user) => {
    console.log(`${user.displayName} left ${roomId}`);
  },
});

External Broadcast

Push messages into a room from outside the WebSocket flow (e.g., an HTTP API handler):

server.broadcastToRoom("room-id", JSON.stringify({ type: "notify", text: "Hello" }));

Server-side Storage Mutations

Mutate a room's CRDT storage from the server. Ops are collected and broadcast to all connected clients. Server mutations do not create undo history entries.

await server.mutateStorage("room-1", (root) => {
  root.set("serverUpdatedAt", Date.now());
});

Returns false if the room doesn't exist or storage isn't initialized.


Server-side Live State

Set a live-state key from the server. Uses "__server__" as the userId and LWW with Date.now() for conflict resolution.

server.setLiveState("room-1", "status", "locked");

Broadcasts state:update to all clients. Returns false if the room doesn't exist.


Get Room Users

Return all connected users in a room.

const users: PresenceUser[] = server.getRoomUsers("room-1");

Returns an empty array if the room doesn't exist.


API Reference

| Export | Description | |--------|-------------| | LivelyServer | Main server class — manages rooms, connections, message routing | | Room | Single room — tracks connections, provides broadcast/send | | RoomManager | Room lifecycle — create, get, cleanup on disconnect | | ServerConfig | Config for LivelyServer — port, path, auth, callbacks | | AuthHandler | Interface for custom auth — authenticate(req) | | PresenceUser | User in a room — userId, displayName, color, connectedAt | | CursorData | Cursor position — userId, displayName, color, x, y, lastUpdate | | RoomConfig | Room tuning — cleanupTimeoutMs, maxConnections | | server.mutateStorage(roomId, cb) | Server-side CRDT mutation — runs callback against root, broadcasts ops | | server.setLiveState(roomId, key, value) | Set live-state key from server — LWW with __server__ userId | | server.getRoomUsers(roomId) | Get all connected PresenceUser[] in a room |

Built-in Behavior

  • Presence: Broadcasts { type: "presence", users } on every join/leave
  • Cursor relay: cursor:update messages are enriched with sender info and relayed to peers (sender excluded)
  • Color assignment: Deterministic color from userId hash — consistent across reconnects
  • Room cleanup: Empty rooms are removed after a configurable timeout (default 30s)