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

DataEphemeral (in-memory)Persistent (Redis)
Cursor positionsYesOnly if multi-instance
Who’s onlineYesYes, for accurate cross-instance counts
Message replayAcceptable for devRequired for production with restarts
Rate limitsYesYes, for distributed enforcement

Was this page helpful?