Rooms - live.room()
Rooms bundle data, presence, cursors, and scoped actions into a single declaration. They are the high-level primitive for collaborative features where multiple related streams share the same topic scope.
Declaring a room
// src/live/collab.js
import { live } from 'svelte-realtime/server';
export const board = live.room({
topic: (ctx, boardId) => 'board:' + boardId,
init: async (ctx, boardId) => db.cards.forBoard(boardId),
presence: (ctx) => ({ name: ctx.user.name, avatar: ctx.user.avatar }),
cursors: true,
guard: async (ctx) => {
if (!ctx.user) throw new LiveError('UNAUTHORIZED');
},
actions: {
addCard: async (ctx, boardId, title) => {
const card = await db.cards.insert({ boardId, title });
ctx.publish('created', card);
return card;
}
}
}); | Option | Description |
|---|---|
topic | Dynamic topic function → receives (ctx, ...args) and returns the topic string |
init | Async function that returns the initial data for the room |
presence | Function returning the presence payload for the current user |
cursors | Set to true to enable cursor tracking |
guard | Optional guard function → runs before any room action or subscription |
actions | Object of named async functions scoped to this room |
Using a room on the client
On the client, the room export becomes an object with sub-streams and actions. Room actions receive the same leading arguments as the topic function (boardId in this case), followed by any action-specific arguments:
<script>
import { board } from '$live/collab';
const data = board.data(boardId); // main data stream
const users = board.presence(boardId); // presence stream
const cursors = board.cursors(boardId); // cursor stream
</script>
{#each $data as card (card.id)}
<Card {card} />
{/each}
<button onclick={() => board.addCard(boardId, 'New card')}>Add</button> Each sub-stream (data, presence, cursors) is a standard Svelte store. They follow the same lifecycle as regular streams → undefined while loading, data when ready, { error } on failure.
Room hooks shortcut
Rooms expose a .hooks property for one-liner wiring in hooks.ws.js:
// src/hooks.ws.js
import { board } from './live/collab.js';
export const { message, close, unsubscribe } = board.hooks; This exports the message, close, and unsubscribe hooks pre-wired for the room’s streams and lifecycle.
Cluster-correct presence
live.room({ presence }) keeps the per-room roster in a per-process Map by default, which is the right thing in single-process dev. In a multi-replica deployment the per-process roster diverges: a subscriber on replica A and a subscriber on replica B each see only the joiners they share a process with, and a refresh on a replica that has no other connected subscribers re-renders the list to “1 online” (self only).
Wire platform.redis in your hooks.ws.js init and the framework switches live.room({ presence }) to a cluster-shared Redis HASH (__live-presence:{topic}). Join and leave transitions are refcounted across replicas - only the first replica to take a user’s count from 0 to 1 publishes join, only the last to take it from 1 to 0 publishes leave - so tab-flip and multi-replica reconnects do not flicker the roster.
// src/hooks.ws.js
import { createRedisClient } from 'svelte-adapter-uws-extensions/redis';
const redis = createRedisClient({ url: process.env.REDIS_URL });
export async function init({ platform }) {
platform.redis = redis.redis;
} platform.redis is the raw ioredis-shaped client (the .redis property of the wrapper). The framework calls hincrby / hset / hdel / hgetall / expire on it directly. If platform.redis is unset, the in-process Map fallback stays in place - so the zero-config dev path keeps working without changes.
The same pattern lights up cluster correctness for related primitives in the ecosystem - see the Redis presence extension for the standalone presence plugin and Scaling guide for the full multi-replica wiring.
Rooms combine Streams, Merge Strategies (presence, cursor, crud), and Auth into a single cohesive unit. See those pages for details on each underlying primitive.
Was this page helpful?