Svelte app slice
A Svelte 5 app slice with context, rune-backed queries, mutations, and actions.
Layout
svelte
<!-- +layout.svelte -->
<script lang="ts">
import { Client } from '@pbvex/client';
import { setClient } from '@pbvex/svelte';
let { children } = $props();
const client = new Client('http://localhost:8090');
setClient(client);
</script>
{@render children()}Login
svelte
<!-- Login.svelte -->
<script lang="ts">
import { getClient } from '@pbvex/svelte';
let { onlogin }: { onlogin: (userId: string) => void } = $props();
const client = getClient();
let email = $state('');
let password = $state('');
let error = $state<string | null>(null);
async function login() {
try {
const result = await client.auth
.collection('users')
.authWithPassword(email, password);
onlogin(result.record.id);
} catch (e) {
error = e instanceof Error ? e.message : 'Login failed';
}
}
</script>
{#if error}
<p class="error">{error}</p>
{/if}
<form onsubmit={(event) => { event.preventDefault(); login(); }}>
<input type="email" bind:value={email} placeholder="Email" />
<input type="password" bind:value={password} placeholder="Password" />
<button type="submit">Log in</button>
</form>Message list
svelte
<!-- MessageView.svelte -->
<script lang="ts">
import { useAction, useMutation, useQuery } from '@pbvex/svelte';
import { api } from './pbvex/_generated/api.js';
let { channel }: { channel: string } = $props();
const messages = useQuery(api.messages.list, () => ({ channel }));
const send = useMutation(api.messages.send);
const notify = useAction(api.messages.notify);
let body = $state('');
async function handleSubmit() {
const sent = await send({ body, channel });
body = '';
await notify({ messageId: sent.id });
}
</script>
{#if messages.isLoading}
<p>Loading…</p>
{:else if messages.error}
<p>Error: {messages.error.message}</p>
{:else}
<ul>
{#each messages.data ?? [] as message (message.id)}
<li>{message.body}</li>
{/each}
</ul>
{/if}
<form onsubmit={(event) => { event.preventDefault(); handleSubmit(); }}>
<input bind:value={body} placeholder="Message" />
<button type="submit">Send</button>
</form>App
svelte
<!-- App.svelte -->
<script lang="ts">
import { getClient } from '@pbvex/svelte';
import Login from './Login.svelte';
import MessageView from './MessageView.svelte';
const client = getClient();
let userId = $state<string | null>(
client.authStore.isValid ? client.authStore.record?.id ?? null : null,
);
$effect(() => {
const syncAuth = () => userId = client.authStore.isValid
? client.authStore.record?.id ?? null
: null;
const unsubscribe = client.authStore.onChange(syncAuth);
const timer = window.setInterval(syncAuth, 30_000);
return () => {
unsubscribe();
window.clearInterval(timer);
};
});
</script>
<h1>PBVex Messages</h1>
{#if userId}
<MessageView channel="general" />
{:else}
<Login onlogin={(id) => userId = id} />
{/if}