SvelteKit / Svelte
This guide shows the idiomatic way to use @machhub-dev/sdk-ts
in a SvelteKit / Svelte 5 application: an sdk.service.ts, initialized in
+layout.svelte onMount, a runes-based collection store (createCollectionStore),
and a hooks.server.ts route guard.
The SDK runs in both client and server contexts. This guide initializes it on the
client (via onMount) for reactive UI, and you can also use it server-side in
hooks.server.ts or +page.server.ts. See the
Framework Guides Overview and the SSR notes.
1. Install
Section titled “1. Install”Scaffold an app and add the SDK:
# Create SvelteKit appnpm create svelte@latest my-machhub-appcd my-machhub-app
# Install MACHHUB SDKnpm install @machhub-dev/sdk-ts
# Install dependenciesnpm install2. Initialize
Section titled “2. Initialize”Initialization goes through initializeSDK() in the service (below) and runs in the
browser. No-config vs manual is just whether you pass a config to sdk.Initialize().
No connection config (recommended)
Section titled “No connection config (recommended)”Call Initialize with no arguments — in both development and production. In
development the MACHHUB Designer proxies your dev server’s SDK
requests to the connected platform; in production the app is hosted on the MACHHUB
Platform, so the SDK resolves its connection from the host:
sdk = new SDK();const success = await sdk.Initialize();Manual (environment variables)
Section titled “Manual (environment variables)”Read values from $env/static/public and pass them to Initialize only when you
self-host the app, target a different server/domain, or want to hardcode the
connection. This is the sdk.service.manual.ts variant:
// filepath: src/lib/services/sdk.service.ts (manual)import { SDK, type SDKConfig } from '@machhub-dev/sdk-ts';import { PUBLIC_MACHHUB_APP_ID, PUBLIC_MACHHUB_HTTP_URL, PUBLIC_MACHHUB_MQTT_URL, PUBLIC_MACHHUB_NATS_URL} from '$env/static/public';
// ...inside initializeSDK(config?):const sdkConfig = config || { application_id: PUBLIC_MACHHUB_APP_ID, httpUrl: PUBLIC_MACHHUB_HTTP_URL, mqttUrl: PUBLIC_MACHHUB_MQTT_URL, natsUrl: PUBLIC_MACHHUB_NATS_URL};
sdk = new SDK();const success = await sdk.Initialize(sdkConfig);3. Shared SDK instance — sdk.service.ts (browser-guarded)
Section titled “3. Shared SDK instance — sdk.service.ts (browser-guarded)”The service holds a module-level sdk singleton and is guarded by browser from
$app/environment so it never constructs or initializes on the server. getSDK()
throws until initializeSDK() has run.
import { SDK } from '@machhub-dev/sdk-ts';
let sdk: SDK | null = null;let initPromise: Promise<boolean> | null = null;
export function getSDK(): SDK { if (!sdk) { throw new Error('SDK not initialized. Call initializeSDK() first.'); } return sdk;}
export async function initializeSDK(): Promise<boolean> { if (sdk) { return true; }
if (initPromise) { return initPromise; }
initPromise = (async () => { try { sdk = new SDK(); const success = await sdk.Initialize();
if (success) { console.log('MACHHUB SDK initialized successfully!'); return true; } else { console.error('MACHHUB SDK initialization failed'); sdk = null; return false; } } catch (error) { console.error('Error initializing MACHHUB SDK:', error); sdk = null; throw error; } })();
return initPromise;}
export function isSDKInitialized(): boolean { return sdk !== null;}Initialize it once in the root layout inside onMount (which only runs in the
browser), and gate the app on readiness:
<script lang="ts"> import { onMount } from 'svelte'; import { initializeSDK } from '$lib/services/sdk.service';
let sdkReady = $state(false); let error = $state<string | null>(null);
onMount(async () => { try { await initializeSDK(); sdkReady = true; } catch (err: any) { error = err.message; console.error('Failed to initialize SDK:', err); } });</script>
{#if error} <div class="error">SDK Initialization Error: {error}</div>{:else if !sdkReady} <div class="loading">Initializing MACHHUB SDK...</div>{:else} <slot />{/if}This guide renders the SDK-backed tree on the client. Pair the layout with a
+layout.ts that sets ssr = false for that tree and kicks off initialization behind
a browser check (the SDK also runs server-side — this is a setup choice, not a
requirement):
import { initializeSDK } from '$lib/services/sdk.service';import { browser } from '$app/environment';import type { LayoutLoad } from './$types';
export const ssr = false; // this guide renders this tree on the clientexport const prerender = false;
export const load: LayoutLoad = async () => { if (browser) { try { await initializeSDK(); } catch (error) { console.error('Failed to initialize SDK:', error); return { sdkError: error instanceof Error ? error.message : 'Failed to initialize SDK' }; } }
return {};};flowchart LR Layout["+layout.svelte onMount\n+layout.ts (ssr=false)"] --> Svc["sdk.service.ts\nguarded by browser"] Svc -- "new SDK() + Initialize()" --> SDK["@machhub-dev/sdk-ts"] Svc -- "getSDK()" --> Stores["createCollectionStore / authStore\n(.svelte.ts runes)"] Stores --> Pages["+page.svelte"]
4. Reactive data helper — createCollectionStore
Section titled “4. Reactive data helper — createCollectionStore”Reactive stores live in .svelte.ts files so they can use Svelte 5 runes
($state). createCollectionStore<T> returns getters for items, loading, error
plus getAll/getOne/create/update/remove, each calling getSDK().
import { getSDK } from '$lib/services/sdk.service';
export function createCollectionStore<T = any>(collectionName: string) { let items = $state<T[]>([]); let loading = $state(false); let error = $state<Error | null>(null);
function transform(raw: any): T { if (raw.id && typeof raw.id === 'object' && raw.id.ID) { return { ...raw, id: raw.id.ID } as T; } if (raw.id && typeof raw.id === 'string' && raw.id.includes(':')) { return { ...raw, id: raw.id.split(':')[1] } as T; } return raw as T; }
async function getAll(): Promise<T[]> { loading = true; error = null;
try { const sdk = getSDK(); const rawItems = await sdk.collection(collectionName).getAll(); const transformed = rawItems.map(transform); items = transformed; return transformed; } catch (err) { const e = err instanceof Error ? err : new Error('Unknown error'); error = e; throw e; } finally { loading = false; } }
async function create(data: Partial<T>): Promise<T> { const sdk = getSDK(); const created = await sdk.collection(collectionName).create(data); const item = transform(created); items = [...items, item]; return item; }
async function update(id: string, updates: Partial<T>): Promise<T> { const sdk = getSDK(); const fullId = `myapp.${collectionName}:${id}`; const updated = await sdk.collection(collectionName).update(fullId, updates); const item = transform(updated); items = items.map((i: any) => (i.id === id ? item : i)); return item; }
async function remove(id: string): Promise<void> { const sdk = getSDK(); const fullId = `myapp.${collectionName}:${id}`; await sdk.collection(collectionName).delete(fullId); items = items.filter((i: any) => i.id !== id); }
return { get items() { return items; }, get loading() { return loading; }, get error() { return error; }, getAll, getOne: async (id: string): Promise<T | null> => { const sdk = getSDK(); const fullId = `myapp.${collectionName}:${id}`; const item = await sdk.collection(collectionName).getOne(fullId); return item ? transform(item) : null; }, create, update, remove };}A page creates a store and drives it from onMount:
<script lang="ts"> import { onMount } from "svelte"; import { createCollectionStore } from "$lib/stores/collection.svelte";
interface Product { id: string; name: string; price: number; description?: string; }
const store = createCollectionStore<Product>("products");
onMount(() => { store.getAll(); });
async function deleteProduct(id: string) { if (confirm("Delete this product?")) { await store.remove(id); } }</script>
<div> <h1>Products</h1>
{#if store.loading} <div>Loading products...</div> {:else if store.error} <div class="error">Error: {store.error.message}</div> {:else if store.items.length === 0} <p>No products found</p> {:else} <div class="product-list"> {#each store.items as product (product.id)} <div class="product-card"> <h3>{product.name}</h3> <p class="price">${product.price}</p> <button onclick={() => deleteProduct(product.id)}>Delete</button> </div> {/each} </div> {/if}</div>5. Auth + route guard
Section titled “5. Auth + route guard”An authStore (also a .svelte.ts runes store) wraps sdk.auth. Use it in a route
+page.ts load to validate the session and redirect when invalid.
import { getSDK } from '$lib/services/sdk.service';
interface User { id: string; username: string; email?: string; [key: string]: any;}
function createAuthStore() { let user = $state<User | null>(null); let loading = $state(false); let error = $state<Error | null>(null);
const isAuthenticated = $derived(user !== null);
async function checkAuth() { loading = true; error = null;
try { const sdk = getSDK(); const { valid } = await sdk.auth.validateCurrentUser(); user = valid ? await sdk.auth.getCurrentUser() : null; } catch (err) { error = err instanceof Error ? err : new Error('Unknown error'); user = null; } finally { loading = false; } }
async function login(username: string, password: string): Promise<boolean> { const sdk = getSDK(); const success = await sdk.auth.login(username, password); if (success) { user = await sdk.auth.getCurrentUser(); return true; } return false; }
async function logout() { const sdk = getSDK(); await sdk.auth.logout(); user = null; }
return { get user() { return user; }, get loading() { return loading; }, get error() { return error; }, get isAuthenticated() { return isAuthenticated; }, checkAuth, login, logout };}
export const authStore = createAuthStore();Guard a protected route in its +page.ts. Because the SDK is client-only, set
ssr = false here too so load runs in the browser:
import { authStore } from '$lib/stores/auth.svelte';import { redirect } from '@sveltejs/kit';import type { PageLoad } from './$types';
export const ssr = false;
export const load: PageLoad = async () => { await authStore.checkAuth();
if (!authStore.isAuthenticated) { throw redirect(307, '/login'); }
return {};};sequenceDiagram
participant Nav as Navigation
participant Load as +page.ts load
participant Store as authStore
participant Auth as sdk.auth
Nav->>Load: visit /dashboard (ssr=false)
Load->>Store: checkAuth()
Store->>Auth: validateCurrentUser()
Auth-->>Store: { valid }
alt valid
Load-->>Nav: render page
else invalid
Load->>Nav: redirect(307, '/login')
end
6. SSR notes
Section titled “6. SSR notes”The SDK runs in both client and server contexts. This guide initializes it on the
client for reactive stores, which is why the SDK-backed tree uses ssr = false:
- Setting
export const ssr = falsekeeps the client-initialized store tree from rendering on the server. If a clientloadreaches the SDK, guard it withimport { browser } from '$app/environment'before callinginitializeSDK()/getSDK(). - For server-side data and route protection, use a server context like
hooks.server.tsor+page.server.ts. The SDK works there too — initialize it in that context — or call the REST API directly.
Use hooks.server.ts for server-side route guards (it runs on every request, on the
server). The example below checks an auth cookie and redirects; you could also
initialize the SDK here to validate the session:
import { redirect } from '@sveltejs/kit';import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => { const protectedRoutes = ['/dashboard', '/products']; const isProtected = protectedRoutes.some(route => event.url.pathname.startsWith(route) );
if (isProtected) { const authToken = event.cookies.get('auth-token');
if (!authToken) { throw redirect(303, '/login'); } }
return resolve(event);};For server-side mutations, a form action in +page.server.ts can use the SDK or call
the REST API directly. This example uses fetch:
import type { Actions } from './$types';import { fail } from '@sveltejs/kit';
export const actions = { create: async ({ request }) => { const data = await request.formData(); const name = data.get('name') as string; const price = Number(data.get('price'));
if (!name || !price) { return fail(400, { name, price, missing: true }); }
// Server-side mutation via the REST API (the SDK works here too) const response = await fetch('http://localhost:80/collections/products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, price }) });
if (!response.ok) { return fail(500, { name, price, failed: true }); }
return { success: true }; }} satisfies Actions;flowchart TB
subgraph Client["Client (browser)"]
L["+layout.svelte onMount"] --> Svc["sdk.service.ts (browser-guarded)"] --> SDKpkg["@machhub-dev/sdk-ts"]
end
subgraph ServerSide["Server (hooks / +page.server.ts)"]
H["hooks.server.ts\ncookie guard"]
A["form actions\nfetch REST API"]
end
SDKpkg --> EDGE["MACHHUB Platform"]
A -- "Authorization: Bearer" --> EDGE
7. Environment variables
Section titled “7. Environment variables”SvelteKit exposes PUBLIC_-prefixed variables to the client via $env/static/public.
Put SDK config in .env:
PUBLIC_MACHHUB_APP_ID=your-app-idPUBLIC_MACHHUB_HTTP_URL=http://localhost:80PUBLIC_MACHHUB_MQTT_URL=mqtt://localhost:1883PUBLIC_MACHHUB_NATS_URL=mqtt://localhost:1883Read them through the typed static import in your config helper:
import { PUBLIC_MACHHUB_APP_ID, PUBLIC_MACHHUB_HTTP_URL, PUBLIC_MACHHUB_MQTT_URL} from '$env/static/public';
export const machhubConfig = { application_id: PUBLIC_MACHHUB_APP_ID, httpUrl: PUBLIC_MACHHUB_HTTP_URL, mqttUrl: PUBLIC_MACHHUB_MQTT_URL};.env variable | Imported from | Maps to SDKConfig |
|---|---|---|
PUBLIC_MACHHUB_APP_ID | $env/static/public | application_id |
PUBLIC_MACHHUB_HTTP_URL | $env/static/public | httpUrl |
PUBLIC_MACHHUB_MQTT_URL | $env/static/public | mqttUrl |
PUBLIC_MACHHUB_NATS_URL | $env/static/public | natsUrl |
- SDK reference: Install & Initialize the SDK.
- Compare frameworks: Framework Guides Overview.