Spell Tome of the Svelte SDK
— This is the way.
Providers
<!-- src/routes/+layout.svelte -->
<script>
import { CStarChatProvider, CStarLibraryProvider } from '@cstar.help/svelte';
let { children } = $props();
</script>
<CStarLibraryProvider teamSlug="acme">
<CStarChatProvider teamSlug="acme">
{@render children()}
</CStarChatProvider>
</CStarLibraryProvider>Library state
const cats = new CategoriesState();
const articles = new ArticlesState(undefined, { categorySlug: 'billing' });
const search = new ArticleSearchState();
// Single article? No per-article class — load it in +page.js:
// const article = await getLibraryClient().article(slug);
// Read fields directly in markup
{#each articles.articles as a (a.id)}...{/each}Chat state
const chat = new ChatState();
const tix = new TicketsState(); // no params arg — fetches the full list
const messages = new MessagesState(undefined, ticketId);
const typing = new TypingState(undefined, ticketId);
await chat.identify(customer, signature);
await tix.create({ title: 'Help' });
await messages.send(draft);Cleanup
import { onDestroy } from 'svelte';
// destroy() exists on classes that open subscriptions:
// MessagesState, TypingState, ArticleSearchState
const messages = new MessagesState(undefined, ticketId);
onDestroy(() => messages.destroy());
// ChatState, TicketsState, ArticlesState, CategoriesState
// have no subscriptions — nothing to tear down. Tear down only what subscribes. Lists are fetch-once + reactive fields.
SSR
// Construction is safe on the server — fields are empty until hydration
const articles = new ArticlesState(undefined, { categorySlug: 'billing' });
{#if articles.isLoading}
<p>Loading…</p>
{:else}
...
{/if} No export const ssr = false needed. State classes skip subscribe-wiring on the server.
Gotchas
- Client comes first, params second.
ArticlesState(client?, params?)andMessagesState(client, ticketId). Passundefinedas the client to use the provider's. - ticketId is positional. Pass a string, not a thunk — the class throws a
typed
TypeErrorwith a hint. - Destroy only the subscribers.
destroy()exists onMessagesState,TypingState, andArticleSearchState. The list/lookup classes don't have it. - Single-instance pattern. Don't
newthe same state class in a loop body — it'll re-fetch on every render. - Realtime is opt-in via the provider. Pass
realtime=falseonCStarChatProviderto force polling.