Skip to content

Diagrams

This page collects the architecture and flow diagrams for the Dichit real-time API. Diagrams are authored in Mermaid and render on GitHub and most Markdown toolchains. They are kept in sync with protocol.md, connection.md, authentication.md, rooms.md, and lifecycle.md.


1. Connection lifecycle

stateDiagram-v2
    [*] --> Connecting: client opens wss://.../ws
    Connecting --> Authenticating: upgrade request + token
    Authenticating --> Open: token valid
    Authenticating --> Closed: token invalid (close 1008)
    Open --> Subscribed: auction.subscribe / room.join
    Subscribed --> Open: auction.unsubscribe / room.leave
    Open --> Closing: client/server close
    Subscribed --> Closing: client/server close
    Closing --> Closed: cleanup done
    Closed --> [*]

See lifecycle.md.

2. Authentication flow

sequenceDiagram
    participant C as Client
    participant S as WebSocket Server
    participant J as JWT Service

    C->>S: GET /ws (Upgrade)
    C->>S: Sec-WebSocket-Protocol: websocket.v1, bearer.<token>
    S->>J: verifyAccessToken(token)
    alt access token valid
        J-->>S: { userId, role, type: 'access' }
        S-->>C: 101 Switching Protocols
        S->>S: build SocketUser (id, role, companyId, permissions)
    else missing/invalid/refresh token
        J-->>S: verification error
        S--xC: close 1008 "Authentication failed"
    end

See authentication.md.

3. Auction bid flow

sequenceDiagram
    participant C as Subscriber client
    participant S as WebSocket Server
    participant U as Unit of Work / DB
    participant R as Redis Pub/Sub
    participant B as Other clients

    C->>S: auction.bid.place { cycleId, amount, idempotencyKey }
    S->>S: validate + authorize + rate-limit
    S->>U: placeBid (transaction)
    alt accepted
        U-->>S: bid accepted (bidId, stateVersion)
        S-->>C: ack (bid result)
        S->>R: publish ws:room:auction:<cycleId>
        R--)S: bid.created / bid.updated
        S-->>B: bid.created (leading) / bid.updated (outbid)
    else rejected by rule
        U-->>S: rejection (conflict/forbidden)
        S-->>C: error CONFLICT / FORBIDDEN
    end

See events/auction.md.

4. Broadcast flow

sequenceDiagram
    participant P as Producer (use case / queue)
    participant S1 as WS Server A
    participant R as Redis Pub/Sub
    participant S2 as WS Server B
    participant C1 as Client on A
    participant C2 as Client on B

    P->>S1: broadcastToRoom(auction:cyc, msg)
    S1->>R: publish ws:room:auction:cyc
    R-->>S1: deliverFromPubSub
    S1-->>C1: send message (room members on A)
    R-->>S2: deliverFromPubSub
    S2-->>C2: send message (room members on B)

See rooms.md.

5. Redis Pub/Sub architecture

flowchart TD
    subgraph Producers
        UC[Use cases / commands]
        Q[Background queues]
    end

    subgraph Redis
        GLOBAL[(ws:global)]
        ROOM1[(ws:room:auction:cyc1)]
        ROOM2[(ws:room:auction:cyc2)]
    end

    subgraph WS1[WebSocket Server A]
        B1[Broadcaster]
        SUB1[Redis subscriber]
        RM1[(RoomManager)]
        SK1[Sockets on A]
    end

    subgraph WS2[WebSocket Server B]
        B2[Broadcaster]
        SUB2[Redis subscriber]
        RM2[(RoomManager)]
        SK2[Sockets on B]
    end

    UC --> GLOBAL
    UC --> ROOM1
    UC --> ROOM2
    Q --> GLOBAL

    GLOBAL --- SUB1 --- B1 --- SK1
    ROOM1 --- SUB1
    ROOM2 --- SUB1
    GLOBAL --- SUB2 --- B2 --- SK2
    ROOM1 --- SUB2
    ROOM2 --- SUB2

    SK1 --> RM1
    SK2 --> RM2

Key points:

  • Producers publish to a channel (ws:global or ws:room:<room>).
  • Every WS server holds a Redis subscriber connection per channel it needs.
  • Broadcaster.deliverFromPubSub fans out to local room members only.
  • A room channel subscription is dropped when no server has members.

6. Multiple WebSocket servers

flowchart LR
    LB[Load Balancer]
    subgraph Pool[WebSocket servers]
        S1[Server A<br/>in-memory rooms + rate buckets]
        S2[Server B<br/>in-memory rooms + rate buckets]
        S3[Server C<br/>in-memory rooms + rate buckets]
    end
    R[Redis Pub/Sub]
    DB[(PostgreSQL)]
    C1[Client 1]
    C2[Client 2]
    C3[Client 3]

    C1 --> LB
    C2 --> LB
    C3 --> LB
    LB --> S1
    LB --> S2
    LB --> S3
    S1 <--> R
    S2 <--> R
    S3 <--> R
    S1 --> DB
    S2 --> DB
    S3 --> DB

Implications:

  • Any client can be served by any instance; room fan-out works globally through Redis.
  • Room membership and rate-limit buckets are per instance.
  • A client reconnecting may land on a different instance; recover via auction.snapshot + lastSequence.

7. Room architecture

flowchart TD
    A[Connection<br/>userId=sub_1] --> R1[auction:cyc1]
    B[Connection<br/>userId=sub_2] --> R1
    C[Connection<br/>company com_1] --> R1
    C --> R2[company:com_1]
    D[Connection<br/>userId=sub_1 2nd device] --> R3[user:sub_1]
    A --> R3
    E[SUPERADMIN connection] --> R4[admins]
    A --> R5[notifications]

    R1 --> Redis1[(ws:room:auction:cyc1)]
    R2 --> Redis2[(ws:room:company:com_1)]
    R4 --> Redis3[(ws:room:admins)]

Room rules summary (see rooms.md):

Room Allowed joiner
auction:<id> any authenticated connection with room:join (real data via auction.subscribe)
company:<id> own company only
user:<id> self only
admins SUPERADMIN only
notifications any authenticated connection

8. Event routing

flowchart TD
    RAW[Raw WebSocket frame] --> JSON[Parse JSON]
    JSON -->|fail| ERR1[error BAD_REQUEST]
    JSON --> ENV[Validate envelope]
    ENV -->|fail| ERR1
    ENV --> ROUTE[Lookup route by type]
    ROUTE -->|unknown| ERR2[error BAD_REQUEST unsupported type]
    ROUTE --> AUTH[Authenticate]
    AUTH -->|fail| ERR3[error UNAUTHORIZED]
    AUTH --> RATE[Rate limit user + IP]
    RATE -->|exceeded| ERR4[error RATE_LIMITED]
    RATE --> VALID[Validate data (Zod)]
    VALID -->|fail| ERR5[error BAD_REQUEST]
    VALID --> PERM[Authorize permissions]
    PERM -->|denied| ERR6[error FORBIDDEN]
    PERM --> HANDLER[Execute handler]
    HANDLER -->|result| ACK[ack envelope]
    HANDLER -->|thrown| MAP[Map error]
    MAP --> ERR7[error INTERNAL / domain code]
    ACK --> SEND[Send to socket]
    ERR1 --> SEND
    ERR2 --> SEND
    ERR3 --> SEND
    ERR4 --> SEND
    ERR5 --> SEND
    ERR6 --> SEND
    ERR7 --> SEND

9. Authorization flow

flowchart TD
    start([Message received]) --> identify{Has authenticated user?}
    identify -->|no| unauth[error UNAUTHORIZED]
    identify -->|yes| perm{Route requires permissions?}
    perm -->|none required| allow[Execute handler]
    perm -->|required P1..Pn| check{Does connection have all P1..Pn?}
    check -->|yes| allow
    check -->|no| denied[error FORBIDDEN]
    allow --> result[ack / domain events]

Permission sets by role (see authentication.md):

Permission SUBSCRIBER COMPANY SUPERADMIN
auction:subscribe
auction:bid:place
auction:staff
broadcast:receive
room:join / room:leave

Keeping diagrams in sync

When protocol behaviour changes, update the matching diagram in this file and the related document in the same change:

Diagram Owning document
1 · 2 · 3 connection.md, authentication.md, events/auction.md
4 · 5 · 6 · 7 rooms.md
8 · 9 protocol.md, authentication.md