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 — 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) — 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.