Guía de WebSockets
Guía de WebSockets
Sección titulada «Guía de WebSockets»Construye tiempo real con el plugin WebSocket oficial y los decoradores. Esta guía está alineada con las versiones actuales de veloce-ts (ver Registro de cambios).
Resumen
Sección titulada «Resumen»@WebSocket(path)— marca la clase como manejador para esa ruta.@OnConnect/@OnMessage/@OnDisconnect— ciclo de vida.WebSocketPlugin— registra rutas y hace el upgrade según el runtime (Bun / Deno / Node).WebSocketConnection—send,broadcast,join,leave,close, y accesonativealWebSocket.
Un solo @OnMessage por clase: los metadatos guardan un único manejador de mensajes (si declaras varios @OnMessage, gana el último). Modela distintos payloads del cliente con un esquema Zod (por ejemplo unión discriminada por un campo type).
Configuración
Sección titulada «Configuración»import 'reflect-metadata';import { VeloceTS, WebSocketPlugin, WebSocket, OnConnect, OnMessage, OnDisconnect, WebSocketConnection,} from 'veloce-ts';import { z } from 'zod';
const ChatMessage = z.object({ type: z.literal('chat'), text: z.string().min(1),});
@WebSocket('/ws/chat')class ChatSocket { @OnConnect() onOpen(client: WebSocketConnection) { client.join('lobby'); client.send({ type: 'welcome', message: 'Conectado' }); }
@OnMessage(ChatMessage) onData(client: WebSocketConnection, msg: z.infer<typeof ChatMessage>) { client.broadcast({ type: 'chat', text: msg.text }, 'lobby'); }
@OnDisconnect() onClose(client: WebSocketConnection) { console.log('desconectado', client.id); }}
const app = new VeloceTS({ title: 'Chat', version: '1.0.0' });app.usePlugin(new WebSocketPlugin());app.include(ChatSocket);await app.compile();app.listen(3000);Notas de registro
Sección titulada «Notas de registro»- Importa
WebSocketPlugindesdeveloce-ts(también enveloce-ts/plugins). - El constructor del plugin no acepta opciones; la ruta es siempre el argumento de
@WebSocket('/ruta'). - Llama a
await app.compile()después de losinclude()para compilar metadatos.
Soporte por runtime
Sección titulada «Soporte por runtime»| Runtime | Ruta de upgrade | Instalación extra |
|---|---|---|
| Bun | upgrade() nativo de Bun.serve() | — |
| Deno | Deno.upgradeWebSocket() | — |
| Node 20+ | @hono/node-ws (envuelve ws) | npm install @hono/node-ws |
En Node el plugin necesita el http.Server real, que solo existe después de app.listen() — por eso inyecta el manejador WebSocket en el hook onStart() del plugin. Dos consecuencias:
- Sirve con
app.listen(). Si le pasasapp.fetcha un servidor que creaste tú, el plugin se queda sin servidor al que engancharse y el upgrade no se completa. - Node también necesita
@hono/node-server, quelisten()ya requiere de todas formas.
Si falta @hono/node-ws, el arranque falla con el comando de instalación en vez de un upgrade roto en runtime:
WebSocket support on Node requires the @hono/node-ws package.Install it with: npm install @hono/node-wsEl heartbeat varía un poco según el runtime — solo Bun expone un ws.ping() a nivel de protocolo; ver la nota en Endurecimiento de conexiones.
Endurecimiento de conexiones
Sección titulada «Endurecimiento de conexiones»Las cuatro opciones son opcionales y se configuran en WebSocketPlugin:
app.usePlugin(new WebSocketPlugin({ heartbeatIntervalMs: 30_000, // por defecto 30 s; 0 lo desactiva heartbeatTimeoutMs: 60_000, // por defecto 2 × heartbeatIntervalMs idleTimeoutMs: 0, // por defecto 0 (desactivado) maxMessageSizeBytes: 1_048_576, // por defecto 1 MiB}));| Opción | Comportamiento |
|---|---|
heartbeatIntervalMs | Cada cuánto hace ping el servidor. Cierra la conexión con código 1001 si no llega tráfico entrante dentro de heartbeatTimeoutMs. |
heartbeatTimeoutMs | Límite de vitalidad. Por defecto el doble del intervalo. |
idleTimeoutMs | Corte duro por inactividad (código 1001) independientemente de las respuestas de heartbeat. Ponlo mayor que heartbeatIntervalMs si usas ambos, ya que los pong cuentan como actividad. |
maxMessageSizeBytes | Los mensajes entrantes demasiado grandes reciben un frame de error y código de cierre 1009 (Message Too Big). |
Las conexiones cerradas se eliminan del registro de conexiones y de todas las salas a las que se habían unido.
Protocolo de heartbeat. En cada tick el servidor envía un ping a nivel de protocolo donde el runtime lo expone (el ws.ping() de Bun) más un frame JSON portable a nivel de aplicación {"type":"ping"}. La vitalidad se mide por cualquier tráfico entrante, porque los eventos pong a nivel de protocolo no son observables desde el manager — Bun los entrega a un handler pong en Bun.serve() que el adaptador no conecta, y los sockets actualizados de Deno no exponen ping() en absoluto. Los clientes deberían responder con {"type":"pong"} o simplemente enviar cualquier mensaje dentro del timeout.
Mientras el heartbeat está activo, los frames entrantes {"type":"ping"} y {"type":"pong"} son mensajes de control reservados y no se reenvían a tus handlers @OnMessage.
WebSocketConnection
Sección titulada «WebSocketConnection»| API | Uso |
|---|---|
id | Identificador estable de la conexión |
send(data) | Envía datos serializables a JSON a este cliente |
broadcast(data, room?) | Envía a todas las conexiones de una sala, o a todas si omites room |
join(room) / leave(room) | Entrar / salir de salas |
close(code?, reason?) | Cierra el socket |
native | Instancia WebSocket subyacente |
Validación de mensajes
Sección titulada «Validación de mensajes»Un esquema Zod opcional en @OnMessage(Schema) valida cada mensaje JSON entrante. Si falla, el manager envía un objeto de error al cliente sin cerrar la conexión.
Ejemplos en el repositorio
Sección titulada «Ejemplos en el repositorio»El proyecto examples/chat-api (Bun) muestra REST + JWT + SQLite junto al flujo WebSocket, con notas sobre 426/501 cuando no hay upgrade (p. ej. pruebas con fetch).
Ver también
Sección titulada «Ver también»- Limitaciones y hoja de ruta
- Plugins
- GraphQL — suscripciones experimentales; valida antes de producción.