Private packages

@onyx/realtime

Yjs collaboration and presence on Durable Objects.

@onyx/realtime provides realtime collaboration and presence. One room per organizationId:page speaks the Yjs sync and awareness protocols over a WebSocket: the shared Y.Doc carries page state (task lists, the notes document) and awareness carries who is on the page. Document snapshots persist across restarts, connections are authenticated and scoped to the caller's active organization by the existing Worker wiring, and local dev simulates the Durable Object. You only work with the React client.

Providing a room

Mount PageRoomProvider once per page so presence and page features share a single WebSocket. The app layout in apps/web keys it on the pathname:

// apps/web/src/routes/_app.tsx
import { PageRoomProvider } from '@onyx/realtime/client';

<PageRoomProvider
  page={pathname}
  user={{ id: user.id, image: user.image ?? null, name: user.name }}
>
  {/* page content, presence avatars, ... */}
</PageRoomProvider>;

The provider is preconfigured with the right connection URL, so consumers never build URLs themselves.

Consuming a room

usePageRoomContext() returns { room, users }. room is null until the connection is established on the client, and it exposes doc (the shared Y.Doc), provider, and page. Always check room.page before binding anything to the doc: during navigation the shared provider swaps rooms while the outgoing page is still mounted.

// apps/web/src/routes/_app/notes.tsx
import { usePageRoomContext } from '@onyx/realtime/client';

function NotesPage() {
  const { room } = usePageRoomContext();
  // Treat a room for a different page as "not connected yet".
  const notesRoom = room !== null && room.page === Route.fullPath ? room : null;
  // Bind notesRoom.doc to Tiptap Collaboration, a shared task list, etc.
}

Presence is just the users array:

const { users } = usePageRoomContext();
// Render an avatar per PresenceUser currently on this page.

PresenceUser (id, name, image) and the isPresenceUser guard for validating awareness states from other clients come from @onyx/realtime/types.

Outside the provider

usePageRoom (also in @onyx/realtime/client) is the underlying hook if you need a room connection outside the shared provider, for example a second room on the same page. Prefer the provider for normal pages so everything shares one WebSocket.

No secrets are required. Authentication reuses the app session.

On this page