Subscriptions

Trax provides real-time GraphQL subscriptions for train lifecycle events. Clients connect via WebSocket and receive events as trains transition through states (started, completed, failed, cancelled).

Subscriptions are powered by HotChocolate's built-in subscription infrastructure with an in-memory pub/sub transport. They are automatically enabled when you call AddTraxGraphQL().

Only trains decorated with [TraxBroadcast] emit subscription events. Trains without the attribute are silently skipped.

Subscription Fields

All subscriptions return a TrainLifecycleEvent payload.

FieldDescription
onTrainStartedFires when a train begins execution
onTrainCompletedFires when a train completes successfully
onTrainFailedFires when a train fails with an exception
onTrainCancelledFires when a train is cancelled via CancellationToken

TrainLifecycleEvent Payload

type TrainLifecycleEvent {
  metadataId: Long!
  externalId: String!
  trainName: String!
  trainState: TrainState!
  timestamp: DateTime!
  failureJunction: String
  failureReason: String
}
FieldDescription
metadataIdThe database metadata row ID for this execution
externalIdThe external identifier assigned to this execution
trainNameThe canonical train name (the service interface's fully-qualified name, e.g. MyApp.Trains.IProcessOrderTrain)
trainStateThe current state of the train (InProgress, Completed, Failed, Cancelled)
timestampWhen the event occurred (end time if available, otherwise current UTC time)
failureJunctionThe junction that failed (only present on failed trains)
failureReasonThe failure message (only present on failed trains)

Examples

Subscribe to all completed trains

subscription {
  onTrainCompleted {
    metadataId
    trainName
    trainState
    timestamp
  }
}

Subscribe to failures

subscription {
  onTrainFailed {
    metadataId
    trainName
    failureJunction
    failureReason
    timestamp
  }
}

Subscribe to all lifecycle events

Open multiple subscriptions in parallel:

# Tab 1
subscription { onTrainStarted { metadataId trainName trainState } }
 
# Tab 2
subscription { onTrainCompleted { metadataId trainName trainState } }
 
# Tab 3
subscription { onTrainFailed { metadataId trainName failureJunction failureReason } }
 
# Tab 4
subscription { onTrainCancelled { metadataId trainName trainState } }

WebSocket Connection

Subscriptions use the GraphQL over WebSocket protocol. Connect to the same endpoint as queries and mutations:

ws://localhost:5000/trax/graphql

In Banana Cake Pop (the built-in GraphQL IDE), subscriptions work out of the box — just write a subscription query and execute it.

For programmatic clients, use any GraphQL client that supports the graphql-ws protocol (e.g., Apollo Client, urql, Strawberry Shake).

Architecture

Subscriptions are powered by the lifecycle hooks system. The GraphQLSubscriptionHook is automatically registered by AddTraxGraphQL() and publishes lifecycle events to HotChocolate's in-memory subscription transport.

At startup, the hook builds a set of canonical train names (using ServiceType.FullName — the interface name) from registrations that have [TraxBroadcast]. On each lifecycle event, it checks the train's metadata name against this set and only publishes if the train is opted in.

ServiceTrain.Run()
  → LifecycleHookRunner.OnCompleted()
    → GraphQLSubscriptionHook.OnCompleted()
      → Check: does metadata.Name match a [TraxBroadcast] train's ServiceType.FullName?
        → Yes → ITopicEventSender.SendAsync("OnTrainCompleted", event)
          → WebSocket clients receive the event
        → No → skip (no event published)

Cross-Process Subscriptions

By default, subscriptions only fire for trains that execute in the same process as the GraphQL API. In distributed deployments where trains are queued and executed by separate worker processes, use UseBroadcaster() to bridge the gap:

// Both hub and worker:
effects.UseBroadcaster(b => b.UseRabbitMq("amqp://guest:guest@localhost:5672"))

When a broadcaster is configured, AddTraxGraphQL() automatically registers a GraphQLTrainEventHandler that receives remote lifecycle events from the message bus and forwards them to HotChocolate's subscription transport. Like the local GraphQLSubscriptionHook, the remote handler filters by [TraxBroadcast] using canonical train names (ServiceType.FullName) — only trains with the attribute produce subscription events, regardless of which process executes them. Events from the local process are de-duplicated automatically.

See UseBroadcaster for full details.

Package

dotnet add package Trax.Api.GraphQL