CLAUDE.md: Ny seksjon med fire eksplisitte regler for data som frontend viser. synkronisering.md: §9 Workers som endrer synlig data — riktig vs feil flyt. adapter_moenster.md: §5 Anti-pattern enrichFromPg — gjentatt 3+ ganger. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.4 KiB
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 activeConnectioni 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 (
deletedIdsSet, 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_outboxhvert 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:
- Legg til feltet i SpacetimeDB Rust-modul (
spacetimedb/src/lib.rs) - Utvid warmup til å laste feltet fra PG
- Utvid sync til å persistere feltet til PG
- Worker skriver til SpacetimeDB via reducer
- 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:
- Start med PG-adapter. Få hele flyten til å fungere med REST/polling først.
- Lag SpacetimeDB-adapter med warmup. Worker laster data fra PG ved oppstart.
- Bruk samme factory-mønster. Felles interface, env-variabel for valg.
- Legg til warmup-config i
channels.config(eller tilsvarende config-felt). - Test begge adaptere uavhengig før du integrerer i UI-komponenten.
- Sjekk at alle felter frontend trenger finnes i SpacetimeDB-modulen før du implementerer adapteren. Utvid modulen først hvis nødvendig.