Ports
Adapters
Demo adapters are the default. Hosted vendors plug in through env vars without code changes.
| Port | Demo ($0) | First paid step | Env |
|---|---|---|---|
| Trips, ledger, tokens | Memory maps | Neon or Supabase Postgres | DATABASE_URL |
| Presence, buckets, pub/sub | Memory maps | Upstash Redis | REDIS_URL |
| Domain events | In-process bus | CloudAMQP | RABBITMQ_URL |
| Cards & Connect | Simulated PI | Stripe Connect | STRIPE_SECRET_KEY |
| Web login | Demo cookie | Supabase Auth | NEXT_PUBLIC_SUPABASE_URL |
| Uploads | Memory signed URL | R2/S3 | UPLOAD_DRIVER=s3 |
| ETA | Haversine | Mapbox, then Google, then OSRM | MAPBOX_ACCESS_TOKEN |
| Unlock | Simulated accept | IoT webhook | IOT_UNLOCK_WEBHOOK |
| Telematics | Simulated neighborhood ping | Bouncie / Samsara / Smartcar / … | TELEMATICS_PROVIDER |
| .data/mail.json + /admin/mail | Resend | RESEND_API_KEY | |
| Voice/SMS | Simulated number | Twilio proxy | TWILIO_ACCOUNT_SID |
getContainer() picks Redis and AMQP only when URLs exist. Everything else keeps working. That is the turnkey: you do not maintain a second “demo backend.”
const cache = hasRedis() ? new RedisCacheStore(...) : new MemoryCacheStore();
const bus = hasRedis() || publisher ? new DurableEventBus(...) : new InMemoryEventBus();