Persistent Replay & Presence
Goal: Understand ephemeral vs persistent state.
Replay: messages that survive restarts
In-memory replay buffers are lost on restart. Redis replay persists them.
import { createReplay } from 'svelte-adapter-uws-extensions/redis';
export const replay = createReplay(redis, { maxPerTopic: 200 }); When a client reconnects, it sends its last sequence number. The replay buffer sends all messages since then - even across restarts.
Auto-routing for live.stream({ replay: true })
For streams declared with replay: true, the framework owns the publish-time wrapping. Install the replay extension on platform.replay and the framework auto-routes every publish for registered topics through platform.replay.publish(...):
// hooks.ws.js
import { createReplay } from 'svelte-adapter-uws-extensions/redis';
import { createMessage, setCronPlatform } from 'svelte-realtime/server';
const replay = createReplay(redis, { maxPerTopic: 200 });
export function init({ platform }) {
platform.replay = replay; // <- framework discovers it from here
setCronPlatform(platform);
}
export const message = createMessage(); // src/live/news.js
import { live } from 'svelte-realtime/server';
export const news = live.stream('news', async () => db.news.recent(), {
merge: 'crud',
key: 'id',
replay: true // <- topic 'news' is now auto-routed through platform.replay
}); The framework registers the topic at declaration time (or at first-subscribe time for dynamic-topic factories) and routes through platform.replay.publish from every framework publish surface: ctx.publish, cron auto-publish, and ctx.publish from inside cron handlers. The dev-mode warn fires once per topic when replay: true is declared but platform.replay is missing.
Production runs silently with zero per-publish overhead - one Map.has check on the publish hot path, gated by the per-topic registry which is empty when no streams use replay.
WRAPPED_FOR_REPLAY escape hatch
If you have a custom platform proxy that already routes through platform.replay.publish (the legacy wrapWithReplay pattern from pre-0.5), mark it so the framework defers its auto-routing and you do not double-write to Redis:
import { WRAPPED_FOR_REPLAY } from 'svelte-realtime/server';
function wrapWithReplay(platform) {
return {
...platform,
[WRAPPED_FOR_REPLAY]: true, // <- explicit opt-out marker
publish(topic, event, data) {
if (isReplayEligible(topic)) {
return platform.replay.publish(topic, event, data);
}
return platform.publish(topic, event, data);
}
};
} Without the marker, the framework’s auto-routing runs alongside the user proxy and double-writes. The marker tells the framework: “this proxy has already handled replay routing, do not run yours on top.” WRAPPED_FOR_REPLAY is exported as a stable Symbol.for(...) so multiple module copies share identity.
Most apps do not need the marker. Drop the custom proxy and let the framework own routing - it is more precise (sourced from the live.stream({ replay: true }) registry, not regex patterns) and removes the asymmetry between RPC and cron seams.
Presence: who’s online across instances
In-memory presence only sees local connections. Redis presence aggregates across all instances.
import { createPresence } from 'svelte-adapter-uws-extensions/redis';
export const presence = createPresence(redis, {
heartbeatInterval: 15000,
expireAfter: 30000
}); Handles multi-tab dedup - the same user in three tabs shows up once.
When to use which
| Data | Ephemeral (in-memory) | Persistent (Redis) |
|---|---|---|
| Cursor positions | Yes | Only if multi-instance |
| Who’s online | Yes | Yes, for accurate cross-instance counts |
| Message replay | Acceptable for dev | Required for production with restarts |
| Rate limits | Yes | Yes, for distributed enforcement |
Related guides
- Scaling Guide - step-by-step from single instance to distributed
- Scaling and Resilience - when and why you need extensions
- Distributed Pub/Sub - Redis pub/sub setup
- Observability - monitoring and metrics
- Extensions package - full API reference
Was this page helpful?