@200systems/mf-sveltekit-adapter
v1.1.6
Published
SvelteKit integration adapter with API client, stores, and hooks
Maintainers
Readme
@200systems/mf-sveltekit-adapter
SvelteKit integration adapter para o microframework TypeScript - fornece integração client-side com APIs Express, gerenciamento de estado reativo, autenticação JWT e hooks.
Features Implementadas
- 🔧 ApiClient - Cliente HTTP com autenticação, retry, timeout e gerenciamento de token
- 🔐 Gerenciamento de Token - Persistência automática de JWT token no localStorage
- 📊 Stores Reativas - Stores Svelte para estado de auth e API com suporte a runes
- 🛡️ Error Handling - Hooks abrangentes de tratamento de erros
- 📝 Request Logging - Log automático de request/response com timing
- 🎣 SvelteKit Hooks - Hooks pré-construídos para autenticação e logging
- 🔄 Retry Logic - Lógica de retry automática com exponential backoff
- ⏱️ Timeout Handling - Controle de timeout configurável para requests
Instalação
npm install @200systems/mf-sveltekit-adapterPeer Dependencies
npm install @sveltejs/kit svelteQuick Start
1. Configuração de Hooks
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { createSvelteKitHooks } from '@200systems/mf-sveltekit-adapter';
const { hooks, handleError } = createSvelteKitHooks({
logger: {
logRequests: true,
logResponses: true,
includeHeaders: false,
excludePaths: ['/favicon.ico', '/robots.txt', '/health'],
context: 'my-app'
},
auth: {
tokenKey: 'auth_token',
publicPaths: ['/login', '/register', '/', '/api/public/*'],
redirectPath: '/login',
onAuthRequired: () => {
console.log('Authentication required');
}
},
errorHandler: {
logErrors: true,
context: 'my-app',
onError: (error, context) => {
// Custom error handling
console.error('App error:', error, context);
}
}
});
export const handle = sequence(...hooks);
export { handleError };2. Configuração do API Client
// src/lib/api.ts
import { createApiClient } from '@200systems/mf-sveltekit-adapter';
export const apiClient = createApiClient({
baseUrl: 'http://localhost:3000/api',
timeout: 10000,
retries: 3,
defaultHeaders: {
'X-App-Version': '1.0.0',
'X-Client': 'sveltekit'
}
});3. Inicialização de Stores
// src/routes/+layout.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { initializeStores, authStore, isAuthenticated } from '@200systems/mf-sveltekit-adapter/client';
import { apiClient } from '$lib/api.js';
onMount(() => {
// Inicializar stores (carrega token do localStorage)
initializeStores();
// Configurar token no API client se disponível
const currentAuth = $authStore;
if (currentAuth.token) {
apiClient.setToken(currentAuth.token);
}
});
function handleLogout() {
authStore.logout();
apiClient.clearToken();
}
</script>
{#if $isAuthenticated}
<nav>
<a href="/dashboard">Dashboard</a>
<a href="/users">Users</a>
<button onclick={handleLogout}>Logout</button>
</nav>
{/if}
<main>
<slot />
</main>4. Uso em Componentes
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
import { authStore, isLoading, currentError, clearErrors } from '@200systems/mf-sveltekit-adapter/client';
import { apiClient } from '$lib/api.js';
import { goto } from '$app/navigation';
let email = $state('');
let password = $state('');
async function handleLogin() {
try {
clearErrors();
authStore.setLoading(true);
const result = await apiClient.post('/auth/login', { email, password });
authStore.login(result.user, result.token);
apiClient.setToken(result.token);
goto('/dashboard');
} catch (error) {
authStore.setError((error as Error).message);
}
}
</script>
<form onsubmit={handleLogin}>
<input bind:value={email} type="email" placeholder="Email" required />
<input bind:value={password} type="password" placeholder="Password" required />
{#if $currentError}
<div class="error">{$currentError}</div>
{/if}
<button type="submit" disabled={$isLoading}>
{$isLoading ? 'Fazendo login...' : 'Login'}
</button>
</form>API Reference
ApiClient
interface ApiClientConfig {
baseUrl: string;
timeout?: number;
retries?: number;
defaultHeaders?: Record<string, string>;
}
class ApiClient {
constructor(config: ApiClientConfig);
// Token management
setToken(token: string): void;
clearToken(): void;
getToken(): string | null;
isAuthenticated(): boolean;
// HTTP methods
get<T>(endpoint: string, options?: RequestOptions): Promise<T>;
post<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
put<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
patch<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
delete<T>(endpoint: string, options?: RequestOptions): Promise<T>;
// Retry mechanism
requestWithRetry<T>(endpoint: string, options?: RequestOptions, retries?: number): Promise<T>;
}
// Factory function
const client = createApiClient(config);Request Options
interface RequestOptions extends RequestInit {
skipAuth?: boolean; // Skip automatic token inclusion
timeout?: number; // Override default timeout
}
// Exemplos de uso
await client.get('/users');
await client.post('/users', userData);
await client.put('/users/123', updateData);
await client.delete('/users/123');
// Com opções customizadas
await client.get('/public-data', { skipAuth: true });
await client.get('/slow-endpoint', { timeout: 30000 });
// Com retry customizado
await client.requestWithRetry('/flaky-endpoint', {}, 5);Stores Reativas
// Auth Store
interface AuthStore {
isAuthenticated: boolean;
user: Record<string, unknown> | null;
token: string | null;
loading: boolean;
error: string | null;
}
// API Store
interface ApiStore {
loading: boolean;
error: string | null;
lastRequest: string | null;
}
// Stores disponíveis
import {
authStore,
apiStore,
isAuthenticated,
currentUser,
isLoading,
currentError,
initializeStores,
clearErrors
} from '@200systems/mf-sveltekit-adapter/client';
// Valores reativos (para usar com $ em Svelte)
$isAuthenticated // boolean
$currentUser // Record<string, unknown> | null
$isLoading // boolean (auth.loading || api.loading)
$currentError // string | null (auth.error || api.error)
// Ações do authStore
authStore.login(user, token);
authStore.logout();
authStore.setLoading(true);
authStore.setError('Error message');
authStore.initialize(); // Carrega do localStorage
// Ações do apiStore
apiStore.setLoading(true);
apiStore.setError('API error');
apiStore.setLastRequest('/api/endpoint');
// Funções utilitárias
initializeStores(); // Inicializa todos os stores
clearErrors(); // Limpa todos os errosHooks do SvelteKit
// Hook individual de logging
const loggerHook = createLoggerHook({
logRequests: true,
logResponses: true,
includeHeaders: false,
excludePaths: ['/health', '/favicon.ico'],
context: 'request-logger'
});
// Hook individual de autenticação
const authHook = createAuthHook({
tokenKey: 'auth_token',
redirectPath: '/login',
publicPaths: ['/login', '/register', '/', '/api/public/*'],
onAuthRequired: () => {
console.log('Authentication required');
}
});
// Handler de erro
const errorHandler = createErrorHandler({
logErrors: true,
context: 'error-handler',
onError: (error, context) => {
// Custom error handling
sendToAnalytics(error, context);
}
});
// Hook combinado (recomendado)
const { hooks, handleError } = createSvelteKitHooks({
logger: { /* config */ },
auth: { /* config */ },
errorHandler: { /* config */ }
});
export const handle = sequence(...hooks);
export { handleError };Exemplos Práticos
Gerenciamento de Usuários
<!-- src/routes/users/+page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { apiClient } from '$lib/api.js';
import { apiStore, isLoading, currentError } from '@200systems/mf-sveltekit-adapter/client';
let users = $state([]);
onMount(async () => {
await loadUsers();
});
async function loadUsers() {
try {
apiStore.setLoading(true);
const response = await apiClient.get('/users');
users = response.data || response;
} catch (error) {
apiStore.setError((error as Error).message);
}
}
async function deleteUser(id: number) {
if (!confirm('Tem certeza?')) return;
try {
await apiClient.delete(`/users/${id}`);
users = users.filter(u => u.id !== id);
} catch (error) {
apiStore.setError((error as Error).message);
}
}
</script>
{#if $isLoading}
<div class="loading">Carregando...</div>
{/if}
{#if $currentError}
<div class="error">{$currentError}</div>
{/if}
<div class="users">
{#each users as user}
<div class="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onclick={() => deleteUser(user.id)}>Deletar</button>
</div>
{/each}
</div>Formulário com Validação
<!-- src/routes/users/create/+page.svelte -->
<script lang="ts">
import { apiClient } from '$lib/api.js';
import { apiStore, isLoading, currentError, clearErrors } from '@200systems/mf-sveltekit-adapter/client';
import { goto } from '$app/navigation';
let form = $state({
name: '',
email: '',
password: ''
});
async function handleSubmit() {
try {
clearErrors();
apiStore.setLoading(true);
const user = await apiClient.post('/users', form);
goto('/users');
} catch (error) {
apiStore.setError((error as Error).message);
}
}
</script>
<form onsubmit|preventDefault={handleSubmit}>
<input bind:value={form.name} placeholder="Nome" required />
<input bind:value={form.email} type="email" placeholder="Email" required />
<input bind:value={form.password} type="password" placeholder="Senha" required />
{#if $currentError}
<div class="error">{$currentError}</div>
{/if}
<button type="submit" disabled={$isLoading}>
{$isLoading ? 'Criando...' : 'Criar Usuário'}
</button>
</form>Proteção de Rotas
// src/routes/dashboard/+layout.server.ts
import { redirect } from '@sveltejs/kit';
export async function load({ locals }) {
// O hook de auth já validou o token
// Se chegou aqui, o usuário está autenticado
if (!locals.authToken) {
throw redirect(302, '/login');
}
return {
authToken: locals.authToken
};
}Integração com Express API
Este adapter foi projetado para funcionar perfeitamente com @200systems/mf-express-adapter:
// O ApiClient automaticamente lida com:
// - Tokens JWT no formato Bearer
// - Respostas padronizadas da API Express
// - Error handling consistente
// - Retry logic para falhas de rede
// - Timeout handling
// Exemplo de resposta esperada da API Express:
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}Suporte a TypeScript
Suporte completo ao TypeScript com tipagem estrita:
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
// Chamadas de API tipadas
const users = await apiClient.get<User[]>('/users');
const user = await apiClient.post<User>('/users', userData);
// Stores tipados
const currentUser: Readable<User | null> = derived(
authStore,
$auth => $auth.user as User
);Características Específicas
Gerenciamento de Token Automático
- Persistência: Tokens são automaticamente salvos no
localStorage
// Automático quando você faz login
authStore.login(user, token); // Token é salvo no localStorage
apiClient.setToken(token); // Cliente configurado automaticamente- Restauração: Tokens são restaurados na inicialização da aplicação
// No +layout.svelte
onMount(() => {
initializeStores(); // Carrega token do localStorage automaticamente
const auth = $authStore;
if (auth.token) {
apiClient.setToken(auth.token); // Cliente pronto para usar
}
});- Limpeza: Tokens são removidos no logout ou erro 401
// Logout manual
authStore.logout(); // Remove do localStorage + limpa store
// Logout automático em 401
await apiClient.get('/protected'); // Se retornar 401, token é limpo automaticamente- Sincronização: ApiClient e stores sincronizam automaticamente
// Sincronização bidirecional
authStore.login(user, token); // ✅ Store + localStorage atualizados
apiClient.setToken(token); // ✅ Cliente configurado
apiClient.clearToken(); // ✅ Cliente limpo
authStore.logout(); // ✅ Store + localStorage limposError Handling Inteligente
- Retry automático: Para erros de rede (não para 4xx)
// Retry automático para erros de rede
await apiClient.get('/flaky-endpoint'); // Retry 3x automaticamente
// Retry customizado
await apiClient.requestWithRetry('/critical-data', {}, 5); // Retry 5x- Exponential backoff: Delay crescente entre tentativas
// Delays automáticos: 1s, 2s, 4s, 8s...
try {
await apiClient.get('/unstable-api');
} catch (error) {
// Só falha após todas as tentativas com delays crescentes
}- 401 handling: Logout automático em tokens expirados
try {
await apiClient.get('/protected');
} catch (error) {
// Se 401: token é limpo automaticamente + store atualizado
if (error.message === 'Authentication required') {
// Usuário já foi deslogado automaticamente
goto('/login');
}
}- Error stores: Centralização de erros em stores reativos
// Qualquer erro fica disponível reativamentenos stores
apiStore.setError('API failed');
authStore.setError('Login failed');
// Em qualquer componente
{#if $currentError}
<div class="error">{$currentError}</div>
{/if}Logging Estruturado
- Request/Response: Log automático com timing
// Configuração no hooks.server.ts
const loggerHook = createLoggerHook({
logRequests: true, // GET /api/users [timing: 245ms]
logResponses: true // GET /api/users 200 OK [duration: 245ms]
});
// Logs automáticos no console/arquivo
// INFO: Incoming request { method: 'GET', url: '/api/users', duration: 245 }- Context preservation: Mantém contexto através da aplicação
// Contexto propagado automaticamente
const logger = createLogger().child('user-service');
// user-service: Processing user creation
// user-service: Database query completed
// user-service: User created successfully- Structured data: Logs estruturados com metadados
// Metadados automáticos em cada log
logger.info('User login', {
userId: 123,
ip: '192.168.1.1',
userAgent: 'Chrome/120.0',
timestamp: '2024-01-15T10:30:00Z',
requestId: 'req_abc123'
});- Performance tracking: Tracking de performance automático
// Timing automático de requests
const startTime = Date.now();
await apiClient.get('/heavy-endpoint');
// Log: Request completed { duration: 1247ms, status: 200 }
// Timing de hooks
// Log: Request processed { url: '/dashboard', duration: 89ms }Melhores Práticas
- Inicialize stores cedo no ciclo de vida da aplicação
- Use stores reativas em vez de gerenciamento manual de estado
- Configure hooks apropriadamente para as necessidades da sua aplicação
- Use TypeScript para melhor experiência de desenvolvimento
- Trate erros graciosamente com os error stores
- Configure paths públicos no hook de autenticação
- Use retry mechanism para endpoints instáveis
Compatibilidade
- Svelte 5.x com runes support
- SvelteKit 2.x com app directory structure
- Node.js 18+ para funcionalidades de servidor
- TypeScript 5.x para tipagem completa
Dependencies
Required
@200systems/mf-logger- Sistema de logging
Peer Dependencies
@sveltejs/kit- SvelteKit framework (^2.0.0)svelte- Svelte framework (^5.0.0)
License
MIT
