Cheatsheet
Common patterns — Python backend and TypeScript frontend side by side
Quick reference for the most common things you'll do with uRun. Each pattern shows the Python GPU runtime and the TypeScript frontend together.
The product primitive is a Session: reference an App, invoke a deployed function by name to get a Session, then exchange media streams and a synced control document with the running GPU.
Streams carry media: ctx.stream("video") on Python, session.stream("video") on TypeScript. Same name, both sides.
Documents carry state: ctx.doc("control") on Python, session.doc("control") on TypeScript. CRDT-backed, synced to the GPU runtime over the session transport.
Store carries weight: store / @store.cache on Python is the content-addressed object store behind every function — model weights, caches, tensors.
The real React surface
The core of @urun-sh/react is useApp() — everything hangs off the Session it returns: session.stream(name) returns a SessionStream (read the inbound track via .track or .on('track', …), publish via .attach(track) / .detach()), and session.doc(key) returns the synced document. On top of that the package ships focused hooks (useRequest / useCompletion / useChat, useSessionTrack, useSessionDoc, useStreamMessages, useSessionEndsAt, useSessionIdle) and drop-in components (media: Camera, Voice, Audio, Video, Image, Mic under a Session; session honesty: UrunSessionClock, UrunIdleWarning) — see the hooks reference and Inputs & Outputs. Every snippet below uses only the real published API.
Setup
import urun
from urun import App, store
app = App("my-app")import { App } from "@urun-sh/core"
const app = App("my-app", {
baseUrl: "https://urun.sh",
orgId: "your-org-id",
})import { UrunProvider, useApp } from "@urun-sh/react"
import { useMemo } from "react"
function Root() {
return (
<UrunProvider
baseUrl="https://urun.sh"
orgId="your-org-id"
appId="my-app"
authProvider="workos"
>
<MyApp />
</UrunProvider>
)
}
function MyApp() {
const app = useApp() // App("my-app") — same name as Python side
const session = useMemo(() => app.generate(), [app]) // calls @app.function on the GPU
const video = useMemo(() => session.stream("video"), [session]) // SessionStream
// video.on("track", (t) => { ... }) // inbound track from ctx.stream("video")
// session.doc("control").set({...}) // CRDT state → ctx.doc("control")
}All examples below assume app / session are created as shown above.
Stream video from GPU to browser
@app.function(gpus="h100:1")
async def generate(ctx: urun.Context):
video = ctx.stream("video", codec="h264", fps=24)
model = store.get("my-model")
def produce(*, stop_event, config):
while not stop_event.is_set():
yield model.generate()
pipeline = (
ctx.source(produce, name="generate", device="cuda")
| ctx.pace(fps=24)
| video
)const session = app.generate()
const video = session.stream("video") // SessionStream
video.on("track", (track) => {
if (track) videoElement.srcObject = new MediaStream([track])
})function VideoPlayer() {
const app = useApp()
const session = useMemo(() => app.generate(), [app])
const video = useMemo(() => session.stream("video"), [session])
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
return video.on("track", (track) => {
if (track && videoRef.current) {
videoRef.current.srcObject = new MediaStream([track])
}
})
}, [video])
return <video ref={videoRef} autoPlay muted playsInline />
}Send a prompt from browser to GPU
@app.function(gpus="h100:1")
async def generate(ctx: urun.Context):
control = ctx.doc("control")
video = ctx.stream("video", codec="h264", fps=24)
def produce(*, stop_event, config):
while not stop_event.is_set():
# live inline doc read — picks up browser writes each chunk
prompt = control.get("prompt.text", "default")
yield model.generate(prompt)
pipeline = (
ctx.source(produce, name="generate", device="cuda")
| ctx.pace(fps=24)
| video
)
await pipeline.run()const session = app.generate()
// Mutate the control doc — CRDT syncs to GPU
session.doc("control").set({
prompt: {
text: "A sunset over the ocean",
reset: true,
},
})function PromptInput() {
const app = useApp()
const session = app.generate()
return (
<input
placeholder="Enter a prompt"
onKeyDown={(e) => {
if (e.key === "Enter") {
session.doc("control").set({
prompt: { text: e.currentTarget.value, reset: true },
})
}
}}
/>
)
}Build an SPMD pipeline across GPUs
@app.function(gpus="b200:2")
async def generate(ctx: urun.Context):
video = ctx.stream("video", codec="h264",
fps=24, rank=1)
control = ctx.doc("control")
pipeline = (
ctx.source(denoise, name="denoise",
device="cuda", rank=0)
| ctx.pipe(vae_decode, name="vae",
device="cuda", rank=1)
| ctx.unbatch(rank=1)
| ctx.pace(fps=24, rank=1)
| video
)
# denoise reads control.get("prompt.text") live, inline
await pipeline.run()// Same as single-GPU — the pipeline
// is transparent to the frontend
const session = app.generate()
const video = session.stream("video")
video.on("track", (track) => {
if (track) videoElement.srcObject = new MediaStream([track])
})
session.doc("control").set({
prompt: { text: "A forest scene" },
})// Multi-GPU pipeline is transparent to React too —
// same Session API as the single-GPU case
function VideoPlayer() {
const app = useApp()
const session = useMemo(() => app.generate(), [app])
const video = useMemo(() => session.stream("video"), [session])
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
return video.on("track", (track) => {
if (track && videoRef.current) videoRef.current.srcObject = new MediaStream([track])
})
}, [video])
return (
<>
<video ref={videoRef} autoPlay muted playsInline />
<button onClick={() => session.doc("control").set({ prompt: { text: "A forest scene" } })}>
Generate
</button>
</>
)
}Cache a model across sessions
@store.cache(prefix="model", gpu_pin=True)
def load_model(device: str):
"""First call loads from disk.
All subsequent calls return the cached object."""
return Model.from_pretrained("my-model").to(device)
@app.function(gpus="h100:1")
async def generate(ctx: urun.Context):
model = load_model(str(ctx.device))
# model stays in GPU memory between sessions// No frontend code needed — caching is
// entirely server-side. The browser just
// connects and gets faster warm starts.// No React code needed — caching is server-side.
// Sessions just start faster on warm workers.
function VideoPlayer() {
const app = useApp()
const session = useMemo(() => app.generate(), [app]) // warm start if model is cached
const video = useMemo(() => session.stream("video"), [session])
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
return video.on("track", (track) => {
if (track && videoRef.current) videoRef.current.srcObject = new MediaStream([track])
})
}, [video])
return <video ref={videoRef} autoPlay muted playsInline />
}Load a model from HuggingFace
from urun import store
# Download, cache, and get the local path (for libraries that need it)
path = store.path("hf://meta-llama/Llama-3-70B")// Model loading is server-side only.
// The frontend just connects — models are
// already loaded and cached on the GPU worker.
const session = app.generate()
const video = session.stream("video")
video.on("track", (track) => {
if (track) videoElement.srcObject = new MediaStream([track])
})// Model loading is server-side only — React just connects
function VideoPlayer() {
const app = useApp()
const session = useMemo(() => app.generate(), [app])
const video = useMemo(() => session.stream("video"), [session])
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
return video.on("track", (track) => {
if (track && videoRef.current) videoRef.current.srcObject = new MediaStream([track])
})
}, [video])
return <video ref={videoRef} autoPlay muted playsInline />
}Send settings from browser to GPU
@app.function(gpus="h100:1")
async def generate(ctx: urun.Context):
control = ctx.doc("control")
video = ctx.stream("video", codec="h264", fps=24)
def produce(*, stop_event, config):
while not stop_event.is_set():
# live inline doc reads — the latest merged settings
yield model.generate(
quality=control.get("settings.quality", "high"),
fps=control.get("settings.fps", 24),
superres=control.get("settings.superres", False),
)
pipeline = (
ctx.source(produce, name="generate", device="cuda")
| ctx.pace(fps=24)
| video
)
await pipeline.run()// Mutate settings — CRDT merges automatically
session.doc("control").set({
settings: {
quality: "high",
fps: 30,
superres: true,
},
})function SettingsPanel() {
const app = useApp()
const session = app.generate()
const doc = session.doc("control")
return (
<label>
Super-resolution
<input
type="checkbox"
onChange={(e) =>
doc.set({ settings: { quality: "high", fps: 30, superres: e.target.checked } })
}
/>
</label>
)
}Handle keyboard/mouse input (interactive)
Continuous input (held keys, pointer motion at 30–60 Hz) rides awareness presence, not the control doc — presence is ephemeral by design, so a dropped tab can never leave a key "held down". Discrete state (a settings toggle, a placed object) still goes on the doc.
@app.function(gpus="b200:1")
async def play(ctx: urun.Context):
video = ctx.stream("video", codec="h264", fps=60)
# ctx.input() samples the browser's awareness input
# (held keys + pointer deltas) — a pipeline HEAD
pipeline = (
ctx.input(hz=60)
| ctx.pipe(render_scene, name="render", device="cuda")
| ctx.pace(fps=60)
| video
)
await pipeline.run()import { createInputPresencePublisher } from "@urun-sh/core"
const session = app.play()
// Publishes held keys + cumulative pointer counters
// into the session's awareness (throttled, default 30 Hz)
const input = createInputPresencePublisher({
awareness: {
setLocalStateField: (f, v) => session.presence.setField(f, v),
},
})
window.addEventListener("keydown", (e) => input.keyDown(e.key))
window.addEventListener("keyup", (e) => input.keyUp(e.key))
window.addEventListener("mousemove", (e) =>
input.movePointer(e.movementX, e.movementY))import { useApp, useInputPresence } from "@urun-sh/react"
import { useRef } from "react"
function GameSurface() {
const app = useApp()
const session = app.play()
// Pointer-lock capture + throttled awareness publishing, handled for you
const { engage, engaged, heldKeys } = useInputPresence(session)
const surface = useRef<HTMLDivElement>(null)
return (
<div ref={surface} onClick={() => surface.current && engage(surface.current)}>
{engaged ? `keys: ${heldKeys.join(" ")}` : "Click to play"}
</div>
)
}Send voice/mic audio to GPU
@app.function(gpus="h100:1")
async def voice_chat(ctx: urun.Context):
mic = ctx.stream("microphone")
# Option 1: consume directly
async for chunk in mic:
transcript = transcribe(chunk)
# Option 2: pipe to OpenAI Realtime
from openai import AsyncOpenAI
client = AsyncOpenAI()
async with client.realtime.connect(model="gpt-4o-realtime") as rt:
mic.pipe(rt.input_audio)
async for event in rt:
if event.type == "response.audio.delta":
await ctx.stream("audio").emit(event.delta)
elif event.type == "response.text.done":
ctx.doc("control").set({"transcript": event.text})const session = app.voice_chat()
const mic = session.stream("microphone") // SessionStream (mirrors ctx.stream)
// Send mic up by attaching a local track
const media = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 1,
sampleRate: 48000,
echoCancellation: true,
noiseSuppression: true,
},
})
await mic.attach(media.getAudioTracks()[0])
// Stop sending
await mic.detach()function VoiceChat() {
const app = useApp()
const session = useMemo(() => app.voice_chat(), [app])
const mic = useMemo(() => session.stream("microphone"), [session])
const [active, setActive] = useState(false)
async function start() {
const media = await navigator.mediaDevices.getUserMedia({
audio: { channelCount: 1, sampleRate: 48000, echoCancellation: true, noiseSuppression: true },
})
await mic.attach(media.getAudioTracks()[0])
setActive(true)
}
async function stop() {
await mic.detach()
setActive(false)
}
return <button onClick={active ? stop : start}>{active ? "Stop" : "Start"} mic</button>
}Deploy a CPU-only app
app = urun.App("my-service")
@app.function(cpus=1)
async def process(ctx: urun.Context):
control = ctx.doc("control")
video = ctx.stream("video", format="raw", fps=10)
def produce(*, stop_event, config):
while not stop_event.is_set():
# live inline doc read
yield process_request(control.get("request"))
pipeline = (
ctx.source(produce, name="process")
| ctx.pace(fps=10)
| video
)
await pipeline.run()urun deploy app.pyDeclare dependencies and credentials
import urun
from urun import App, Dependencies, Credentials, store
app = App("my-app")
DEPS = Dependencies(
python=["torch>=2.4", "transformers", "diffusers"],
apt=["ffmpeg", "libopus0"],
)
CREDS = Credentials(
optional_env=["HF_TOKEN", "OPENAI_API_KEY"],
)
@app.function(gpus="h100:1", deps=DEPS, credentials=CREDS)
async def generate(ctx: urun.Context):
...React API quick reference
The anchor hook is useApp() — sessions, streams, and docs all hang off the Session you get from app.<fn>(). Purpose-built hooks (useRequest, useChat, useSessionTrack, useSessionIdle, …) and media components (Camera, Voice, Audio) are in the hooks reference and Inputs & Outputs.
import { UrunProvider, useApp } from "@urun-sh/react"
import { useEffect, useMemo, useRef } from "react"
function App() {
return (
<UrunProvider
baseUrl="https://urun.sh"
orgId="your-org-id"
appId="my-app"
authProvider="workos"
>
<VideoPlayer />
</UrunProvider>
)
}
function VideoPlayer() {
const app = useApp()
const session = useMemo(() => app.generate(), [app]) // invoke the deployed @app.function
const video = useMemo(() => session.stream("video"), [session]) // SessionStream
const ref = useRef<HTMLVideoElement>(null)
useEffect(() => {
// Read the inbound track:
return video.on("track", (track) => {
if (track && ref.current) ref.current.srcObject = new MediaStream([track])
})
// Or read the latest synchronously: video.track
}, [video])
// session.doc("control").set({ prompt: { text: "..." } }) → ctx.doc("control")
// video.attachVideo(localTrack) / video.detachVideo() → push input up
return <video ref={ref} autoPlay muted playsInline />
}UrunProvider handles auth internally — it acquires a token from the auth provider (WorkOS by default) and uses the orgId to resolve the correct JWKS endpoint. No manual token wiring needed. For customer-JWT auth, bring your own token validated against the JWKS you registered with urun auth trust-jwk. See the TypeScript SDK reference for the full hook + SessionStream API.