EnglishGitHub
Prototyppackages/state-store/src

State Store

@ralphschuler/state-store

Besitzt eigene Zustandskopien mit expliziten Updates und abmeldbaren Subscriptions.

storeproxysubscriptions

01 · Problem

Wofür braucht man das?

Direkt geteilter Objektzustand verrät nicht, wer ihn wann verändert hat, und koppelt Leser, Schreiber sowie Seiteneffekte eng aneinander.

02 · Denkmodell

Das mentale Modell

Ein Store besitzt den Zustand. Schreiboperationen erzeugen eine neue Version und informieren nur Abonnenten des geänderten Schlüssels.

Im Repository

Besitzt eigene Zustandskopien mit expliziten Updates und abmeldbaren Subscriptions. Werte müssen per structuredClone kopierbar sein. Verschachtelte Änderungen benötigen set; reservierte Proxy-Namen dürfen keine State-Schlüssel sein.

03 · Kontrollfluss

Was passiert in welcher Reihenfolge?

  1. Initialzustand defensiv kopieren und nur über eine kleine Store-API zugänglich machen.
  2. Bei set alten und neuen Wert vergleichen und den Zustand aktualisieren.
  3. Passende Listener mit Wert, Vorwert und Schlüssel benachrichtigen.
  4. Eine unsubscribe-Funktion zurückgeben und Listener beim Aufräumen entfernen.

04 · Bauteile

Die entscheidenden Verträge

createStateStore / StateStoreErzeugt einen Store mit eigenem Zustand.
set / snapshotAktualisiert explizit und liest unabhängige Kopien.
subscribeBeobachtet eine Eigenschaft und liefert eine Abmeldefunktion.

05 · Selbst bauen

Selbst implementieren

Eine explizite API ist leichter zu verstehen und zu testen als magische Proxy-Semantik.

  1. Zustand privat halten und Werte über get auslesen.
  2. Schreibzugriffe in set bündeln und unveränderte Werte überspringen.
  3. Subscriptions in Sets verwalten und immer eine unsubscribe-Funktion liefern.
Konzeptskizze · kann ausgelassene Helfer verwenden
class Store<S extends object> {
  private listeners = new Set<(state: Readonly<S>) => void>();

  constructor(private state: S) {}
  get() { return this.state as Readonly<S>; }
  set(update: Partial<S>) {
    this.state = { ...this.state, ...update };
    for (const listener of this.listeners) listener(this.state);
  }
  subscribe(listener: (state: Readonly<S>) => void) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
}

06 · Verifizieren

Was solltest du testen?

  • set behält unberührte Schlüssel und veröffentlicht den neuen Snapshot.
  • unsubscribe verhindert alle späteren Benachrichtigungen.
  • Ein Listener darf sich während einer Benachrichtigung sicher abmelden.

Selbst ausprobieren

Formuliere die erste Prüfaussage als fehlschlagenden Test, implementiere die kleinste Korrektur und ergänze einen Grenzfall.

set behält unberührte Schlüssel und veröffentlicht den neuen Snapshot.

Ansatz vergleichen

Dein Test sollte beim gezielten Verletzen der Invariante fehlschlagen und nach der Korrektur wieder bestehen. Halte den Aufbau deterministisch und prüfe ein beobachtbares Ergebnis.

07 · Grenzen

Kompromisse und Stolperfallen

  • Snapshots sind nachvollziehbar, kopieren bei großen Objekten jedoch mehr Daten.
  • Schlüsselbezogene Listener sparen Arbeit, vergrößern aber die API.
  • Proxies wirken bequem, erschweren Debugging und kollidieren leicht mit Store-Methoden.
Wichtig

Werte müssen per structuredClone kopierbar sein. Verschachtelte Änderungen benötigen set; reservierte Proxy-Namen dürfen keine State-Schlüssel sein.

08 · Weiterdenken

Quellcode und verwandte Ideen

yarn tsx packages/state-store/examples/basic.ts