Client APIs
Virtual imports
Every file in src/live/ generates a $live/ import. The Vite plugin reads your server code and generates lightweight client stubs.
// src/live/chat.js exports sendMessage (live) and messages (stream)
import { sendMessage, messages } from '$live/chat'; live()exports become async functionslive.stream()exports become Svelte storeslive.validated()exports become async functions (validation runs server-side)
Store subscription
Stream stores are standard Svelte stores. Use the $ prefix:
<script>
import { messages } from '$live/chat';
</script>
{#each $messages as msg (msg.id)}
<p>{msg.text}</p>
{/each} Pagination
Return { data, hasMore, cursor } from your stream init to enable cursor pagination:
// Server
export const posts = live.stream('posts', async (ctx) => {
const limit = 20;
const rows = await db.posts.list({ limit: limit + 1, after: ctx.cursor });
const hasMore = rows.length > limit;
const data = hasMore ? rows.slice(0, limit) : rows;
const cursor = data.at(-1)?.id ?? null;
return { data, hasMore, cursor };
}, { merge: 'crud', key: 'id' }); <!-- Client -->
<script>
import { posts } from '$live/feed';
</script>
{#each $posts as post (post.id)}
<p>{post.title}</p>
{/each}
{#if posts.hasMore}
<button onclick={() => posts.loadMore()}>Load more</button>
{/if} Optimistic updates
Apply changes instantly, roll back on failure:
<script>
import { todos, addTodo } from '$live/todos';
async function add(text) {
const tempId = 'temp-' + Date.now();
const rollback = todos.optimistic('created', { id: tempId, text });
try {
await addTodo(text);
} catch {
rollback();
}
}
</script> Undo and redo
<script>
import { todos } from '$live/todos';
todos.enableHistory(100);
</script>
<button onclick={() => todos.undo()} disabled={!todos.canUndo}>Undo</button>
<button onclick={() => todos.redo()} disabled={!todos.canRedo}>Redo</button> SSR hydration
Load data server-side, then hydrate the client store:
// src/routes/chat/+page.server.js
export async function load({ platform }) {
const { messages } = await import('$live/chat');
return { messages: await messages.load(platform) };
} <script>
import { messages } from '$live/chat';
let { data } = $props();
const msgs = messages.hydrate(data.messages);
</script> The hydrated store still subscribes for live updates on first render. It keeps the SSR data visible instead of showing undefined during the initial fetch. Guards still run during .load() calls. Pass { user } as the second argument if your guard or init function needs user data.
For dynamic streams (those with a topic function), call the stream first to get the per-args store, then hydrate:
// src/routes/team/[id]/+page.server.js
export async function load({ platform, locals, params }) {
const { invitations } = await import('$live/invitation');
const data = await invitations.load(platform, { args: [params.id], user: locals.user });
return { invitations: data };
} <!-- src/routes/team/[id]/+page.svelte -->
<script>
import { invitations } from '$live/invitation';
import { page } from '$app/state';
let { data } = $props();
const invites = invitations(page.params.id).hydrate(data.invitations);
</script>
{#each $invites as invite (invite.id)}
<p>{invite.email}</p>
{/each} Connection hooks
<script>
import { configure } from 'svelte-realtime/client';
configure({
onConnect() { /* reconnected */ },
onDisconnect() { /* connection lost */ },
beforeReconnect() { /* before each reconnect attempt (can be async) */ }
});
</script> | Option | Description |
|---|---|
url | Full WebSocket URL for cross-origin or native app usage (e.g. 'wss://api.example.com/ws') |
auth | true (or a custom path) to enable an HTTP preflight before each WebSocket upgrade so cookies set by the server’s authenticate hook ride a normal HTTP response. Required behind Cloudflare Tunnel and other proxies that drop Set-Cookie on 101 responses. Requires svelte-adapter-uws >= 0.4.12. See Cloudflare-Tunnel cookie fix. |
onConnect() | Called when the WebSocket connection opens after a reconnect |
onDisconnect() | Called when the WebSocket connection closes |
beforeReconnect() | Called before each reconnection attempt (can be async) |
Cross-origin and native app usage
When using svelte-realtime from a client that runs on a different origin (Svelte Native, React Native, or any standalone app), pass the url option to point at your SvelteKit backend:
import { configure } from 'svelte-realtime/client';
configure({
url: 'wss://my-sveltekit-app.com/ws'
}); When url is set, the default same-origin WebSocket URL is bypassed entirely. All RPC calls, streams, and pub/sub work the same way. Requires svelte-adapter-uws 0.4.8+.
Combine stores
<script>
import { combine } from 'svelte-realtime/client';
import { orders, inventory } from '$live/dashboard';
const dashboard = combine(orders, inventory, (o, i) => ({
pending: o?.filter(x => x.status === 'pending').length ?? 0,
lowStock: i?.filter(x => x.qty < 10) ?? []
}));
</script> Offline queue
import { configure } from 'svelte-realtime/client';
configure({
offline: {
queue: true,
maxQueue: 100,
maxAge: 60000,
beforeReplay(call) {
// Return false to drop stale mutations
return Date.now() - call.queuedAt < 60000;
},
onReplayError(call, error) {
console.warn('Replay failed:', call.path, error);
}
}
}); When offline queuing is enabled, RPC calls made while disconnected return promises that resolve when the call is replayed after reconnection. If the queue overflows, the oldest entry is dropped and its promise rejects with QUEUE_FULL. If maxAge is set, queued calls older than that threshold are rejected with STALE at replay time.
Delta sync and replay
Delta sync
Enable delta sync for efficient reconnection on streams with large datasets. Instead of refetching all data, the server sends only what changed since the client’s last known version.
export const inventory = live.stream('inventory', async (ctx) => {
return db.inventory.all();
}, {
merge: 'crud',
key: 'sku',
delta: {
version: () => db.inventory.lastModified(),
diff: async (sinceVersion) => {
const changes = await db.inventory.changedSince(sinceVersion);
return changes; // null to force full refetch
}
}
}); How it works:
- On first connect, the client gets the full dataset plus a
versionvalue - On reconnect, the client sends its last known
version - If versions match: server responds with
{ unchanged: true }(nearly zero bytes) - If versions differ: server calls
diff(sinceVersion)and sends only the changes - If diff returns
null: falls back to full refetch
Replay
Enable seq-based replay for gap-free stream reconnection. When a client reconnects, it sends its last known sequence number. If the server has the missed events buffered, it sends only those instead of a full refetch.
export const feed = live.stream('feed', async (ctx) => {
return db.feed.latest(50);
}, { merge: 'latest', max: 50, replay: true }); Replay requires the replay extension from svelte-adapter-uws-extensions. When replay is not available or the gap is too large, the client falls back to a full refetch automatically.
With adapter 0.4.0+, the replay end marker sends { reqId } (replay complete) or { reqId, truncated: true } (cache miss). When truncated, the client automatically resets its sequence number and triggers a full refetch.
Was this page helpful?