Supabase
Overzicht
Section titled “Overzicht”Supabase is de primaire backend: authenticatie, PostgreSQL database, realtime subscriptions en object storage. Beam gebruikt drie Supabase clients met verschillende rechten.
| Client | Locatie | Key | Rechten |
|---|---|---|---|
| Dashboard | Browser | Anon key + user JWT | RLS enforced |
| API Worker | Server | Service role key | RLS bypass (admin) |
| Public Site | Server | Anon key | Read-only |
- Flow: PKCE (Proof Key for Code Exchange) — geen implicit grant.
- Token verificatie: Altijd via
supabase.auth.getUser(), nooitgetSession(). - Magic links: Gebruikt voor team uitnodigingen. Supabase genereert de link, Resend verstuurt de email.
- Email verificatie: Verplicht bij registratie.
Waarom PKCE?
Section titled “Waarom PKCE?”PKCE voorkomt authorization code interception attacks. Bij implicit grant worden tokens via de URL fragment teruggestuurd, wat kwetsbaar is voor XSS. PKCE gebruikt een code verifier/challenge flow die server-side wordt gevalideerd.
Database
Section titled “Database”- Engine: PostgreSQL (Supabase-managed)
- Migraties: 43+ Supabase migraties in
supabase/migrations/ - RLS: Row Level Security op alle tabellen — zie Data & Architectuur voor policies
- Performance pattern:
(SELECT auth.uid())wordt door PostgreSQL gecached per statement, voorkomt herhaalde auth lookups
Helper functies
Section titled “Helper functies”12 SQL helper functies voor team isolation, quota enforcement en data cleanup. Volledige lijst met beschrijvingen in Data & Architectuur — SQL Helper Functies.
Kernfuncties:
get_team_owner_id()— cached team owner lookup, kern van alle RLS policiesbootstrap_new_user()— auto-create team + site bij sign-upcheck_media_quota()— enforce 500 MB limiet per site
Realtime
Section titled “Realtime”Twee sync-mechanismen:
Cross-tab (BroadcastChannel API)
Section titled “Cross-tab (BroadcastChannel API)”Instant sync tussen browser tabs van dezelfde gebruiker. Geen server roundtrip.
Tab A: delete media → broadcastMediaDeleted(id)Tab B: useMediaRealtimeSync() ← luistert → markDeletedMedia(id)Cross-user (Supabase Realtime)
Section titled “Cross-user (Supabase Realtime)”Live sync tussen verschillende gebruikers via PostgreSQL postgres_changes events.
User A: save pattern → Supabase UPDATE eventUser B: usePatternRealtimeSync() ← luistert → replacePatternBlocks()Hooks:
useMediaRealtimeSync()— Luistert naarDELETEopmedia. Vervangt verwijderde achtergronden met witte kleur +_deleted_mediaflag. Deduplicatie viaprocessedRefSet (10s cleanup).usePatternRealtimeSync()— Luistert naarUPDATEoppatterns. Vervangt pattern container children.
Storage
Section titled “Storage”| Bucket | Zichtbaarheid | Doel | Limiet |
|---|---|---|---|
avatars | Publiek | Profielfoto’s | — |
media | Publiek | Legacy (R2 is primair) | 10 MB |
pattern-previews | Publiek | Pattern thumbnails | — |
Environment Variables
Section titled “Environment Variables”| Variabele | Waar | Type |
|---|---|---|
VITE_SUPABASE_URL | Dashboard | Build-time |
VITE_SUPABASE_ANON_KEY | Dashboard | Build-time |
SUPABASE_URL | API | Var |
SUPABASE_ANON_KEY | API | Secret |
SUPABASE_SERVICE_ROLE_KEY | API | Secret |
PUBLIC_SUPABASE_URL | Public Site | Build-time |
PUBLIC_SUPABASE_ANON_KEY | Public Site | Build-time |
Limieten
Section titled “Limieten”| Limiet | Waarde |
|---|---|
| Database connecties | Supabase-managed (pooling) |
| Realtime concurrent connections | 200 (free tier) |
| Storage upload max | 10 MB per bestand |
| Auth rate limit | 30 signups/hour (Supabase default) |
Troubleshooting
Section titled “Troubleshooting”| Probleem | Oorzaak | Oplossing |
|---|---|---|
| ”JWT expired” errors | Token niet ververst | supabase.auth.getUser() forceert refresh |
| RLS blokkeert query | Missende policy of verkeerde owner chain | Check get_team_owner_id() output |
| Realtime events missen | Channel niet gesubscribed of quota bereikt | Check Supabase dashboard voor active connections |
| ”duplicate key” op insert | Race condition bij concurrent inserts | Gebruik ON CONFLICT of client-side dedup |