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

yrby-client

v0.4.3

Published

JavaScript client for the yrby y-websocket protocol: a ready-made ActionCable/AnyCable provider, a transport-agnostic protocol session (sync steps, encode/decode, awareness), and a reliable-delivery core (ack-tracked queue, sync-since-last-ack, retransmit

Downloads

1,034

Readme

yrby-client

The client core for the yrby y-websocket protocol — everything a Yjs provider needs except the transport. Bring your own socket (ActionCable, AnyCable, raw WebSocket); this owns the protocol.

Three layers, use whichever you need:

  • ActionCableProvider — a ready-made Yjs provider for ActionCable / AnyCable. Pass a Y.Doc, a cable consumer, and a channel; it wires the subscription and you're collaborating. Awareness/presence rides AnyCable whisper when available via an awareness-only envelope and falls back to normal sends on plain ActionCable; document updates always go through the server as reliable recorded/acked updates.
  • YProtocolSession — the transport-agnostic core. Binds to a Y.Doc (+ optional Awareness) and owns the y-protocols message encode/decode, the sync-step handshake (SyncStep1 / SyncStep2 / Update), awareness, and reliable delivery. Speaks raw Uint8Array frames; you wire any socket.
  • ReliableSync — the zero-dependency reliable-delivery state machine on its own: ack-tracked queue, sync-since-last-ack (the unacked tail merged into one causally-complete delta), cumulative acks, retransmit, and reconnect replay. Compose it yourself if you already have your own framing.

Install

npm install yrby-client

ActionCableProvider and YProtocolSession need yjs and y-protocols (peers — your app already has them), plus an ActionCable/AnyCable consumer. ReliableSync has no dependencies; import it on its own via yrby-client/reliable if that's all you want.

Written in TypeScript and ships bundled type declarations, so TS projects get full types (typed options, methods, and errors) with no @types package — and plain-JS projects use the same compiled ESM with nothing extra to install.

ActionCableProvider (the easy path)

import { ActionCableProvider } from "yrby-client";
import * as Y from "yjs";
import { createConsumer } from "@anycable/web"; // or @rails/actioncable

const doc = new Y.Doc();
const consumer = createConsumer();
const provider = new ActionCableProvider(doc, consumer, "DocumentChannel", { id: docId });

provider.connect(); // does not auto-connect — wire your editor binding first

// Observe the connection (one signal, no separate "sync" event):
provider.onStatusChange(({ status }) => render(status)); // returns an unsubscribe fn
//   "connecting"  -> subscription created, transport not up yet
//   "connected"   -> transport up, exchanging sync steps (show "syncing")
//   "synced"      -> caught up with the server
//   "disconnected"-> torn down via disconnect()/destroy()
//                    (a dropped transport ActionCable will retry shows as "connecting")

// provider.status     -> the current status (same union as above)
// provider.awareness  -> the provider's Awareness instance (always a fresh one)
// provider.synced     -> caught up with the server
// provider.hasPending -> unacked local edits in flight
// provider.destroy()  -> tear down

On disconnect() / destroy() — and on browser pagehide — the provider broadcasts a presence removal so peers drop your cursor immediately instead of waiting for the awareness timeout. destroy() is synchronous (the unsubscribe is deferred one microtask so that removal flushes first) and tears down the Awareness it created. (ActionCableProvider always creates its own; to bring your own Awareness, drop down to YProtocolSession, which leaves it for you to own.)

On the server, include Y::ActionCable::Sync in a channel named DocumentChannel (the yrby-actioncable gem). The server subscribes document broadcasts and AnyCable awareness whispers on separate streams, so the document stream is not whisper-enabled. Need a different transport or framing? Drop down to YProtocolSession and supply your own send.

The provider uses one JSON envelope shape:

client -> server document frame      { update: "<base64 frame>", id: 42 }
server -> client document frame      { update: "<base64 frame>" }
server -> client acknowledgement     { ack: 42 }
AnyCable awareness whisper           { awareness: "<base64 awareness frame>" }

YProtocolSession

import { YProtocolSession, toBase64, fromBase64 } from "yrby-client";
import * as Y from "yjs";
import { Awareness } from "y-protocols/awareness";

const doc = new Y.Doc();
const awareness = new Awareness(doc);

const session = new YProtocolSession(doc, {
  awareness,
  // transmit one raw frame; `id` is set for reliable doc updates -> tag your envelope
  send: (frame, id) => {
    const payload = { update: toBase64(frame) };
    if (id !== undefined) payload.id = id;
    subscription.send(payload);
  },
});

// wire your transport's callbacks:
subscription.connected    = () => session.onConnect();      // handshake + replay
subscription.disconnected = () => session.onDisconnect();   // pause + clear presence
subscription.received = (msg) => {
  if (msg.ack !== undefined) return session.ack(msg.ack);   // reliable ack envelope
  const reply = session.receive(fromBase64(msg.update));     // decode + apply
  if (reply) subscription.send({ update: toBase64(reply) });     // e.g. answer a SyncStep1
};
// session.synced -> caught up; session.hasPending -> unacked edits in flight
// session.destroy() -> detach listeners + stop retransmits

Local document edits and awareness changes are picked up automatically from the doc's / awareness's update events — you never call anything for outbound edits.

Pass onError(error, context) (on either ActionCableProvider or YProtocolSession) to observe dropped frames: a malformed or truncated message is decoded defensively, dropped, and reported here rather than thrown into your transport callback. Defaults to a console.warn.

ReliableSync (standalone)

import { ReliableSync } from "yrby-client/reliable"; // zero-dep
import * as Y from "yjs";

const rs = new ReliableSync({
  send: (update, id) => { /* frame + transmit */ },
  merge: Y.mergeUpdates,
});

rs.enqueue(update);  // a local document update
rs.onAck(id);        // an { ack: id } arrived
rs.onConnect();      // (re)connected — replay the tail, resume retransmits
rs.onDisconnect();   // dropped — keep the queue, pause

Pending updates are retained and replayed until the server acknowledges them. Document delivery stays queued and ack-tracked for the lifetime of the session.

How it fits

The server counterpart — ack generation, gap detection, record-before-distribute — is the yrby-actioncable gem's Y::ActionCable::Sync. This package is the client half of the same protocol.

License

MIT