GitHub
Prototyppackages/binary-serializer/src

Binary Serializer

@ralphschuler/binary-serializer

Übersetzt strukturierte Werte anhand eines Schemas in ein reproduzierbares Byteformat und wieder zurück.

binarycodecschema

01 · Problem

Wofür braucht man das?

Binärformate sparen Platz und geben ein exaktes Wire-Layout vor, verlangen dafür aber konsistente Feldreihenfolge, Größenberechnung, Bounds-Prüfung und Versionierung.

02 · Denkmodell

Das mentale Modell

Jeder Datentyp ist ein Codec mit drei Operationen: Größe berechnen, ab Offset schreiben und ab Offset lesen. Strukturen komponieren diese kleinen Codecs in expliziter Reihenfolge.

Im Repository

Decorators speichern ein Schema in reflect-metadata und BinarySerializer schreibt Buffer-Felder. Aktuelle Decorator-Konfiguration, Größenberechnung und verschachtelte Objekte sind inkonsistent; der Test kompiliert nicht.

03 · Kontrollfluss

Was passiert in welcher Reihenfolge?

  1. Schema mit fester Feldreihenfolge und eindeutigem Typ pro Feld definieren.
  2. Exakte Bytezahl vor der Allokation rekursiv berechnen.
  3. Felder schreiben und den Offset nach jeder Operation fortführen.
  4. Beim Lesen Bounds prüfen, Werte rekonstruieren und finalen Offset zurückgeben.
  5. Formatkennung und Version vor Nutzdaten setzen.

04 · Bauteile

Die entscheidenden Verträge

@Serializable / @Serialized Legacy-Decorators für Klassen- und Feldschema.
@AsNumber / @AsBoolean / @AsString / @AsArray Deklarieren konkrete Binärrepräsentationen.
BinarySerializer.serialize / deserialize Schreibt oder liest anhand des gespeicherten Schemas.

05 · Build it yourself

Selbst implementieren

Ein explizites Codec-Objekt ist leichter zu testen und zu versionieren als implizite Decorator-Metadaten.

  1. Definiere einen Codec-Vertrag, der Wert und Offset gemeinsam weiterreicht.
  2. Baue Primitive wie u32 und bool mit strikten Bereichsprüfungen.
  3. Komponiere Arrays und Structs, ohne Feldreihenfolge aus Reflection abzuleiten.
minimal.ts · unabhängig vom Package
type Decoded<T> = { value: T; offset: number };

interface Codec<T> {
  size(value: T): number;
  encode(view: DataView, offset: number, value: T): number;
  decode(view: DataView, offset: number): Decoded<T>;
}

const u32: Codec<number> = {
  size: () => 4,
  encode: (view, at, value) => (view.setUint32(at, value, true), at + 4),
  decode: (view, at) => ({ value: view.getUint32(at, true), offset: at + 4 }),
};

06 · Verifizieren

Was du testen solltest

  • encode gefolgt von decode ergibt für jedes gültige Objekt denselben Wert und Endoffset.
  • Zu kurze Buffer, zu große Zahlen und falsche Längen werden vor dem Zugriff abgelehnt.
  • Bekannte Golden Bytes sichern Endianness, Feldreihenfolge und Versionsheader.

07 · Grenzen

Kompromisse und Stolperfallen

  • Feste Felder sind schnell, aber schlecht erweiterbar; variable Felder benötigen Längenpräfixe.
  • UTF-8-Längen zählen Bytes, nicht JavaScript-Zeichen.
  • Schemaänderungen ohne Versionsstrategie machen gespeicherte Daten unlesbar.
Wichtig

Wire-Layouts dürfen niemals von Property- oder Decorator-Einfügereihenfolge abhängen. Reihenfolge und Endianness gehören in den expliziten Vertrag.

08 · Weiterdenken

Quellcode und Nachbarn

Originalcode auf GitHub ansehen