Rydder opp siste «v2»-referanser i docs (status_quo, migration_safety, personlig_workspace, spacetimedb_integrasjon). Legger til editor-seksjon i universell_input.md (TipTap, presets, tekstlagring) og oppdaterer nodes.md med content/metadata.document-modellen. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.1 KiB
Erfaring: SpacetimeDB-integrasjon
1. Genererte bindings — navnekonvensjoner
SpacetimeDB sin spacetime generate --lang typescript produserer bindings med inkonsistente konvensjoner. Sjekk alltid de genererte filene i stedet for å gjette.
| Hva | Konvensjon | Eksempel |
|---|---|---|
Tabell-accessor på conn.db |
snake_case | conn.db.chat_message |
Reducer-accessor på conn.reducers |
camelCase | conn.reducers.sendMessage() |
| Felt-navn i tabellrader | camelCase | row.channelId, row.authorName |
| Reducer-parametere | Enkelt objekt | sendMessage({ id, channelId, body, ... }) |
Felle: Man forventer at tabell-accessorer er camelCase (chatMessage), men de er snake_case.
2. DbConnection.builder() — API-detaljer (SDK v2)
DbConnection.builder()
.withUri(spacetimeUrl)
.withDatabaseName(moduleName) // IKKE withModuleName
.withToken(token)
.onConnect((connection) => { // første param er DbConnection, ikke EventContext
connection.subscriptionBuilder()
.subscribe([`SELECT * FROM chat_message WHERE channel_id = '${id}'`]);
})
.build();
Feller:
withModuleName()finnes ikke — brukwithDatabaseName()onConnect-callback mottarDbConnection, ikkeEventContextonConnectError-callback har signatur(ctx, errMessage)dererrMessageer en string
3. Timestamps — bruk SDK-metoder, ikke interne felter
SpacetimeDB Timestamp-objektet har en intern property __timestamp_micros_since_unix_epoch__ (BigInt). Ikke bruk den direkte — bruk SDK-metodene:
// FEIL — microsSinceEpoch finnes ikke, __timestamp_micros_since_unix_epoch__ er internt
const micros = row.createdAt?.microsSinceEpoch;
// RIKTIG — bruk SDK-metoder
const iso = row.createdAt?.toISOString(); // "2026-03-15T23:57:11.677139Z"
const date = row.createdAt?.toDate(); // Date-objekt
const ms = row.createdAt?.toDate()?.getTime(); // millisekunder for sortering
Timestamp-parsing i Rust-modul (warmup)
PG returnerer timestamps som "2026-03-15 23:57:11.677139+00". chrono parser IKKE +00 — krever +00:00:
// FEIL — chrono gir ParseError(TooShort) på "+00"
let dt = s.parse::<chrono::DateTime<chrono::FixedOffset>>();
// RIKTIG — normaliser PG-offset først
let normalized = if s.ends_with("+00") { format!("{}:00", s) } else { s.to_string() };
let dt = chrono::DateTime::parse_from_str(&normalized, "%Y-%m-%d %H:%M:%S%.f%:z");
Uten korrekt parsing faller load_messages tilbake til ctx.timestamp (nåtidspunkt), og alle meldinger får samme klokkeslett.
Referanse: spacetimedb/src/lib.rs — parse_timestamp(), web/src/lib/chat/spacetime.svelte.ts — spacetimeRowToMessage().
4. Rust-modul — borrow checker med SpacetimeDB-makroer
SpacetimeDB-makroer genererer kode som tar eierskap over struct-felter. Bruk verdier før du flytter dem inn i structs:
// FEIL — channel_id er moved inn i ChatMessage, kan ikke bruke i log!() etterpå
let msg = ChatMessage { channel_id, body, ... };
log::info!("Melding i kanal {}", channel_id); // borrow after move
// RIKTIG — bruk verdien før struct-opprettelse
let log_msg = format!("Melding i kanal {}", channel_id);
let msg = ChatMessage { channel_id, body, ... };
log::info!("{}", log_msg);
5. Publisering og lokal testing
# Publiser modul mot lokal SpacetimeDB (må kjøre i Docker først)
cd spacetimedb
spacetime publish sidelinja-realtime --server local
# Generer TypeScript-bindings
spacetime generate --lang typescript --out-dir ../web/src/lib/chat/module_bindings \
--module-path .
Merk: SpacetimeDB-modulen publiseres manuelt med spacetime publish mot server-instansen.
6. Arkitekturendring: SpacetimeDB som cache foran PG (mars 2026)
Tidligere: Hybrid-adapter der frontend merget data fra PG (historikk) og SpacetimeDB (sanntid) med dedup, deletedIds og BigInt-workarounds.
Ny modell:
- PG autoritativ — all persistent data i PostgreSQL
- SpacetimeDB = varm cache — worker gjør warmup (PG → ST) ved oppstart
- Frontend snakker KUN med ST — ingen PG API-kall fra chat-adapteren
- Worker håndterer toveissynk — ST → PG for nye/redigerte/slettede meldinger og reaksjoner
Warmup-flyt
- Worker starter →
warmup::run()leser kanaler med config fra PG - Per kanal: sjekker
channels.config.warmup_mode(all/messages/days/none) - Kaller
clear_channelreducer (unngår duplikater ved restart) - Trådbasert henting: Finner kvalifiserende tråder, henter alle meldinger i disse (komplett med svar)
messages-modus: de N nyeste trådene (sortert etter siste aktivitet)days-modus: alle tråder med minst én melding i tidsvinduet- Et svar som kvalifiserer tar med hele tråden (inkludert eldre trådstarter)
- Kaller
load_messagesreducer med JSON-array - Laster også reaksjoner via
load_reactionsreducer
Per-kanal konfigurasjon
- Lagres i
channels.configJSONB:warmup_mode+warmup_value - Admin-UI:
/admin/channels— tabell med inline-redigering - Default:
"all"(last alt). Andre:"messages"(siste N tråder),"days"(siste N dager),"none"(inaktiv)
Sync-flyt (ST → PG)
- SyncOutbox-events prosesseres hver 1. sekund
- Støtter:
messages/insert,messages/delete,messages/update,messages/ai_update,message_reactions/insert,message_reactions/delete ai_update-action: oppdaterer body + metadata + edited_at i PG, inserter revisjon
7. Subscription-begrensninger
SpacetimeDB-subscriptions støtter IKKE JOINs. En subscription-query som SELECT mr.* FROM message_reaction mr JOIN chat_message cm ON cm.id = mr.message_id WHERE ... feiler stille — onApplied kalles aldri, og ingen data vises.
Bruk kun enkle SELECT * FROM tabell WHERE ...-queries i .subscribe([...]). Filtrer heller klient-side etter at data er lastet.
Eksempel:
// FEIL — feiler stille, ingen data
.subscribe([
`SELECT * FROM chat_message WHERE channel_id = '${id}'`,
`SELECT mr.* FROM message_reaction mr JOIN chat_message cm ON cm.id = mr.message_id WHERE cm.channel_id = '${id}'`
]);
// RIKTIG — last alle reaksjoner, filtrer i koden
.subscribe([
`SELECT * FROM chat_message WHERE channel_id = '${id}'`,
`SELECT * FROM message_reaction`
]);
Fallback
PG-polling adapter (pg.svelte.ts) brukes kun når SpacetimeDB ikke er konfigurert. Markeres som readonly: true.
8. Reducer-parameternavn — unngå underscore-prefix
Kodeeksemplene i denne seksjonen er fra v1 og bruker
workspace_id-parametere. Workspace-modellen er erstattet av noder og edges (sedocs/retninger/bruker_ikke_workspace.md), men lærdommen om underscore-prefix gjelder generelt for alle SpacetimeDB-reducere.
SpacetimeDB eksponerer Rust-parameternavn direkte i HTTP JSON API-et. Underscore-prefix (_workspace_id) blir til _workspace_id i JSON, ikke workspace_id:
// FEIL — HTTP-kall med {"workspace_id": "..."} feiler med 400
pub fn set_ai_processing(ctx: &ReducerContext, id: String, _workspace_id: String) { ... }
// RIKTIG — bruk vanlig navn, suppress warning med let _ = &var;
pub fn set_ai_processing(ctx: &ReducerContext, id: String, workspace_id: String) -> Result<(), String> {
let _ = &workspace_id;
// ...
}
9. Schema-migrering ved nye kolonner
Å legge til kolonner på eksisterende SpacetimeDB-tabeller krever --delete-data ved publish. Dette sletter all data og krever warmup på nytt:
# Feiler uten --delete-data:
# "Adding a column metadata to table chat_message requires a default value annotation"
echo "y" | spacetime publish sidelinja-realtime --server local --delete-data
10. AI-worker-flyt via SpacetimeDB
Worker som gjør AI-behandling av meldinger:
- Leser meldingens body fra PG (OK — PG er persistent lager)
- Kaller
set_ai_processingreducer → frontend ser pulsering umiddelbart - Kaller AI Gateway med prompt
- Kaller
ai_update_messagereducer → SpacetimeDB oppdaterer body/metadata/edited_at atomisk, lagrer revisjon, legger outbox-entry - Sync-worker persisterer til PG via
ai_updateaction - Ved feil:
clear_ai_processingreducer rydder flagget