# Erfaring: Adapter-mønster for chat (PG ↔ SpacetimeDB) ## 1. Mønsteret Et felles interface (`ChatConnection`) med to implementasjoner: - **SpacetimeDB-adapter** (primær) — all data fra SpacetimeDB, worker håndterer warmup + sync - **PG-adapter** (fallback) — polling hvert 3 sek, brukes kun når SpacetimeDB ikke er konfigurert Factory-funksjon velger adapter basert på miljøvariabel (`VITE_SPACETIMEDB_URL`). ``` ChatBlock.svelte → createChat() → SpacetimeDB-adapter (primær) → PG-adapter (fallback, readonly) ``` **Fordeler:** - Kan teste PG-adapter isolert uten Docker/SpacetimeDB - Fallback er trivielt — fjern env-variabelen - Komponenten vet ingenting om hvilken adapter som brukes - ChatConnection-interface har `edit()`, `delete()`, `react()` — ingen direkte PG API-kall fra komponenten **Referanse:** `web/src/lib/chat/` — hele mappen er organisert etter dette mønsteret. ## 2. Historisk anti-pattern: Lazy wrapper som byttet adapter Vi prøvde først en "lazy wrapper" som startet med PG-adapter og byttet til SpacetimeDB-adapter når tilkoblingen var klar. Problemet: - En plain `let activeConnection` i wrapperen er **ikke reaktiv** i Svelte 5 - Når wrapperen byttet adapter, forsvant meldingene — ny adapter startet med tom liste - Svelte-komponentene så aldri byttet fordi proxy-referansen ikke oppdaterte seg **Lærdom:** Ikke bytt adapter runtime. Velg én ved oppstart. ## 3. Historisk anti-pattern: Hybrid-adapter (PG + SpacetimeDB samtidig) Den andre iterasjonen brukte en hybrid-adapter som hentet historikk fra PG via REST og lyttet på SpacetimeDB for nye meldinger. Dette skapte: - Kompleks dedup-logikk (`deletedIds` Set, merge av PG- og ST-meldinger) - Race conditions mellom PG-polling og SpacetimeDB-callbacks - BigInt-konverteringer og workarounds i frontend **Løsningen:** SpacetimeDB som cache foran PG. Worker gjør warmup (PG → ST) ved oppstart, frontend snakker kun med SpacetimeDB. Ingen merge-logikk nødvendig. ## 4. Nåværende arkitektur SpacetimeDB er en varm cache foran PostgreSQL: - **Worker warmup:** Ved oppstart lastes meldinger + reaksjoner fra PG → SpacetimeDB per kanal - **Frontend → SpacetimeDB:** Subscription gir alle meldinger (historikk fra warmup + nye) - **SpacetimeDB → PG:** Sync-worker poller `sync_outbox` hvert sekund - **PG er autoritativ** — ved SpacetimeDB-restart oppvarmes fra PG **Fordeler over hybrid:** - Ingen dedup, merge eller deletedIds - Frontend-koden er dramatisk enklere - Konsistent datamodell — alt kommer fra én kilde - Reaksjoner håndteres via SpacetimeDB-tabeller, ikke PG API ## 5. Historisk anti-pattern: "PG-lekkasje" i SpacetimeDB-adapteren Gjentatt feil (mars 2026, minst 3 iterasjoner): Når en ny feature trenger data som SpacetimeDB-modulen ikke har (metadata, edited_at, revisjoner), er det fristende å legge til en `enrichFromPg()`-funksjon som henter fra PG direkte. Dette bryter hele poenget med caching-laget. **Symptomer:** - SpacetimeDB-adapteren har `fetch('/api/messages/...')` kall - Worker skriver til PG først, SpacetimeDB er "best-effort" - Etter AI-vask vises ikke metadata/revisjoner fordi de bare finnes i PG - Race conditions mellom SpacetimeDB-oppdateringer og PG-fetch **Hvorfor det skjer:** Det er raskere å lage en PG API-rute enn å utvide SpacetimeDB-modulen (Rust compile, publish, regenerer bindings). Men det skaper teknisk gjeld som akkumulerer og undergraver hele arkitekturen. **Riktig løsning når SpacetimeDB mangler et felt:** 1. Legg til feltet i SpacetimeDB Rust-modul (`spacetimedb/src/lib.rs`) 2. Utvid warmup til å laste feltet fra PG 3. Utvid sync til å persistere feltet til PG 4. Worker skriver til SpacetimeDB via reducer 5. Frontend leser kun fra SpacetimeDB **Aldri:** Legg til `fetch('/api/.../metadata')` i SpacetimeDB-adapteren. ## 6. Anbefaling for neste komponent Når Kanban eller Whiteboard skal bygges med SpacetimeDB: 1. **Start med PG-adapter.** Få hele flyten til å fungere med REST/polling først. 2. **Lag SpacetimeDB-adapter med warmup.** Worker laster data fra PG ved oppstart. 3. **Bruk samme factory-mønster.** Felles interface, env-variabel for valg. 4. **Legg til warmup-config** i `channels.config` (eller tilsvarende config-felt). 5. **Test begge adaptere uavhengig** før du integrerer i UI-komponenten. 6. **Sjekk at alle felter frontend trenger finnes i SpacetimeDB-modulen** før du implementerer adapteren. Utvid modulen først hvis nødvendig.