[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-rivet-live-cursors":3,"mdc--wn0v1t-key":41,"related-org-rivet-live-cursors":2704,"related-repo-rivet-live-cursors":2896},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":12,"stars":30,"repoUrl":31,"updatedAt":32,"license":33,"forks":34,"topics":35,"repo":36,"sourceUrl":39,"mdContent":40},"live-cursors","implement live cursors and multiplayer presence","Live cursors and multiplayer presence with Rivet Actors: per-connection cursor state, realtime updates over events or raw WebSockets, and throttling.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},"rivet","Rivet","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Frivet.png","rivet-dev",[13,15,18,21,24,27],{"name":9,"slug":8,"type":14},"tag",{"name":16,"slug":17,"type":14},"WebSockets","websockets",{"name":19,"slug":20,"type":14},"Collaboration","collaboration",{"name":22,"slug":23,"type":14},"Real-time","real-time",{"name":25,"slug":26,"type":14},"Frontend","frontend",{"name":28,"slug":29,"type":14},"UX Design","ux-design",17,"https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Fskills","2026-07-21T05:37:47.391088",null,7,[],{"repoUrl":31,"stars":30,"forks":34,"topics":37,"description":38},[],"Generated skill files for Rivet AI integrations","https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Fskills\u002Ftree\u002FHEAD\u002Flive-cursors","---\nname: \"live-cursors\"\ndescription: \"Live cursors and multiplayer presence with Rivet Actors: per-connection cursor state, realtime updates over events or raw WebSockets, and throttling.\"\n---\n\n# Live Cursors and Presence\n\n**IMPORTANT: Before doing anything, you MUST read `BASE_SKILL.md` in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.**\n\n## Working Examples\n\nIf you need a reference implementation, read the raw working example code in these templates:\n\n- [cursors](https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Frivet\u002Ftree\u002Fmain\u002Fexamples\u002Fcursors)\n- [cursors-raw-websocket](https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Frivet\u002Ftree\u002Fmain\u002Fexamples\u002Fcursors-raw-websocket)\n\n\nPatterns for building live cursors, multiplayer presence, and realtime cursor sharing with RivetKit. One room actor fans cursor positions out to every connected client, keyed per room with [actor keys](\u002Fdocs\u002Factors\u002Fkeys).\n\n## Starter Code\n\nStart with one of the two working variants on GitHub. Both implement the same collaborative cursor canvas with persistent text labels; they differ only in transport.\n\n| Variant | Starter Code | Transport | Presence Storage |\n| --- | --- | --- | --- |\n| `cursors` | [GitHub](https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Frivet\u002Ftree\u002Fmain\u002Fexamples\u002Fcursors) | Typed [actions](\u002Fdocs\u002Factors\u002Factions) and [events](\u002Fdocs\u002Factors\u002Fevents) over the RivetKit connection | `connState` per connection |\n| `cursors-raw-websocket` | [GitHub](https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Frivet\u002Ftree\u002Fmain\u002Fexamples\u002Fcursors-raw-websocket) | Raw [`onWebSocket` handler](\u002Fdocs\u002Factors\u002Fwebsocket-handler) with a custom JSON message protocol | Socket map in `createVars` |\n\nUse `cursors` by default: typed actions, typed events, and automatic connection tracking cover most apps with less code. Use `cursors-raw-websocket` when you need full control of the wire format, for example a custom JSON or binary protocol, or clients that do not use the RivetKit client library.\n\n## Connection State vs Persistent State\n\nPresence is ephemeral by definition. A cursor position is only meaningful while its connection is alive, so it belongs in per-connection storage, not in persistent actor state. Persistent state is reserved for data that must survive disconnects and actor restarts.\n\n| Data | Where It Lives | Why |\n| --- | --- | --- |\n| Cursor position | `connState` (`cursors`) or the `createVars` socket map (`cursors-raw-websocket`) | Scoped to one connection and discarded with it. Stale presence cannot accumulate in storage. |\n| Text labels (`textLabels`) | Persistent actor `state` in both variants | Canvas content must survive disconnects and actor restarts. |\n\nIn the `cursors` variant, `updateCursor` writes `c.conn.state.cursor` and `getRoomState` rebuilds the presence snapshot by iterating `c.conns.values()`, so the cursor map is always derived from live connections rather than stored. See [Connections](\u002Fdocs\u002Factors\u002Fconnections) for `connState` and [State](\u002Fdocs\u002Factors\u002Fstate) for persistence semantics.\n\n## Presence Lifecycle\n\n- **Join**: The `cursors-raw-websocket` variant pushes an `init` message with the current `{ cursors, textLabels }` snapshot as soon as a socket connects. The `cursors` variant has no explicit join broadcast; the client calls the `getRoomState` action once after connecting to seed its local maps, and peers first see a new user on that user's first `cursorMoved` broadcast.\n- **Move**: Every `updateCursor` call writes the connection's presence entry, then broadcasts `cursorMoved` to all connections, including the sender.\n- **Leave**: The `cursors` variant handles leave in `onDisconnect`, broadcasting `cursorRemoved` with the connection's last cursor. The raw variant does the same from the socket `close` listener, then deletes the session from the `vars.websockets` map. Clients delete that user from their local cursor map, so stale cursors disappear the moment a tab closes.\n\nSee [Lifecycle](\u002Fdocs\u002Factors\u002Flifecycle) for `onDisconnect` and `createVars`.\n\n## Update Throttling\n\nNeither example throttles. Both frontends send a cursor update on every raw `mousemove` event with no debounce or interval cap. That is fine for a demo, but a fast mouse on a high-refresh display can emit hundreds of events per second per user. The patterns below are recommended production hardening on top of the starter code, not something the examples implement.\n\n| Layer | Pattern | Guidance |\n| --- | --- | --- |\n| Client (smoothness) | Throttle to 20-30Hz | Sample the latest pointer position every 33-50ms and send only that. Drop intermediate moves, but always flush the final position so cursors settle at the true location. Interpolate between received positions on the rendering side. |\n| Server (enforcement) | Per-connection rate limit | Track the last accepted update timestamp per connection and drop or coalesce updates arriving faster than your cap. Client throttles are cooperative; the actor is the enforcement boundary. |\n\n## Actors\n\n- **Key**: `cursorRoom[roomId]` (the frontend defaults `roomId` to `\"general\"`)\n- **Responsibility**: Holds per-connection cursor presence in `connState`, persists shared text labels in actor state, and broadcasts cursor and text updates to all connections.\n- **Actions**\n  - `updateCursor`\n  - `updateText`\n  - `removeText`\n  - `getRoomState`\n- **Events**\n  - `cursorMoved`\n  - `cursorRemoved`\n  - `textUpdated`\n  - `textRemoved`\n- **Queues**\n  - None\n- **State**\n  - JSON\n  - `textLabels` (persistent)\n  - `connState.cursor` per connection (ephemeral)\n\n- **Key**: `cursorRoom[roomId]` (resolved via `client.cursorRoom.getOrCreate(roomId)`)\n- **Responsibility**: Exposes a raw WebSocket endpoint, tracks live sockets and their cursors in a `createVars` map keyed by a `sessionId` query parameter, persists text labels, and manually fans JSON frames out to every socket.\n- **Actions**\n  - `getOrCreate` (stub returning `{ status: \"ok\" }`; the frontend resolves the actor ID with the client handle's `getOrCreate(roomId).resolve()`, which creates the actor without dispatching this action)\n  - `getRoomState`\n- **Queues**\n  - None\n- **State**\n  - JSON\n  - `textLabels` (persistent)\n  - `vars.websockets` map of `sessionId` to socket and cursor (in-memory, lost on restart)\n\nThe raw variant defines no RivetKit events. Its message names are `type` fields on raw JSON frames:\n\n| Direction | Message `type` | Payload |\n| --- | --- | --- |\n| Client to server | `updateCursor` | `{ userId, x, y }` |\n| Client to server | `updateText` | `{ id, userId, text, x, y }` |\n| Client to server | `removeText` | `{ id }` |\n| Server to client | `init` | `{ cursors, textLabels }` snapshot on connect |\n| Server to client | `cursorMoved`, `textUpdated`, `textRemoved`, `cursorRemoved` | The corresponding cursor, label, or ID payload |\n\n## Lifecycle\n\n### cursors (Actions + Events)\n\n```mermaid\nsequenceDiagram\n\tparticipant A as Client A\n\tparticipant R as cursorRoom\n\tparticipant B as Other Clients\n\n\tA->>R: connect via useActor (cursorRoom[roomId])\n\tA->>R: getRoomState()\n\tR-->>A: {cursors, textLabels}\n\tloop every mouse move\n\t\tA->>R: updateCursor(userId, x, y)\n\t\tNote over R: write c.conn.state.cursor\n\t\tR-->>B: cursorMoved (broadcast)\n\tend\n\tA->>R: updateText(id, userId, text, x, y)\n\tNote over R: upsert persistent state.textLabels\n\tR-->>B: textUpdated (broadcast)\n\tNote over A: tab closes\n\tNote over R: onDisconnect reads conn.state.cursor\n\tR-->>B: cursorRemoved (broadcast)\n```\n\n### cursors-raw-websocket\n\n```mermaid\nsequenceDiagram\n\tparticipant A as Client A\n\tparticipant R as cursorRoom\n\tparticipant B as Other Clients\n\n\tA->>R: getOrCreate(roomId).resolve()\n\tR-->>A: actorId\n\tA->>R: open WebSocket \u002Fgateway\u002F{actorId}\u002Fwebsocket?sessionId=...\n\tNote over R: close 1008 if sessionId is missing\n\tNote over R: store socket in vars.websockets\n\tR-->>A: init {cursors, textLabels}\n\tloop every mouse move\n\t\tA->>R: {type: \"updateCursor\"} frame\n\t\tNote over R: update session cursor in vars\n\t\tR-->>B: cursorMoved frame\n\tend\n\tNote over A: socket closes\n\tR-->>B: cursorRemoved frame\n\tNote over R: delete session from vars.websockets\n```\n\n## Security Checklist\n\nBoth examples ship without authentication so the presence pattern stays readable. Everything below is recommended hardening for production, not behavior the examples implement.\n\n- **Identity**: Bind presence identity to the connection (`c.conn.id` in the actions variant, a server-generated session ID in the raw variant). Never trust a client-supplied `userId`; in the examples it is a random client-generated string, so any client can impersonate or remove any cursor.\n- **Authorization**: Authorize label mutations by owner. In the examples, `updateText` accepts arbitrary `id` and `userId` arguments and `removeText` accepts an arbitrary `id`, so any client can edit or delete any label.\n- **Input validation**: Clamp `x` and `y` to canvas bounds, cap text label length, and cap the total `textLabels` count so persistent state cannot grow unbounded.\n- **Rate limiting**: Enforce a per-connection cap on `updateCursor` (for example 30Hz) and on label writes, as described in [Update Throttling](#update-throttling).\n- **Protocol strictness (raw variant)**: Validate message shape before use and close the socket on malformed JSON instead of logging and continuing. Reject duplicate `sessionId` values rather than silently overwriting another session's socket entry.\n\n## Reference Map\n\n### Actors\n\n- [Access Control](reference\u002Factors\u002Faccess-control.md)\n- [Actions](reference\u002Factors\u002Factions.md)\n- [Actor Keys](reference\u002Factors\u002Fkeys.md)\n- [Actor Runtime Socket](reference\u002Factors\u002Factor-runtime-socket.md)\n- [Actor Statuses](reference\u002Factors\u002Fstatuses.md)\n- [Authentication](reference\u002Factors\u002Fauthentication.md)\n- [Cloudflare Workers Quickstart](reference\u002Factors\u002Fquickstart\u002Fcloudflare.md)\n- [Communicating Between Actors](reference\u002Factors\u002Fcommunicating-between-actors.md)\n- [Connections](reference\u002Factors\u002Fconnections.md)\n- [Custom Inspector Tabs](reference\u002Factors\u002Finspector-tabs.md)\n- [Debugging](reference\u002Factors\u002Fdebugging.md)\n- [Design Patterns](reference\u002Factors\u002Fdesign-patterns.md)\n- [Destroying Actors](reference\u002Factors\u002Fdestroy.md)\n- [Effect.ts Quickstart (Beta)](reference\u002Factors\u002Fquickstart\u002Feffect.md)\n- [Errors](reference\u002Factors\u002Ferrors.md)\n- [Fetch and WebSocket Handler](reference\u002Factors\u002Ffetch-and-websocket-handler.md)\n- [Helper Types](reference\u002Factors\u002Fhelper-types.md)\n- [Icons & Names](reference\u002Factors\u002Fappearance.md)\n- [In-Memory State](reference\u002Factors\u002Fstate.md)\n- [Input Parameters](reference\u002Factors\u002Finput.md)\n- [Lifecycle](reference\u002Factors\u002Flifecycle.md)\n- [Limits](reference\u002Factors\u002Flimits.md)\n- [Low-Level HTTP Request Handler](reference\u002Factors\u002Frequest-handler.md)\n- [Low-Level KV Storage](reference\u002Factors\u002Fkv.md)\n- [Low-Level WebSocket Handler](reference\u002Factors\u002Fwebsocket-handler.md)\n- [Metadata](reference\u002Factors\u002Fmetadata.md)\n- [Next.js Quickstart](reference\u002Factors\u002Fquickstart\u002Fnext-js.md)\n- [Node.js & Bun Quickstart](reference\u002Factors\u002Fquickstart\u002Fbackend.md)\n- [Queues & Run Loops](reference\u002Factors\u002Fqueues.md)\n- [React Quickstart](reference\u002Factors\u002Fquickstart\u002Freact.md)\n- [Realtime](reference\u002Factors\u002Fevents.md)\n- [Rust Quickstart (Beta)](reference\u002Factors\u002Fquickstart\u002Frust.md)\n- [Scaling & Concurrency](reference\u002Factors\u002Fscaling.md)\n- [Schedule & Cron](reference\u002Factors\u002Fschedule.md)\n- [Sharing and Joining State](reference\u002Factors\u002Fsharing-and-joining-state.md)\n- [SQLite](reference\u002Factors\u002Fsqlite.md)\n- [SQLite + Drizzle](reference\u002Factors\u002Fsqlite-drizzle.md)\n- [Supabase Functions Quickstart](reference\u002Factors\u002Fquickstart\u002Fsupabase.md)\n- [Testing](reference\u002Factors\u002Ftesting.md)\n- [Troubleshooting](reference\u002Factors\u002Ftroubleshooting.md)\n- [Types](reference\u002Factors\u002Ftypes.md)\n- [Vanilla HTTP API](reference\u002Factors\u002Fhttp-api.md)\n- [Versions & Upgrades](reference\u002Factors\u002Fversions.md)\n- [Workflows](reference\u002Factors\u002Fworkflows.md)\n\n### Cli\n\n- [CLI](reference\u002Fcli.md)\n\n### Clients\n\n- [Node.js & Bun](reference\u002Fclients\u002Fjavascript.md)\n- [React](reference\u002Fclients\u002Freact.md)\n- [Rust (Beta)](reference\u002Fclients\u002Frust.md)\n- [Swift](reference\u002Fclients\u002Fswift.md)\n- [SwiftUI](reference\u002Fclients\u002Fswiftui.md)\n\n### Cookbook\n\n- [AI Agent](reference\u002Fcookbook\u002Fai-agent.md)\n- [Chat Room](reference\u002Fcookbook\u002Fchat-room.md)\n- [Collaborative Text Editor](reference\u002Fcookbook\u002Fcollaborative-text-editor.md)\n- [Cron Jobs and Scheduled Tasks](reference\u002Fcookbook\u002Fcron-jobs.md)\n- [Database per Tenant](reference\u002Fcookbook\u002Fper-tenant-database.md)\n- [Deploying Rivet in a VPC or Air-Gapped Network](reference\u002Fcookbook\u002Fvpc-air-gapped.md)\n- [Live Cursors and Presence](reference\u002Fcookbook\u002Flive-cursors.md)\n- [Multiplayer Game](reference\u002Fcookbook\u002Fmultiplayer-game.md)\n\n### Deploy\n\n- [Container Runner](reference\u002Fdeploy\u002Fcontainer-runner.md)\n- [Deploy To Amazon Web Services Lambda](reference\u002Fdeploy\u002Faws-lambda.md)\n- [Deploying to AWS ECS](reference\u002Fdeploy\u002Faws-ecs.md)\n- [Deploying to Cloudflare Workers](reference\u002Fdeploy\u002Fcloudflare.md)\n- [Deploying to Freestyle](reference\u002Fdeploy\u002Ffreestyle.md)\n- [Deploying to Google Cloud Run](reference\u002Fdeploy\u002Fgcp-cloud-run.md)\n- [Deploying to Hetzner](reference\u002Fdeploy\u002Fhetzner.md)\n- [Deploying to Kubernetes](reference\u002Fdeploy\u002Fkubernetes.md)\n- [Deploying to Railway](reference\u002Fdeploy\u002Frailway.md)\n- [Deploying to Rivet Compute](reference\u002Fdeploy\u002Frivet-compute.md)\n- [Deploying to Supabase Functions](reference\u002Fdeploy\u002Fsupabase.md)\n- [Deploying to Vercel](reference\u002Fdeploy\u002Fvercel.md)\n- [Deploying to VMs & Bare Metal](reference\u002Fdeploy\u002Fvm-and-bare-metal.md)\n\n### General\n\n- [Actor Configuration](reference\u002Fgeneral\u002Factor-configuration.md)\n- [Architecture](reference\u002Fgeneral\u002Farchitecture.md)\n- [Cross-Origin Resource Sharing](reference\u002Fgeneral\u002Fcors.md)\n- [Documentation for LLMs & AI](reference\u002Fgeneral\u002Fdocs-for-llms.md)\n- [Edge Networking](reference\u002Fgeneral\u002Fedge.md)\n- [Endpoints](reference\u002Fgeneral\u002Fendpoints.md)\n- [Environment Variables](reference\u002Fgeneral\u002Fenvironment-variables.md)\n- [HTTP Server](reference\u002Fgeneral\u002Fhttp-server.md)\n- [Logging](reference\u002Fgeneral\u002Flogging.md)\n- [Pool Configuration](reference\u002Fgeneral\u002Fpool-configuration.md)\n- [Production Checklist](reference\u002Fgeneral\u002Fproduction-checklist.md)\n- [Registry Configuration](reference\u002Fgeneral\u002Fregistry-configuration.md)\n- [Runtime Modes](reference\u002Fgeneral\u002Fruntime-modes.md)\n- [WASM vs Native SDK](reference\u002Fgeneral\u002Fwasm-vs-native-sdk.md)\n\n### Self Hosting\n\n- [Configuration](reference\u002Fself-hosting\u002Fconfiguration.md)\n- [Docker Compose](reference\u002Fself-hosting\u002Fdocker-compose.md)\n- [Docker Container](reference\u002Fself-hosting\u002Fdocker-container.md)\n- [File System](reference\u002Fself-hosting\u002Ffilesystem.md)\n- [FoundationDB (Enterprise)](reference\u002Fself-hosting\u002Ffoundationdb.md)\n- [Installing Rivet Engine](reference\u002Fself-hosting\u002Finstall.md)\n- [Kubernetes](reference\u002Fself-hosting\u002Fkubernetes.md)\n- [Multi-Region](reference\u002Fself-hosting\u002Fmulti-region.md)\n- [PostgreSQL](reference\u002Fself-hosting\u002Fpostgres.md)\n- [Production Checklist](reference\u002Fself-hosting\u002Fproduction-checklist.md)\n- [Railway Deployment](reference\u002Fself-hosting\u002Frailway.md)\n- [Render Deployment](reference\u002Fself-hosting\u002Frender.md)\n- [TLS & Certificates](reference\u002Fself-hosting\u002Ftls.md)\n\n",{"data":42,"body":43},{"name":4,"description":6},{"type":44,"children":45},"root",[46,55,74,81,86,113,126,132,137,275,294,300,305,409,474,480,610,634,640,653,716,722,1062,1075,1245,1250,1257,1440,1444,1596,1602,1607,1753,1759,1764,2160,2166,2178,2184,2232,2238,2312,2318,2438,2444,2573,2579,2698],{"type":47,"tag":48,"props":49,"children":51},"element","h1",{"id":50},"live-cursors-and-presence",[52],{"type":53,"value":54},"text","Live Cursors and Presence",{"type":47,"tag":56,"props":57,"children":58},"p",{},[59],{"type":47,"tag":60,"props":61,"children":62},"strong",{},[63,65,72],{"type":53,"value":64},"IMPORTANT: Before doing anything, you MUST read ",{"type":47,"tag":66,"props":67,"children":69},"code",{"className":68},[],[70],{"type":53,"value":71},"BASE_SKILL.md",{"type":53,"value":73}," in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.",{"type":47,"tag":75,"props":76,"children":78},"h2",{"id":77},"working-examples",[79],{"type":53,"value":80},"Working Examples",{"type":47,"tag":56,"props":82,"children":83},{},[84],{"type":53,"value":85},"If you need a reference implementation, read the raw working example code in these templates:",{"type":47,"tag":87,"props":88,"children":89},"ul",{},[90,103],{"type":47,"tag":91,"props":92,"children":93},"li",{},[94],{"type":47,"tag":95,"props":96,"children":100},"a",{"href":97,"rel":98},"https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Frivet\u002Ftree\u002Fmain\u002Fexamples\u002Fcursors",[99],"nofollow",[101],{"type":53,"value":102},"cursors",{"type":47,"tag":91,"props":104,"children":105},{},[106],{"type":47,"tag":95,"props":107,"children":110},{"href":108,"rel":109},"https:\u002F\u002Fgithub.com\u002Frivet-dev\u002Frivet\u002Ftree\u002Fmain\u002Fexamples\u002Fcursors-raw-websocket",[99],[111],{"type":53,"value":112},"cursors-raw-websocket",{"type":47,"tag":56,"props":114,"children":115},{},[116,118,124],{"type":53,"value":117},"Patterns for building live cursors, multiplayer presence, and realtime cursor sharing with RivetKit. One room actor fans cursor positions out to every connected client, keyed per room with ",{"type":47,"tag":95,"props":119,"children":121},{"href":120},"\u002Fdocs\u002Factors\u002Fkeys",[122],{"type":53,"value":123},"actor keys",{"type":53,"value":125},".",{"type":47,"tag":75,"props":127,"children":129},{"id":128},"starter-code",[130],{"type":53,"value":131},"Starter Code",{"type":47,"tag":56,"props":133,"children":134},{},[135],{"type":53,"value":136},"Start with one of the two working variants on GitHub. Both implement the same collaborative cursor canvas with persistent text labels; they differ only in transport.",{"type":47,"tag":138,"props":139,"children":140},"table",{},[141,169],{"type":47,"tag":142,"props":143,"children":144},"thead",{},[145],{"type":47,"tag":146,"props":147,"children":148},"tr",{},[149,155,159,164],{"type":47,"tag":150,"props":151,"children":152},"th",{},[153],{"type":53,"value":154},"Variant",{"type":47,"tag":150,"props":156,"children":157},{},[158],{"type":53,"value":131},{"type":47,"tag":150,"props":160,"children":161},{},[162],{"type":53,"value":163},"Transport",{"type":47,"tag":150,"props":165,"children":166},{},[167],{"type":53,"value":168},"Presence Storage",{"type":47,"tag":170,"props":171,"children":172},"tbody",{},[173,226],{"type":47,"tag":146,"props":174,"children":175},{},[176,185,194,215],{"type":47,"tag":177,"props":178,"children":179},"td",{},[180],{"type":47,"tag":66,"props":181,"children":183},{"className":182},[],[184],{"type":53,"value":102},{"type":47,"tag":177,"props":186,"children":187},{},[188],{"type":47,"tag":95,"props":189,"children":191},{"href":97,"rel":190},[99],[192],{"type":53,"value":193},"GitHub",{"type":47,"tag":177,"props":195,"children":196},{},[197,199,205,207,213],{"type":53,"value":198},"Typed ",{"type":47,"tag":95,"props":200,"children":202},{"href":201},"\u002Fdocs\u002Factors\u002Factions",[203],{"type":53,"value":204},"actions",{"type":53,"value":206}," and ",{"type":47,"tag":95,"props":208,"children":210},{"href":209},"\u002Fdocs\u002Factors\u002Fevents",[211],{"type":53,"value":212},"events",{"type":53,"value":214}," over the RivetKit connection",{"type":47,"tag":177,"props":216,"children":217},{},[218,224],{"type":47,"tag":66,"props":219,"children":221},{"className":220},[],[222],{"type":53,"value":223},"connState",{"type":53,"value":225}," per connection",{"type":47,"tag":146,"props":227,"children":228},{},[229,237,245,264],{"type":47,"tag":177,"props":230,"children":231},{},[232],{"type":47,"tag":66,"props":233,"children":235},{"className":234},[],[236],{"type":53,"value":112},{"type":47,"tag":177,"props":238,"children":239},{},[240],{"type":47,"tag":95,"props":241,"children":243},{"href":108,"rel":242},[99],[244],{"type":53,"value":193},{"type":47,"tag":177,"props":246,"children":247},{},[248,250,262],{"type":53,"value":249},"Raw ",{"type":47,"tag":95,"props":251,"children":253},{"href":252},"\u002Fdocs\u002Factors\u002Fwebsocket-handler",[254,260],{"type":47,"tag":66,"props":255,"children":257},{"className":256},[],[258],{"type":53,"value":259},"onWebSocket",{"type":53,"value":261}," handler",{"type":53,"value":263}," with a custom JSON message protocol",{"type":47,"tag":177,"props":265,"children":266},{},[267,269],{"type":53,"value":268},"Socket map in ",{"type":47,"tag":66,"props":270,"children":272},{"className":271},[],[273],{"type":53,"value":274},"createVars",{"type":47,"tag":56,"props":276,"children":277},{},[278,280,285,287,292],{"type":53,"value":279},"Use ",{"type":47,"tag":66,"props":281,"children":283},{"className":282},[],[284],{"type":53,"value":102},{"type":53,"value":286}," by default: typed actions, typed events, and automatic connection tracking cover most apps with less code. Use ",{"type":47,"tag":66,"props":288,"children":290},{"className":289},[],[291],{"type":53,"value":112},{"type":53,"value":293}," when you need full control of the wire format, for example a custom JSON or binary protocol, or clients that do not use the RivetKit client library.",{"type":47,"tag":75,"props":295,"children":297},{"id":296},"connection-state-vs-persistent-state",[298],{"type":53,"value":299},"Connection State vs Persistent State",{"type":47,"tag":56,"props":301,"children":302},{},[303],{"type":53,"value":304},"Presence is ephemeral by definition. A cursor position is only meaningful while its connection is alive, so it belongs in per-connection storage, not in persistent actor state. Persistent state is reserved for data that must survive disconnects and actor restarts.",{"type":47,"tag":138,"props":306,"children":307},{},[308,329],{"type":47,"tag":142,"props":309,"children":310},{},[311],{"type":47,"tag":146,"props":312,"children":313},{},[314,319,324],{"type":47,"tag":150,"props":315,"children":316},{},[317],{"type":53,"value":318},"Data",{"type":47,"tag":150,"props":320,"children":321},{},[322],{"type":53,"value":323},"Where It Lives",{"type":47,"tag":150,"props":325,"children":326},{},[327],{"type":53,"value":328},"Why",{"type":47,"tag":170,"props":330,"children":331},{},[332,376],{"type":47,"tag":146,"props":333,"children":334},{},[335,340,371],{"type":47,"tag":177,"props":336,"children":337},{},[338],{"type":53,"value":339},"Cursor position",{"type":47,"tag":177,"props":341,"children":342},{},[343,348,350,355,357,362,364,369],{"type":47,"tag":66,"props":344,"children":346},{"className":345},[],[347],{"type":53,"value":223},{"type":53,"value":349}," (",{"type":47,"tag":66,"props":351,"children":353},{"className":352},[],[354],{"type":53,"value":102},{"type":53,"value":356},") or the ",{"type":47,"tag":66,"props":358,"children":360},{"className":359},[],[361],{"type":53,"value":274},{"type":53,"value":363}," socket map (",{"type":47,"tag":66,"props":365,"children":367},{"className":366},[],[368],{"type":53,"value":112},{"type":53,"value":370},")",{"type":47,"tag":177,"props":372,"children":373},{},[374],{"type":53,"value":375},"Scoped to one connection and discarded with it. Stale presence cannot accumulate in storage.",{"type":47,"tag":146,"props":377,"children":378},{},[379,391,404],{"type":47,"tag":177,"props":380,"children":381},{},[382,384,390],{"type":53,"value":383},"Text labels (",{"type":47,"tag":66,"props":385,"children":387},{"className":386},[],[388],{"type":53,"value":389},"textLabels",{"type":53,"value":370},{"type":47,"tag":177,"props":392,"children":393},{},[394,396,402],{"type":53,"value":395},"Persistent actor ",{"type":47,"tag":66,"props":397,"children":399},{"className":398},[],[400],{"type":53,"value":401},"state",{"type":53,"value":403}," in both variants",{"type":47,"tag":177,"props":405,"children":406},{},[407],{"type":53,"value":408},"Canvas content must survive disconnects and actor restarts.",{"type":47,"tag":56,"props":410,"children":411},{},[412,414,419,421,427,429,435,436,442,444,450,452,458,460,465,466,472],{"type":53,"value":413},"In the ",{"type":47,"tag":66,"props":415,"children":417},{"className":416},[],[418],{"type":53,"value":102},{"type":53,"value":420}," variant, ",{"type":47,"tag":66,"props":422,"children":424},{"className":423},[],[425],{"type":53,"value":426},"updateCursor",{"type":53,"value":428}," writes ",{"type":47,"tag":66,"props":430,"children":432},{"className":431},[],[433],{"type":53,"value":434},"c.conn.state.cursor",{"type":53,"value":206},{"type":47,"tag":66,"props":437,"children":439},{"className":438},[],[440],{"type":53,"value":441},"getRoomState",{"type":53,"value":443}," rebuilds the presence snapshot by iterating ",{"type":47,"tag":66,"props":445,"children":447},{"className":446},[],[448],{"type":53,"value":449},"c.conns.values()",{"type":53,"value":451},", so the cursor map is always derived from live connections rather than stored. See ",{"type":47,"tag":95,"props":453,"children":455},{"href":454},"\u002Fdocs\u002Factors\u002Fconnections",[456],{"type":53,"value":457},"Connections",{"type":53,"value":459}," for ",{"type":47,"tag":66,"props":461,"children":463},{"className":462},[],[464],{"type":53,"value":223},{"type":53,"value":206},{"type":47,"tag":95,"props":467,"children":469},{"href":468},"\u002Fdocs\u002Factors\u002Fstate",[470],{"type":53,"value":471},"State",{"type":53,"value":473}," for persistence semantics.",{"type":47,"tag":75,"props":475,"children":477},{"id":476},"presence-lifecycle",[478],{"type":53,"value":479},"Presence Lifecycle",{"type":47,"tag":87,"props":481,"children":482},{},[483,538,562],{"type":47,"tag":91,"props":484,"children":485},{},[486,491,493,498,500,506,508,514,516,521,523,528,530,536],{"type":47,"tag":60,"props":487,"children":488},{},[489],{"type":53,"value":490},"Join",{"type":53,"value":492},": The ",{"type":47,"tag":66,"props":494,"children":496},{"className":495},[],[497],{"type":53,"value":112},{"type":53,"value":499}," variant pushes an ",{"type":47,"tag":66,"props":501,"children":503},{"className":502},[],[504],{"type":53,"value":505},"init",{"type":53,"value":507}," message with the current ",{"type":47,"tag":66,"props":509,"children":511},{"className":510},[],[512],{"type":53,"value":513},"{ cursors, textLabels }",{"type":53,"value":515}," snapshot as soon as a socket connects. The ",{"type":47,"tag":66,"props":517,"children":519},{"className":518},[],[520],{"type":53,"value":102},{"type":53,"value":522}," variant has no explicit join broadcast; the client calls the ",{"type":47,"tag":66,"props":524,"children":526},{"className":525},[],[527],{"type":53,"value":441},{"type":53,"value":529}," action once after connecting to seed its local maps, and peers first see a new user on that user's first ",{"type":47,"tag":66,"props":531,"children":533},{"className":532},[],[534],{"type":53,"value":535},"cursorMoved",{"type":53,"value":537}," broadcast.",{"type":47,"tag":91,"props":539,"children":540},{},[541,546,548,553,555,560],{"type":47,"tag":60,"props":542,"children":543},{},[544],{"type":53,"value":545},"Move",{"type":53,"value":547},": Every ",{"type":47,"tag":66,"props":549,"children":551},{"className":550},[],[552],{"type":53,"value":426},{"type":53,"value":554}," call writes the connection's presence entry, then broadcasts ",{"type":47,"tag":66,"props":556,"children":558},{"className":557},[],[559],{"type":53,"value":535},{"type":53,"value":561}," to all connections, including the sender.",{"type":47,"tag":91,"props":563,"children":564},{},[565,570,571,576,578,584,586,592,594,600,602,608],{"type":47,"tag":60,"props":566,"children":567},{},[568],{"type":53,"value":569},"Leave",{"type":53,"value":492},{"type":47,"tag":66,"props":572,"children":574},{"className":573},[],[575],{"type":53,"value":102},{"type":53,"value":577}," variant handles leave in ",{"type":47,"tag":66,"props":579,"children":581},{"className":580},[],[582],{"type":53,"value":583},"onDisconnect",{"type":53,"value":585},", broadcasting ",{"type":47,"tag":66,"props":587,"children":589},{"className":588},[],[590],{"type":53,"value":591},"cursorRemoved",{"type":53,"value":593}," with the connection's last cursor. The raw variant does the same from the socket ",{"type":47,"tag":66,"props":595,"children":597},{"className":596},[],[598],{"type":53,"value":599},"close",{"type":53,"value":601}," listener, then deletes the session from the ",{"type":47,"tag":66,"props":603,"children":605},{"className":604},[],[606],{"type":53,"value":607},"vars.websockets",{"type":53,"value":609}," map. Clients delete that user from their local cursor map, so stale cursors disappear the moment a tab closes.",{"type":47,"tag":56,"props":611,"children":612},{},[613,615,621,622,627,628,633],{"type":53,"value":614},"See ",{"type":47,"tag":95,"props":616,"children":618},{"href":617},"\u002Fdocs\u002Factors\u002Flifecycle",[619],{"type":53,"value":620},"Lifecycle",{"type":53,"value":459},{"type":47,"tag":66,"props":623,"children":625},{"className":624},[],[626],{"type":53,"value":583},{"type":53,"value":206},{"type":47,"tag":66,"props":629,"children":631},{"className":630},[],[632],{"type":53,"value":274},{"type":53,"value":125},{"type":47,"tag":75,"props":635,"children":637},{"id":636},"update-throttling",[638],{"type":53,"value":639},"Update Throttling",{"type":47,"tag":56,"props":641,"children":642},{},[643,645,651],{"type":53,"value":644},"Neither example throttles. Both frontends send a cursor update on every raw ",{"type":47,"tag":66,"props":646,"children":648},{"className":647},[],[649],{"type":53,"value":650},"mousemove",{"type":53,"value":652}," event with no debounce or interval cap. That is fine for a demo, but a fast mouse on a high-refresh display can emit hundreds of events per second per user. The patterns below are recommended production hardening on top of the starter code, not something the examples implement.",{"type":47,"tag":138,"props":654,"children":655},{},[656,677],{"type":47,"tag":142,"props":657,"children":658},{},[659],{"type":47,"tag":146,"props":660,"children":661},{},[662,667,672],{"type":47,"tag":150,"props":663,"children":664},{},[665],{"type":53,"value":666},"Layer",{"type":47,"tag":150,"props":668,"children":669},{},[670],{"type":53,"value":671},"Pattern",{"type":47,"tag":150,"props":673,"children":674},{},[675],{"type":53,"value":676},"Guidance",{"type":47,"tag":170,"props":678,"children":679},{},[680,698],{"type":47,"tag":146,"props":681,"children":682},{},[683,688,693],{"type":47,"tag":177,"props":684,"children":685},{},[686],{"type":53,"value":687},"Client (smoothness)",{"type":47,"tag":177,"props":689,"children":690},{},[691],{"type":53,"value":692},"Throttle to 20-30Hz",{"type":47,"tag":177,"props":694,"children":695},{},[696],{"type":53,"value":697},"Sample the latest pointer position every 33-50ms and send only that. Drop intermediate moves, but always flush the final position so cursors settle at the true location. Interpolate between received positions on the rendering side.",{"type":47,"tag":146,"props":699,"children":700},{},[701,706,711],{"type":47,"tag":177,"props":702,"children":703},{},[704],{"type":53,"value":705},"Server (enforcement)",{"type":47,"tag":177,"props":707,"children":708},{},[709],{"type":53,"value":710},"Per-connection rate limit",{"type":47,"tag":177,"props":712,"children":713},{},[714],{"type":53,"value":715},"Track the last accepted update timestamp per connection and drop or coalesce updates arriving faster than your cap. Client throttles are cooperative; the actor is the enforcement boundary.",{"type":47,"tag":75,"props":717,"children":719},{"id":718},"actors",[720],{"type":53,"value":721},"Actors",{"type":47,"tag":87,"props":723,"children":724},{},[725,758,775,820,865,881,917,939,963,1008,1022],{"type":47,"tag":91,"props":726,"children":727},{},[728,733,735,741,743,749,751,757],{"type":47,"tag":60,"props":729,"children":730},{},[731],{"type":53,"value":732},"Key",{"type":53,"value":734},": ",{"type":47,"tag":66,"props":736,"children":738},{"className":737},[],[739],{"type":53,"value":740},"cursorRoom[roomId]",{"type":53,"value":742}," (the frontend defaults ",{"type":47,"tag":66,"props":744,"children":746},{"className":745},[],[747],{"type":53,"value":748},"roomId",{"type":53,"value":750}," to ",{"type":47,"tag":66,"props":752,"children":754},{"className":753},[],[755],{"type":53,"value":756},"\"general\"",{"type":53,"value":370},{"type":47,"tag":91,"props":759,"children":760},{},[761,766,768,773],{"type":47,"tag":60,"props":762,"children":763},{},[764],{"type":53,"value":765},"Responsibility",{"type":53,"value":767},": Holds per-connection cursor presence in ",{"type":47,"tag":66,"props":769,"children":771},{"className":770},[],[772],{"type":53,"value":223},{"type":53,"value":774},", persists shared text labels in actor state, and broadcasts cursor and text updates to all connections.",{"type":47,"tag":91,"props":776,"children":777},{},[778,783],{"type":47,"tag":60,"props":779,"children":780},{},[781],{"type":53,"value":782},"Actions",{"type":47,"tag":87,"props":784,"children":785},{},[786,794,803,812],{"type":47,"tag":91,"props":787,"children":788},{},[789],{"type":47,"tag":66,"props":790,"children":792},{"className":791},[],[793],{"type":53,"value":426},{"type":47,"tag":91,"props":795,"children":796},{},[797],{"type":47,"tag":66,"props":798,"children":800},{"className":799},[],[801],{"type":53,"value":802},"updateText",{"type":47,"tag":91,"props":804,"children":805},{},[806],{"type":47,"tag":66,"props":807,"children":809},{"className":808},[],[810],{"type":53,"value":811},"removeText",{"type":47,"tag":91,"props":813,"children":814},{},[815],{"type":47,"tag":66,"props":816,"children":818},{"className":817},[],[819],{"type":53,"value":441},{"type":47,"tag":91,"props":821,"children":822},{},[823,828],{"type":47,"tag":60,"props":824,"children":825},{},[826],{"type":53,"value":827},"Events",{"type":47,"tag":87,"props":829,"children":830},{},[831,839,847,856],{"type":47,"tag":91,"props":832,"children":833},{},[834],{"type":47,"tag":66,"props":835,"children":837},{"className":836},[],[838],{"type":53,"value":535},{"type":47,"tag":91,"props":840,"children":841},{},[842],{"type":47,"tag":66,"props":843,"children":845},{"className":844},[],[846],{"type":53,"value":591},{"type":47,"tag":91,"props":848,"children":849},{},[850],{"type":47,"tag":66,"props":851,"children":853},{"className":852},[],[854],{"type":53,"value":855},"textUpdated",{"type":47,"tag":91,"props":857,"children":858},{},[859],{"type":47,"tag":66,"props":860,"children":862},{"className":861},[],[863],{"type":53,"value":864},"textRemoved",{"type":47,"tag":91,"props":866,"children":867},{},[868,873],{"type":47,"tag":60,"props":869,"children":870},{},[871],{"type":53,"value":872},"Queues",{"type":47,"tag":87,"props":874,"children":875},{},[876],{"type":47,"tag":91,"props":877,"children":878},{},[879],{"type":53,"value":880},"None",{"type":47,"tag":91,"props":882,"children":883},{},[884,888],{"type":47,"tag":60,"props":885,"children":886},{},[887],{"type":53,"value":471},{"type":47,"tag":87,"props":889,"children":890},{},[891,896,906],{"type":47,"tag":91,"props":892,"children":893},{},[894],{"type":53,"value":895},"JSON",{"type":47,"tag":91,"props":897,"children":898},{},[899,904],{"type":47,"tag":66,"props":900,"children":902},{"className":901},[],[903],{"type":53,"value":389},{"type":53,"value":905}," (persistent)",{"type":47,"tag":91,"props":907,"children":908},{},[909,915],{"type":47,"tag":66,"props":910,"children":912},{"className":911},[],[913],{"type":53,"value":914},"connState.cursor",{"type":53,"value":916}," per connection (ephemeral)",{"type":47,"tag":91,"props":918,"children":919},{},[920,924,925,930,932,938],{"type":47,"tag":60,"props":921,"children":922},{},[923],{"type":53,"value":732},{"type":53,"value":734},{"type":47,"tag":66,"props":926,"children":928},{"className":927},[],[929],{"type":53,"value":740},{"type":53,"value":931}," (resolved via ",{"type":47,"tag":66,"props":933,"children":935},{"className":934},[],[936],{"type":53,"value":937},"client.cursorRoom.getOrCreate(roomId)",{"type":53,"value":370},{"type":47,"tag":91,"props":940,"children":941},{},[942,946,948,953,955,961],{"type":47,"tag":60,"props":943,"children":944},{},[945],{"type":53,"value":765},{"type":53,"value":947},": Exposes a raw WebSocket endpoint, tracks live sockets and their cursors in a ",{"type":47,"tag":66,"props":949,"children":951},{"className":950},[],[952],{"type":53,"value":274},{"type":53,"value":954}," map keyed by a ",{"type":47,"tag":66,"props":956,"children":958},{"className":957},[],[959],{"type":53,"value":960},"sessionId",{"type":53,"value":962}," query parameter, persists text labels, and manually fans JSON frames out to every socket.",{"type":47,"tag":91,"props":964,"children":965},{},[966,970],{"type":47,"tag":60,"props":967,"children":968},{},[969],{"type":53,"value":782},{"type":47,"tag":87,"props":971,"children":972},{},[973,1000],{"type":47,"tag":91,"props":974,"children":975},{},[976,982,984,990,992,998],{"type":47,"tag":66,"props":977,"children":979},{"className":978},[],[980],{"type":53,"value":981},"getOrCreate",{"type":53,"value":983}," (stub returning ",{"type":47,"tag":66,"props":985,"children":987},{"className":986},[],[988],{"type":53,"value":989},"{ status: \"ok\" }",{"type":53,"value":991},"; the frontend resolves the actor ID with the client handle's ",{"type":47,"tag":66,"props":993,"children":995},{"className":994},[],[996],{"type":53,"value":997},"getOrCreate(roomId).resolve()",{"type":53,"value":999},", which creates the actor without dispatching this action)",{"type":47,"tag":91,"props":1001,"children":1002},{},[1003],{"type":47,"tag":66,"props":1004,"children":1006},{"className":1005},[],[1007],{"type":53,"value":441},{"type":47,"tag":91,"props":1009,"children":1010},{},[1011,1015],{"type":47,"tag":60,"props":1012,"children":1013},{},[1014],{"type":53,"value":872},{"type":47,"tag":87,"props":1016,"children":1017},{},[1018],{"type":47,"tag":91,"props":1019,"children":1020},{},[1021],{"type":53,"value":880},{"type":47,"tag":91,"props":1023,"children":1024},{},[1025,1029],{"type":47,"tag":60,"props":1026,"children":1027},{},[1028],{"type":53,"value":471},{"type":47,"tag":87,"props":1030,"children":1031},{},[1032,1036,1045],{"type":47,"tag":91,"props":1033,"children":1034},{},[1035],{"type":53,"value":895},{"type":47,"tag":91,"props":1037,"children":1038},{},[1039,1044],{"type":47,"tag":66,"props":1040,"children":1042},{"className":1041},[],[1043],{"type":53,"value":389},{"type":53,"value":905},{"type":47,"tag":91,"props":1046,"children":1047},{},[1048,1053,1055,1060],{"type":47,"tag":66,"props":1049,"children":1051},{"className":1050},[],[1052],{"type":53,"value":607},{"type":53,"value":1054}," map of ",{"type":47,"tag":66,"props":1056,"children":1058},{"className":1057},[],[1059],{"type":53,"value":960},{"type":53,"value":1061}," to socket and cursor (in-memory, lost on restart)",{"type":47,"tag":56,"props":1063,"children":1064},{},[1065,1067,1073],{"type":53,"value":1066},"The raw variant defines no RivetKit events. Its message names are ",{"type":47,"tag":66,"props":1068,"children":1070},{"className":1069},[],[1071],{"type":53,"value":1072},"type",{"type":53,"value":1074}," fields on raw JSON frames:",{"type":47,"tag":138,"props":1076,"children":1077},{},[1078,1104],{"type":47,"tag":142,"props":1079,"children":1080},{},[1081],{"type":47,"tag":146,"props":1082,"children":1083},{},[1084,1089,1099],{"type":47,"tag":150,"props":1085,"children":1086},{},[1087],{"type":53,"value":1088},"Direction",{"type":47,"tag":150,"props":1090,"children":1091},{},[1092,1094],{"type":53,"value":1093},"Message ",{"type":47,"tag":66,"props":1095,"children":1097},{"className":1096},[],[1098],{"type":53,"value":1072},{"type":47,"tag":150,"props":1100,"children":1101},{},[1102],{"type":53,"value":1103},"Payload",{"type":47,"tag":170,"props":1105,"children":1106},{},[1107,1132,1156,1180,1206],{"type":47,"tag":146,"props":1108,"children":1109},{},[1110,1115,1123],{"type":47,"tag":177,"props":1111,"children":1112},{},[1113],{"type":53,"value":1114},"Client to server",{"type":47,"tag":177,"props":1116,"children":1117},{},[1118],{"type":47,"tag":66,"props":1119,"children":1121},{"className":1120},[],[1122],{"type":53,"value":426},{"type":47,"tag":177,"props":1124,"children":1125},{},[1126],{"type":47,"tag":66,"props":1127,"children":1129},{"className":1128},[],[1130],{"type":53,"value":1131},"{ userId, x, y }",{"type":47,"tag":146,"props":1133,"children":1134},{},[1135,1139,1147],{"type":47,"tag":177,"props":1136,"children":1137},{},[1138],{"type":53,"value":1114},{"type":47,"tag":177,"props":1140,"children":1141},{},[1142],{"type":47,"tag":66,"props":1143,"children":1145},{"className":1144},[],[1146],{"type":53,"value":802},{"type":47,"tag":177,"props":1148,"children":1149},{},[1150],{"type":47,"tag":66,"props":1151,"children":1153},{"className":1152},[],[1154],{"type":53,"value":1155},"{ id, userId, text, x, y }",{"type":47,"tag":146,"props":1157,"children":1158},{},[1159,1163,1171],{"type":47,"tag":177,"props":1160,"children":1161},{},[1162],{"type":53,"value":1114},{"type":47,"tag":177,"props":1164,"children":1165},{},[1166],{"type":47,"tag":66,"props":1167,"children":1169},{"className":1168},[],[1170],{"type":53,"value":811},{"type":47,"tag":177,"props":1172,"children":1173},{},[1174],{"type":47,"tag":66,"props":1175,"children":1177},{"className":1176},[],[1178],{"type":53,"value":1179},"{ id }",{"type":47,"tag":146,"props":1181,"children":1182},{},[1183,1188,1196],{"type":47,"tag":177,"props":1184,"children":1185},{},[1186],{"type":53,"value":1187},"Server to client",{"type":47,"tag":177,"props":1189,"children":1190},{},[1191],{"type":47,"tag":66,"props":1192,"children":1194},{"className":1193},[],[1195],{"type":53,"value":505},{"type":47,"tag":177,"props":1197,"children":1198},{},[1199,1204],{"type":47,"tag":66,"props":1200,"children":1202},{"className":1201},[],[1203],{"type":53,"value":513},{"type":53,"value":1205}," snapshot on connect",{"type":47,"tag":146,"props":1207,"children":1208},{},[1209,1213,1240],{"type":47,"tag":177,"props":1210,"children":1211},{},[1212],{"type":53,"value":1187},{"type":47,"tag":177,"props":1214,"children":1215},{},[1216,1221,1223,1228,1229,1234,1235],{"type":47,"tag":66,"props":1217,"children":1219},{"className":1218},[],[1220],{"type":53,"value":535},{"type":53,"value":1222},", ",{"type":47,"tag":66,"props":1224,"children":1226},{"className":1225},[],[1227],{"type":53,"value":855},{"type":53,"value":1222},{"type":47,"tag":66,"props":1230,"children":1232},{"className":1231},[],[1233],{"type":53,"value":864},{"type":53,"value":1222},{"type":47,"tag":66,"props":1236,"children":1238},{"className":1237},[],[1239],{"type":53,"value":591},{"type":47,"tag":177,"props":1241,"children":1242},{},[1243],{"type":53,"value":1244},"The corresponding cursor, label, or ID payload",{"type":47,"tag":75,"props":1246,"children":1248},{"id":1247},"lifecycle",[1249],{"type":53,"value":620},{"type":47,"tag":1251,"props":1252,"children":1254},"h3",{"id":1253},"cursors-actions-events",[1255],{"type":53,"value":1256},"cursors (Actions + Events)",{"type":47,"tag":1258,"props":1259,"children":1264},"pre",{"className":1260,"code":1261,"language":1262,"meta":1263,"style":1263},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","sequenceDiagram\n    participant A as Client A\n    participant R as cursorRoom\n    participant B as Other Clients\n\n    A->>R: connect via useActor (cursorRoom[roomId])\n    A->>R: getRoomState()\n    R-->>A: {cursors, textLabels}\n    loop every mouse move\n        A->>R: updateCursor(userId, x, y)\n        Note over R: write c.conn.state.cursor\n        R-->>B: cursorMoved (broadcast)\n    end\n    A->>R: updateText(id, userId, text, x, y)\n    Note over R: upsert persistent state.textLabels\n    R-->>B: textUpdated (broadcast)\n    Note over A: tab closes\n    Note over R: onDisconnect reads conn.state.cursor\n    R-->>B: cursorRemoved (broadcast)\n","mermaid","",[1265],{"type":47,"tag":66,"props":1266,"children":1267},{"__ignoreMap":1263},[1268,1279,1288,1297,1306,1316,1325,1333,1342,1351,1360,1369,1378,1387,1396,1405,1414,1422,1431],{"type":47,"tag":1269,"props":1270,"children":1273},"span",{"class":1271,"line":1272},"line",1,[1274],{"type":47,"tag":1269,"props":1275,"children":1276},{},[1277],{"type":53,"value":1278},"sequenceDiagram\n",{"type":47,"tag":1269,"props":1280,"children":1282},{"class":1271,"line":1281},2,[1283],{"type":47,"tag":1269,"props":1284,"children":1285},{},[1286],{"type":53,"value":1287},"    participant A as Client A\n",{"type":47,"tag":1269,"props":1289,"children":1291},{"class":1271,"line":1290},3,[1292],{"type":47,"tag":1269,"props":1293,"children":1294},{},[1295],{"type":53,"value":1296},"    participant R as cursorRoom\n",{"type":47,"tag":1269,"props":1298,"children":1300},{"class":1271,"line":1299},4,[1301],{"type":47,"tag":1269,"props":1302,"children":1303},{},[1304],{"type":53,"value":1305},"    participant B as Other Clients\n",{"type":47,"tag":1269,"props":1307,"children":1309},{"class":1271,"line":1308},5,[1310],{"type":47,"tag":1269,"props":1311,"children":1313},{"emptyLinePlaceholder":1312},true,[1314],{"type":53,"value":1315},"\n",{"type":47,"tag":1269,"props":1317,"children":1319},{"class":1271,"line":1318},6,[1320],{"type":47,"tag":1269,"props":1321,"children":1322},{},[1323],{"type":53,"value":1324},"    A->>R: connect via useActor (cursorRoom[roomId])\n",{"type":47,"tag":1269,"props":1326,"children":1327},{"class":1271,"line":34},[1328],{"type":47,"tag":1269,"props":1329,"children":1330},{},[1331],{"type":53,"value":1332},"    A->>R: getRoomState()\n",{"type":47,"tag":1269,"props":1334,"children":1336},{"class":1271,"line":1335},8,[1337],{"type":47,"tag":1269,"props":1338,"children":1339},{},[1340],{"type":53,"value":1341},"    R-->>A: {cursors, textLabels}\n",{"type":47,"tag":1269,"props":1343,"children":1345},{"class":1271,"line":1344},9,[1346],{"type":47,"tag":1269,"props":1347,"children":1348},{},[1349],{"type":53,"value":1350},"    loop every mouse move\n",{"type":47,"tag":1269,"props":1352,"children":1354},{"class":1271,"line":1353},10,[1355],{"type":47,"tag":1269,"props":1356,"children":1357},{},[1358],{"type":53,"value":1359},"        A->>R: updateCursor(userId, x, y)\n",{"type":47,"tag":1269,"props":1361,"children":1363},{"class":1271,"line":1362},11,[1364],{"type":47,"tag":1269,"props":1365,"children":1366},{},[1367],{"type":53,"value":1368},"        Note over R: write c.conn.state.cursor\n",{"type":47,"tag":1269,"props":1370,"children":1372},{"class":1271,"line":1371},12,[1373],{"type":47,"tag":1269,"props":1374,"children":1375},{},[1376],{"type":53,"value":1377},"        R-->>B: cursorMoved (broadcast)\n",{"type":47,"tag":1269,"props":1379,"children":1381},{"class":1271,"line":1380},13,[1382],{"type":47,"tag":1269,"props":1383,"children":1384},{},[1385],{"type":53,"value":1386},"    end\n",{"type":47,"tag":1269,"props":1388,"children":1390},{"class":1271,"line":1389},14,[1391],{"type":47,"tag":1269,"props":1392,"children":1393},{},[1394],{"type":53,"value":1395},"    A->>R: updateText(id, userId, text, x, y)\n",{"type":47,"tag":1269,"props":1397,"children":1399},{"class":1271,"line":1398},15,[1400],{"type":47,"tag":1269,"props":1401,"children":1402},{},[1403],{"type":53,"value":1404},"    Note over R: upsert persistent state.textLabels\n",{"type":47,"tag":1269,"props":1406,"children":1408},{"class":1271,"line":1407},16,[1409],{"type":47,"tag":1269,"props":1410,"children":1411},{},[1412],{"type":53,"value":1413},"    R-->>B: textUpdated (broadcast)\n",{"type":47,"tag":1269,"props":1415,"children":1416},{"class":1271,"line":30},[1417],{"type":47,"tag":1269,"props":1418,"children":1419},{},[1420],{"type":53,"value":1421},"    Note over A: tab closes\n",{"type":47,"tag":1269,"props":1423,"children":1425},{"class":1271,"line":1424},18,[1426],{"type":47,"tag":1269,"props":1427,"children":1428},{},[1429],{"type":53,"value":1430},"    Note over R: onDisconnect reads conn.state.cursor\n",{"type":47,"tag":1269,"props":1432,"children":1434},{"class":1271,"line":1433},19,[1435],{"type":47,"tag":1269,"props":1436,"children":1437},{},[1438],{"type":53,"value":1439},"    R-->>B: cursorRemoved (broadcast)\n",{"type":47,"tag":1251,"props":1441,"children":1442},{"id":112},[1443],{"type":53,"value":112},{"type":47,"tag":1258,"props":1445,"children":1447},{"className":1260,"code":1446,"language":1262,"meta":1263,"style":1263},"sequenceDiagram\n    participant A as Client A\n    participant R as cursorRoom\n    participant B as Other Clients\n\n    A->>R: getOrCreate(roomId).resolve()\n    R-->>A: actorId\n    A->>R: open WebSocket \u002Fgateway\u002F{actorId}\u002Fwebsocket?sessionId=...\n    Note over R: close 1008 if sessionId is missing\n    Note over R: store socket in vars.websockets\n    R-->>A: init {cursors, textLabels}\n    loop every mouse move\n        A->>R: {type: \"updateCursor\"} frame\n        Note over R: update session cursor in vars\n        R-->>B: cursorMoved frame\n    end\n    Note over A: socket closes\n    R-->>B: cursorRemoved frame\n    Note over R: delete session from vars.websockets\n",[1448],{"type":47,"tag":66,"props":1449,"children":1450},{"__ignoreMap":1263},[1451,1458,1465,1472,1479,1486,1494,1502,1510,1518,1526,1534,1541,1549,1557,1565,1572,1580,1588],{"type":47,"tag":1269,"props":1452,"children":1453},{"class":1271,"line":1272},[1454],{"type":47,"tag":1269,"props":1455,"children":1456},{},[1457],{"type":53,"value":1278},{"type":47,"tag":1269,"props":1459,"children":1460},{"class":1271,"line":1281},[1461],{"type":47,"tag":1269,"props":1462,"children":1463},{},[1464],{"type":53,"value":1287},{"type":47,"tag":1269,"props":1466,"children":1467},{"class":1271,"line":1290},[1468],{"type":47,"tag":1269,"props":1469,"children":1470},{},[1471],{"type":53,"value":1296},{"type":47,"tag":1269,"props":1473,"children":1474},{"class":1271,"line":1299},[1475],{"type":47,"tag":1269,"props":1476,"children":1477},{},[1478],{"type":53,"value":1305},{"type":47,"tag":1269,"props":1480,"children":1481},{"class":1271,"line":1308},[1482],{"type":47,"tag":1269,"props":1483,"children":1484},{"emptyLinePlaceholder":1312},[1485],{"type":53,"value":1315},{"type":47,"tag":1269,"props":1487,"children":1488},{"class":1271,"line":1318},[1489],{"type":47,"tag":1269,"props":1490,"children":1491},{},[1492],{"type":53,"value":1493},"    A->>R: getOrCreate(roomId).resolve()\n",{"type":47,"tag":1269,"props":1495,"children":1496},{"class":1271,"line":34},[1497],{"type":47,"tag":1269,"props":1498,"children":1499},{},[1500],{"type":53,"value":1501},"    R-->>A: actorId\n",{"type":47,"tag":1269,"props":1503,"children":1504},{"class":1271,"line":1335},[1505],{"type":47,"tag":1269,"props":1506,"children":1507},{},[1508],{"type":53,"value":1509},"    A->>R: open WebSocket \u002Fgateway\u002F{actorId}\u002Fwebsocket?sessionId=...\n",{"type":47,"tag":1269,"props":1511,"children":1512},{"class":1271,"line":1344},[1513],{"type":47,"tag":1269,"props":1514,"children":1515},{},[1516],{"type":53,"value":1517},"    Note over R: close 1008 if sessionId is missing\n",{"type":47,"tag":1269,"props":1519,"children":1520},{"class":1271,"line":1353},[1521],{"type":47,"tag":1269,"props":1522,"children":1523},{},[1524],{"type":53,"value":1525},"    Note over R: store socket in vars.websockets\n",{"type":47,"tag":1269,"props":1527,"children":1528},{"class":1271,"line":1362},[1529],{"type":47,"tag":1269,"props":1530,"children":1531},{},[1532],{"type":53,"value":1533},"    R-->>A: init {cursors, textLabels}\n",{"type":47,"tag":1269,"props":1535,"children":1536},{"class":1271,"line":1371},[1537],{"type":47,"tag":1269,"props":1538,"children":1539},{},[1540],{"type":53,"value":1350},{"type":47,"tag":1269,"props":1542,"children":1543},{"class":1271,"line":1380},[1544],{"type":47,"tag":1269,"props":1545,"children":1546},{},[1547],{"type":53,"value":1548},"        A->>R: {type: \"updateCursor\"} frame\n",{"type":47,"tag":1269,"props":1550,"children":1551},{"class":1271,"line":1389},[1552],{"type":47,"tag":1269,"props":1553,"children":1554},{},[1555],{"type":53,"value":1556},"        Note over R: update session cursor in vars\n",{"type":47,"tag":1269,"props":1558,"children":1559},{"class":1271,"line":1398},[1560],{"type":47,"tag":1269,"props":1561,"children":1562},{},[1563],{"type":53,"value":1564},"        R-->>B: cursorMoved frame\n",{"type":47,"tag":1269,"props":1566,"children":1567},{"class":1271,"line":1407},[1568],{"type":47,"tag":1269,"props":1569,"children":1570},{},[1571],{"type":53,"value":1386},{"type":47,"tag":1269,"props":1573,"children":1574},{"class":1271,"line":30},[1575],{"type":47,"tag":1269,"props":1576,"children":1577},{},[1578],{"type":53,"value":1579},"    Note over A: socket closes\n",{"type":47,"tag":1269,"props":1581,"children":1582},{"class":1271,"line":1424},[1583],{"type":47,"tag":1269,"props":1584,"children":1585},{},[1586],{"type":53,"value":1587},"    R-->>B: cursorRemoved frame\n",{"type":47,"tag":1269,"props":1589,"children":1590},{"class":1271,"line":1433},[1591],{"type":47,"tag":1269,"props":1592,"children":1593},{},[1594],{"type":53,"value":1595},"    Note over R: delete session from vars.websockets\n",{"type":47,"tag":75,"props":1597,"children":1599},{"id":1598},"security-checklist",[1600],{"type":53,"value":1601},"Security Checklist",{"type":47,"tag":56,"props":1603,"children":1604},{},[1605],{"type":53,"value":1606},"Both examples ship without authentication so the presence pattern stays readable. Everything below is recommended hardening for production, not behavior the examples implement.",{"type":47,"tag":87,"props":1608,"children":1609},{},[1610,1636,1681,1713,1736],{"type":47,"tag":91,"props":1611,"children":1612},{},[1613,1618,1620,1626,1628,1634],{"type":47,"tag":60,"props":1614,"children":1615},{},[1616],{"type":53,"value":1617},"Identity",{"type":53,"value":1619},": Bind presence identity to the connection (",{"type":47,"tag":66,"props":1621,"children":1623},{"className":1622},[],[1624],{"type":53,"value":1625},"c.conn.id",{"type":53,"value":1627}," in the actions variant, a server-generated session ID in the raw variant). Never trust a client-supplied ",{"type":47,"tag":66,"props":1629,"children":1631},{"className":1630},[],[1632],{"type":53,"value":1633},"userId",{"type":53,"value":1635},"; in the examples it is a random client-generated string, so any client can impersonate or remove any cursor.",{"type":47,"tag":91,"props":1637,"children":1638},{},[1639,1644,1646,1651,1653,1659,1660,1665,1667,1672,1674,1679],{"type":47,"tag":60,"props":1640,"children":1641},{},[1642],{"type":53,"value":1643},"Authorization",{"type":53,"value":1645},": Authorize label mutations by owner. In the examples, ",{"type":47,"tag":66,"props":1647,"children":1649},{"className":1648},[],[1650],{"type":53,"value":802},{"type":53,"value":1652}," accepts arbitrary ",{"type":47,"tag":66,"props":1654,"children":1656},{"className":1655},[],[1657],{"type":53,"value":1658},"id",{"type":53,"value":206},{"type":47,"tag":66,"props":1661,"children":1663},{"className":1662},[],[1664],{"type":53,"value":1633},{"type":53,"value":1666}," arguments and ",{"type":47,"tag":66,"props":1668,"children":1670},{"className":1669},[],[1671],{"type":53,"value":811},{"type":53,"value":1673}," accepts an arbitrary ",{"type":47,"tag":66,"props":1675,"children":1677},{"className":1676},[],[1678],{"type":53,"value":1658},{"type":53,"value":1680},", so any client can edit or delete any label.",{"type":47,"tag":91,"props":1682,"children":1683},{},[1684,1689,1691,1697,1698,1704,1706,1711],{"type":47,"tag":60,"props":1685,"children":1686},{},[1687],{"type":53,"value":1688},"Input validation",{"type":53,"value":1690},": Clamp ",{"type":47,"tag":66,"props":1692,"children":1694},{"className":1693},[],[1695],{"type":53,"value":1696},"x",{"type":53,"value":206},{"type":47,"tag":66,"props":1699,"children":1701},{"className":1700},[],[1702],{"type":53,"value":1703},"y",{"type":53,"value":1705}," to canvas bounds, cap text label length, and cap the total ",{"type":47,"tag":66,"props":1707,"children":1709},{"className":1708},[],[1710],{"type":53,"value":389},{"type":53,"value":1712}," count so persistent state cannot grow unbounded.",{"type":47,"tag":91,"props":1714,"children":1715},{},[1716,1721,1723,1728,1730,1735],{"type":47,"tag":60,"props":1717,"children":1718},{},[1719],{"type":53,"value":1720},"Rate limiting",{"type":53,"value":1722},": Enforce a per-connection cap on ",{"type":47,"tag":66,"props":1724,"children":1726},{"className":1725},[],[1727],{"type":53,"value":426},{"type":53,"value":1729}," (for example 30Hz) and on label writes, as described in ",{"type":47,"tag":95,"props":1731,"children":1733},{"href":1732},"#update-throttling",[1734],{"type":53,"value":639},{"type":53,"value":125},{"type":47,"tag":91,"props":1737,"children":1738},{},[1739,1744,1746,1751],{"type":47,"tag":60,"props":1740,"children":1741},{},[1742],{"type":53,"value":1743},"Protocol strictness (raw variant)",{"type":53,"value":1745},": Validate message shape before use and close the socket on malformed JSON instead of logging and continuing. Reject duplicate ",{"type":47,"tag":66,"props":1747,"children":1749},{"className":1748},[],[1750],{"type":53,"value":960},{"type":53,"value":1752}," values rather than silently overwriting another session's socket entry.",{"type":47,"tag":75,"props":1754,"children":1756},{"id":1755},"reference-map",[1757],{"type":53,"value":1758},"Reference Map",{"type":47,"tag":1251,"props":1760,"children":1762},{"id":1761},"actors-1",[1763],{"type":53,"value":721},{"type":47,"tag":87,"props":1765,"children":1766},{},[1767,1776,1784,1793,1802,1811,1820,1829,1838,1846,1855,1864,1873,1882,1891,1900,1909,1918,1927,1936,1945,1953,1962,1971,1980,1989,1998,2007,2016,2025,2034,2043,2052,2061,2070,2079,2088,2097,2106,2115,2124,2133,2142,2151],{"type":47,"tag":91,"props":1768,"children":1769},{},[1770],{"type":47,"tag":95,"props":1771,"children":1773},{"href":1772},"reference\u002Factors\u002Faccess-control.md",[1774],{"type":53,"value":1775},"Access Control",{"type":47,"tag":91,"props":1777,"children":1778},{},[1779],{"type":47,"tag":95,"props":1780,"children":1782},{"href":1781},"reference\u002Factors\u002Factions.md",[1783],{"type":53,"value":782},{"type":47,"tag":91,"props":1785,"children":1786},{},[1787],{"type":47,"tag":95,"props":1788,"children":1790},{"href":1789},"reference\u002Factors\u002Fkeys.md",[1791],{"type":53,"value":1792},"Actor Keys",{"type":47,"tag":91,"props":1794,"children":1795},{},[1796],{"type":47,"tag":95,"props":1797,"children":1799},{"href":1798},"reference\u002Factors\u002Factor-runtime-socket.md",[1800],{"type":53,"value":1801},"Actor Runtime Socket",{"type":47,"tag":91,"props":1803,"children":1804},{},[1805],{"type":47,"tag":95,"props":1806,"children":1808},{"href":1807},"reference\u002Factors\u002Fstatuses.md",[1809],{"type":53,"value":1810},"Actor Statuses",{"type":47,"tag":91,"props":1812,"children":1813},{},[1814],{"type":47,"tag":95,"props":1815,"children":1817},{"href":1816},"reference\u002Factors\u002Fauthentication.md",[1818],{"type":53,"value":1819},"Authentication",{"type":47,"tag":91,"props":1821,"children":1822},{},[1823],{"type":47,"tag":95,"props":1824,"children":1826},{"href":1825},"reference\u002Factors\u002Fquickstart\u002Fcloudflare.md",[1827],{"type":53,"value":1828},"Cloudflare Workers Quickstart",{"type":47,"tag":91,"props":1830,"children":1831},{},[1832],{"type":47,"tag":95,"props":1833,"children":1835},{"href":1834},"reference\u002Factors\u002Fcommunicating-between-actors.md",[1836],{"type":53,"value":1837},"Communicating Between Actors",{"type":47,"tag":91,"props":1839,"children":1840},{},[1841],{"type":47,"tag":95,"props":1842,"children":1844},{"href":1843},"reference\u002Factors\u002Fconnections.md",[1845],{"type":53,"value":457},{"type":47,"tag":91,"props":1847,"children":1848},{},[1849],{"type":47,"tag":95,"props":1850,"children":1852},{"href":1851},"reference\u002Factors\u002Finspector-tabs.md",[1853],{"type":53,"value":1854},"Custom Inspector Tabs",{"type":47,"tag":91,"props":1856,"children":1857},{},[1858],{"type":47,"tag":95,"props":1859,"children":1861},{"href":1860},"reference\u002Factors\u002Fdebugging.md",[1862],{"type":53,"value":1863},"Debugging",{"type":47,"tag":91,"props":1865,"children":1866},{},[1867],{"type":47,"tag":95,"props":1868,"children":1870},{"href":1869},"reference\u002Factors\u002Fdesign-patterns.md",[1871],{"type":53,"value":1872},"Design Patterns",{"type":47,"tag":91,"props":1874,"children":1875},{},[1876],{"type":47,"tag":95,"props":1877,"children":1879},{"href":1878},"reference\u002Factors\u002Fdestroy.md",[1880],{"type":53,"value":1881},"Destroying Actors",{"type":47,"tag":91,"props":1883,"children":1884},{},[1885],{"type":47,"tag":95,"props":1886,"children":1888},{"href":1887},"reference\u002Factors\u002Fquickstart\u002Feffect.md",[1889],{"type":53,"value":1890},"Effect.ts Quickstart (Beta)",{"type":47,"tag":91,"props":1892,"children":1893},{},[1894],{"type":47,"tag":95,"props":1895,"children":1897},{"href":1896},"reference\u002Factors\u002Ferrors.md",[1898],{"type":53,"value":1899},"Errors",{"type":47,"tag":91,"props":1901,"children":1902},{},[1903],{"type":47,"tag":95,"props":1904,"children":1906},{"href":1905},"reference\u002Factors\u002Ffetch-and-websocket-handler.md",[1907],{"type":53,"value":1908},"Fetch and WebSocket Handler",{"type":47,"tag":91,"props":1910,"children":1911},{},[1912],{"type":47,"tag":95,"props":1913,"children":1915},{"href":1914},"reference\u002Factors\u002Fhelper-types.md",[1916],{"type":53,"value":1917},"Helper Types",{"type":47,"tag":91,"props":1919,"children":1920},{},[1921],{"type":47,"tag":95,"props":1922,"children":1924},{"href":1923},"reference\u002Factors\u002Fappearance.md",[1925],{"type":53,"value":1926},"Icons & Names",{"type":47,"tag":91,"props":1928,"children":1929},{},[1930],{"type":47,"tag":95,"props":1931,"children":1933},{"href":1932},"reference\u002Factors\u002Fstate.md",[1934],{"type":53,"value":1935},"In-Memory State",{"type":47,"tag":91,"props":1937,"children":1938},{},[1939],{"type":47,"tag":95,"props":1940,"children":1942},{"href":1941},"reference\u002Factors\u002Finput.md",[1943],{"type":53,"value":1944},"Input Parameters",{"type":47,"tag":91,"props":1946,"children":1947},{},[1948],{"type":47,"tag":95,"props":1949,"children":1951},{"href":1950},"reference\u002Factors\u002Flifecycle.md",[1952],{"type":53,"value":620},{"type":47,"tag":91,"props":1954,"children":1955},{},[1956],{"type":47,"tag":95,"props":1957,"children":1959},{"href":1958},"reference\u002Factors\u002Flimits.md",[1960],{"type":53,"value":1961},"Limits",{"type":47,"tag":91,"props":1963,"children":1964},{},[1965],{"type":47,"tag":95,"props":1966,"children":1968},{"href":1967},"reference\u002Factors\u002Frequest-handler.md",[1969],{"type":53,"value":1970},"Low-Level HTTP Request Handler",{"type":47,"tag":91,"props":1972,"children":1973},{},[1974],{"type":47,"tag":95,"props":1975,"children":1977},{"href":1976},"reference\u002Factors\u002Fkv.md",[1978],{"type":53,"value":1979},"Low-Level KV Storage",{"type":47,"tag":91,"props":1981,"children":1982},{},[1983],{"type":47,"tag":95,"props":1984,"children":1986},{"href":1985},"reference\u002Factors\u002Fwebsocket-handler.md",[1987],{"type":53,"value":1988},"Low-Level WebSocket Handler",{"type":47,"tag":91,"props":1990,"children":1991},{},[1992],{"type":47,"tag":95,"props":1993,"children":1995},{"href":1994},"reference\u002Factors\u002Fmetadata.md",[1996],{"type":53,"value":1997},"Metadata",{"type":47,"tag":91,"props":1999,"children":2000},{},[2001],{"type":47,"tag":95,"props":2002,"children":2004},{"href":2003},"reference\u002Factors\u002Fquickstart\u002Fnext-js.md",[2005],{"type":53,"value":2006},"Next.js Quickstart",{"type":47,"tag":91,"props":2008,"children":2009},{},[2010],{"type":47,"tag":95,"props":2011,"children":2013},{"href":2012},"reference\u002Factors\u002Fquickstart\u002Fbackend.md",[2014],{"type":53,"value":2015},"Node.js & Bun Quickstart",{"type":47,"tag":91,"props":2017,"children":2018},{},[2019],{"type":47,"tag":95,"props":2020,"children":2022},{"href":2021},"reference\u002Factors\u002Fqueues.md",[2023],{"type":53,"value":2024},"Queues & Run Loops",{"type":47,"tag":91,"props":2026,"children":2027},{},[2028],{"type":47,"tag":95,"props":2029,"children":2031},{"href":2030},"reference\u002Factors\u002Fquickstart\u002Freact.md",[2032],{"type":53,"value":2033},"React Quickstart",{"type":47,"tag":91,"props":2035,"children":2036},{},[2037],{"type":47,"tag":95,"props":2038,"children":2040},{"href":2039},"reference\u002Factors\u002Fevents.md",[2041],{"type":53,"value":2042},"Realtime",{"type":47,"tag":91,"props":2044,"children":2045},{},[2046],{"type":47,"tag":95,"props":2047,"children":2049},{"href":2048},"reference\u002Factors\u002Fquickstart\u002Frust.md",[2050],{"type":53,"value":2051},"Rust Quickstart (Beta)",{"type":47,"tag":91,"props":2053,"children":2054},{},[2055],{"type":47,"tag":95,"props":2056,"children":2058},{"href":2057},"reference\u002Factors\u002Fscaling.md",[2059],{"type":53,"value":2060},"Scaling & Concurrency",{"type":47,"tag":91,"props":2062,"children":2063},{},[2064],{"type":47,"tag":95,"props":2065,"children":2067},{"href":2066},"reference\u002Factors\u002Fschedule.md",[2068],{"type":53,"value":2069},"Schedule & Cron",{"type":47,"tag":91,"props":2071,"children":2072},{},[2073],{"type":47,"tag":95,"props":2074,"children":2076},{"href":2075},"reference\u002Factors\u002Fsharing-and-joining-state.md",[2077],{"type":53,"value":2078},"Sharing and Joining State",{"type":47,"tag":91,"props":2080,"children":2081},{},[2082],{"type":47,"tag":95,"props":2083,"children":2085},{"href":2084},"reference\u002Factors\u002Fsqlite.md",[2086],{"type":53,"value":2087},"SQLite",{"type":47,"tag":91,"props":2089,"children":2090},{},[2091],{"type":47,"tag":95,"props":2092,"children":2094},{"href":2093},"reference\u002Factors\u002Fsqlite-drizzle.md",[2095],{"type":53,"value":2096},"SQLite + Drizzle",{"type":47,"tag":91,"props":2098,"children":2099},{},[2100],{"type":47,"tag":95,"props":2101,"children":2103},{"href":2102},"reference\u002Factors\u002Fquickstart\u002Fsupabase.md",[2104],{"type":53,"value":2105},"Supabase Functions Quickstart",{"type":47,"tag":91,"props":2107,"children":2108},{},[2109],{"type":47,"tag":95,"props":2110,"children":2112},{"href":2111},"reference\u002Factors\u002Ftesting.md",[2113],{"type":53,"value":2114},"Testing",{"type":47,"tag":91,"props":2116,"children":2117},{},[2118],{"type":47,"tag":95,"props":2119,"children":2121},{"href":2120},"reference\u002Factors\u002Ftroubleshooting.md",[2122],{"type":53,"value":2123},"Troubleshooting",{"type":47,"tag":91,"props":2125,"children":2126},{},[2127],{"type":47,"tag":95,"props":2128,"children":2130},{"href":2129},"reference\u002Factors\u002Ftypes.md",[2131],{"type":53,"value":2132},"Types",{"type":47,"tag":91,"props":2134,"children":2135},{},[2136],{"type":47,"tag":95,"props":2137,"children":2139},{"href":2138},"reference\u002Factors\u002Fhttp-api.md",[2140],{"type":53,"value":2141},"Vanilla HTTP API",{"type":47,"tag":91,"props":2143,"children":2144},{},[2145],{"type":47,"tag":95,"props":2146,"children":2148},{"href":2147},"reference\u002Factors\u002Fversions.md",[2149],{"type":53,"value":2150},"Versions & Upgrades",{"type":47,"tag":91,"props":2152,"children":2153},{},[2154],{"type":47,"tag":95,"props":2155,"children":2157},{"href":2156},"reference\u002Factors\u002Fworkflows.md",[2158],{"type":53,"value":2159},"Workflows",{"type":47,"tag":1251,"props":2161,"children":2163},{"id":2162},"cli",[2164],{"type":53,"value":2165},"Cli",{"type":47,"tag":87,"props":2167,"children":2168},{},[2169],{"type":47,"tag":91,"props":2170,"children":2171},{},[2172],{"type":47,"tag":95,"props":2173,"children":2175},{"href":2174},"reference\u002Fcli.md",[2176],{"type":53,"value":2177},"CLI",{"type":47,"tag":1251,"props":2179,"children":2181},{"id":2180},"clients",[2182],{"type":53,"value":2183},"Clients",{"type":47,"tag":87,"props":2185,"children":2186},{},[2187,2196,2205,2214,2223],{"type":47,"tag":91,"props":2188,"children":2189},{},[2190],{"type":47,"tag":95,"props":2191,"children":2193},{"href":2192},"reference\u002Fclients\u002Fjavascript.md",[2194],{"type":53,"value":2195},"Node.js & Bun",{"type":47,"tag":91,"props":2197,"children":2198},{},[2199],{"type":47,"tag":95,"props":2200,"children":2202},{"href":2201},"reference\u002Fclients\u002Freact.md",[2203],{"type":53,"value":2204},"React",{"type":47,"tag":91,"props":2206,"children":2207},{},[2208],{"type":47,"tag":95,"props":2209,"children":2211},{"href":2210},"reference\u002Fclients\u002Frust.md",[2212],{"type":53,"value":2213},"Rust (Beta)",{"type":47,"tag":91,"props":2215,"children":2216},{},[2217],{"type":47,"tag":95,"props":2218,"children":2220},{"href":2219},"reference\u002Fclients\u002Fswift.md",[2221],{"type":53,"value":2222},"Swift",{"type":47,"tag":91,"props":2224,"children":2225},{},[2226],{"type":47,"tag":95,"props":2227,"children":2229},{"href":2228},"reference\u002Fclients\u002Fswiftui.md",[2230],{"type":53,"value":2231},"SwiftUI",{"type":47,"tag":1251,"props":2233,"children":2235},{"id":2234},"cookbook",[2236],{"type":53,"value":2237},"Cookbook",{"type":47,"tag":87,"props":2239,"children":2240},{},[2241,2250,2259,2268,2277,2286,2295,2303],{"type":47,"tag":91,"props":2242,"children":2243},{},[2244],{"type":47,"tag":95,"props":2245,"children":2247},{"href":2246},"reference\u002Fcookbook\u002Fai-agent.md",[2248],{"type":53,"value":2249},"AI Agent",{"type":47,"tag":91,"props":2251,"children":2252},{},[2253],{"type":47,"tag":95,"props":2254,"children":2256},{"href":2255},"reference\u002Fcookbook\u002Fchat-room.md",[2257],{"type":53,"value":2258},"Chat Room",{"type":47,"tag":91,"props":2260,"children":2261},{},[2262],{"type":47,"tag":95,"props":2263,"children":2265},{"href":2264},"reference\u002Fcookbook\u002Fcollaborative-text-editor.md",[2266],{"type":53,"value":2267},"Collaborative Text Editor",{"type":47,"tag":91,"props":2269,"children":2270},{},[2271],{"type":47,"tag":95,"props":2272,"children":2274},{"href":2273},"reference\u002Fcookbook\u002Fcron-jobs.md",[2275],{"type":53,"value":2276},"Cron Jobs and Scheduled Tasks",{"type":47,"tag":91,"props":2278,"children":2279},{},[2280],{"type":47,"tag":95,"props":2281,"children":2283},{"href":2282},"reference\u002Fcookbook\u002Fper-tenant-database.md",[2284],{"type":53,"value":2285},"Database per Tenant",{"type":47,"tag":91,"props":2287,"children":2288},{},[2289],{"type":47,"tag":95,"props":2290,"children":2292},{"href":2291},"reference\u002Fcookbook\u002Fvpc-air-gapped.md",[2293],{"type":53,"value":2294},"Deploying Rivet in a VPC or Air-Gapped Network",{"type":47,"tag":91,"props":2296,"children":2297},{},[2298],{"type":47,"tag":95,"props":2299,"children":2301},{"href":2300},"reference\u002Fcookbook\u002Flive-cursors.md",[2302],{"type":53,"value":54},{"type":47,"tag":91,"props":2304,"children":2305},{},[2306],{"type":47,"tag":95,"props":2307,"children":2309},{"href":2308},"reference\u002Fcookbook\u002Fmultiplayer-game.md",[2310],{"type":53,"value":2311},"Multiplayer Game",{"type":47,"tag":1251,"props":2313,"children":2315},{"id":2314},"deploy",[2316],{"type":53,"value":2317},"Deploy",{"type":47,"tag":87,"props":2319,"children":2320},{},[2321,2330,2339,2348,2357,2366,2375,2384,2393,2402,2411,2420,2429],{"type":47,"tag":91,"props":2322,"children":2323},{},[2324],{"type":47,"tag":95,"props":2325,"children":2327},{"href":2326},"reference\u002Fdeploy\u002Fcontainer-runner.md",[2328],{"type":53,"value":2329},"Container Runner",{"type":47,"tag":91,"props":2331,"children":2332},{},[2333],{"type":47,"tag":95,"props":2334,"children":2336},{"href":2335},"reference\u002Fdeploy\u002Faws-lambda.md",[2337],{"type":53,"value":2338},"Deploy To Amazon Web Services Lambda",{"type":47,"tag":91,"props":2340,"children":2341},{},[2342],{"type":47,"tag":95,"props":2343,"children":2345},{"href":2344},"reference\u002Fdeploy\u002Faws-ecs.md",[2346],{"type":53,"value":2347},"Deploying to AWS ECS",{"type":47,"tag":91,"props":2349,"children":2350},{},[2351],{"type":47,"tag":95,"props":2352,"children":2354},{"href":2353},"reference\u002Fdeploy\u002Fcloudflare.md",[2355],{"type":53,"value":2356},"Deploying to Cloudflare Workers",{"type":47,"tag":91,"props":2358,"children":2359},{},[2360],{"type":47,"tag":95,"props":2361,"children":2363},{"href":2362},"reference\u002Fdeploy\u002Ffreestyle.md",[2364],{"type":53,"value":2365},"Deploying to Freestyle",{"type":47,"tag":91,"props":2367,"children":2368},{},[2369],{"type":47,"tag":95,"props":2370,"children":2372},{"href":2371},"reference\u002Fdeploy\u002Fgcp-cloud-run.md",[2373],{"type":53,"value":2374},"Deploying to Google Cloud Run",{"type":47,"tag":91,"props":2376,"children":2377},{},[2378],{"type":47,"tag":95,"props":2379,"children":2381},{"href":2380},"reference\u002Fdeploy\u002Fhetzner.md",[2382],{"type":53,"value":2383},"Deploying to Hetzner",{"type":47,"tag":91,"props":2385,"children":2386},{},[2387],{"type":47,"tag":95,"props":2388,"children":2390},{"href":2389},"reference\u002Fdeploy\u002Fkubernetes.md",[2391],{"type":53,"value":2392},"Deploying to Kubernetes",{"type":47,"tag":91,"props":2394,"children":2395},{},[2396],{"type":47,"tag":95,"props":2397,"children":2399},{"href":2398},"reference\u002Fdeploy\u002Frailway.md",[2400],{"type":53,"value":2401},"Deploying to Railway",{"type":47,"tag":91,"props":2403,"children":2404},{},[2405],{"type":47,"tag":95,"props":2406,"children":2408},{"href":2407},"reference\u002Fdeploy\u002Frivet-compute.md",[2409],{"type":53,"value":2410},"Deploying to Rivet Compute",{"type":47,"tag":91,"props":2412,"children":2413},{},[2414],{"type":47,"tag":95,"props":2415,"children":2417},{"href":2416},"reference\u002Fdeploy\u002Fsupabase.md",[2418],{"type":53,"value":2419},"Deploying to Supabase Functions",{"type":47,"tag":91,"props":2421,"children":2422},{},[2423],{"type":47,"tag":95,"props":2424,"children":2426},{"href":2425},"reference\u002Fdeploy\u002Fvercel.md",[2427],{"type":53,"value":2428},"Deploying to Vercel",{"type":47,"tag":91,"props":2430,"children":2431},{},[2432],{"type":47,"tag":95,"props":2433,"children":2435},{"href":2434},"reference\u002Fdeploy\u002Fvm-and-bare-metal.md",[2436],{"type":53,"value":2437},"Deploying to VMs & Bare Metal",{"type":47,"tag":1251,"props":2439,"children":2441},{"id":2440},"general",[2442],{"type":53,"value":2443},"General",{"type":47,"tag":87,"props":2445,"children":2446},{},[2447,2456,2465,2474,2483,2492,2501,2510,2519,2528,2537,2546,2555,2564],{"type":47,"tag":91,"props":2448,"children":2449},{},[2450],{"type":47,"tag":95,"props":2451,"children":2453},{"href":2452},"reference\u002Fgeneral\u002Factor-configuration.md",[2454],{"type":53,"value":2455},"Actor Configuration",{"type":47,"tag":91,"props":2457,"children":2458},{},[2459],{"type":47,"tag":95,"props":2460,"children":2462},{"href":2461},"reference\u002Fgeneral\u002Farchitecture.md",[2463],{"type":53,"value":2464},"Architecture",{"type":47,"tag":91,"props":2466,"children":2467},{},[2468],{"type":47,"tag":95,"props":2469,"children":2471},{"href":2470},"reference\u002Fgeneral\u002Fcors.md",[2472],{"type":53,"value":2473},"Cross-Origin Resource Sharing",{"type":47,"tag":91,"props":2475,"children":2476},{},[2477],{"type":47,"tag":95,"props":2478,"children":2480},{"href":2479},"reference\u002Fgeneral\u002Fdocs-for-llms.md",[2481],{"type":53,"value":2482},"Documentation for LLMs & AI",{"type":47,"tag":91,"props":2484,"children":2485},{},[2486],{"type":47,"tag":95,"props":2487,"children":2489},{"href":2488},"reference\u002Fgeneral\u002Fedge.md",[2490],{"type":53,"value":2491},"Edge Networking",{"type":47,"tag":91,"props":2493,"children":2494},{},[2495],{"type":47,"tag":95,"props":2496,"children":2498},{"href":2497},"reference\u002Fgeneral\u002Fendpoints.md",[2499],{"type":53,"value":2500},"Endpoints",{"type":47,"tag":91,"props":2502,"children":2503},{},[2504],{"type":47,"tag":95,"props":2505,"children":2507},{"href":2506},"reference\u002Fgeneral\u002Fenvironment-variables.md",[2508],{"type":53,"value":2509},"Environment Variables",{"type":47,"tag":91,"props":2511,"children":2512},{},[2513],{"type":47,"tag":95,"props":2514,"children":2516},{"href":2515},"reference\u002Fgeneral\u002Fhttp-server.md",[2517],{"type":53,"value":2518},"HTTP Server",{"type":47,"tag":91,"props":2520,"children":2521},{},[2522],{"type":47,"tag":95,"props":2523,"children":2525},{"href":2524},"reference\u002Fgeneral\u002Flogging.md",[2526],{"type":53,"value":2527},"Logging",{"type":47,"tag":91,"props":2529,"children":2530},{},[2531],{"type":47,"tag":95,"props":2532,"children":2534},{"href":2533},"reference\u002Fgeneral\u002Fpool-configuration.md",[2535],{"type":53,"value":2536},"Pool Configuration",{"type":47,"tag":91,"props":2538,"children":2539},{},[2540],{"type":47,"tag":95,"props":2541,"children":2543},{"href":2542},"reference\u002Fgeneral\u002Fproduction-checklist.md",[2544],{"type":53,"value":2545},"Production Checklist",{"type":47,"tag":91,"props":2547,"children":2548},{},[2549],{"type":47,"tag":95,"props":2550,"children":2552},{"href":2551},"reference\u002Fgeneral\u002Fregistry-configuration.md",[2553],{"type":53,"value":2554},"Registry Configuration",{"type":47,"tag":91,"props":2556,"children":2557},{},[2558],{"type":47,"tag":95,"props":2559,"children":2561},{"href":2560},"reference\u002Fgeneral\u002Fruntime-modes.md",[2562],{"type":53,"value":2563},"Runtime Modes",{"type":47,"tag":91,"props":2565,"children":2566},{},[2567],{"type":47,"tag":95,"props":2568,"children":2570},{"href":2569},"reference\u002Fgeneral\u002Fwasm-vs-native-sdk.md",[2571],{"type":53,"value":2572},"WASM vs Native SDK",{"type":47,"tag":1251,"props":2574,"children":2576},{"id":2575},"self-hosting",[2577],{"type":53,"value":2578},"Self Hosting",{"type":47,"tag":87,"props":2580,"children":2581},{},[2582,2591,2600,2609,2618,2627,2636,2645,2654,2663,2671,2680,2689],{"type":47,"tag":91,"props":2583,"children":2584},{},[2585],{"type":47,"tag":95,"props":2586,"children":2588},{"href":2587},"reference\u002Fself-hosting\u002Fconfiguration.md",[2589],{"type":53,"value":2590},"Configuration",{"type":47,"tag":91,"props":2592,"children":2593},{},[2594],{"type":47,"tag":95,"props":2595,"children":2597},{"href":2596},"reference\u002Fself-hosting\u002Fdocker-compose.md",[2598],{"type":53,"value":2599},"Docker Compose",{"type":47,"tag":91,"props":2601,"children":2602},{},[2603],{"type":47,"tag":95,"props":2604,"children":2606},{"href":2605},"reference\u002Fself-hosting\u002Fdocker-container.md",[2607],{"type":53,"value":2608},"Docker Container",{"type":47,"tag":91,"props":2610,"children":2611},{},[2612],{"type":47,"tag":95,"props":2613,"children":2615},{"href":2614},"reference\u002Fself-hosting\u002Ffilesystem.md",[2616],{"type":53,"value":2617},"File System",{"type":47,"tag":91,"props":2619,"children":2620},{},[2621],{"type":47,"tag":95,"props":2622,"children":2624},{"href":2623},"reference\u002Fself-hosting\u002Ffoundationdb.md",[2625],{"type":53,"value":2626},"FoundationDB (Enterprise)",{"type":47,"tag":91,"props":2628,"children":2629},{},[2630],{"type":47,"tag":95,"props":2631,"children":2633},{"href":2632},"reference\u002Fself-hosting\u002Finstall.md",[2634],{"type":53,"value":2635},"Installing Rivet Engine",{"type":47,"tag":91,"props":2637,"children":2638},{},[2639],{"type":47,"tag":95,"props":2640,"children":2642},{"href":2641},"reference\u002Fself-hosting\u002Fkubernetes.md",[2643],{"type":53,"value":2644},"Kubernetes",{"type":47,"tag":91,"props":2646,"children":2647},{},[2648],{"type":47,"tag":95,"props":2649,"children":2651},{"href":2650},"reference\u002Fself-hosting\u002Fmulti-region.md",[2652],{"type":53,"value":2653},"Multi-Region",{"type":47,"tag":91,"props":2655,"children":2656},{},[2657],{"type":47,"tag":95,"props":2658,"children":2660},{"href":2659},"reference\u002Fself-hosting\u002Fpostgres.md",[2661],{"type":53,"value":2662},"PostgreSQL",{"type":47,"tag":91,"props":2664,"children":2665},{},[2666],{"type":47,"tag":95,"props":2667,"children":2669},{"href":2668},"reference\u002Fself-hosting\u002Fproduction-checklist.md",[2670],{"type":53,"value":2545},{"type":47,"tag":91,"props":2672,"children":2673},{},[2674],{"type":47,"tag":95,"props":2675,"children":2677},{"href":2676},"reference\u002Fself-hosting\u002Frailway.md",[2678],{"type":53,"value":2679},"Railway Deployment",{"type":47,"tag":91,"props":2681,"children":2682},{},[2683],{"type":47,"tag":95,"props":2684,"children":2686},{"href":2685},"reference\u002Fself-hosting\u002Frender.md",[2687],{"type":53,"value":2688},"Render Deployment",{"type":47,"tag":91,"props":2690,"children":2691},{},[2692],{"type":47,"tag":95,"props":2693,"children":2695},{"href":2694},"reference\u002Fself-hosting\u002Ftls.md",[2696],{"type":53,"value":2697},"TLS & Certificates",{"type":47,"tag":2699,"props":2700,"children":2701},"style",{},[2702],{"type":53,"value":2703},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":2705,"total":30},[2706,2727,2745,2762,2776,2792,2801,2818,2836,2851,2867,2882],{"slug":2707,"name":2707,"fn":2708,"description":2709,"org":2710,"tags":2711,"stars":30,"repoUrl":31,"updatedAt":2726},"ai-agent","build AI agent backends with Rivet","Build an AI agent backend with persistent memory: one Rivet Actor per conversation, queued message handling, and streaming LLM responses as realtime events.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2712,2715,2718,2721,2724,2725],{"name":2713,"slug":2714,"type":14},"Agents","agents",{"name":2716,"slug":2717,"type":14},"Backend","backend",{"name":2719,"slug":2720,"type":14},"LLM","llm",{"name":2722,"slug":2723,"type":14},"Memory","memory",{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},"2026-07-21T05:37:45.411803",{"slug":2728,"name":2728,"fn":2729,"description":2730,"org":2731,"tags":2732,"stars":30,"repoUrl":31,"updatedAt":2744},"ai-agent-workspace","create persistent workspaces for AI agents","Give every AI agent its own computer: a persistent workspace with a filesystem, processes, shells, networking, and agent sessions on a lightweight in-process OS.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2733,2734,2737,2740,2741],{"name":2713,"slug":2714,"type":14},{"name":2735,"slug":2736,"type":14},"File Storage","file-storage",{"name":2738,"slug":2739,"type":14},"Infrastructure","infrastructure",{"name":9,"slug":8,"type":14},{"name":2742,"slug":2743,"type":14},"Sandboxing","sandboxing","2026-06-14T08:06:57.274844",{"slug":2746,"name":2746,"fn":2747,"description":2748,"org":2749,"tags":2750,"stars":30,"repoUrl":31,"updatedAt":2761},"chat-room","build realtime chat rooms with Rivet","Build a realtime chat room backend with Rivet Actors: one actor per room, SQLite-backed message history, and WebSocket broadcast to every connected client.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2751,2752,2755,2756,2757,2760],{"name":2716,"slug":2717,"type":14},{"name":2753,"slug":2754,"type":14},"Messaging","messaging",{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},{"name":2758,"slug":2759,"type":14},"SQL","sql",{"name":16,"slug":17,"type":14},"2026-07-21T05:37:48.403494",{"slug":2763,"name":2763,"fn":2764,"description":2765,"org":2766,"tags":2767,"stars":30,"repoUrl":31,"updatedAt":2775},"collaborative-text-editor","build collaborative text editors with Rivet","Build a collaborative text editor backend with Yjs CRDTs and Rivet Actors: per-document actors relay sync and awareness updates and persist snapshots.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2768,2769,2770,2773,2774],{"name":2716,"slug":2717,"type":14},{"name":19,"slug":20,"type":14},{"name":2771,"slug":2772,"type":14},"Documents","documents",{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},"2026-07-21T05:37:49.412829",{"slug":2777,"name":2777,"fn":2778,"description":2779,"org":2780,"tags":2781,"stars":30,"repoUrl":31,"updatedAt":2791},"cron-jobs","schedule durable cron jobs with Rivet","Patterns for durable one-shot, calendar, and fixed-interval work on Rivet Actors.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2782,2785,2786,2787,2788],{"name":2783,"slug":2784,"type":14},"Automation","automation",{"name":2716,"slug":2717,"type":14},{"name":2738,"slug":2739,"type":14},{"name":9,"slug":8,"type":14},{"name":2789,"slug":2790,"type":14},"Scheduling","scheduling","2026-07-21T05:37:43.395911",{"slug":4,"name":4,"fn":5,"description":6,"org":2793,"tags":2794,"stars":30,"repoUrl":31,"updatedAt":32},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2795,2796,2797,2798,2799,2800],{"name":19,"slug":20,"type":14},{"name":25,"slug":26,"type":14},{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},{"name":28,"slug":29,"type":14},{"name":16,"slug":17,"type":14},{"slug":2802,"name":2802,"fn":2803,"description":2804,"org":2805,"tags":2806,"stars":30,"repoUrl":31,"updatedAt":2817},"multiplayer-game","build multiplayer games with Rivet","Pragmatic patterns for building multiplayer games: matchmaking, tick loops, realtime state, interest management, and validation.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2807,2809,2812,2815,2816],{"name":2464,"slug":2808,"type":14},"architecture",{"name":2810,"slug":2811,"type":14},"Engineering","engineering",{"name":2813,"slug":2814,"type":14},"Game Development","game-development",{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},"2026-07-21T05:37:44.627991",{"slug":2819,"name":2819,"fn":2820,"description":2821,"org":2822,"tags":2823,"stars":30,"repoUrl":31,"updatedAt":2835},"per-tenant-database","implement multi-tenant data isolation with Rivet","Multi-tenant data isolation with one Rivet Actor per tenant: the actor key is the tenant id, so each tenant gets its own isolated dataset and migrations.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2824,2825,2828,2831,2834],{"name":2464,"slug":2808,"type":14},{"name":2826,"slug":2827,"type":14},"Database","database",{"name":2829,"slug":2830,"type":14},"Migration","migration",{"name":2832,"slug":2833,"type":14},"Multi-Tenant","multi-tenant",{"name":9,"slug":8,"type":14},"2026-07-21T05:37:46.414425",{"slug":2837,"name":2837,"fn":2838,"description":2839,"org":2840,"tags":2841,"stars":30,"repoUrl":31,"updatedAt":2850},"rivetkit","build and debug Rivet Actors","RivetKit backend and Rivet Actor runtime guidance. Use for building, modifying, debugging, or testing Rivet Actors, registries, serverless\u002Frunner modes, deployment, or actor-based workflows.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2842,2843,2844,2846,2849],{"name":2713,"slug":2714,"type":14},{"name":2716,"slug":2717,"type":14},{"name":1863,"slug":2845,"type":14},"debugging",{"name":2847,"slug":2848,"type":14},"Deployment","deployment",{"name":9,"slug":8,"type":14},"2026-07-24T05:38:58.10133",{"slug":2852,"name":2852,"fn":2853,"description":2854,"org":2855,"tags":2856,"stars":30,"repoUrl":31,"updatedAt":2866},"rivetkit-client-javascript","use RivetKit JavaScript client","RivetKit JavaScript client guidance. Use for browser, Node.js, or Bun clients that connect to Rivet Actors with rivetkit\u002Fclient, create clients, call actions, or manage connections.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2857,2858,2859,2862,2863],{"name":2716,"slug":2717,"type":14},{"name":25,"slug":26,"type":14},{"name":2860,"slug":2861,"type":14},"JavaScript","javascript",{"name":9,"slug":8,"type":14},{"name":2864,"slug":2865,"type":14},"SDK","sdk","2026-07-30T05:31:37.725576",{"slug":2868,"name":2868,"fn":2869,"description":2870,"org":2871,"tags":2872,"stars":30,"repoUrl":31,"updatedAt":2881},"rivetkit-client-react","use RivetKit React client","RivetKit React client guidance. Use for React apps that connect to Rivet Actors with @rivetkit\u002Freact, create hooks with createRivetKit, or manage realtime state with useActor.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2873,2874,2876,2877,2878],{"name":25,"slug":26,"type":14},{"name":2204,"slug":2875,"type":14},"react",{"name":9,"slug":8,"type":14},{"name":2864,"slug":2865,"type":14},{"name":2879,"slug":2880,"type":14},"State Management","state-management","2026-07-30T05:31:36.618873",{"slug":2883,"name":2883,"fn":2884,"description":2885,"org":2886,"tags":2887,"stars":30,"repoUrl":31,"updatedAt":2895},"rivetkit-client-rust","build RivetKit Rust clients","RivetKit Rust client guidance. Use for Rust clients and backends that connect to Rivet Actors with rivetkit::client, create typed actor handles, call actions, or manage connections.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2888,2891,2892],{"name":2889,"slug":2890,"type":14},"API Development","api-development",{"name":2716,"slug":2717,"type":14},{"name":2893,"slug":2894,"type":14},"Rust","rust","2026-07-24T05:39:02.105888",{"items":2897,"total":1407},[2898,2907,2915,2924,2932,2940,2949],{"slug":2707,"name":2707,"fn":2708,"description":2709,"org":2899,"tags":2900,"stars":30,"repoUrl":31,"updatedAt":2726},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2901,2902,2903,2904,2905,2906],{"name":2713,"slug":2714,"type":14},{"name":2716,"slug":2717,"type":14},{"name":2719,"slug":2720,"type":14},{"name":2722,"slug":2723,"type":14},{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},{"slug":2728,"name":2728,"fn":2729,"description":2730,"org":2908,"tags":2909,"stars":30,"repoUrl":31,"updatedAt":2744},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2910,2911,2912,2913,2914],{"name":2713,"slug":2714,"type":14},{"name":2735,"slug":2736,"type":14},{"name":2738,"slug":2739,"type":14},{"name":9,"slug":8,"type":14},{"name":2742,"slug":2743,"type":14},{"slug":2746,"name":2746,"fn":2747,"description":2748,"org":2916,"tags":2917,"stars":30,"repoUrl":31,"updatedAt":2761},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2918,2919,2920,2921,2922,2923],{"name":2716,"slug":2717,"type":14},{"name":2753,"slug":2754,"type":14},{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},{"name":2758,"slug":2759,"type":14},{"name":16,"slug":17,"type":14},{"slug":2763,"name":2763,"fn":2764,"description":2765,"org":2925,"tags":2926,"stars":30,"repoUrl":31,"updatedAt":2775},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2927,2928,2929,2930,2931],{"name":2716,"slug":2717,"type":14},{"name":19,"slug":20,"type":14},{"name":2771,"slug":2772,"type":14},{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},{"slug":2777,"name":2777,"fn":2778,"description":2779,"org":2933,"tags":2934,"stars":30,"repoUrl":31,"updatedAt":2791},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2935,2936,2937,2938,2939],{"name":2783,"slug":2784,"type":14},{"name":2716,"slug":2717,"type":14},{"name":2738,"slug":2739,"type":14},{"name":9,"slug":8,"type":14},{"name":2789,"slug":2790,"type":14},{"slug":4,"name":4,"fn":5,"description":6,"org":2941,"tags":2942,"stars":30,"repoUrl":31,"updatedAt":32},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2943,2944,2945,2946,2947,2948],{"name":19,"slug":20,"type":14},{"name":25,"slug":26,"type":14},{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14},{"name":28,"slug":29,"type":14},{"name":16,"slug":17,"type":14},{"slug":2802,"name":2802,"fn":2803,"description":2804,"org":2950,"tags":2951,"stars":30,"repoUrl":31,"updatedAt":2817},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2952,2953,2954,2955,2956],{"name":2464,"slug":2808,"type":14},{"name":2810,"slug":2811,"type":14},{"name":2813,"slug":2814,"type":14},{"name":22,"slug":23,"type":14},{"name":9,"slug":8,"type":14}]