DTO conventions
A DTO is a plain C# class whose shape gets serialised across the bridge. DTOs are used as:
- Event payload types:
Event<DamageEvent>. - Nested state: a
[BridgeState]property of typeObservable<PlayerStats>wherePlayerStatsis itself a class with its own observable members. - Action parameter types:
[BridgeAction] public void Submit(SubmissionData data).
Rules
-
Public class.
public class FooEvent { ... }. No structs (yet — structs may be added in a future ABI revision). -
Public fields or auto-properties. The serialiser walks public fields and auto-properties. Private members are ignored.
public class DamageEvent {public float Amount;public bool IsCritical;} -
Supported field types:
- Primitives:
int,long,float,double,bool string- Enums (serialised as their underlying integer)
List<T>of supported types- Other DTOs
- Primitives:
-
No constructor logic that matters. The deserialiser uses the parameterless constructor (or none) and sets fields. If you put logic in a constructor, it does not run on the UI side.
-
No reference loops. DTOs are serialised as a tree. If two DTOs reference each other, the serialiser stack-overflows.
Naming
The TypeScript type generator translates C# names to TS using the same casing rules as bridge members:
- C#
DamageEvent→ TSDamageEvent. - C#
Amountfield → TSamount. - C#
IsCriticalfield → TSisCritical. - Acronyms stay as a unit: C#
HudVisible→ TShudVisible, but C#HUDSettings→ TShudSettings.
A worked example
public class InventoryItem { public string Id; public int Quantity;}
public class PlayerLoadout { public List<InventoryItem> Items; public string Weapon;}
[Bridge]public partial class GameBridge { [BridgeState] public Observable<PlayerLoadout> Loadout { get; } = new(new PlayerLoadout { Items = new(), Weapon = "shortsword", });}The generated TS:
interface InventoryItem { id: string; quantity: number }interface PlayerLoadout { items: InventoryItem[]; weapon: string }
bridge.state.loadout // PlayerLoadout, reactive