[data-reveal]{opacity:1!important;transform:none!important}
Change Feeds
Database

Change Feeds

Change feeds provide real-time notifications when documents are inserted, updated, or deleted. Both Stream<T> and SingleSelection<T> expose a changes() method that returns change events.

The Changes Interface

interface Changes<T> {
  changeType: "added" | "removed" | "modified";
  oldValue?: T;
  newValue?: T;
}
PropertyDescription
changeTypeThe type of change: "added", "removed", or "modified".
oldValueThe document value before the change. Undefined for new documents.
newValueThe document value after the change. Undefined for deletions.

Create a Change Feed

Call changes() on a stream or single selection to build a feed query. Consume it with async iteration to receive events as they arrive:

import { Schema } from "@antelopejs/interface-database";

const schema = Schema.get("myapp")!;
const users = schema.instance().table("users");

// Monitor all changes to a table
const allChanges = users.changes();

// Monitor changes to a specific document
const userChanges = users.get("user-123").changes();

Process Change Events

Each change event includes a changeType field that indicates the nature of the change:

for await (const change of users.changes()) {
  if (change.changeType === "added") {
    console.log("New document:", change.newValue);
  } else if (change.changeType === "modified") {
    console.log("Updated from:", change.oldValue);
    console.log("Updated to:", change.newValue);
  } else {
    console.log("Deleted document:", change.oldValue);
  }
}

Use with Async Iteration

changes() returns Query<Changes<T>[]>, but Query's cursor and async iterator yield each array element: one Changes<T> event, not a batch. A direct await uses run() and asks the provider for a completed result; an open-ended feed may never complete, so use iteration for continuous monitoring.

import { Schema } from "@antelopejs/interface-database";
import type { Changes } from "@antelopejs/interface-database/common";

interface User {
  _id: string;
  name: string;
}

export async function watchUsers(onChange: (change: Changes<User>) => boolean) {
  const schema = Schema.get<{ users: User }>("myapp");
  if (!schema) throw new Error("Register the myapp schema first");
  const feed = schema.instance().table("users").changes();

  for await (const change of feed) {
    console.log(`${change.changeType}:`, change.newValue ?? change.oldValue);
    if (!onChange(change)) break;
  }
}

Breaking out of the loop closes its cursor. When using cursor() manually, call return() when finished.

Use Cases

Change feeds are well suited for:

  • Real-time UI updates - Push changes to connected clients as they happen.
  • Event-driven architectures - React to data changes with downstream processing.
  • Change observation - Observe writes while a feed is active. Durable auditing requires provider-specific delivery and recovery guarantees; this interface alone does not promise a complete audit log.
  • Cache invalidation - Clear or update caches when the underlying data changes.