Skip to content

Database Schema

StackVitals uses Supabase Postgres with migrations applied from supabase/migrations/*.sql in numeric order.

TablePurpose
projectsOne row per tracked app, keyed by a free-form slug matching the collector config
providersProvider registry — aws, amplify, supabase, resend, openai, github, cloudflare
resourcesProvider resources — deployments, domains, databases, API accounts, etc.
metric_snapshotsStatus, counts, usage, latency, and deploy state over time
cost_snapshotsDaily or monthly cost by provider/service; account-level by default
health_checksUptime, HTTP status, response time, and last successful check
collector_runsAudit trail for each collection run, including errors
dashboard_usersEmail allowlist for RLS-based access control

metric_snapshots, cost_snapshots, and health_checks are append-only. The read layer picks the latest per logical key rather than updating in place. This preserves history and avoids update conflicts.

Cost rows stay account-level (project_id null) unless a collector can map a cost to a specific project. The dashboard does not guess per-project cost splits.

projects.slug is a free-form string that must match the slugs in projects.config.json. providers.key maps to the ProviderKey TypeScript type. New provider keys are added together with their collector adapter, never ahead of it.

Row-level security restricts all reads to authenticated users whose email appears in dashboard_users. This is the durable data boundary — the frontend email allowlist (VITE_DASHBOARD_ALLOWED_EMAIL) is an additional gate, not a replacement.

Migrations are applied in numeric order from supabase/migrations/:

  1. Core schema (projects, providers, resources, snapshots, health checks, collector runs)
  2. Dashboard users table + RLS policies
  3. Additional indexes and refinements

Run npx supabase db reset to reapply all migrations and seeds from scratch during development.