Queries
Queries are organized into two groups under the root Query type:
type Query {
discover: DiscoverQueries!
operations: OperationsQueries!
}
discover: auto-generated typed query fields for trains annotated with[TraxQuery]operations: predefined operational queries: health status, registered trains, manifests, manifest groups, and execution history
Discover Queries (Auto-Generated)
Trax auto-generates strongly-typed query fields for trains that opt in with [TraxQuery]. Only trains with this attribute appear under discover.
Each whitelisted query train gets a single field named after the train (no prefix). The field accepts a strongly-typed input argument and returns the train’s output type directly. Trains with Namespace set are grouped under a sub-namespace (e.g. discover { players { lookupPlayer } }).
Naming Convention
The query field names are derived from the train’s service interface name (or overridden via [TraxQuery(Name = "...")]):
- Strip the
Iprefix - Strip the
Trainsuffix - Use the result as the field name (lowercase first letter)
For example, ILookupPlayerTrain produces lookupPlayer.
Example
Given a train annotated with [TraxQuery]:
public record LookupPlayerInput
{
public required string PlayerId { get; init; }
}
public record LookupPlayerOutput
{
public required string PlayerId { get; init; }
public required int Rank { get; init; }
}
The schema exposes:
query {
discover {
lookupPlayer(input: { playerId: "player-42" }) {
playerId
rank
}
}
}
Query trains with typed output
When a query train has a non-Unit output type, the output type is returned directly (not wrapped in a response type):
type DiscoverQueries {
lookupPlayer(input: LookupPlayerInput!): LookupPlayerOutput!
}
Query trains with Unit output
When a query train has Unit output, it returns a response with the execution metadata:
| Field | Type | Description |
|---|---|---|
metadataId | Long! | Metadata ID of the completed execution |
Operations Queries
health
Returns the current health status of the Trax scheduler system. This is the same data reported by the ASP.NET IHealthCheck at /trax/health, exposed as a structured GraphQL type.
query {
operations {
health {
status
description
queueDepth
inProgress
failedLastHour
deadLetters
}
}
}
Returns: HealthStatus!
HealthStatus fields
| Field | Type | Description |
|---|---|---|
status | String! | "Healthy" or "Degraded" |
description | String! | Human-readable summary |
queueDepth | Int! | Work items with status Queued |
inProgress | Int! | Executions with TrainState.InProgress |
failedLastHour | Int! | Failed executions in the last hour |
deadLetters | Int! | Dead letters with status AwaitingIntervention |
Status is Degraded when deadLetters > 0 or failedLastHour > 10.
trains
Returns every train registered in the DI container, including a runtime-generated input schema describing each property on the input type.
query {
operations {
trains {
serviceTypeName
implementationTypeName
inputTypeName
outputTypeName
lifetime
inputSchema {
name
typeName
isNullable
}
}
}
}
Returns: [TrainInfo!]!
TrainInfo fields
| Field | Type | Description |
|---|---|---|
serviceTypeName | String! | Friendly name of the service interface (e.g. IServiceTrain<OrderInput, OrderResult>) |
implementationTypeName | String! | Friendly name of the concrete class |
inputTypeName | String! | Friendly name of the input type |
outputTypeName | String! | Friendly name of the output type |
lifetime | String! | DI lifetime (Singleton, Scoped, Transient) |
inputSchema | [InputPropertySchema!]! | Public readable properties on the input type |
InputPropertySchema fields
| Field | Type | Description |
|---|---|---|
name | String! | Property name |
typeName | String! | Friendly type name (e.g. String, Int32, DateTime?) |
isNullable | Boolean! | Whether the property is nullable |
manifests
Returns a paginated list of scheduler manifests, ordered by ID descending (newest first). Supports both offset-based and keyset cursor pagination.
query {
operations {
manifests(skip: 0, take: 10) {
items {
id
externalId
name
isEnabled
scheduleType
cronExpression
intervalSeconds
maxRetries
timeoutSeconds
lastSuccessfulRun
manifestGroupId
dependsOnManifestId
priority
}
totalCount
isEstimatedCount
skip
take
nextCursor
}
}
}
| Parameter | Type | Default | Description |
|---|---|---|---|
skip | Int | 0 | Number of records to skip (offset pagination) |
take | Int | 25 | Number of records to return |
afterId | Long | null | Keyset cursor. Returns records with id < afterId. When provided, skip is ignored. See Pagination |
Returns: PagedResult<ManifestSummary>
ManifestSummary fields
| Field | Type | Description |
|---|---|---|
id | Long! | Database ID |
externalId | String! | Unique external identifier (used for upsert/trigger) |
name | String! | Train type name |
isEnabled | Boolean! | Whether the manifest is active |
scheduleType | ScheduleType! | Cron or Interval |
cronExpression | String | Cron expression (when scheduleType is Cron) |
intervalSeconds | Int | Interval in seconds (when scheduleType is Interval) |
maxRetries | Int! | Maximum retry count on failure |
timeoutSeconds | Int | Execution timeout |
lastSuccessfulRun | DateTime | Timestamp of last successful execution |
manifestGroupId | Long! | Parent group ID |
dependsOnManifestId | Long | ID of the manifest this one depends on |
priority | Int! | Dispatch priority (0-31, higher runs first) |
manifest
Returns a single manifest by database ID.
query {
operations {
manifest(id: 42) {
id
externalId
name
isEnabled
scheduleType
cronExpression
priority
}
}
}
| Parameter | Type | Required | Description |
|---|---|---|---|
id | Long! | Yes | The manifest’s database ID |
Returns: ManifestSummary (nullable, returns null if the ID does not exist)
manifestGroups
Returns a paginated list of manifest groups, ordered by ID descending. Supports both offset-based and keyset cursor pagination.
query {
operations {
manifestGroups(skip: 0, take: 10) {
items {
id
name
maxActiveJobs
priority
isEnabled
createdAt
updatedAt
}
totalCount
isEstimatedCount
skip
take
nextCursor
}
}
}
| Parameter | Type | Default | Description |
|---|---|---|---|
skip | Int | 0 | Number of records to skip (offset pagination) |
take | Int | 25 | Number of records to return |
afterId | Long | null | Keyset cursor. Returns records with id < afterId. See Pagination |
Returns: PagedResult<ManifestGroupSummary>
ManifestGroupSummary fields
| Field | Type | Description |
|---|---|---|
id | Long! | Database ID |
name | String! | Group name |
maxActiveJobs | Int | Concurrency limit for the group (null = unlimited) |
priority | Int! | Default priority for manifests in this group |
isEnabled | Boolean! | Whether the group is active |
createdAt | DateTime! | When the group was created |
updatedAt | DateTime! | When the group was last modified |
executions
Returns a paginated list of train executions (metadata records), ordered by ID descending (newest first). Supports both offset-based and keyset cursor pagination.
query {
operations {
executions(skip: 0, take: 10) {
items {
id
externalId
name
trainState
startTime
endTime
failureJunction
failureReason
manifestId
cancellationRequested
}
totalCount
isEstimatedCount
skip
take
nextCursor
}
}
}
| Parameter | Type | Default | Description |
|---|---|---|---|
skip | Int | 0 | Number of records to skip (offset pagination) |
take | Int | 25 | Number of records to return |
afterId | Long | null | Keyset cursor. Returns records with id < afterId. See Pagination |
Returns: PagedResult<ExecutionSummary>
ExecutionSummary fields
| Field | Type | Description |
|---|---|---|
id | Long! | Metadata ID |
externalId | String! | External identifier |
name | String! | Train type name |
trainState | TrainState! | Current state (Pending, InProgress, Completed, Failed, Cancelled) |
startTime | DateTime! | When execution began |
endTime | DateTime | When execution finished (null if still running) |
failureJunction | String | Name of the junction that failed (null if no failure) |
failureReason | String | Exception message on failure |
manifestId | Long | Associated manifest ID (null if not scheduler-initiated) |
cancellationRequested | Boolean! | Whether cancellation was requested |
execution
Returns a single execution by metadata ID.
query {
operations {
execution(id: 100) {
id
externalId
name
trainState
startTime
endTime
failureJunction
failureReason
}
}
}
| Parameter | Type | Required | Description |
|---|---|---|---|
id | Long! | Yes | The execution’s metadata ID |
Returns: ExecutionSummary (nullable, returns null if the ID does not exist)
PagedResult
All paginated queries return the same wrapper type:
| Field | Type | Description |
|---|---|---|
items | [T!]! | The page of results |
totalCount | Int! | Total number of records matching the query |
skip | Int! | The skip value that was applied |
take | Int! | The take value that was applied |
isEstimatedCount | Boolean! | true when totalCount is a fast estimate rather than an exact count. See Pagination |
nextCursor | Long | ID of the last item in the page. Pass as afterId to fetch the next page via keyset pagination. null when no items are returned |
Pagination
Paginated queries support two strategies. Both can be used interchangeably. The dashboard uses offset pagination internally, while API consumers can opt into keyset cursors for better deep-page performance.
Offset pagination (default)
Pass skip and take as before. This uses SQL OFFSET/LIMIT under the hood. Performance degrades on deep pages (high skip values) because the database must scan and discard rows up to the offset.
query {
operations {
executions(skip: 100, take: 25) { items { id } totalCount }
}
}
Keyset cursor pagination
Pass afterId (the nextCursor from the previous page) instead of skip. This uses WHERE id < @afterId, which is constant-time regardless of how deep you paginate because it seeks directly to the cursor position via the primary key index.
# First page
query {
operations {
executions(take: 25) { items { id } totalCount nextCursor }
}
}
# Next page: pass nextCursor as afterId
query {
operations {
executions(afterId: 4201, take: 25) { items { id } totalCount nextCursor }
}
}
When afterId is provided, skip is ignored.
Estimated counts
For unfiltered queries on large tables (>10,000 rows), totalCount uses PostgreSQL’s pg_class.reltuples statistic instead of an exact COUNT(*). This is O(1) rather than O(n), and the difference matters when the metadata table has millions of rows.
When the estimate is used, isEstimatedCount is true. The estimate is updated by PostgreSQL’s autovacuum/autoanalyze and is typically accurate within a few percent. For filtered queries or small tables, an exact count is always used and isEstimatedCount is false.