# Datalaget **Status: Besluttet. Revidert mars 2026 — SpacetimeDB fjernet.** > PostgreSQL er eneste datakilde. Sanntid via PG `LISTEN/NOTIFY` > og WebSocket i portvokteren. CAS lagrer binærdata. > Apache AGE legges til ved behov for Cypher-traverseringer. ## Lagmodell ``` GUI (SvelteKit) │ skriv │ les (sanntid, WebSocket) ▼ ▼ Portvokteren (Rust) Portvokteren ──WebSocket──→ GUI │ validering ▲ └──→ PostgreSQL ──NOTIFY──→──┘ ``` ### Skrivestien GUI → portvokteren → validering → PG. Frontend oppdateres via WebSocket-push utløst av PG NOTIFY. ### Lesestien (sanntid) PG → portvokteren → WebSocket → GUI. Portvokteren holder en in-memory cache av aktive subscriptions og pusher relevante endringer til tilkoblede klienter. ### Lesestien (tunge spørringer) GUI → portvokteren → PG. Fulltekstsøk, pgvector, statistikk, AGE-traverseringer. ## PostgreSQL — eneste datakilde Én sannhetskilde. Ingen synk, ingen konsistensproblemer: - **Sanntid:** `LISTEN/NOTIFY` → portvokteren → WebSocket - **Fulltekstsøk:** `tsvector` på `nodes.content` og `nodes.title` - **Semantisk søk:** pgvector for embedding-basert likhet - **Graftraversering:** rekursive CTEs, Apache AGE ved behov - **Statistikk:** aggregeringer, tidsserier - **Tilgangsmatrise:** `node_access` beregnet fra edges ### Apache AGE — ved behov De fleste spørringer er grunne (1-3 hopp) og håndteres av CTEs. AGE legges til som PG-extension når Cypher-semantikk faktisk trengs: 1. **Nå:** PG med nodes/edges-tabeller og CTEs 2. **Når CTEs blir smertefulle:** Legg til AGE 3. **Usannsynlig:** Evaluer Neo4j hvis AGE ikke holder AGE er en extension, ikke en migrering. ## CAS — binærlagring Lyd, bilde, video lagres content-addressable på disk. CAS-noder i grafen bærer metadata (`cas_hash`, `mime`, `size_bytes`). Selve biten lever utenfor PG. Pruning-regler basert på modalitet, edges og aksessmønstre. Se [maskinrommet](maskinrommet.md). ## Sanntid via PG LISTEN/NOTIFY PG har innebygd pub/sub. Portvokteren lytter og videresender: ``` PG: NOTIFY node_changed, '{"id":"abc","kind":"content"}' → Portvokteren mottar → Sjekker tilgangsmatrise: hvem skal se denne endringen? → Pusher til relevante WebSocket-tilkoblinger → Frontend oppdaterer reaktivt ``` ### Trigger i PG ```sql CREATE OR REPLACE FUNCTION notify_node_change() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('node_changed', json_build_object( 'op', TG_OP, 'id', NEW.id, 'kind', NEW.node_kind )::text ); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER nodes_notify AFTER INSERT OR UPDATE ON nodes FOR EACH ROW EXECUTE FUNCTION notify_node_change(); ``` Tilsvarende for edges. ### WebSocket i portvokteren Portvokteren holder: - Map av tilkoblede klienter → brukerens node_id - Map av node_id → synlige noder (fra node_access) - Ved NOTIFY: filtrer på tilgang, push til relevante klienter Én retning (PG → klient), ingen reducer-logikk, ingen konsistensproblemer. ## Historikk: SpacetimeDB (fjernet mars 2026) SpacetimeDB ble brukt som sanntidslag i v1/prototype. Det løste sanntid elegant, men for produksjon på én server skapte det unødvendig kompleksitet: - **Synk-kompleksitet.** PG ↔ STDB synk var en egen feilkategori. - **Dobbelt vedlikehold.** STDB-modul med reducers måtte holdes i synk med PG-skjema. - **Ekstra SPOF.** Enda en tjeneste å overvåke og restarte. - **Unødvendig for skalaen.** PG LISTEN/NOTIFY + WebSocket gir ~5ms latency — umerkelig forskjell for brukere. SpacetimeDB ble faset ut i fire steg: WebSocket-lag, frontend- migrering, fjern skrivestien, fjern alt. Se erfaringsdocs for lærdommer: `docs/erfaringer/spacetimedb_integrasjon.md`. ## Forhold til andre retninger - [Noder er sentrum](bruker_ikke_workspace.md) — tilgangsmatrise beregnet fra edge-grafen, brukes for WebSocket-filtrering - [Universell input og mottak](universell_input.md) — noder og edges er datamodellen for alle tre primitiver - [Maskinrommet / Portvokteren](maskinrommet.md) — CAS-pruning, edge-drevet ressursorkestrering, validering før skriving, WebSocket-endepunkt for sanntid