Edges & Protocols
Edges represent the transport-layer connections between nodes. Understanding edge direction is the single most important concept for building correct cloud-arch diagrams.
The Edge Direction Rule
connect(A, B) means A opens the TCP connection to B.
The arrow direction answers the question: who dials whom? It does NOT indicate the direction of data flow. Once a TCP connection is established, data flows in both directions. Responses travel back along the same edge in reverse animation — you never need a duplicate edge for responses.
Think of it as asking: which process runs connect() or dial() at the OS socket level?
- Spark reads Postgres via JDBC → Spark calls
connect()→connect('spark', 'pg') - A Kafka consumer reads from a broker → Consumer calls
connect()→connect('flink', 'kafka') - Airflow submits a Spark job via the K8s API → Airflow calls
connect()→connect('airflow', 'spark')
Correct vs wrong examples
| Scenario | Correct | Wrong | Reason |
|---|---|---|---|
| Spark reads Postgres via JDBC | connect('spark', 'pg') | connect('pg', 'spark') | Spark opens the JDBC socket to Postgres |
| Airflow submits Spark job | connect('airflow', 'spark') | connect('spark', 'airflow') | Airflow calls the K8s/Spark API |
| Debezium reads WAL | connect('debezium', 'pg-primary') | connect('pg-primary', 'debezium') | Debezium opens the replication slot |
| Kafka consumer | connect('flink', 'kafka') | connect('kafka', 'flink') | Consumer connects to the broker |
| Trino queries Iceberg | connect('trino', 'iceberg') | connect('iceberg', 'trino') | Trino calls the Iceberg REST catalog API |
Responses do not need new edges
The FlowBuilder handles reverse animation automatically:
// ONE edge in the topology
topology.connect('client', 'api', { protocol: 'http', label: 'REST' });
// Animation: request goes forward, response goes backward on the SAME edge
flow
.from('client').to('api') // forward
.showMessage('[GET] /data')
.from('api').to('client') // REVERSE — no extra edge needed
.showMessage('[200 OK] Here is your data');Edge types
The type property in ConnectionOptions controls which React component renders the edge.
| Type | Component | Description |
|---|---|---|
floating | AnimatedFloatingEdge | Default. Smooth animated dashes. Works correctly with nodes inside groups. |
animated | AnimatedEdge | Simple animated dashes. Use for basic diagrams without group nesting. |
pulse | PulseEdge | Pulsing glow effect. Recommended for Kafka, RabbitMQ, and event-stream connections. |
topology.connect('producer', 'kafka', { type: 'pulse', protocol: 'kafka' });
topology.connect('api', 'db', { type: 'floating', protocol: 'postgresql' });Protocol color map and line style
The protocol property controls the edge color and the dash pattern. Always set it explicitly on every connect() — the validator reports EDGE_WITHOUT_PROTOCOL as a warning when it is missing.
The line style rule. A solid line is a long-lived connection: a socket that stays open, a database session, a broker subscription, a replication stream. A dashed line is request/response over HTTP: the connection is opened for one exchange and closed. Anything not on the solid list — including unknown and semantic values like call, internal, syscall, diagnose — renders dashed.
| Protocol | Color | Hex | Dash pattern | Typical use |
|---|---|---|---|---|
http | Green | #4CAF50 | Dashed (request/response) | REST APIs, HTTP webhooks |
https | Blue | #2196F3 | Dashed (request/response) | TLS-encrypted HTTP |
grpc | Purple | #9C27B0 | Dashed (request/response) | gRPC unary calls (grpc-stream is solid) |
graphql | — | — | Dashed (request/response) | GraphQL over HTTP |
postgresql | Dark blue | #336791 | Solid (long-lived session) | PostgreSQL / JDBC |
mysql | Steel blue | #4479A1 | Solid (long-lived session) | MySQL |
mongodb | Green | #47A248 | Solid (long-lived session) | MongoDB connections |
redis | Red | #DC382D | Solid (long-lived session) | Redis commands |
kafka | Orange | #FF9800 | Solid (long-lived session) | Kafka produce / consume (pulse edge) |
rabbitmq | Orange | #FF9800 | Solid (long-lived session) | AMQP messages (pulse edge) |
websocket | Teal | #00BCD4 | Solid (long-lived session) | WebSocket connections |
tcp | Gray | #607D8B | Solid (long-lived session) | Raw TCP |
s3 | Green | #569A31 | Dashed (request/response) | Object storage (S3, MinIO) over HTTP |
The full solid list lives in apps/web/src/constants/edgeSemantics.ts (SESSION_PROTOCOLS): tcp, websocket, wss, sse, sql, postgresql, postgres, mysql, mongodb, jdbc, clickhouse, cassandra, redis, resp, zookeeper, kafka, rabbitmq, amqp, pulsar, nats, replication, stream, grpc-stream.
If no protocol is specified, the edge renders solid as tcp (the type value is consulted first, then tcp). Do not rely on that: always set protocol explicitly so the line style carries meaning. A manual style: { strokeDasharray } on the edge overrides the semantic pattern.
Full edge options reference
topology.connect(source: string, target: string, options?: {
label?: string; // Text shown at the midpoint of the edge
protocol?: string; // Controls edge color
type?: 'floating' | 'animated' | 'pulse'; // Edge component
layer?: string; // Visibility layer
animated?: boolean; // Enable/disable dash animation (default: true)
bidirectional?: boolean; // Render arrowheads on both ends (default: false)
})Common mistakes
Mistake 1: Adding reverse edges for responses
// WRONG — creates two visible edges on the canvas
topology.connect('client', 'api', { protocol: 'http' });
topology.connect('api', 'client', { protocol: 'http' }); // redundant
// CORRECT — one edge, FlowBuilder handles reverse automatically
topology.connect('client', 'api', { protocol: 'http' });Mistake 2: Edge bypasses a proxy
// WRONG — client bypasses the circuit breaker in the response path
topology.connect('client', 'cb', { protocol: 'http' });
topology.connect('cb', 'service', { protocol: 'http' });
topology.connect('service', 'client', { protocol: 'http' }); // bypasses cb!
// CORRECT — responses go back through the circuit breaker
topology.connect('client', 'cb', { protocol: 'http' });
topology.connect('cb', 'service', { protocol: 'http' });
// Response path: service → cb (reverse) → client (reverse)Mistake 3: Wrong initiator direction
// WRONG — a Kafka broker does not dial consumers
topology.connect('kafka', 'flink', { protocol: 'kafka' });
// CORRECT — Flink consumers dial the Kafka broker
topology.connect('flink', 'kafka', { protocol: 'kafka' });