Skip to content

Latest commit

 

History

History

README.md

Contrail — PostgreSQL Example

A complete example of using Contrail to index AT Protocol calendar events and RSVPs with PostgreSQL. Includes persistent Jetstream ingestion (long-running listener) and user discovery/backfill.

Setup

# Copy this folder to a new project
cp -r examples/postgres my-contrail-app
cd my-contrail-app

# Install dependencies
npm install

Note: The contrail dependency in package.json points at github:flo-bit/contrail. If you're using a fork with PostgreSQL support that hasn't been merged yet, update the dependency to point at your fork's branch:

npm install github:your-username/contrail#your-branch

Or install from a local checkout:

npm install /path/to/your/contrail

Start PostgreSQL

Option A: Docker (recommended)

docker compose up -d

This starts PostgreSQL on port 5433 (to avoid conflicts with a local instance) with a contrail database ready to use. Override the port with PG_PORT=5432 docker compose up -d.

Option B: Native PostgreSQL

createdb contrail

Set DATABASE_URL to point at your local instance:

export DATABASE_URL="postgresql://user:password@localhost:5432/contrail"

Configure

Edit config.ts to define your collections, queryable fields, relations, and references. See the Contrail README for all options.

Run

1. Discover users and backfill records

npm run sync

This finds users from ATProto relays and backfills their existing records from PDS. Safe to interrupt and restart — progress is saved per-DID in the database.

2. Start persistent ingestion

npm run ingest

This opens a long-lived Jetstream connection and continuously indexes new records as they appear on the network. Events are batched and flushed every 5 seconds (or every 50 events, whichever comes first). Handles reconnection automatically.

Press Ctrl+C for graceful shutdown — the current batch is flushed and the cursor is saved so the next run picks up where it left off.

3. Serve the XRPC API

npm run serve

Your XRPC API is now available at http://localhost:3000:

# List events sorted by RSVP count
/xrpc/rsvp.atmo.event.listRecords?sort=rsvpsCount

# Upcoming events with 10+ going RSVPs
/xrpc/rsvp.atmo.event.listRecords?startsAtMin=2026-03-16&rsvpsGoingCountMin=10

# Single event with hydrated RSVPs and profiles
/xrpc/rsvp.atmo.event.getRecord?uri=at://...&hydrateRsvps=10&profiles=true

# Search events
/xrpc/rsvp.atmo.event.listRecords?search=meetup

# RSVPs for a specific event
/xrpc/rsvp.atmo.rsvp.listRecords?subjectUri=at://...

Running everything together

In production you'd typically run sync once (or periodically), then keep ingest and serve running as separate processes:

# Initial sync (run once, or periodically to discover new users)
npm run sync

# In separate terminals (or use a process manager)
npm run ingest
npm run serve