Assinaturas em Tempo Real
Visão Geral
Seção intitulada “Visão Geral”O SDK Cliente da Rebase fornece assinaturas de dados em tempo real via WebSocket. Quando os registros mudam no servidor, seus callbacks assinados disparam imediatamente com os dados atualizados.
A conexão WebSocket é estabelecida automaticamente quando uma websocketUrl está disponível (derivada de baseUrl por padrão). A reconexão e a atualização de tokens são tratadas de forma transparente.
Assinar uma Coleção
Seção intitulada “Assinar uma Coleção”Use listen() para assinar uma consulta de coleção. O callback dispara sempre que o conjunto de dados correspondente muda:
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();O método listen() aceita os mesmos FindParams que find() — você pode filtrar, ordenar e paginar sua assinatura:
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); });Assinatura
Seção intitulada “Assinatura”listen( params: FindParams | undefined, onUpdate: (response: FindResponse<M>) => void, onError?: (error: Error) => void): () => void // returns unsubscribe functionMetadados em Duas Fases
Seção intitulada “Metadados em Duas Fases”Quando listen() dispara, ele emite atualizações em até duas fases:
-
Imediata (estimada): O primeiro callback dispara instantaneamente com as entidades e metadados de paginação heurísticos (
total= número de entidades retornadas,hasMore= se a contagem é igual ao limite solicitado). Essa emissão carregameta.total: true. -
Autoritativa (opcional): Uma consulta de contagem assíncrona é executada em segundo plano. Se o
totalouhasMoreautoritativo diferir da estimativa, um segundo callback dispara com metadados corrigidos e sem o sinalizadorestimated. Se os valores coincidirem, a segunda emissão é totalmente ignorada — seu callback dispara apenas uma vez.
Se a consulta de contagem falhar, nenhuma segunda emissão ocorre. O sinalizador estimated: true da primeira emissão permanece como sinal de que os metadados são heurísticos. Isso não é tratado como um erro de assinatura.
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 }); } });Dica: Se você não precisar distinguir entre metadados estimados e autoritativos, pode ignorar o sinalizador
estimated— ambas as emissões carregam o mesmo arraydata.
Assinar uma Única Entidade
Seção intitulada “Assinar uma Única Entidade”Use listenById() para observar um registro específico pelo seu 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); });Assinatura
Seção intitulada “Assinatura”listenById( id: string | number, onUpdate: (entity: Entity<M> | undefined) => void, onError?: (error: Error) => void): () => void // returns unsubscribe functionO callback recebe undefined quando a entidade é excluída.
Construtor de Consultas Fluente
Seção intitulada “Construtor de Consultas Fluente”Você também pode assinar através do construtor de consultas fluente. Isso é equivalente a chamar listen() com parâmetros, mas permite encadear .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 a Assinatura
Seção intitulada “Cancelar a Assinatura”Toda assinatura retorna uma função unsubscribe. Chame-a para parar de receber atualizações e limpar o listener do WebSocket:
const unsubscribe = client.data.products.listen( undefined, (response) => { /* ... */ });
// Later, when the component unmounts or you no longer need updates:unsubscribe();No React, use a limpeza do useEffect:
useEffect(() => { const unsubscribe = client.data.products.listen( { where: { active: ["==", true] } }, (response) => setProducts(response.data) ); return () => unsubscribe();}, []);Autenticação e Reconexão
Seção intitulada “Autenticação e Reconexão”O cliente WebSocket cuida da autenticação automaticamente:
- No login ou na atualização do token, o novo token é enviado ao servidor WebSocket através de uma mensagem
authenticate. - No logout, a conexão WebSocket é desconectada.
- Se a conexão cair, o cliente se reconecta automaticamente e restabelece todas as assinaturas ativas.
Nenhum gerenciamento manual de tokens é necessário — a integração entre client.auth e a camada WebSocket é tratada internamente.
Canais de Broadcast
Seção intitulada “Canais de Broadcast”Os canais de broadcast permitem enviar mensagens arbitrárias entre clientes conectados — ideal para chat, notificações ou recursos colaborativos:
// 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();Os canais são leves e efêmeros — existem enquanto pelo menos um cliente estiver assinado.
Por padrão, os broadcasts não são reenviados. Eles alcançam apenas os membros conectados no momento. É o que se quer para notificações que se autocorrigem — um aviso de «alguém salvou» é substituído pelo salvamento seguinte — e não custa nada. Para um fluxo de operações, onde uma lacuna silenciosa causa divergência, ative o histórico de mensagens no canal.
Histórico de Mensagens e Recuperação
Seção intitulada “Histórico de Mensagens e Recuperação”Um canal pode ser configurado para reter seus broadcasts, de modo que um cliente que se reconecta recupere o que perdeu em vez de ressincronizar do zero. É isso que torna os canais utilizáveis como transporte para edição colaborativa.
A retenção é configurada no servidor, por padrão de canal — veja Backend Realtime. Um cliente não pode ativá-la por conta própria, porque um canal é criado por quem o nomeia, e uma profundidade de histórico escolhida pelo cliente permitiria a qualquer visitante comprometer seu backend com armazenamento ilimitado.
Em um canal com retenção, passe { history: true } e o SDK faz o 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();No join() e após cada reconexão, o SDK pede ao servidor tudo o que vem depois do último número de sequência que viu, e entrega o resultado pelos mesmos handlers. Não há um segundo caminho de código a escrever: um handler que aplica uma operação corretamente ao vivo a aplica corretamente na recuperação.
Números de sequência
Seção intitulada “Números de sequência”Todo broadcast em um canal com retenção carrega um seq — por canal, sem lacunas e crescente. É o ponto de retomada do 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 farPersista channel.sequence se quiser que a recuperação sobreviva também a um recarregamento de página, e devolva-o via history({ sinceSeq }).
Buscar o histórico explicitamente
Seção intitulada “Buscar o histórico explicitamente”const { messages, retained, latestSeq } = await channel.history({ sinceSeq: 0, limit: 100});retained: false significa que o canal não guarda histórico e nunca guardará — uma resposta explícita, para que você possa distinguir «você não perdeu nada» de «este canal não tem regra de retenção». No segundo caso, um cliente que precisa convergir tem de recorrer a uma ressincronização completa.
latestSeq é a maior sequência que o servidor possui, tenha este lote chegado a ela ou não. Se estiver muito além do seu último seq entregue, você está mais atrasado do que uma página e ressincronizar pode sair mais barato que paginar.
Rastreamento de Presença
Seção intitulada “Rastreamento de Presença”A presença permite rastrear quais usuários estão online e sincronizar o estado compartilhado entre todos os 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();A presença é construída sobre os canais de broadcast com diferenciação automática de estado — apenas as mudanças são transmitidas.
Quando Usar o Tempo Real
Seção intitulada “Quando Usar o Tempo Real”| Caso de Uso | Método |
|---|---|
| Painel com dados ao vivo | listen() com filtros |
| Chat ou mensagens | channel.broadcast() |
| Edição colaborativa / fluxos de operações | channel(name, { history: true }) |
| Indicadores de digitação / status online | channel.track() + channel.onPresence() |
| Página de detalhe com atualizações ao vivo | listenById() |
| Monitoramento do painel de administração | listen() com orderBy e limit |
Dica: Para buscas de dados pontuais, use
find()oufindById(). As assinaturas são ideais para dados que mudam com frequência e precisam ser refletidos na interface imediatamente.
Próximos Passos
Seção intitulada “Próximos Passos”- Consultar Dados — Operações CRUD e construtor de consultas
- Autenticação — Login e gerenciamento de sessões
- Backend em Tempo Real — Configuração de WebSocket do lado do servidor
