Amba

Live Updates

Subscribe to collections and conversations for push delivery — render new rows and messages the moment they happen, instead of polling.

Amba can push changes to your app over a live connection, so your UI updates the moment something changes — no polling loop, no refresh button. Two surfaces are live today:

  • Collections — subscribe to row changes in any collection.
  • Messaging — subscribe to a conversation and receive new messages as they are sent.

Both use the same tiny shape: open a subscription, get a callback per change, and call the returned handle to stop. The SDK keeps the connection alive and reconnects automatically with backoff if it drops.

Availability. Live updates are generally available, including for shipped apps. Long-lived connections are served from a dedicated realtime host — see Production availability for the SDK versions that route there automatically and what to allowlist if your app pins outbound hosts.

Collections

Chain .where(column, value) to narrow, then .subscribe(cb). You receive only rows your session owns — delivery mirrors Amba.collections.find() exactly.

import { Amba } from '@layers/amba-web';
 
const unsubscribe = Amba.collection('messages')
  .where('circle_id', circleId)
  .subscribe((change) => {
    // change.op: "INSERT" | "UPDATE" | "DELETE"
    // change.row: the affected row
    switch (change.op) {
      case 'INSERT':
        addRow(change.row);
        break;
      case 'UPDATE':
        replaceRow(change.row);
        break;
      case 'DELETE':
        removeRow(change.row);
        break;
    }
  });
 
// later, when the view unmounts:
unsubscribe();

Messaging

Subscribe to a conversation to render incoming messages live instead of polling listMessages. You receive a conversation's messages only if your session is a participant of it.

import { Amba } from '@layers/amba-web';
 
// Open (or fetch the existing) conversation, then go live.
const conversation = await Amba.messaging.createConversation({
  participant_ids: [otherUserId],
  type: 'direct',
});
 
const unsubscribe = Amba.messaging.conversation(conversation.id).subscribe((change) => {
  // change.op === "INSERT" for a new message
  // change.row is the message
  appendMessageToThread(change.row);
});
 
// Sending still uses the normal call — the subscriber sees it arrive.
await Amba.messaging.sendMessage(conversation.id, { body: 'hey 👋' });
 
// later, when the thread view closes:
unsubscribe();

A common pattern: render the conversation list with conversations(), the thread with listMessages(), then attach a conversation(id).subscribe(...) so new messages stream in. This replaces a polling timer (for example, a 4-second listMessages loop) with push delivery.

Connection lifecycle

.subscribe(cb, options) accepts optional callbacks and returns an unsubscribe handle:

const unsubscribe = Amba.messaging
  .conversation(conversation.id)
  .subscribe((change) => appendMessageToThread(change.row), {
    onOpen: () => setStatus('live'),
    onError: (err) => setStatus('reconnecting'),
  });
  • onOpen fires each time the live connection is (re)established.
  • onError fires before the SDK retries; the SDK reconnects on its own with exponential backoff, so you don't need to re-subscribe.
  • Calling the returned handle stops listening and closes the connection.

Partial payloads

For deletes — and for the rare row that's too large to deliver inline — change.row may be partial and change.truncated is true. When you need the full body, re-fetch with Amba.collections.find() / findOne() (collections) or Amba.messaging.listMessages() (messaging).

Server-side (Node)

The same builders exist on @layers/amba-node, both for a signed-in session and for a server-side per-user handle via asUser(userId):

import { AmbaClient } from '@layers/amba-node';
 
const amba = new AmbaClient({
  projectId: process.env.AMBA_PROJECT_ID!,
  serverKey: process.env.AMBA_SERVER_KEY!,
});
 
const unsubscribe = amba
  .asUser(userId)
  .messaging.conversation(conversationId)
  .subscribe((change) => {
    // react to new messages for this user on the server
  });

React Native & Expo

The exact same builders ship in @layers/amba-react-native and @layers/amba-expo — identical method names, identical change shape — so the code above is portable across web, server, and mobile with only the import swapped:

import { Amba } from '@layers/amba-react-native'; // or '@layers/amba-expo'
 
// Collections
const unsubscribe = Amba.collection('messages')
  .where('circle_id', circleId)
  .subscribe((change) => {
    // change.op / change.row — same shape as web
  });
 
// Messaging
const stop = Amba.messaging.conversation(conversationId).subscribe((change) => {
  // change.row is the new message
});
 
// Gamification (the signed-in user's xp / level / achievement / streak)
const stopXp = Amba.gamification.subscribe((change) => {
  // change.kind, change.data
});

On mobile the live connection is delivered through the platform's native networking, so it works in release builds without any extra setup or peer dependency. Reconnect-with-backoff behaves exactly as it does on web.

Production availability

Live updates are generally available, including for shipped apps. Long-lived subscriptions connect to a dedicated realtime host (realtime.amba.host) that has no request-duration ceiling, so a subscription stays open as long as your app does.

The SDK handles routing automatically:

  • Default setup — when the SDK points at the default production API, live connections go to realtime.amba.host. No configuration needed. If your app pins outbound hosts (network security config, allowlists), add realtime.amba.host alongside api.amba.dev.
  • Custom apiUrl — if you override apiUrl (a proxy, a staging environment, a self-hosted setup), live connections use that same origin, exactly as before.

Automatic routing ships in @layers/amba-web 4.0.6, @layers/amba-node 4.0.7, and @layers/amba-react-native 4.0.5 (Expo inherits it via the React Native package). Earlier versions still work but hold live connections on the standard API edge, which limits how long a single connection stays open — upgrade for always-on streaming in production.

As with any live channel, keep your fetch path able to recover state on reconnect: the SDK retries with backoff, and your app resumes from its last fetched state while a connection is re-established.

On this page