How to connect live clients

This guide shows you how to serve a Session or Hub over the connection adapters transports ships today: WebSocket, Server-Sent Events, Jupyter comm, and anywidget custom messages.

Serve a session over WebSocket

Install the WebSocket dependencies:

pip install "transports[connections]" uvicorn

Create a Starlette app:

import asyncio

import transports
from pydantic import BaseModel
from starlette.applications import Starlette
from starlette.routing import WebSocketRoute

class Counter(BaseModel):
    tick: int = 0

session = transports.Session()
counter = Counter()
session.host(counter)
server = transports.Server(session)

async def ticker():
    while True:
        await asyncio.sleep(1)
        counter.tick += 1

async def startup():
    asyncio.create_task(transports.autosync(server))
    asyncio.create_task(ticker())

app = Starlette(
    routes=[WebSocketRoute("/ws", transports.ws_endpoint(server))],
    on_startup=[startup],
)

Run it:

uvicorn app:app --reload

Run one autosync task per Server or Hub. It drains host-side mutations and broadcasts the resulting patches to all open connections.

Mirror the server in a browser

Initialize the wasm package, connect, and render whenever a message arrives.

import init, { Client, fromValue } from "1kbgz/transports";

await init();

const client = new Client();
const ws = client.connect("ws://127.0.0.1:8000/ws");

ws.addEventListener("message", () => {
  const [id] = client.ids();
  if (id === undefined) return;
  render(fromValue(client.value(id)));
});

To send an edit, create the next core Value and send the encoded proposal frame:

import { toValue } from "1kbgz/transports";

const [id] = client.ids();
ws.send(client.edit(id, toValue({ tick: 10 })));

The local mirror updates when the server echoes the authoritative patch.

Mirror the server in Python

Client.connect() runs a receive loop until the WebSocket closes.

client = transports.Client()
await client.connect("ws://127.0.0.1:8000/ws")

For Python clients that also send edits, manage the WebSocket loop directly and send the frame returned by client.edit(id, value).

Use MessagePack on a connection

Pass codec="msgpack" on the client. The client appends ?codec=msgpack; the server sends binary frames to that connection and can still serve JSON clients at the same time.

client = transports.Client(codec="msgpack")
await client.connect("ws://127.0.0.1:8000/ws")
const client = new Client("msgpack");
const ws = client.connect("ws://127.0.0.1:8000/ws");

Stream receive-only updates over SSE

Use SSE for dashboards and other receive-only clients.

pip install "transports[sse]"
import asyncio
from starlette.applications import Starlette
from starlette.routing import Route

async def startup():
    asyncio.create_task(transports.autosync(server))

app = Starlette(
    routes=[Route("/sse", transports.sse_endpoint(server))],
    on_startup=[startup],
)

Python client:

client = transports.Client()
await client.connect_sse("http://127.0.0.1:8000/sse")

Browser client:

const client = new Client();
const events = client.connectSSE("http://127.0.0.1:8000/sse");

SSE is JSON/text and server-to-client only. Use WebSocket when clients need to send edits.

Use a Jupyter comm

Install the comm dependency:

pip install "transports[jupyter]"

Wire a kernel comm to a Server or Hub:

from comm import create_comm

comm = create_comm(target_name="transports")
transports.serve_comm(server, comm)

# after mutating hosted models
transports.sync(server)

The comm carries JSON wire strings in data, so serve_comm rejects non-JSON codecs.

Use anywidget custom messages

serve_anywidget uses an anywidget-style send / on_msg object. The frontend sends {"ready": true} before snapshots are delivered.

conn = transports.serve_anywidget(server, widget)

# after mutating hosted models
transports.sync(server)

Frontend messages use the same client protocol:

const client = new Client();

model.on("msg:custom", (content) => {
  if (content.wire) client.recv(content.wire);
});

model.send({ ready: true });

Use model.send({ wire: client.edit(id, value) }) to send an edit from the frontend.

Serve a Hub

A Hub satisfies the same connection contract as Server, so the same adapters serve it: transports.ws_endpoint(hub) for WebSocket, transports.sse_endpoint(hub) for SSE, and the same serve_comm / serve_anywidget helpers for Jupyter (with autosync(hub) or sync(hub)).

hub = transports.Hub(key=lambda ws: ws.path_params["tenant"])
app = Starlette(routes=[WebSocketRoute("/ws/{tenant}", transports.ws_endpoint(hub))])