https://github.com/ClockworkLabs/SpacetimeDB
https://github.com/sveltejs/svelte
https://github.com/sveltestrap/sveltestrap
A reactive, type-safe integration layer for SpacetimeDB with Svelte 5's runes system.
If you just wanna look at the example
If you just wanna copy the binding code
The SpacetimeDBProvider component wraps your app and provides the SpacetimeDB connection to all child components. It manages the connection by connecting and disconnecting on component lifecycle events and makes it available through Svelte's context system. The uri, moduleName and token variables are reactive props, meaning the connection is rebuilt if any of these change. Please note that Providers currently cannot be nested. Sibling component providers work just fine, just make sure the DbConnection you use in the children makes sense.
<script>
import SpacetimeDBProvider from "$lib/components/spacetime/svelte_spacetime/SpacetimeDBProvider.svelte";
import App from "../App.svelte";
import { DbConnection } from '$lib/components/spacetime/module_bindings';
let moduleName = $state('groupchat1');
let uri = $state('ws://localhost:3000');
</script>
<input bind:value={moduleName}/>
<input bind:value={uri}>
<SpacetimeDBProvider
dbConnection={DbConnection}
{uri}
moduleName={selectedModule}
>
<App/>
</SpacetimeDBProvider>
<script lang="ts">
import { getSpacetimeContext } from './lib/components/spacetime/svelte_spacetime/SpacetimeContext.svelte';
import type { DbConnection } from './lib/components/spacetime/module_bindings';
// call this anywhere inside a child of SpacetimeDBProvider
let spacetimeContext = getSpacetimeContext<DbConnection>();
</script>
<!-- isActive and identity are reactive states and can be used like this: -->
<div>
{spacetimeContext.connection.isActive ? '🟢 Connected' : '🔴 Disconnected'}
{spacetimeContext.connection.identity?.toHexString() ?? ''}
</div>
STQuery is the main class for querying SpacetimeDB tables reactively. It provides:
constructor(
tableName: string, // Table name ( accepts camelCase or snake_case)
whereClause?: Expr<ColumnName>, // Optional filter expression
)
<script lang="ts">
import { STQuery } from './lib/components/spacetime/svelte_spacetime';
import { DbConnection, User } from './lib/components/spacetime/module_bindings';
let selectedGroupChat: GroupChat | undefined = $state();
// Simple query - get all groupchats
let groupchats = new STQuery<DbConnection, GroupChat>('groupchat');
// this typo would throw a compile error 🐈
// let groupchats = new STQuery<DbConnection, GroupChat>('groupcat');
</script>
<ul>
{#each groupchats.rows as groupchat}
<li onclick={() => selectedGroupChat = groupchat}>
{groupchat.id}
</li>
{/each}
</ul>
But for now:
// Filtered query - get messages for specific group chat
let groupChatMessages = $derived.by(() => {
if(!selectedGroupChat) {
return null;
} else {
return new STQuery<DbConnection, Message>('message',
where(eq('groupchatId', selectedGroupChat.id)))
);
}
);
Build complex queries using type-safe helper functions. Value accepts Identity types as well and are converted to the correct hexstring representation.
import { where, eq, and, or, not, type ClientIdentity } from './lib/components/spacetime/svelte_spacetime';
eq(column, value) - Equalitywhere(eq('groupchatId', 123))
// SQL: WHERE groupchat_id = 123
where(eq('identity', someIdentity))
// SQL: WHERE identity = 0x...
not(expression) - Negationwhere(not(eq('identity', identity)))
// SQL: WHERE identity != <current_user_identity>
and(...expressions) - Logical ANDwhere(and(
eq('groupchatId', 123),
not(eq('identity', identity))
))
// SQL: WHERE groupchat_id = 123 AND identity != <current_user_identity>
or(...expressions) - Logical ORwhere(or(
eq('groupchatId', 123),
eq('groupchatId', 456)
))
// SQL: WHERE groupchat_id = 123 OR groupchat_id = 456
// Get messages from multiple group chats, excluding the current user's messages
// to display them as toasts
const identity = spacetimeContext.connection.identity;
const messagesQuery = new STQuery<DbConnection, Message>('message',
where(
and(
or(...clientMemberships.rows.map(m => eq('groupchatId', m.groupchatId))),
not(eq('sender', identity))
)
)
);
Since we track state dependencies (like the .rows variable) to clean up the STDB Subscription once they are not needed anymore. But there might be cases where you don't need the rows but instead just want to add callbacks or the rows dependency is conditional. Which is why we add a dependency to the events variable too by using an $effect to register our callbacks. I couldn't think of a cleaner way to do this in svelte so feel free to suggest improvements to this system. Also I couldn't get typescript working to disallow adding onUpdate and onDelete callbacks on tables that don't have a primary key.
<script lang="ts">
import { STQuery } from './lib/components/spacetime/svelte_spacetime';
import { DbConnection, Message } from './lib/components/spacetime/module_bindings';
let messages = $derived(
new STQuery<DbConnection, Message>('message', where(eq('groupchatId', selectedGroupChat.id)))
);
// Register event handlers in an effect
$effect(() => {
if (messages) {
messages.events.onInsert((newMessage) => {
console.log('New message:', newMessage.text);
showNotification(newMessage);
});
messages.events.onDelete((deletedMessage) => {
console.log('Message deleted:', deletedMessage.id);
});
messages.events.onUpdate((oldMessage, newMessage) => {
console.log('Message edited:', oldMessage.text, '->', newMessage.text);
});
}
});
</script>