@bimetal/server-sync
v0.39.0
Published
Node sync-server skeleton for @bimetal/sync. WebSocket layer over a consumer-supplied http.Server, pluggable EventStore as authority. No HTTP framework; optional per-connection authentication and read/write authorization hooks.
Maintainers
Readme
@bimetal/server-sync
Ein Node-Sync-Server-Skelett für @bimetal/sync. Es legt eine WebSocket-Schicht über einen
konsumenten-gelieferten http.Server und nutzt einen einsteckbaren EventStore als Autorität.
Bewusst minimal: kein HTTP-Framework. Das Paket kümmert sich um das WebSocket-Sync-Protokoll, das Weiterreichen an die EventStore-Autorität und — wenn der Konsument die Haken setzt — um die Befugnis je Verbindung (bimetal-304). Ohne die Haken darf jede Verbindung, die das Upgrade überlebt, jeden Strom lesen und in jeden schreiben; Auth gehört dann in eine HTTP-Middleware vor dem Upgrade.
Installation
npm install @bimetal/server-sync @bimetal/sync @bimetal/event-sourcingWas das Paket liefert
| Export | Zweck |
|--------|-------|
| createSyncWebSocketServer(options) | Hängt den Sync-WebSocket-Handler an einen bestehenden http.Server und bedient verbundene Clients gegen den als Autorität übergebenen EventStore (Append validiert über Optimistic Locking, Broadcast an Subscriber). |
| SyncWebSocketServerOptions.undoDomain | Schaltet das Undo-Kommando frei (undo/redo aus @bimetal/sync): Stream lesen → expectFrame gegen die envelope.id des jüngsten Events dieser Entität prüfen → Aggregat falten → Umkehrung bauen → über eventStore.append anfügen. Kein Löschen, kein Rückspulen; jeder Subscriber sieht die Umkehrung als gewöhnliches Live-Event, weil sie den normalen Append-Weg nimmt. Der Typ ist SyncUndoDomain aus @bimetal/sync — ein Port (Aggregat + Kommando-Bauer), kein Domain-Import. Kollidiert expectFrame, kommt append-rejected mit Grund concurrency und der Stream wächst nicht. Ohne undoDomain antwortet der Server mit einer benannten Ablehnung (Grund unknown, der Text nennt undoDomain), damit ein wartender Client nicht in seine Zeitgrenze läuft. Ob der Server das Kommando trägt, sagt er jedem Client im ersten Frame der Verbindung (hello, bimetal-292); @bimetal/sync-ws setzt undo/redo nur bei Zusage. |
Befugnis je Verbindung (bimetal-304)
| Option | Wirkung |
|--------|---------|
| authenticate(req) | Läuft beim Upgrade, bevor ein WebSocket entsteht. null/undefined → 401, kein Upgrade; ein Wurf → 500. Jeder andere Wert ist der principal dieser Verbindung. |
| readOnly: true | Jedes Schreiben wird mit append-rejected, Grund auth, abgewiesen — append ebenso wie die Kommandos undo/redo. Der Store wird nicht berührt. Der Frame trägt bei auth keine Versionsfelder (expectedVersion/actualVersion): gemessen wurde keine Version, eine Zahl wäre erfunden (bimetal-323). Dasselbe gilt für jeden Grund ausser concurrency (bimetal-341). |
| authorizeAppend(ctx) | ctx = { principal, connectionId, streamId, events, expectedVersion }; gefragt wird vor dem Store (bei undo/redo mit der gebauten Umkehrung). false → append-rejected/auth, ein Wurf → append-rejected/unknown. |
| authorizeSubscribe(ctx) | ctx = { principal, connectionId, streamId, fromVersion }. false → der eigene Frame subscribe-rejected (Grund auth, bei einem Wurf unknown): kein Events-Frame, kein Snapshot, kein caught-up, am Store wird nichts abonniert. Die Verbindung bleibt stehen; @bimetal/sync-ws beendet die Subscription endgültig (kein Reconnect) und meldet sie über onRejected. |
Versionsfelder nur bei concurrency (bimetal-341). append-rejected trägt expectedVersion/actualVersion
nur, wo eine Version gemessen ist: beim Konflikt aus dem Store (ConcurrencyError) beide, bei der
expectFrame-Kollision eines undo/redo nur actualVersion (die Erwartung war eine Frame-Marke, keine
Version). Die Gründe auth, unknown (Wurf von authorizeAppend, Kommando ohne undoDomain) und validation
(unbekannter Store-Fehler, werfendes Aggregat) kommen ohne Versionsfelder. Der Decoder in @bimetal/sync lässt
die Felder eines älteren Servers bei diesen Gründen fallen.
Wire-Bruch in Gegenrichtung: Ein Client bis 0.38 an einem Server ab 0.39 verträgt das nicht. Sein Decoder
verlangt expectedVersion/actualVersion bei jedem Grund ausser auth als Zahl; der Frame zu validation/
unknown und die expectFrame-Kollision (ohne expectedVersion) gelten ihm als Protokollfehler — er schliesst
die Verbindung (4002) und verbindet neu, das offene Append endet als Abbruch statt mit dem benannten Grund.
Client und Server gehören zur selben Version.
principal und connectionId sind die Nachweisspur „wer hat über welche Verbindung was versucht" — geführt
wird sie im Haken des Konsumenten, der Server schreibt kein Protokoll.
Die Gegenseite im Browser ist @bimetal/sync-ws (SyncTransport-Adapter mit
Reconnect + transparenter Subscription-Wiederherstellung).
Architektur
Cross-cutting (Server-Seite von sync). Tiefe: Event-Sourcing-Konzept und Backend-Überblick — dort auch „Wie groß wird das?“, die Grenze des Log-im-Client.
