Skip to content

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 type Observable<PlayerStats> where PlayerStats is itself a class with its own observable members.
  • Action parameter types: [BridgeAction] public void Submit(SubmissionData data).

Rules

  1. Public class. public class FooEvent { ... }. No structs (yet — structs may be added in a future ABI revision).

  2. 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;
    }
  3. Supported field types:

    • Primitives: int, long, float, double, bool
    • string
    • Enums (serialised as their underlying integer)
    • List<T> of supported types
    • Other DTOs
  4. 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.

  5. 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 → TS DamageEvent.
  • C# Amount field → TS amount.
  • C# IsCritical field → TS isCritical.
  • Acronyms stay as a unit: C# HudVisible → TS hudVisible, but C# HUDSettings → TS hudSettings.

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