Suscripciones en Tiempo Real
Resumen
Sección titulada «Resumen»El SDK del Cliente de Rebase proporciona suscripciones a datos en tiempo real mediante WebSocket. Cuando los registros cambian en el servidor, sus callbacks suscritos se disparan de inmediato con los datos actualizados.
La conexión WebSocket se establece automáticamente cuando hay una websocketUrl disponible (derivada de baseUrl de forma predeterminada). La reconexión y el refresco de tokens se gestionan de forma transparente.
Suscribirse a una Colección
Sección titulada «Suscribirse a una Colección»Use listen() para suscribirse a una consulta de colección. El callback se dispara cada vez que cambia el conjunto de datos coincidente:
const unsubscribe = client.data.products.listen( { where: { active: ["==", true] }, limit: 50 }, (response) => { console.log("Products updated:", response.data); console.log("Total:", response.meta.total); });
// Stop listening when doneunsubscribe();El método listen() acepta los mismos FindParams que find() — puede filtrar, ordenar y paginar su suscripción:
const unsubscribe = client.data.orders.listen( { where: { status: ["==", "pending"] }, orderBy: ["created_at", "desc"], limit: 20 }, (response) => { renderOrders(response.data); }, (error) => { console.error("Subscription error:", error); });listen( params: FindParams | undefined, onUpdate: (response: FindResponse<M>) => void, onError?: (error: Error) => void): () => void // returns unsubscribe functionMetadatos en Dos Fases
Sección titulada «Metadatos en Dos Fases»Cuando listen() se dispara, emite actualizaciones en hasta dos fases:
-
Inmediata (estimada): El primer callback se dispara al instante con las entidades y metadatos de paginación heurísticos (
total= número de entidades devueltas,hasMore= si el conteo es igual al límite solicitado). Esta emisión llevameta.total: true. -
Autoritativa (opcional): Una consulta de conteo asíncrona se ejecuta en segundo plano. Si el
totalohasMoreautoritativo difiere de la estimación, se dispara un segundo callback con metadatos corregidos y sin la marcaestimated. Si los valores coinciden, la segunda emisión se omite por completo — su callback se dispara solo una vez.
Si la consulta de conteo falla, no se produce una segunda emisión. La marca estimated: true de la primera emisión permanece como señal de que los metadatos son heurísticos. Esto no se trata como un error de suscripción.
client.data.products.listen( { where: { active: ["==", true] }, limit: 50 }, (response) => { if (response.meta.total) { // First-paint: render immediately, total/hasMore may change renderProducts(response.data, { loading: true }); } else { // Authoritative: safe to render final pagination controls renderProducts(response.data, { loading: false }); } });Consejo: Si no necesita distinguir entre metadatos estimados y autoritativos, puede ignorar la marca
estimated— ambas emisiones llevan el mismo arraydata.
Suscribirse a una Sola Entidad
Sección titulada «Suscribirse a una Sola Entidad»Use listenById() para observar un registro específico por su ID:
const unsubscribe = client.data.products.listenById( 42, (entity) => { if (entity) { console.log("Product changed:", entity.values.name); } else { console.log("Product was deleted"); } }, (error) => { console.error("Subscription error:", error); });listenById( id: string | number, onUpdate: (entity: Entity<M> | undefined) => void, onError?: (error: Error) => void): () => void // returns unsubscribe functionEl callback recibe undefined cuando la entidad se elimina.
Constructor de Consultas Fluido
Sección titulada «Constructor de Consultas Fluido»También puede suscribirse a través del constructor de consultas fluido. Es equivalente a llamar a listen() con parámetros, pero permite encadenar .where(), .orderBy(), etc.:
const unsubscribe = client.data.products .where("active", "==", true) .orderBy("created_at", "desc") .limit(20) .listen( (response) => console.log("Updated:", response.data), (error) => console.error("Error:", error) );Cancelar la Suscripción
Sección titulada «Cancelar la Suscripción»Cada suscripción devuelve una función unsubscribe. Llámela para dejar de recibir actualizaciones y limpiar el listener de WebSocket:
const unsubscribe = client.data.products.listen( undefined, (response) => { /* ... */ });
// Later, when the component unmounts or you no longer need updates:unsubscribe();En React, use la limpieza de useEffect:
useEffect(() => { const unsubscribe = client.data.products.listen( { where: { active: ["==", true] } }, (response) => setProducts(response.data) ); return () => unsubscribe();}, []);Autenticación y Reconexión
Sección titulada «Autenticación y Reconexión»El cliente WebSocket gestiona la autenticación automáticamente:
- Al iniciar sesión o refrescar el token, el nuevo token se envía al servidor WebSocket mediante un mensaje
authenticate. - Al cerrar sesión, la conexión WebSocket se desconecta.
- Si la conexión se cae, el cliente se reconecta automáticamente y restablece todas las suscripciones activas.
No se necesita gestión manual de tokens — la integración entre client.auth y la capa WebSocket se gestiona internamente.
Canales de Difusión (Broadcast)
Sección titulada «Canales de Difusión (Broadcast)»Los canales de difusión le permiten enviar mensajes arbitrarios entre clientes conectados — ideal para chat, notificaciones o funciones colaborativas:
// Obtain a channel. This alone opens no connection.const channel = client.realtime.channel("chat-room");
// Listen for broadcasts. Pass an event name to filter, or omit it for all.channel.onBroadcast("message", (payload) => { console.log("New message:", payload);});
// Send to every other member — the sender never receives its own message.await channel.broadcast("message", { text: "Hello, world!", userId: currentUser.id});
// Leave, releasing handlers and timers.await channel.leave();Los canales son ligeros y efímeros — existen mientras al menos un cliente esté suscrito. Las llamadas repetidas a channel() con el mismo nombre devuelven el mismo objeto, por lo que dos componentes pueden adjuntar manejadores de forma independiente sin que uno corte al otro al salir.
Las tramas de canal y presencia no requieren una cuenta: los visitantes anónimos pueden unirse a canales públicos, y el servidor sigue autorizando cada trama.
Por defecto, las difusiones no se reproducen. Solo llegan a los miembros conectados en ese momento. Eso es lo que se quiere para notificaciones que se autocorrigen — un aviso de «alguien ha guardado» queda sustituido por el siguiente guardado — y no cuesta nada. Para un flujo de operaciones, donde un hueco silencioso causa divergencia, active el historial de mensajes en el canal.
Historial de Mensajes y Recuperación
Sección titulada «Historial de Mensajes y Recuperación»Un canal puede configurarse para conservar sus difusiones, de modo que un cliente que se reconecta recupere lo que se perdió en lugar de resincronizarse desde cero. Esto es lo que hace que los canales sirvan como transporte para la edición colaborativa.
La retención se configura en el servidor, por patrón de canal — consulte Backend de Tiempo Real. Un cliente no puede activarla por su cuenta, porque un canal lo crea quien lo nombra, y una profundidad de historial elegida por el cliente permitiría a cualquier visitante comprometer su backend con almacenamiento ilimitado.
En un canal con retención, pase { history: true } y el SDK hace el resto:
const channel = client.realtime.channel("doc:42", { history: true });
// Handlers receive replayed messages exactly like live ones, in order.channel.onBroadcast("op", (payload) => { applyOperation(payload);});
await channel.join();Al hacer join() y tras cada reconexión, el SDK pide al servidor todo lo posterior al último número de secuencia que vio, y entrega el resultado a través de los mismos manejadores. No hay un segundo camino de código que escribir: un manejador que aplica una operación correctamente en vivo la aplica correctamente al recuperar.
Números de secuencia
Sección titulada «Números de secuencia»Cada difusión en un canal con retención lleva un seq — por canal, sin huecos y creciente. Es el punto de reanudación del cliente.
channel.onBroadcast((event) => { console.log(event.seq); // 1, 2, 3, … console.log(event.replayed); // true when delivered by catch-up});
console.log(channel.sequence); // highest seq delivered so farGuarde channel.sequence si quiere que la recuperación sobreviva también a una recarga de página, y devuélvalo mediante history({ sinceSeq }).
Obtener el historial explícitamente
Sección titulada «Obtener el historial explícitamente»const { messages, retained, latestSeq } = await channel.history({ sinceSeq: 0, limit: 100});retained: false significa que el canal no guarda historial y nunca lo hará — una respuesta explícita, para que pueda distinguir «no se perdió nada» de «este canal no tiene regla de retención». En el segundo caso, un cliente que necesite converger debe recurrir a una resincronización completa.
latestSeq es la secuencia más alta que tiene el servidor, haya llegado o no este lote hasta ella. Si está muy por delante de su último seq entregado, va más atrasado que una página y resincronizar puede salir más barato que paginar.
Seguimiento de Presencia
Sección titulada «Seguimiento de Presencia»La presencia le permite rastrear qué usuarios están en línea y sincronizar el estado compartido entre todos los participantes:
const channel = client.realtime.channel("editors");
// Publish your presence. This is also what opens the connection.await channel.track({ userId: currentUser.id, status: "editing", cursor: { x: 100, y: 200 }});
// One handler for every change. `presences` is always the full roster;// `diff` is what changed, when you only care about the delta.channel.onPresence((presences, diff) => { console.log("Online users:", Object.keys(presences)); if (diff) { console.log("joined:", Object.keys(diff.joins)); console.log("left:", Object.keys(diff.leaves)); }});
// Calling track() again replaces your state — this is how you publish a// moving cursor.await channel.track({ userId: currentUser.id, status: "idle" });
// Stop publishing without leaving the channel.await channel.untrack();La presencia se construye sobre los canales de difusión con diferenciación automática del estado — solo se transmiten los cambios.
Cuándo Usar el Tiempo Real
Sección titulada «Cuándo Usar el Tiempo Real»| Caso de Uso | Método |
|---|---|
| Panel con datos en vivo | listen() con filtros |
| Chat o mensajería | channel.broadcast() |
| Edición colaborativa / flujos de operaciones | channel(name, { history: true }) |
| Indicadores de escritura / estado en línea | channel.track() + channel.onPresence() |
| Página de detalle con actualizaciones en vivo | listenById() |
| Monitorización del panel de administración | listen() con orderBy y limit |
Consejo: Para obtener datos una sola vez, use
find()ofindById()en su lugar. Las suscripciones son mejores para datos que cambian con frecuencia y deben reflejarse en la UI de inmediato.
Próximos Pasos
Sección titulada «Próximos Pasos»- Consultar Datos — Operaciones CRUD y constructor de consultas
- Autenticación — Inicio de sesión y gestión de sesiones
- Backend en Tiempo Real — Configuración de WebSocket del lado del servidor
