@cstar.help/svelte
v0.9.0
Published
Svelte 5 rune-based state classes for the cStar customer support platform
Maintainers
Readme
@cstar.help/svelte
Svelte 5 rune classes for the cStar customer support platform. Build custom chat widgets and knowledge base UIs with reactive $state classes.
- Svelte 5 runes —
$stateclasses, not stores - Three modules — Chat (real-time messaging), Library (knowledge base), and Community (forum)
- TypeScript-first — full type definitions included
- Tiny — ~15 KB, tree-shakeable, only peer deps
Install
npm install @cstar.help/svelte @cstar.help/jsChat
Wrap your app in <CStarChatProvider> and instantiate state classes in child components.
Setup
<script>
import { CStarChatProvider } from '@cstar.help/svelte';
</script>
<CStarChatProvider teamSlug="acme">
<ChatWidget />
</CStarChatProvider>Identify the Customer
<script>
import { ChatState, getChatClient } from '@cstar.help/svelte';
const client = getChatClient();
const chat = new ChatState(client);
async function connect() {
await chat.identify(
{ externalId: 'usr_123', email: '[email protected]', timestamp: Math.floor(Date.now() / 1000) },
hmacSignature // computed server-side
);
}
</script>
{#if chat.isIdentified}
<p>Connected! Realtime: {chat.isRealtimeReady}</p>
{:else}
<button onclick={connect}>Connect</button>
{/if}Tickets
<script>
import { TicketsState, getChatClient } from '@cstar.help/svelte';
const client = getChatClient();
const tix = new TicketsState(client); // auto-fetches on creation
</script>
{#if tix.isLoading}
<p>Loading...</p>
{:else}
<ul>
{#each tix.tickets as ticket (ticket.id)}
<li>{ticket.subject}</li>
{/each}
</ul>
{/if}Messages + Typing Indicators
<script>
import { onDestroy } from 'svelte';
import { MessagesState, TypingState, getChatClient } from '@cstar.help/svelte';
let { ticketId } = $props();
const client = getChatClient();
const msgs = new MessagesState(client, ticketId); // auto-fetches + subscribes to real-time
const typing = new TypingState(client, ticketId);
let text = $state('');
async function send() {
await msgs.send(text); // optimistic — appears instantly
text = '';
}
onDestroy(() => {
msgs.destroy();
typing.destroy();
});
</script>
{#each msgs.messages as msg (msg.id)}
<div>{msg.content}</div>
{/each}
{#each typing.typingAgents as agent (agent.agentId)}
<p>{agent.agentName} is typing...</p>
{/each}
<input
bind:value={text}
oninput={() => typing.sendTyping(text.length > 0)}
onkeydown={(e) => e.key === 'Enter' && send()}
/>Knowledge Base
Wrap in <CStarLibraryProvider> for public knowledge base access — no auth required.
Setup
<script>
import { CStarLibraryProvider } from '@cstar.help/svelte';
</script>
<CStarLibraryProvider teamSlug="acme">
<HelpCenter />
</CStarLibraryProvider>Categories + Articles
<script>
import { CategoriesState, ArticlesState, getLibraryClient } from '@cstar.help/svelte';
const client = getLibraryClient();
const cats = new CategoriesState(client);
const articles = new ArticlesState(client, { categorySlug: 'getting-started' });
</script>
<nav>
{#each cats.categories as cat (cat.id)}
<a>{cat.name}</a>
{/each}
</nav>
<ul>
{#each articles.articles as article (article.id)}
<li>{article.title}</li>
{/each}
</ul>Search with Debouncing
<script>
import { onDestroy } from 'svelte';
import { ArticleSearchState, getLibraryClient } from '@cstar.help/svelte';
const client = getLibraryClient();
const search = new ArticleSearchState(client); // 300ms debounce built-in
onDestroy(() => search.destroy());
</script>
<input oninput={(e) => search.search(e.currentTarget.value)} placeholder="Search..." />
{#if search.isLoading}
<p>Searching...</p>
{/if}
{#each search.results as article (article.id)}
<a href="/articles/{article.slug}">{article.title}</a>
{/each}Community
Wrap in <CStarCommunityProvider> for public community forum access — no auth required.
Setup
<script>
import { CStarCommunityProvider } from '@cstar.help/svelte';
</script>
<CStarCommunityProvider teamSlug="acme">
<CommunityForum />
</CStarCommunityProvider>Topics + Posts
<script>
import { TopicsState, PostsState, getCommunityClient } from '@cstar.help/svelte';
const client = getCommunityClient();
const topicsState = new TopicsState(client);
const postsState = new PostsState(client, { sort: 'votes' });
</script>
<nav>
{#each topicsState.topics as topic (topic.id)}
<a>{topic.name}</a>
{/each}
</nav>
<p>{postsState.count} posts</p>
<ul>
{#each postsState.posts as post (post.id)}
<li>{post.title} — {post.voteCount} votes</li>
{/each}
</ul>Single Post with Comments
<script>
import { PostState, getCommunityClient } from '@cstar.help/svelte';
let { slug } = $props();
const client = getCommunityClient();
const postState = new PostState(client, slug);
</script>
{#if postState.data}
<h1>{postState.data.post.title}</h1>
{#each postState.data.comments as comment (comment.id)}
<p>{comment.body}</p>
{/each}
{/if}Search
<script>
import { CommunitySearchState, getCommunityClient } from '@cstar.help/svelte';
const client = getCommunityClient();
const search = new CommunitySearchState(client);
</script>
<input oninput={(e) => search.search(e.currentTarget.value)} placeholder="Search posts..." />
{#if search.results}
{#each search.results.data as post (post.id)}
<a>{post.title}</a>
{/each}
{/if}API Reference
Chat Classes
| Class | Reactive Properties | Methods | Cleanup |
| --------------- | ------------------------------------------ | ----------------------------------------------- | ------- |
| ChatState | isIdentified, isRealtimeReady, error | identify(customer, signature), disconnect() | No |
| TicketsState | tickets, isLoading, error, hasMore | refresh(), create(params) | No |
| MessagesState | messages, isLoading, error | send(content), refresh(), destroy() | Yes |
| TypingState | typingAgents | sendTyping(isTyping), destroy() | Yes |
Library Classes
| Class | Reactive Properties | Methods | Cleanup |
| -------------------- | --------------------------------------------- | ------------------------------- | ------- |
| CategoriesState | categories, isLoading, error | refresh() | No |
| ArticlesState | articles, isLoading, error | refresh(), getArticle(slug) | No |
| ArticleSearchState | results, totalCount, isLoading, error | search(query), destroy() | Yes |
Community Classes
| Class | Reactive Properties | Methods | Cleanup |
| ---------------------- | -------------------------------------- | --------------- | ------- |
| TopicsState | topics, isLoading, error | refresh() | No |
| PostsState | posts, count, isLoading, error | refresh() | No |
| PostState | data, isLoading, error | refresh() | No |
| CommunitySearchState | results, isLoading, error | search(query) | No |
Context Functions
| Function | Returns | Description |
| ---------------------- | ----------------- | ------------------------------------------------ |
| getChatClient() | ChatClient | Get client from nearest CStarChatProvider |
| getLibraryClient() | LibraryClient | Get client from nearest CStarLibraryProvider |
| getCommunityClient() | CommunityClient | Get client from nearest CStarCommunityProvider |
Classes marked Cleanup: Yes subscribe to real-time events or use timers. Call destroy() in onDestroy().
Requirements
- Svelte 5+
- @cstar.help/js 0.1.0+
- Node.js 18+ (for SSR with SvelteKit)
License
MIT
