Version: 3.0 (Praktisch + Fokussiert)
Datum: 02. November 2025
Zielgruppe: Entwickler die JETZT einen Store erstellen müssen
Zweck: Schnelle Entscheidung → Direkte Implementation
// FRAGE: Wie viele localStorage-Keys brauche ich?
// → 1 statischer Key (z.B. 'app-settings')
// → Nutze persisted()
// → 5 Minuten Setup
// → Beispiel: AuthStore, ThemeStore
// → Mehrere/dynamische Keys (z.B. 'chat-${boardId}')
// → Nutze Manual localStorage
// → 15 Minuten Setup
// → Beispiel: BoardStore, ChatStore| Kriterium | persisted() | Manual localStorage |
|---|---|---|
| Storage-Keys | 1 statisch | Mehrere/dynamisch |
| Datenstruktur | Plain Objects | Klassen OK |
| Async Init | ❌ Nein | ✅ Ja |
| Setup-Zeit | 5 Min | 15 Min |
| Auto-Sync | ✅ Ja | ❌ Manuell |
| Beispiel | ThemeStore | BoardStore, AuthStore, ChatStore |
Wann: 1 statischer localStorage-Key, einfache Daten
// src/lib/stores/themeStore.svelte.ts
import { persisted } from 'svelte-persisted-store';
import { get } from 'svelte/store';
export class ThemeStore {
// 1️⃣ Persisted Store
private store = persisted<'light' | 'dark'>('theme', 'dark');
// 2️⃣ Reactive State
public theme = $state(get(this.store));
// 3️⃣ Methods
public toggle(): void {
this.theme = this.theme === 'light' ? 'dark' : 'light';
this.store.set(this.theme); // ← Auto-sync!
}
}
export const themeStore = new ThemeStore();Fertig! Keine saveToStorage(), keine triggerUpdate() nötig.
Wann: Mehrere/dynamische Keys, Klassen-Hierarchien, Async Init
// src/lib/stores/chatStore.svelte.ts
import { ChatSession } from '$lib/classes/ChatModel';
export class ChatStore {
// 1️⃣ State
private currentBoardId = $state<string | null>(null);
private session = $state<ChatSession | null>(null);
private updateTrigger = $state(0);
// 2️⃣ Derived
public messages = $derived.by(() => {
this.updateTrigger; // ← Dependency!
return this.session?.messages || [];
});
// 3️⃣ Load (dynamischer Key!)
private loadFromStorage(boardId: string): ChatSession {
const key = `chat-session-${boardId}`;
const stored = localStorage.getItem(key);
return stored
? new ChatSession(JSON.parse(stored))
: new ChatSession({ id: boardId });
}
// 4️⃣ Save
private saveToStorage(): void {
if (!this.session || !this.currentBoardId) return;
const key = `chat-session-${this.currentBoardId}`;
localStorage.setItem(key, JSON.stringify(
this.session.getContextData()
));
}
// 5️⃣ Trigger (zentral!)
private triggerUpdate(): void {
this.updateTrigger++;
this.saveToStorage();
}
// 6️⃣ Public API
public loadSession(boardId: string): void {
this.currentBoardId = boardId;
this.session = this.loadFromStorage(boardId);
this.updateTrigger++;
}
public addMessage(content: string, role: 'user' | 'assistant'): void {
this.session?.addMessage({ content, role });
this.triggerUpdate(); // ← IMMER!
}
}
export const chatStore = new ChatStore();Essenz: Dynamische Keys + triggerUpdate() Pattern
// 1️⃣ Pattern wählen
[ ] 1 statischer Key + Plain Objects? → persisted()
[ ] Mehrere Keys ODER Klassen? → Manual localStorage
// 2️⃣ Bei persisted()
[ ] .svelte.ts Datei?
[ ] persisted<T>(key, default) importiert?
// 3️⃣ Bei Manual localStorage
[ ] private state = $state(loadFromStorage())
[ ] private updateTrigger = $state(0)
[ ] public derived = $derived.by(() => { updateTrigger; ... })
[ ] private triggerUpdate() { updateTrigger++; save(); }
[ ] Alle Public APIs rufen triggerUpdate() auf?
[ ] getContextData() auf allen Klassen?| Store | Pattern | Grund |
|---|---|---|
| AuthStore | Manual localStorage | Async Init, NDK, Session-Logik |
| BoardStore | Manual localStorage | Dynamische Keys (kanban-${id}), Klassen |
| ChatStore | Manual localStorage | Dynamische Keys (chat-${boardId}) |
| SettingsStore | Manual localStorage | Async config.json merge |
| SyncManager | Manual localStorage | IndexedDB Queue, Event-Management |
| ThemeStore | persisted() | 1 Key, einfach |
| NotificationStore | persisted() | 1 Key, einfach |
Siehe: CHATSTORE.md für ChatStore-Implementierung
Zukünftig: Base class für alle Manual Stores
Siehe: BASESTORES.md für Details
export abstract class BaseComplexStore<T> {
protected updateTrigger = $state(0);
protected abstract getStorageKey(): string;
protected triggerUpdate(): void {
this.updateTrigger++;
this.persistData(); // ← Zentral!
}
}❌ const store = persisted(`key-${id}`, data);
✅ Nutze Manual localStorage❌ const store = persisted<Board>('board', new Board(...));
✅ Nutze Manual localStorage + reconstructBoard()❌ this.board.findColumn('x').addCard({...});
✅ boardStore.createCard('x', '...');❌ getContextData() {
return { id, name }; // author FEHLT!
}
✅ getContextData() {
return { id, name, author }; // ALLE Felder!
}A: Ja, für verschiedene Domains:
// ✅ OK
export class AppStore {
private session = persisted('session', null); // Static
private boards = $state(this.loadBoards()); // Dynamic
}A: Mock localStorage + Vitest:
import { vi } from 'vitest';
global.localStorage = {
getItem: vi.fn(() => '{"id":"1"}'),
setItem: vi.fn(),
clear: vi.fn()
} as any;
const store = new MyStore();
expect(localStorage.setItem).toHaveBeenCalled();A: Serialisierungsmethode auf Model-Klassen:
// In Board, Column, Card, etc.
getContextData(full: boolean = false): PlainObject {
return {
id: this.id,
name: this.name,
// ... ALLE Felder!
};
}
// Wird für localStorage genutzt:
const json = this.board.getContextData(true);
localStorage.setItem('key', JSON.stringify(json));A: Wenn du:
- Mehrere Keys brauchst
- Klassen einführst
- Async Init brauchst
Dann neuer Store.
A: Weil:
- ❌
persisted('board-${id}', ...)funktioniert NICHT - ❌ Klassen-Instanzen werden zerstört
- ❌ Keine async Initialization
A: ❌ NEIN! Alle bestehenden Stores bleiben wie sie sind:
- BoardStore → Custom (Multi-Board, Export/Import zu komplex)
- ChatStore → Custom (Memory Ranking, AI Context zu speziell)
- AuthStore → Custom (NDK Integration, async profile fetching)
- SettingsStore → Custom (async config.json loading)
BaseStore ist nur für NEUE einfache Stores (z.B. NotificationStore, RecentBoardsStore in Phase 2+)
Siehe: BASESTORES.md für Details
| Wenn du... | Nutze | Grund |
|---|---|---|
| Einfache Session-Daten | persisted() | 1 Zeile Code |
| Multi-Board-System | Manual localStorage | Dynamische Keys |
| Externe Config laden | Manual localStorage | Async Init |
| KI-Chat-Verlauf | Manual localStorage | Klassen-Hierarchie |
| Nostr-Event-Queue | Manual localStorage | Komplexe Logik |
| Neue einfache Stores (Phase 2+) | BaseComplexStore/BaseSimpleStore | DRY Pattern (~30 Zeilen gespart) |
🚨 WICHTIG: Bestehende Stores (Board, Chat, Auth, Settings) NICHT zu BaseStore migrieren!
- STORES/README.md — Store-API-Referenz
- STORES/AUTHSTORE.md — AuthStore Beispiel
- STORES/BOARDSTORE.md — BoardStore Beispiel
- TO-FIX/STORE-PATTERN-ANALYSIS.md — Historische Analyse (Warum?)
| Version | Fokus | Zeilen | Datum |
|---|---|---|---|
| v1.0 | Vollständige Analyse (mit Historie) | 1072 | 01.11.2025 |
| v2.0 | Praktische Zukunftsentwicklung | ~300 | 02.11.2025 |
| v3.0 | Maximal praktisch + fokussiert | ~200 | 02.11.2025 |
| v3.1 | BaseStore Klarstellung | ~210 | 02.11.2025 |
v3.1 Changes:
- ✅ FAQ erweitert: "Sollten wir BaseStore nutzen?" → NEIN für bestehende Stores
- ✅ Zusammenfassung erweitert mit BaseStore-Zeile
- ✅ Verweis auf BASESTORES.md hinzugefügt
- ✅ Klarstellung: Bestehende Stores bleiben wie sie sind
v3.0 Changes:
- ✅ ChatStore als Real-World Beispiel für Manual Pattern
- ✅ Pattern 2 von ~100 Zeilen auf ~30 Zeilen reduziert
- ✅ Checkliste auf Essentials reduziert
- ✅ FAQ bleibt gleich (gut balanciert)
- ✅ Anti-Patterns auf 4 reduziert
- ✅ 33% kürzer als v2.0
Status: ✅ PRAKTISCH & FOKUSSIERT
Zielgruppe: Entwickler die JETZT Stores bauen
Aktualisierung: 02.11.2025 (v3.1)