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> Connection hooks
<script>
import { configure } from 'svelte-realtime/client';
configure({
onConnect() { /* reconnected */ },
onDisconnect() { /* connection lost */ }
});
</script> 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
}
}); RPCs called while disconnected queue up and replay on reconnect.
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?