Amba
SDKsFeatures

Offline & resilience

Buffer writes when the device is offline and replay them in order on reconnect, apply optimistic updates with automatic rollback, and keep push delivery alive across token rotation.

Real apps go offline — a subway tunnel, an elevator, a flaky cafe network. The SDK has three opt-in capabilities so a lost connection never means lost data or a broken UI:

  1. Automatic offline queue — a write that fails because the device is offline is buffered to a durable on-device outbox and replayed in order the moment connectivity returns. Replays are idempotent, so a write that reached the server before the connection dropped is never applied twice.
  2. Optimistic updates — update your local view immediately, fire the real write, and roll the local change back automatically if the write fails.
  3. Push-token auto-rotation — re-register the device push token whenever the OS rotates it, so notifications keep arriving without the app noticing.

All three are opt-in and additive — existing code keeps its exact behavior until you turn them on.

Automatic offline queue

Call Amba.offline.enable() once after configure(). From then on, a collection write (insert / update / delete) or a track() that fails with a transport error is queued instead of throwing. On reconnect the queue drains in FIFO order.

import { Amba } from '@layers/amba-web';
 
await Amba.configure({ projectId: 'proj_...', clientKey: 'amb_dev_ck_...' });
 
// Turn the outbox on. On the web it auto-flushes on the browser `online` event.
Amba.offline.enable();
 
// If the device is offline, this is buffered instead of throwing — your code
// keeps running. It replays automatically when the connection returns.
await Amba.collections.insert('todos', { title: 'Buy milk' });
 
// Inspect / drive the queue:
console.log(await Amba.offline.pendingCount()); // writes awaiting replay
await Amba.offline.flush(); // force a replay attempt now

Idempotent replay

Each queued collection write carries a stable idempotency key, sent on both the live attempt and every replay. If a write actually reached the server before the connection dropped, the replay is recognized as a duplicate and ignored — you never get a double-insert. This is automatic; you don't manage the keys.

Buffered events (track) carry an idempotency key too, and Amba records each key once, so an event replayed any number of times is recorded once. On React Native the live attempt of an engagement event sends the same key, so an event whose request reached Amba but timed out is recorded once when it replays. On Web and Node the live attempt carries no key, so such an event can be recorded twice. Replays keep the event's original time, so an event keeps its real occurred_at instead of being stamped at flush. A replay that finds no valid session stays in the queue and lands once the session is back.

Request deadlines

Every request runs under a deadline that covers sending it and reading the whole response. A request still unfinished at its deadline is aborted and fails with the retryable NETWORK_ERROR, so the outbox queues it (when it is a write) and keeps it for the next flush. A request the network never answers can't hold up the queue, a sign-in or a later call.

OptionDefaultApplies to
requestTimeoutMs15000 (15 s)Every API request.
longRequestTimeoutMs300000 (5 min)AI generation (for a stream, the time until it starts), functions.invoke(), media uploads and downloads.

Pass either to configure(). functions.invoke(name, body, { timeoutMs }) sets the deadline for one invocation. A request that moves more than 64 KiB (an upload, a download, a large event batch) gets one more second for every further 8 KiB, so it can finish on a slow link. A realtime subscription must start streaming within requestTimeoutMs; a connection that hasn't is retried with backoff, and once it streams it stays open for as long as you subscribe.

A request that timed out may still have reached Amba. Collection inserts and queued events carry idempotency keys, so their replays are safe. Before you retry another write yourself after a NETWORK_ERROR (a purchase, a currency spend), read back its result.

Each write belongs to the user who made it

Every queued write records the signed-in user (or, before any sign-in, the install's anonymous id). A flush replays only the current user's writes, and pending() / pendingCount() list only theirs. When someone else signs in on the same device without a sign-out, the earlier user's writes wait and replay once that user signs back in. Amba.auth.signOut() empties the queue, so nothing a signed-out user queued can land in the next account; a write whose request was still in flight at sign-out is refused with OFFLINE_QUEUE_PURGED. A write queued before sign-in belongs to the install's anonymous id, so once that id changes (signOut(true), or every page load with web sessionPersistence: 'memory') it can't replay and is dropped. Writes queued by an SDK version without owners are dropped too. Dropped writes are counted in dropped.

The queue API

MethodDescription
Amba.offline.enable()Turn the outbox on. Returns a disable function. Idempotent.
Amba.offline.isEnabled()Whether the outbox is currently on.
Amba.offline.pendingCount()Number of the current user's writes awaiting replay.
Amba.offline.pending()Snapshot the current user's pending writes (a copy).
Amba.offline.flush()Force a replay attempt now → { flushed, dropped, remaining }.
Amba.offline.clear()Drop every queued write without replaying.

A queued write that the server later rejects deterministically (e.g. a VALIDATION_ERROR it would fail every time) is dropped on replay rather than retried forever — it's reported in the dropped count (and a one-time warning) so one poisoned write can't wedge everything behind it. Transient connectivity failures, by contrast, keep the queue intact for the next attempt.

This is distinct from Amba.sync (manual change replay for explicit offline sync flows). Amba.offline is the automatic, fire-and-forget safety net for ordinary writes — turn it on and forget about it.

Optimistic updates

Show the change instantly, reconcile in the background, and roll back cleanly if the write fails. The collection surface has thin convenience helpers for the common single-write case:

await Amba.collections.optimisticInsert('todos', draft, {
  // 1. Update the local view now. Return a snapshot to restore on failure.
  apply: () => {
    const prev = todos;
    setTodos([...todos, draft]);
    return prev;
  },
  // 2. Undo if the write fails. Receives whatever `apply` returned.
  rollback: (prev) => setTodos(prev),
});
// optimisticUpdate(name, id, set, view) and optimisticDelete(name, id, view) too.

On success the helper resolves with the server's authoritative row. On failure it runs your rollback and rethrows the original error, so you can surface it.

Optimistic writes always hit the network directly — they are not diverted to the offline outbox even when Amba.offline is enabled. Optimistic UI needs an unambiguous outcome, so the write resolves only on a confirmed server write and rejects (running rollback) on any failure, including being offline. To buffer-and-replay instead, use the plain insert / update / delete with the outbox on.

For multi-step flows, the framework-agnostic withOptimistic helper takes any async mutate — apply and rollback operate on whatever local view you hand them (a React state setter, a store, an in-memory cache). Give it a mutate that fails on error (a transaction, a function call, your own request) so a failure rejects and triggers rollback:

import { Amba, withOptimistic } from '@layers/amba-web';
 
await withOptimistic({
  apply: () => {
    const prev = cart;
    setCart([...cart, item]);
    return prev;
  },
  // A transaction commits atomically or throws — never silently queues.
  mutate: () =>
    Amba.collections.transaction([
      { collection: 'cart', op: 'insert', row: item },
      { collection: 'inventory', op: 'update', id: item.invId, set: { reserved: 1 } },
    ]),
  rollback: (prev) => setCart(prev),
});

Push-token auto-rotation

The OS can rotate a device's push token at any time (reinstall, restore, key roll). When it does, the old token stops delivering. Auto-rotation re-registers the fresh token through the idempotent registration path so notifications keep arriving.

// In your service worker, where `pushsubscriptionchange` fires.
import { Amba } from '@layers/amba-web';
 
await Amba.configure({ projectId: 'proj_...', clientKey: 'amb_dev_ck_...' });
Amba.push.enableAutoRotation(); // re-registers the new subscription on rotation

Re-registration is idempotent, so re-registering the same device is a no-op server-side — it's always safe to leave auto-rotation on.

On this page