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

@loro-extended/adapter-websocket-compat

v3.0.2

Published

WebSocket network adapter implementing the Loro Syncing Protocol for compatibility with Loro servers (use @loro-extended/adapter-websocket for native loro-extended protocol)

Readme

@loro-extended/adapter-websocket-compat

Compatibility Adapter: This adapter implements the Loro Syncing Protocol for interoperability with Loro servers. For loro-extended to loro-extended communication, use the native adapter @loro-extended/adapter-websocket instead.

WebSocket network adapter implementing the Loro Syncing Protocol for @loro-extended/repo.

When to Use This Adapter

Use this adapter when you need to:

  • Connect to a Loro Protocol server
  • Interoperate with other Loro Protocol clients
  • Maintain backward compatibility with existing Loro Protocol deployments

For new loro-extended projects, prefer @loro-extended/adapter-websocket which:

  • Directly transmits ChannelMsg types without translation
  • Supports all loro-extended message types natively
  • Has simpler implementation and better debugging

Features

  • Full Loro Syncing Protocol compliance (except fragmentation >256KB)
  • Framework-agnostic design with a WebSocket handler interface
  • Bidirectional communication over a single WebSocket connection
  • Translation layer between Loro Syncing Protocol and loro-extended messages
  • Room = DocId mapping for simplicity
  • Ephemeral data integration with existing loro-extended presence system
  • Automatic reconnection with exponential backoff
  • Keepalive ping/pong for connection health

Installation

pnpm add @loro-extended/adapter-websocket-compat

Usage

Client

import { Repo } from "@loro-extended/repo";
import { WsClientNetworkAdapter } from "@loro-extended/adapter-websocket-compat/client";

const adapter = new WsClientNetworkAdapter({
  url: "ws://localhost:3000/ws",
  reconnect: {
    enabled: true,
    maxAttempts: 10,
    baseDelay: 1000,
    maxDelay: 30000,
  },
  keepaliveInterval: 30000,
});

const repo = new Repo({
  identity: { peerId: "client-1", name: "Client", type: "user" },
  adapters: [adapter],
});

Server with Express + ws

import express from "express";
import { createServer } from "http";
import { WebSocketServer } from "ws";
import { Repo } from "@loro-extended/repo";
import {
  WsServerNetworkAdapter,
  wrapWsSocket,
} from "@loro-extended/adapter-websocket-compat/server";

const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });

const adapter = new WsServerNetworkAdapter();

const repo = new Repo({
  identity: { peerId: "server", name: "Server", type: "service" },
  adapters: [adapter],
});

wss.on("connection", (ws, req) => {
  // Extract peer ID from query string
  const url = new URL(req.url!, `http://${req.headers.host}`);
  const peerId = url.searchParams.get("peerId");

  const { connection, start } = adapter.handleConnection({
    socket: wrapWsSocket(ws),
    peerId: peerId || undefined,
  });

  start();
});

server.listen(3000);

Server with Hono

import { Hono } from "hono";
import { upgradeWebSocket } from "hono/cloudflare-workers"; // or your runtime
import { Repo } from "@loro-extended/repo";
import {
  WsServerNetworkAdapter,
  wrapStandardWebSocket,
} from "@loro-extended/adapter-websocket-compat/server";

const app = new Hono();
const adapter = new WsServerNetworkAdapter();

const repo = new Repo({
  identity: { peerId: "server", name: "Server", type: "service" },
  adapters: [adapter],
});

app.get(
  "/ws",
  upgradeWebSocket((c) => {
    let connection: ReturnType<typeof adapter.handleConnection>["connection"];

    return {
      onOpen(evt, ws) {
        const peerId = c.req.query("peerId");
        const result = adapter.handleConnection({
          socket: wrapStandardWebSocket(ws.raw as WebSocket),
          peerId: peerId || undefined,
        });
        connection = result.connection;
        result.start();
      },
      onClose() {
        // Connection cleanup is handled automatically
      },
    };
  })
);

export default app;

Protocol

This adapter implements the Loro Syncing Protocol which uses binary messages over WebSocket for efficient real-time synchronization.

Message Types

| Code | Type | Description | | ---- | -------------- | ------------------------ | | 0x00 | JoinRequest | Request to join a room | | 0x01 | JoinResponseOk | Successful join response | | 0x02 | JoinError | Join failed | | 0x03 | DocUpdate | Document update data | | 0x06 | UpdateError | Update failed | | 0x07 | Leave | Leave a room |

CRDT Types (Magic Bytes)

| Magic | Type | Description | | ------ | ------------------- | ---------------------------------- | | %LOR | Loro Document | Persistent document data | | %EPH | Ephemeral Store | Transient presence/cursor data | | %EPS | Persisted Ephemeral | Ephemeral data that gets persisted |

Keepalive

The client sends ping text frames every 30 seconds (configurable). The server responds with pong. This keeps the connection alive through proxies and load balancers.

API Reference

WsClientNetworkAdapter

interface WsClientOptions {
  /** WebSocket URL to connect to */
  url: string | ((peerId: PeerID) => string);

  /** Optional: Custom WebSocket implementation (for Node.js) */
  WebSocket?: typeof WebSocket;

  /** Reconnection options */
  reconnect?: {
    enabled: boolean;
    maxAttempts?: number;
    baseDelay?: number;
    maxDelay?: number;
  };

  /** Keepalive interval in ms (default: 30000) */
  keepaliveInterval?: number;
}

WsServerNetworkAdapter

class WsServerNetworkAdapter {
  /** Handle a new WebSocket connection */
  handleConnection(options: WsConnectionOptions): WsConnectionResult;

  /** Get an active connection by peer ID */
  getConnection(peerId: PeerID): WsConnection | undefined;

  /** Get all active connections */
  getAllConnections(): WsConnection[];

  /** Check if a peer is connected */
  isConnected(peerId: PeerID): boolean;

  /** Broadcast a message to all connected peers */
  broadcast(msg: ChannelMsg): void;

  /** Number of connected peers */
  readonly connectionCount: number;
}

WsSocket Interface

To integrate with any WebSocket library, implement this interface:

interface WsSocket {
  send(data: Uint8Array | string): void;
  close(code?: number, reason?: string): void;
  onMessage(handler: (data: Uint8Array | string) => void): void;
  onClose(handler: (code: number, reason: string) => void): void;
  onError(handler: (error: Error) => void): void;
  readonly readyState: "connecting" | "open" | "closing" | "closed";
}

Helper wrappers are provided:

  • wrapWsSocket(ws) - For the ws library (Node.js)
  • wrapStandardWebSocket(ws) - For the standard WebSocket API (browser)

Comparison with Native Adapter

| Feature | Native Adapter | Compat Adapter | |---------|---------------|----------------| | Protocol | loro-extended native | Loro Syncing Protocol | | Message translation | None | Required | | Batch support | Native | Requires workaround | | Semantic preservation | Full | Partial | | Interop with Loro servers | No | Yes |

License

MIT