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 = "...")]):

  1. Strip the I prefix
  2. Strip the Train suffix
  3. 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:

FieldTypeDescription
metadataIdLong!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

FieldTypeDescription
statusString!"Healthy" or "Degraded"
descriptionString!Human-readable summary
queueDepthInt!Work items with status Queued
inProgressInt!Executions with TrainState.InProgress
failedLastHourInt!Failed executions in the last hour
deadLettersInt!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

FieldTypeDescription
serviceTypeNameString!Friendly name of the service interface (e.g. IServiceTrain<OrderInput, OrderResult>)
implementationTypeNameString!Friendly name of the concrete class
inputTypeNameString!Friendly name of the input type
outputTypeNameString!Friendly name of the output type
lifetimeString!DI lifetime (Singleton, Scoped, Transient)
inputSchema[InputPropertySchema!]!Public readable properties on the input type

InputPropertySchema fields

FieldTypeDescription
nameString!Property name
typeNameString!Friendly type name (e.g. String, Int32, DateTime?)
isNullableBoolean!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
    }
  }
}
ParameterTypeDefaultDescription
skipInt0Number of records to skip (offset pagination)
takeInt25Number of records to return
afterIdLongnullKeyset cursor — returns records with id < afterId. When provided, skip is ignored. See Pagination

Returns: PagedResult<ManifestSummary>

ManifestSummary fields

FieldTypeDescription
idLong!Database ID
externalIdString!Unique external identifier (used for upsert/trigger)
nameString!Train type name
isEnabledBoolean!Whether the manifest is active
scheduleTypeScheduleType!Cron or Interval
cronExpressionStringCron expression (when scheduleType is Cron)
intervalSecondsIntInterval in seconds (when scheduleType is Interval)
maxRetriesInt!Maximum retry count on failure
timeoutSecondsIntExecution timeout
lastSuccessfulRunDateTimeTimestamp of last successful execution
manifestGroupIdLong!Parent group ID
dependsOnManifestIdLongID of the manifest this one depends on
priorityInt!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
    }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe 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
    }
  }
}
ParameterTypeDefaultDescription
skipInt0Number of records to skip (offset pagination)
takeInt25Number of records to return
afterIdLongnullKeyset cursor — returns records with id < afterId. See Pagination

Returns: PagedResult<ManifestGroupSummary>

ManifestGroupSummary fields

FieldTypeDescription
idLong!Database ID
nameString!Group name
maxActiveJobsIntConcurrency limit for the group (null = unlimited)
priorityInt!Default priority for manifests in this group
isEnabledBoolean!Whether the group is active
createdAtDateTime!When the group was created
updatedAtDateTime!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
    }
  }
}
ParameterTypeDefaultDescription
skipInt0Number of records to skip (offset pagination)
takeInt25Number of records to return
afterIdLongnullKeyset cursor — returns records with id < afterId. See Pagination

Returns: PagedResult<ExecutionSummary>

ExecutionSummary fields

FieldTypeDescription
idLong!Metadata ID
externalIdString!External identifier
nameString!Train type name
trainStateTrainState!Current state (Pending, InProgress, Completed, Failed, Cancelled)
startTimeDateTime!When execution began
endTimeDateTimeWhen execution finished (null if still running)
failureJunctionStringName of the junction that failed (null if no failure)
failureReasonStringException message on failure
manifestIdLongAssociated manifest ID (null if not scheduler-initiated)
cancellationRequestedBoolean!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
    }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe execution's metadata ID

Returns: ExecutionSummary (nullable — returns null if the ID does not exist)


PagedResult

All paginated queries return the same wrapper type:

FieldTypeDescription
items[T!]!The page of results
totalCountInt!Total number of records matching the query
skipInt!The skip value that was applied
takeInt!The take value that was applied
isEstimatedCountBoolean!true when totalCount is a fast estimate rather than an exact count. See Pagination
nextCursorLongID 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.