Architektur
Für alle, die mitbauen: wie die Teile zusammenpassen. Jede Entscheidung hat
ein ADR in specs/adr/, mit Index in dessen README.md;
der Plan ist specs/plan.md.
Grundsätze
- Der Server hält die Wahrheit. Clients halten einen Lese-Cache und einen Ausgang von Feldänderungen, gestempelt mit einer hybriden logischen Uhr (HLC) und ihrem Gerät. Der Server entscheidet Last Writer Wins je Feld. Es gibt kein zweites Op-Log.
- Ein Konto, eine Datenbank. Jedes Konto ist eine eigene
SQLite-Datei;
server.dbhält Konten und Sitzungen. - Verfügbarkeit wird berechnet, nie gespeichert, an genau einer
Stelle:
samu-core. Server und Handy rufen denselben Code auf. - Zeit: Ein ganzer Tag ist ein lokales Datum ohne Zone; eine Uhrzeit ist ein Zeitpunkt plus Zone. Wiederholung ist eine RRULE plus einer von drei Modi.
- Die API ist der Kern. Jede Oberfläche ist ein Client; jede Route hat ein Kommando in der CLI.
Crates
crates/
samu-api/ Wire-Typen (nur serde), eigens versioniert und veröffentlicht
samu-core/ Modellregeln, Verfügbarkeit, Wiederholung, Schnellerfassung,
Suche, Forecast, Erinnerungen, Bericht, Gewohnheiten; kein I/O, kein async
samu-store/ SQLite (rusqlite), nummerierte Migrationen
samu-server/ axum: API, Server-Sent Events, Hooks, Kalender, die Web-Oberfläche
eingebettet; Binary `samu-server` (serve, replica, profile, front)
samu-client/ HTTP-Client (rustls) auf samu-api; geteilt von CLI und App
samu-cli/ Binary `samu`
samu-replica/ Spiegel, Ausgang und Sync eines Clients, ohne uniffi;
geteilt von App und lokaler Replik
samu-mobile/ Rust-Seite der Android-App (uniffi-Bindings)Abhängigkeiten zeigen in eine Richtung: samu-api ← samu-core ←
samu-store ← samu-server; samu-api ← samu-client ← samu-cli,
samu-mobile; samu-core, samu-client ← samu-replica ←
samu-mobile, samu-server. Nichts hängt von samu-server ab;
samu-api hängt von nichts im Workspace ab.
Weitere Teile
| Verzeichnis | |
|---|---|
web/ | die Web-Oberfläche: ES-Module, kein Framework, kein Build-Schritt; eingebettet ins Server-Binary (crates/samu-server/src/web.rs listet die Dateien) |
android/ | Kotlin und Compose über samu-mobile |
emacs/ | samu.el, über die CLI |
dms/ | das Desktop-Widget, über die CLI |
tui/, pult/ | fzf-Menüs und ihr Helfer, über die CLI |
deploy/ | Dockerfile, Compose-Datei, systemd-Units |
specs/ | Plan, ADRs, Recherche |
testdata/ | nur synthetische Daten |
Datenfluss
Web ────┐
CLI ────┼── HTTPS ──► samu-server ──► accounts/<name>.db
Emacs ──┤ (über CLI) │
Widget ─┘ └─ GET /events (SSE) ──► jeder Client
Android-App: lokaler Spiegel + Ausgang ──► POST /changes, GET /changes
Replik auf einem Rechner: dieselbe API auf 127.0.0.1, Spiegel + AusgangBauen und prüfen
Stabiles Rust (Edition 2024), siehe rust-toolchain.toml.
make check # fmt, clippy -D warnings, Tests, Paket samu-api, samu.el
make install # samu nach ~/.local/bin
cargo build -p samu-server
target/debug/samu-server --data /tmp/samu serve --listen 127.0.0.1:8492Regeln, die das Projekt einhält:
- Jede neue Funktion bekommt Tests, jeder Bugfix einen Regressionstest. Tests nutzen nur synthetische Daten und rufen keine Dienste von außen auf.
- Jede API-Route bekommt im selben Commit ein CLI-Kommando. Ausgabe der
CLI: Daten auf stdout, Meldungen auf stderr,
--format plain|json|jsonl|tsv, festgelegte Exit-Codes. - Jede Änderung an
samu-apikommt mit einer JSON-Fixture untercrates/samu-api/tests/fixtures/. - Schema-Änderungen nur als nummerierte Migrationen in
samu-store. - Kein
unsafeaußerhalb von FFI, keinunwrap()außerhalb von Tests.
Schwesterprojekt
samudo hat ein Schwesterprojekt, catastr, einen Speicher für Dokumente
und Wissen. Aufgaben verlinken dessen Objekte per URI (catastr:<id>), und
catastr liest samudo als Quelle über dieselbe API und das Crate
samu-api. Keins fasst die Datenbank des anderen an.