Schema and migrations
The tables, the migration list, the adapter contract and how to write your own.
Core code never talks to a driver. It talks to StatusDatabaseAdapter, and
SQLite, D1 and PostgreSQL are three implementations of that one interface.
Anything matching it — an ORM you already use, a hosted API, an in-memory
fake — can be passed as database.
Tables
| Table | Holds |
|---|---|
status_results | One row per check: name, type, status, latency, message, timestamp. |
status_daily_summaries | One row per monitor-day: totals, down checks, down minutes, latency totals and extremes, latest state. |
incidents | Title, status, severity, affected monitors, auto, timestamps. |
incident_updates | Timeline entries belonging to an incident. |
maintenance_windows | Title, description, affected monitors, scheduled range, status. |
notification_log | Delivery attempts: channel, event, status, attempts, error. |
subscribers | Public subscriptions, with a unique index on the destination. |
__heyo_status_migrations | Applied migration ids. |
Timestamps are epoch milliseconds everywhere, which is what makes history portable between SQLite, D1 and PostgreSQL.
Migration list
| Id | Adds |
|---|---|
0000_initial | status_results, status_daily_summaries |
0001_incidents_maintenance | incidents, incident_updates, maintenance_windows |
0002_notification_log | notification_log |
0003_subscribers | subscribers |
0004_subscriber_uniqueness | the unique index on the subscriber destination |
Each adapter records applied ids in __heyo_status_migrations, so applying
them twice is a no-op.
Applying them
| Adapter | How |
|---|---|
| SQLite | Automatic on construction, unless migrate: false. |
| D1 | wrangler d1 migrations apply <name> --local|--remote, or await database.migrate(). |
| PostgreSQL | heyo-status db migrate, or await database.migrate(). |
The command calls migrate() when the adapter exposes it, and otherwise
reports that embedded migrations are already applied. Production request
handlers never migrate on their own.
The adapter contract
Configuration validation checks that all 21 methods exist before accepting a
database, so a partial implementation fails at startup rather than on the
first incident.
Contracts worth honouring when implementing your own:
listUpcoming()must refreshscheduled→in-progress→completedagainstnowbefore returning, and must not return completed windows.deleteSubscriber()returnsfalsefor an unknown id rather than throwing.confirmSubscriber()consumes the token; a second call must returnnull.summarizeHistory()must be idempotent — upsert the summary, then delete the raw rows it consumed.getSnapshot()returns both raw results and daily summaries for the window; snapshot construction merges them.
Deferring construction
Use it whenever the adapter needs a per-request binding or a deployment secret. The resolver runs at most once, on the first query, and a failure is not cached — a fix that only needs an environment variable works on the next request.
The wrapper also forwards migrate() to the resolved adapter, which is how
heyo-status db migrate works with D1 and PostgreSQL overlays.
Drizzle schema
The Drizzle table definitions are exported for direct queries against the same database: