- Dokumentert at subscriptions ikke støtter JOINs (feiler stille) - refresh() kaller enrichFromPg() for å hente fersk metadata fra PG - Whitespace-normalisering i autogenererte module_bindings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.4 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. BigInt-tidsstempler
SpacetimeDB Timestamp-type bruker microsSinceEpoch som er BigInt. JavaScript kan ikke blande BigInt med Number:
// FEIL — TypeError: Cannot mix BigInt and other types
const ms = micros / 1000;
// RIKTIG — sjekk type, bruk BigInt-divisjon
const ms = typeof micros === 'bigint'
? Number(micros / 1000n)
: Number(micros) / 1000;
Referanse: 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: Bruk ./dev.sh for å starte hele stacken automatisk (inkl. SpacetimeDB publish + binding-generering). ./dev.sh --clean starter blankt.
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,message_reactions/insert,message_reactions/delete
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.