Start

Quickstart

Clone the repo, start the Durable Object Worker and the playground, then open two clients on the same workspace. The shortest API loop is hello, mutate, onFrame, view.

Install

git clone https://github.com/dinosaurav/yttrium.git
cd yttrium
npm install
npm test

Tests are deterministic. They run the reducer and a seeded fake network; they do not open sockets.

Run the playground

Use two terminals:

npm run worker        # demo identity adapter, Durable Object on :8787
npm run playground    # Vite UI on :5173

Open http://localhost:5173/. Home is the lab: Ada and Beau side by side in a fresh workspace of your own (the URL gains ?ns=room-…; share it to bring others in). Type in #general, draw on board, or edit notes. The other pane should see the commit without a refresh.

Useful routes:

/                         two live clients (Ada + Beau)
/app/demo/ada             one client as Ada
/app/demo/beau            one client as Beau
/app/demo/ada/notes       Ada, notes already selected
/docs                     this site

Vite talks to ws://localhost:8787. The hosted Worker serves the same UI at ytt.rium.dev and uses same-origin wss:.

The selector is a development identity switch (?as= and workspace=), not a login. Ada starts as admin and Beau as writer. Same-origin pages can use it; a mismatched Origin is rejected.

Send a mutation

A client holds a Session. The host holds a DomainServer. Both call the same apply.

import { Session } from "yttrium-engine/client";
import { ROOT_ID, encodeFrame, decodeFrame } from "yttrium-engine/protocol";

const session = new Session("ada");
ws.send(encodeFrame(session.hello()));
ws.send(encodeFrame(session.mutate({
  type: "add_child",
  parent_id: ROOT_ID,
  slice_id: session.createSliceId(),
  fields: { text: "hi" },
})));
ws.onmessage = (event) => {
  session.onFrame(decodeFrame(typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data)));
  session.view();
};

mutate writes an overlay entry immediately. view() folds that overlay over authoritative state. When an ack arrives, the overlay entry retires. Persist the outbox before sending if the edit must survive reload; the playground does this through Outbox.

Talk to a domain

The Worker upgrades GET /domain/:encoded-name to a WebSocket. Demo domain names look like:

ws/demo/acl
ws/demo/catalog
ws/demo/user/ada
ws/demo/channel/general
ws/demo/document/notes
ws/demo/canvas/board

The playground attaches ?as=ada&workspace=demo so the development adapter can bind a principal. A real application supplies its own authenticate hook and does not treat a query parameter as proof of identity.

Deploy

npm run deploy        # build playground assets, then wrangler deploy

That publishes the Worker, the Durable Object class, and the playground/docs assets. See Host for persistence, hibernation, and logs.

Next