npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@200systems/mf-sveltekit-adapter

v1.1.6

Published

SvelteKit integration adapter with API client, stores, and hooks

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-adapter

Peer Dependencies

npm install @sveltejs/kit svelte

Quick 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 erros

Hooks 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 limpos

Error 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

  1. Inicialize stores cedo no ciclo de vida da aplicação
  2. Use stores reativas em vez de gerenciamento manual de estado
  3. Configure hooks apropriadamente para as necessidades da sua aplicação
  4. Use TypeScript para melhor experiência de desenvolvimento
  5. Trate erros graciosamente com os error stores
  6. Configure paths públicos no hook de autenticação
  7. 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