Guía de Plugins
Guía de Plugins
Sección titulada «Guía de Plugins»Aprende a usar y crear plugins para Veloce.
Tabla de Contenidos
Sección titulada «Tabla de Contenidos»- Introducción
- Plugins Integrados
- Usando Plugins
- Creando Plugins Personalizados
- Ejemplos de Plugins
- Mejores Prácticas
Introducción
Sección titulada «Introducción»El sistema de plugins de Veloce te permite extender el framework con funcionalidad adicional. Los plugins pueden agregar rutas, middleware, modificar la aplicación e integrarse con servicios externos.
Interfaz de Plugin
Sección titulada «Interfaz de Plugin»import type { VeloceTS } from 'veloce-ts';
interface Plugin { name: string; version?: string; dependencies?: string[];
/** Called when the plugin is registered — mutate the app here. */ install(app: VeloceTS): void | Promise<void>;
/** Called after the server starts listening. Added in v2.0.0. */ onStart?(app: VeloceTS): void | Promise<void>;
/** Called during graceful shutdown, in reverse install order. Added in v2.0.0. */ onStop?(app: VeloceTS): void | Promise<void>;}Hooks de ciclo de vida
Sección titulada «Hooks de ciclo de vida»install() corre en el momento de app.compile(), en orden de dependencias. onStart() corre una vez que el servidor acepta conexiones. onStop() corre durante el apagado gradual (SIGTERM/SIGINT) y en un app.shutdown() explícito, en orden inverso de instalación — así un plugin siempre se libera antes que aquellos de los que depende.
import type { Plugin, VeloceTS } from 'veloce-ts';
class MetricsPlugin implements Plugin { name = 'metrics'; private timer?: ReturnType<typeof setInterval>;
install(app: VeloceTS) { app.get('/metrics', { handler: () => this.snapshot() }); }
// El servidor está activo — es seguro iniciar trabajo en segundo plano async onStart() { this.timer = setInterval(() => this.flush(), 10_000); }
// Libera todo para que el proceso pueda salir limpiamente async onStop() { if (this.timer) clearInterval(this.timer); await this.flush(); }}Los errores lanzados desde onStop() se recolectan en lugar de propagarse de inmediato, así que un plugin que falla no impide que los demás se liberen; se relanzan juntos como un AggregateError una vez que todos los hooks corrieron.
Plugins Integrados
Sección titulada «Plugins Integrados»OpenAPIPlugin
Sección titulada «OpenAPIPlugin»Genera documentación OpenAPI automáticamente desde tus rutas.
import { Veloce, OpenAPIPlugin } from 'veloce-ts';
const app = new Veloce();
app.usePlugin(new OpenAPIPlugin({ path: '/openapi.json', docsPath: '/docs', title: 'Mi API', version: '1.0.0', description: 'Descripción de la API',}));Opciones:
path: Ruta para servir la especificación JSON de OpenAPI (por defecto:/openapi.json)docsPath: Ruta para servir Swagger UI (por defecto:/docs)title: Título de la APIversion: Versión de la APIdescription: Descripción de la API
Acceso:
- Swagger UI:
http://localhost:3000/docs - Especificación OpenAPI:
http://localhost:3000/openapi.json
GraphQLPlugin
Sección titulada «GraphQLPlugin»Habilita soporte para GraphQL con generación automática de esquemas.
import { Veloce, GraphQLPlugin } from 'veloce-ts';
const app = new Veloce();
app.usePlugin(new GraphQLPlugin({ path: '/graphql', playground: true,}));Opciones (constructor actual):
path: Ruta del endpoint GraphQL (por defecto:/graphql)playgroundPath: Ruta del Playground (por defecto:/graphql/playground)playground: Habilitar GraphQL Playground (por defecto:true)context: Fábrica de contexto por petición
La introspección y CORS se gestionan según el plugin y el middleware global; ver la guía GraphQL.
WebSocketPlugin
Sección titulada «WebSocketPlugin»Registra rutas definidas con @WebSocket. El constructor no acepta opciones; la ruta es el argumento del decorador. El upgrade funciona en Bun, Deno y Node — Node pasa por la peer dependency opcional @hono/node-ws y debe servirse con app.listen(); ver WebSockets.
import { Veloce, WebSocketPlugin } from 'veloce-ts';
const app = new Veloce();
app.usePlugin(new WebSocketPlugin());HealthCheckPlugin
Sección titulada «HealthCheckPlugin»Registra /health, /ready y /live. Los checks personalizados van en un array checks.
import { Veloce, HealthCheckPlugin, HealthCheckers } from 'veloce-ts';import { sql } from 'drizzle-orm';
const app = new Veloce();
app.usePlugin(new HealthCheckPlugin({ path: '/health', readyPath: '/ready', livePath: '/live', checks: [ HealthCheckers.database(async () => { await db.execute(sql`SELECT 1`); return true; }), HealthCheckers.disk(process.cwd(), 90), HealthCheckers.memory(512), ],}));Respuesta de ejemplo (/health):
{ "status": "healthy", "uptime": 3600, "timestamp": "2026-03-27T12:00:00.000Z", "checks": { "database": { "status": "healthy", "message": "Database connection OK" }, "disk": { "status": "healthy", "message": "Disk usage: 45.0% (threshold: 90%)", "usagePercent": 45 }, "memory": { "status": "healthy", "message": "Heap usage: 62.00MB / 512MB", "heapUsedMB": "62.00", "maxUsageMB": 512 } }}Helpers de HealthCheckers:
HealthCheckers.alwaysHealthy()— resultado fijo healthy (pruebas)HealthCheckers.database(pingFn)—pingFndevuelvebooleanoPromise<boolean>; unhealthy si esfalseo lanzaHealthCheckers.memory(maxUsageMB?)— heap frente al límite (por defecto 512 MB)HealthCheckers.disk(path?, maxUsagePercent?)— usafs.statfsdonde exista (Node 18+ / Bun)
Usando Plugins
Sección titulada «Usando Plugins»Uso Básico
Sección titulada «Uso Básico»import { Veloce, OpenAPIPlugin, GraphQLPlugin } from 'veloce-ts';
const app = new Veloce();
// Usar múltiples pluginsapp.usePlugin(new OpenAPIPlugin());app.usePlugin(new GraphQLPlugin());Múltiples plugins
Sección titulada «Múltiples plugins»Registra cada plugin con usePlugin — no existe un array plugins en el constructor:
const app = new Veloce();
app.usePlugin(new OpenAPIPlugin({ path: '/api-docs.json', docsPath: '/api-docs',}));app.usePlugin(new GraphQLPlugin({ path: '/gql', playground: process.env.NODE_ENV !== 'production',}));Creando Plugins Personalizados
Sección titulada «Creando Plugins Personalizados»Plugin Simple
Sección titulada «Plugin Simple»import { Plugin, Veloce } from 'veloce-ts';
class HelloPlugin implements Plugin { name = 'hello-plugin'; version = '1.0.0';
install(app: VeloceTS) { // Agregar una ruta app.get('/hello', { handler: async (c) => { return { message: '¡Hola desde el plugin!' }; }, });
console.log('HelloPlugin instalado'); }}
// Usar el pluginconst app = new Veloce();app.usePlugin(new HelloPlugin());Plugin con Configuración
Sección titulada «Plugin con Configuración»interface LoggerPluginConfig { level: 'debug' | 'info' | 'warn' | 'error'; format: 'json' | 'text';}
class LoggerPlugin implements Plugin { name = 'logger-plugin'; version = '1.0.0';
constructor(private config: LoggerPluginConfig) {}
install(app: VeloceTS) { // Agregar middleware de logging app.use(async (c, next) => { const start = Date.now();
await next();
const duration = Date.now() - start; const log = { method: c.req.method, url: c.req.url, status: c.res.status, duration: `${duration}ms`, };
if (this.config.format === 'json') { console.log(JSON.stringify(log)); } else { console.log(`${log.method} ${log.url} - ${log.status} (${log.duration})`); } }); }}
// Usar con configuraciónapp.usePlugin(new LoggerPlugin({ level: 'info', format: 'json',}));Ejemplos de Plugins
Sección titulada «Ejemplos de Plugins»Plugin de Autenticación
Sección titulada «Plugin de Autenticación»interface AuthPluginConfig { secret: string; expiresIn: string;}
class AuthPlugin implements Plugin { name = 'auth-plugin';
constructor(private config: AuthPluginConfig) {}
install(app: VeloceTS) { // Agregar servicio de autenticación class AuthService { generateToken(userId: string) { // Generar token JWT return 'token'; }
verifyToken(token: string) { // Verificar token JWT return { userId: '1' }; } }
app.getContainer().register(AuthService, { scope: 'singleton' });
// Agregar rutas de autenticación app.post('/auth/login', { handler: async (c) => { const { email, password } = await c.req.json(); // Validar credenciales const token = 'token-generado'; return { token }; }, }); }}Plugin de Rate Limiting
Sección titulada «Plugin de Rate Limiting»interface RateLimitConfig { windowMs: number; max: number;}
class RateLimitPlugin implements Plugin { name = 'rate-limit-plugin'; private requests = new Map<string, number[]>();
constructor(private config: RateLimitConfig) {}
install(app: VeloceTS) { app.use(async (c, next) => { const ip = c.req.header('x-forwarded-for') || 'unknown'; const now = Date.now();
// Obtener timestamps de peticiones para esta IP const timestamps = this.requests.get(ip) || [];
// Remover timestamps antiguos const validTimestamps = timestamps.filter( t => now - t < this.config.windowMs );
// Verificar si se excedió el límite if (validTimestamps.length >= this.config.max) { return c.json({ error: 'Demasiadas peticiones' }, 429); }
// Agregar timestamp actual validTimestamps.push(now); this.requests.set(ip, validTimestamps);
await next(); }); }}
app.usePlugin(new RateLimitPlugin({ windowMs: 15 * 60 * 1000, // 15 minutos max: 100, // 100 peticiones por ventana}));Mejores Prácticas
Sección titulada «Mejores Prácticas»1. Usa Nombres Descriptivos
Sección titulada «1. Usa Nombres Descriptivos»// ✓ Buenoclass AuthenticationPlugin implements Plugin { name = 'authentication-plugin';}
// ✗ Maloclass MyPlugin implements Plugin { name = 'plugin1';}2. Versiona tus Plugins
Sección titulada «2. Versiona tus Plugins»class MyPlugin implements Plugin { name = 'my-plugin'; version = '1.0.0';}3. Documenta las Opciones de Configuración
Sección titulada «3. Documenta las Opciones de Configuración»/** * Opciones de configuración para CachePlugin */interface CachePluginConfig { /** Tiempo de vida en segundos */ ttl: number; /** Número máximo de elementos en caché */ maxSize: number; /** Prefijo de clave de caché */ prefix?: string;}
class CachePlugin implements Plugin { constructor(private config: CachePluginConfig) {}}4. Maneja Errores Apropiadamente
Sección titulada «4. Maneja Errores Apropiadamente»class MyPlugin implements Plugin { name = 'my-plugin';
async install(app: VeloceTS) { try { await this.initialize(); } catch (error) { console.error(`Falló la inicialización de ${this.name}:`, error); throw error; } }
private async initialize() { // Lógica de inicialización }}5. Haz los Plugins Configurables
Sección titulada «5. Haz los Plugins Configurables»// ✓ Bueno - Configurableclass LoggerPlugin implements Plugin { constructor(private config: LoggerConfig) {}}
// ✗ Malo - Hardcodedclass LoggerPlugin implements Plugin { private level = 'info'; // Hardcoded}Próximos Pasos
Sección titulada «Próximos Pasos»- Lee la Guía de Pruebas
- Revisa la Guía de Inicio
- Aprende sobre Inyección de Dependencias