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

@defensestation/ysync-go

v0.2.0

Published

Browser Yjs providers (Connect RPC and WebSocket) for the ysync-go collaboration backend

Readme

@defensestation/ysync-go

Browser Yjs providers for the ysync-go collaboration backend. Two interchangeable providers - pick per environment, mix freely: a WebSocket client and a Connect RPC client collaborate live on the same document.

npm install @defensestation/ysync-go yjs

Quick start

import * as Y from "yjs";
import { WebSocketYjsProvider } from "@defensestation/ysync-go";

const ydoc = new Y.Doc();
const provider = new WebSocketYjsProvider({
  ydoc,
  docId: "doc-1",
  baseUrl: "https://collab.example.com",
  user: { name: "Ada", color: "#4338ca" },
});

// Bind ydoc to your editor (BlockNote, Tiptap, y-prosemirror, y-codemirror…)
// and provider.awareness to its cursor/presence plugin.

provider.onStatus((status) => console.log(status)); // connecting | connected | disconnected
provider.destroy(); // on unmount: announces departure, closes cleanly

Or over Connect RPC (unary + server-streaming; works everywhere fetch streams responses):

import { ConnectYjsProvider } from "@defensestation/ysync-go";

const provider = new ConnectYjsProvider({
  ydoc,
  docId: "doc-1",
  baseUrl: "https://collab.example.com",
  user: { name: "Ada" },
});

Choosing a provider

| | WebSocketYjsProvider | ConnectYjsProvider | | --- | --- | --- | | Wire | JSON frames over /ysync/ws | ysync.v1.YjsSyncService (Connect) | | Edits upstream | one message per update | micro-batched unary pushes with idempotency keys | | Best when | plain WebSockets fit your infra | you already run Connect/gRPC middleware (auth interceptors, gateways) |

Both reconnect automatically with exponential backoff (500 ms → 5 s), re-join with the document's state vector, and resync - offline edits made while disconnected are delivered on reconnect (Yjs deduplicates replays).

Common API (both providers)

| Member | Description | | --- | --- | | awareness | The y-protocols Awareness instance - hand it to your editor's cursor plugin. The local state's user field is prefilled from options.user. | | clientId | Stable per-provider ID (UUID unless you pass clientId). | | status / onStatus(fn) | "connecting" \| "connected" \| "disconnected"; returns an unsubscribe function. | | identity / onIdentity(fn) | Server-assigned presence identity (sanitized name, palette color distinct among participants) - use it for cursor rendering instead of trusting local input. | | destroy() | Detaches from the doc, announces departure so peers drop the cursor immediately, and closes the connection. |

Options

Shared: ydoc, docId, user: { name, color? }, baseUrl? (defaults to window.location.origin), clientId?, reconnectInitialDelayMs?, reconnectMaxDelayMs?, awarenessThrottleMs? (default 150 ms, leading+trailing throttle for cursor moves).

ConnectYjsProvider only: updateFlushDelayMs? (default 100 ms) - micro-batch window merging keystrokes into one push while a request is in flight; retries resend the identical payload under the same idempotency key. rpcClient? lets you supply your own Connect client (e.g. with auth interceptors); the default client forwards user.name/user.color as x-user-* headers, which the server treats as unauthenticated hints.

Server

This package is the client half. The server is a Go library - github.com/defensestation/ysync-go - with Postgres persistence, Redis multi-node fanout, and auth hooks. Clone the repo and go run ./examples/minimal -addr :8080 for a complete local server.

The full wire protocol (for writing clients in other languages) is documented in docs/protocol.md.

Generated protobuf descriptors are exported under @defensestation/ysync-go/ysync/v1 if you need raw RPC access.

Compaction adapter (Node 18+)

The server's update log grows until the host folds it - compaction is required in production (see the repository README). The snapshot must be built with real Yjs (Go-side merges corrupt nested-XML documents - BlockNote, ProseMirror, Tiptap), so this package ships the adapter:

import { compactDocuments } from "@defensestation/ysync-go/compaction";

// AWS Lambda handler, Cloud Run job, or any Node 18+ process:
export const handler = async () => {
  const run = await compactDocuments(
    {
      baseUrl: "https://collab.internal.example.com",
      headers: () => ({ authorization: `Bearer ${process.env.COMPACT_TOKEN}` }),
      threshold: 200, // fold once a doc exceeds this many log records
      keep: 50, //       newest records kept individually attributed
    },
    await listCandidateDocIds(), // doc discovery is the host app's job
  );
  if (run.errors.length > 0) {
    throw new Error(`${run.errors.length} document(s) failed`);
  }
  return run; // { compacted, skipped, results, errors }
};

compactDocument(options, docId) is the single-document primitive. Everything is idempotent - retries and concurrent runs converge - and one bad document never aborts a compactDocuments run.

For cron-style schedulers there is a CLI:

npx ysync-compact --base-url http://localhost:8080 --docs doc-1,doc-2 \
  --threshold 200 --keep 50 --header 'authorization: Bearer ...'
# or stream doc IDs, one per line:
psql -Atc 'select id from documents' | npx ysync-compact --base-url ... --docs -

Compaction rewrites stored history: gate the server's Authz.CanCompact so only this job's identity holds it.

License

MIT