Skip to content

Stores & Events

Overview

@authrim/sveltekit uses two complementary mechanisms for state management:

  • Svelte Stores — Reactive Readable stores for UI binding
  • Events — Typed event emitter for side effects and custom logic

Events are the source of truth. Stores are automatically updated (projected) from events — you never write to stores directly.

Stores

Access stores via auth.stores:

<script lang="ts">
import { getAuthContext } from '@authrim/sveltekit';
const auth = getAuthContext();
const { session, user, isAuthenticated, loadingState, error } = auth.stores;
</script>
{#if $isAuthenticated}
<p>Hello, {$user?.displayName}</p>
{:else}
<p>Not signed in</p>
{/if}

Store Reference

StoreTypeDescription
sessionReadable<Session | null>Current session object
userReadable<User | null>Current user object
isAuthenticatedReadable<boolean>Derived from session !== null
loadingStateReadable<AuthLoadingState>Current loading state
errorReadable<AuthError | null>Last authentication error

AuthLoadingState

type AuthLoadingState =
| 'idle' // Stable — no operation in progress
| 'initializing' // Initial session check
| 'authenticating' // Login/signup in progress
| 'refreshing' // Session refresh in progress
| 'signing_out'; // Sign out in progress

'idle' is the resting state. After any operation completes (success or error), the state returns to 'idle'.

Loading State Example

<script lang="ts">
import { getAuthContext } from '@authrim/sveltekit';
const auth = getAuthContext();
const { loadingState, error } = auth.stores;
</script>
{#if $loadingState === 'authenticating'}
<div class="overlay">Signing in...</div>
{:else if $loadingState === 'signing_out'}
<div class="overlay">Signing out...</div>
{/if}
{#if $error}
<div class="alert" role="alert">
<p>{$error.message}</p>
<code>{$error.code}</code>
</div>
{/if}

Store Error Type

interface AuthError {
code: string; // Error code (e.g., 'AR003001')
message: string; // Human-readable message
details?: unknown; // Additional metadata
}

Events

Subscribe to auth events via auth.on(). Returns an unsubscribe function.

const unsubscribe = auth.on('auth:login', (payload) => {
console.log('Logged in:', payload.user.displayName);
console.log('Method:', payload.method); // 'passkey' | 'emailCode' | 'social'
});
// Cleanup
unsubscribe();

Event Reference

EventPayloadTriggered When
auth:login{ session, user, method }User successfully logs in
auth:logout{ redirectUri? }User signs out
auth:error{ error: AuthError }Authentication error occurs
session:changed{ session, user }Session or user data changes
session:expired{ reason }Session becomes invalid
token:refreshed{ session }Token successfully refreshed

Event Payload Types

interface AuthEventPayloads {
'auth:login': {
session: Session;
user: User;
method: 'passkey' | 'emailCode' | 'social';
};
'auth:logout': { redirectUri?: string };
'auth:error': { error: AuthError };
'session:changed': { session: Session | null; user: User | null };
'session:expired': { reason: 'timeout' | 'revoked' | 'logout' };
'token:refreshed': { session: Session };
}

Event Usage in Components

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { getAuthContext } from '@authrim/sveltekit';
import { goto } from '$app/navigation';
const auth = getAuthContext();
let unsubscribes: (() => void)[] = [];
onMount(() => {
unsubscribes.push(
auth.on('auth:login', () => {
goto('/dashboard');
}),
auth.on('session:expired', ({ reason }) => {
if (reason === 'revoked') {
goto('/login?reason=session-revoked');
}
}),
);
});
onDestroy(() => {
unsubscribes.forEach(fn => fn());
});
</script>

Event → Store Projection

Events automatically update stores. You don’t need to manually sync them:

EventStore Updates
auth:loginsession ← payload.session, user ← payload.user, loadingState'idle', errornull
auth:logoutsessionnull, usernull, loadingState'idle', errornull
auth:errorerror ← payload.error, loadingState'idle'
session:changedsession ← payload.session, user ← payload.user

SSR Synchronization

When using AuthProvider with SSR data, the initial session is synced synchronously (before first render) to avoid hydration mismatch:

sequenceDiagram
    participant Server as Server (hooks)
    participant Layout as +layout.svelte
    participant Provider as AuthProvider
    participant Stores as Svelte Stores

    Server->>Layout: SSR auth data
    Layout->>Provider: initialSession, initialUser
    Provider->>Stores: _syncFromSSR() (synchronous)
    Note over Stores: session & user set
before first render Provider->>Provider: onMount: revalidate in background

This ensures $isAuthenticated is correct on the first render — no flash of unauthenticated content.

Next Steps