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

@siltrun/client

v0.4.0

Published

Silt transport-core client SDK — two lanes (presence/events) over one WebTransport connection, plus the browser-clean predict core (@siltrun/client/predict).

Readme

@siltrun/client

The browser SDK for Silt rooms. One WebTransport connection carries two lanes: a droppable datagram lane for high-frequency signals like pointer position, and a reliable ordered lane for actions that must not be lost. A plain WebSocket cannot give you the first one, because it head-of-line-blocks under packet loss.

The same client talks to both kinds of room. In a relay room Silt just carries packets and each peer is its own authority. In a compute room your own tick(state, inputs) runs server-side at 60Hz and broadcasts authoritative state.

No framework dependency. If you are using React, @siltrun/react wraps this connection in a useRoom hook.

Install

npm i @siltrun/client

Join a room

import { joinRoom } from "@siltrun/client";

const room = await joinRoom("http://localhost:4000", { id: crypto.randomUUID() });

// droppable lane: latest-wins, safe to call every frame
addEventListener("pointermove", (e) => {
  room.presence.set({ x: e.clientX, y: e.clientY });
});

// reliable lane: ordered, never dropped
room.events.send({ type: "chat", text: "hello" });

room.on("presence", (peerId, state) => { /* another peer's latest presence */ });
room.on("event", (peerId, event) => { /* a reliable message from a peer */ });
room.on("join", (peer) => { /* peer.id, peer.state */ });
room.on("leave", (peerId, reason) => { /* "left" | "timeout" */ });
room.on("status", ({ status, error }) => { /* see below */ });

await room.close();

room.peers holds the other peers and their latest presence, populated at join. Every on(...) call returns a function that unsubscribes it.

The URL can point straight at a WebTransport endpoint, or at a room-info URL that answers GET <url>/.well-known/silt. The latter is what siltrun dev serves on port 4000, and it is how the client discovers the endpoint and pins the local development certificate for you.

Compute rooms

When the room runs your own contract, the server broadcasts state every tick:

room.on("state", ({ tick, state, appliedSeq }) => {
  render(state as GameState);
});

state is untyped on the wire, so the type parameter is your assertion about the shape, not a runtime check.

appliedSeq is the last input sequence number the server has applied for this peer. It is the ack that client-side reconciliation lines up against. In a compute room presence.set returns the { seq, clientTick } stamp for the input it just sent, so you can match your local history to that ack. In a relay room there is no envelope and it returns undefined.

Lockstep rooms

When the room declares inputs: { schedule: "tick", delay: N }, the server assigns every input a tick and broadcasts the ordered per-tick log to every peer, so a deterministic simulation can run identically on every client:

if (room.lockstep) {                       // { schedule, delay } | null
  const { seq, sent } = room.inputs.send({ laser: true });
  sent.catch((e) => engine.onInputLost(seq, e));

  room.on("log", ({ tick, inputs }) => {   // contiguous, one callback per tick
    for (const { from, seq, data } of inputs) engine.apply(from, seq, data);
  });

  room.on("gap", ({ expected, got, reason }) => {
    // "reconnect" — expected after a transport drop; the log resumes at the room's
    // current tick, so re-seed. "protocol" — a gap within one connection; treat the
    // simulation as unsound.
    engine.reseed();
  });
}

inputs.send is the reliable lane: it throws synchronously if the room is not connected, and hands back sent so an async write failure is observable too — a lost scheduled input is a hole in a replicated timeline that nothing recovers. You learn the tick an input landed on by finding (from === your id, seq) in a "log" frame.

presence.set throws in a lockstep room — the datagram input lane is disabled there. Calling inputs.send in a room that did not opt in throws too.

Connection status

room.on("status", ...) reports connecting, connected, reconnecting, failed, or closed, with the underlying error when there is one. Failures are never swallowed.

Reconnect is automatic and reuses the same id. On transport loss the client redials with exponential backoff and gives up with status failed after a bounded number of attempts, so a silent infinite redial loop is not possible. Options on joinRoom control the identity, certificate pinning, the connect and snapshot timeouts, and the backoff shape.

Subpath exports

  • @siltrun/client/predict runs your room contract optimistically in a Web Worker and reconciles it against the server's ack, so local input feels immediate while the server still owns the truth.
  • @siltrun/client/predict/worker is the worker entry that pairs with it.

Docs

Full guides and API reference: https://silt.run/docs/