Queries

Queries are organized into two groups under the root Query type:

type Query {
  discover: DiscoverQueries!
  operations: OperationsQueries!  # only when ExposeOperationQueries() is set
}
  • discover: auto-generated typed query fields for trains annotated with [TraxQuery]
  • operations: predefined operational queries: health status, registered trains, manifests, manifest groups, execution history, and the nested deadLetters namespace. Off by default, opt in with ExposeOperationQueries() on the builder.

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. Pass hideAdminTrains: true to exclude the framework's internal scheduler trains (manifest manager, job dispatcher, dead letter cleanup, etc.) from the result; the dashboard uses this flag when its "Hide admin trains" toggle is on.

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
ParameterTypeDefaultDescription
hideAdminTrainsBooleanfalseWhen true, filters out framework-internal scheduler trains (matches AdminTrains.FullNames in Trax.Scheduler.Configuration)

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

Manifest group queries live under the operations.manifestGroups namespace, not at the top level. The namespace holds the paged list (groups), single-group lookup (group), and cross-group dependency graph (graph). See manifestGroups (nested under operations).


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, 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.

config (nested under operations)

The operations.config namespace returns the live scheduler runtime settings (the dashboard-editable subset of SchedulerConfiguration, LocalWorkerOptions, and MetadataCleanupConfiguration). The dashboard's ServerSettingsPage and this query both read from the same in-memory singleton, so they agree.

Persistence: settings written via operations.config.updateScheduler (or the dashboard) are stored in the singleton-row trax.scheduler_config table and re-applied to the in-memory singleton at startup by the SchedulerConfigBootstrapHostedService. Settings survive restarts.

scheduler

query {
  operations {
    config {
      scheduler {
        manifestManagerEnabled
        jobDispatcherEnabled
        manifestManagerPollingInterval
        jobDispatcherPollingInterval
        maxActiveJobs
        defaultMaxRetries
        defaultRetryDelay
        retryBackoffMultiplier
        maxRetryDelay
        defaultJobTimeout
        stalePendingTimeout
        recoverStuckJobsOnStartup
        deadLetterRetentionPeriod
        autoPurgeDeadLetters
        localWorkerCount
        metadataCleanupInterval
        metadataCleanupRetention
      }
    }
  }
}

Returns: SchedulerConfigSnapshot.

SchedulerConfigSnapshot fields

FieldTypeDescription
manifestManagerEnabledBoolean!Whether the manifest manager polling service runs
jobDispatcherEnabledBoolean!Whether the job dispatcher polling service runs
manifestManagerPollingIntervalTimeSpan!How often the manifest manager polls
jobDispatcherPollingIntervalTimeSpan!How often the job dispatcher polls
maxActiveJobsIntGlobal concurrency cap. Null means no cap
defaultMaxRetriesInt!Default retry budget for new manifests
defaultRetryDelayTimeSpan!First-retry delay
retryBackoffMultiplierFloat!Exponential backoff factor
maxRetryDelayTimeSpan!Upper bound on backoff
defaultJobTimeoutTimeSpan!Default per-execution timeout
stalePendingTimeoutTimeSpan!When pending entries are reaped
recoverStuckJobsOnStartupBoolean!Whether stuck-job recovery runs on startup
deadLetterRetentionPeriodTimeSpan!How long resolved dead letters are kept before purging
autoPurgeDeadLettersBoolean!Whether the dead letter cleanup service runs
localWorkerCountIntIn-process worker thread count. Null when UseLocalWorkers() is not configured
metadataCleanupIntervalTimeSpanMetadata cleanup poll interval. Null when cleanup is not configured
metadataCleanupRetentionTimeSpanHow long completed metadata is kept. Null when cleanup is not configured

metrics (nested under operations)

The operations.metrics namespace returns the data behind the dashboard's KPI cards, charts, and server health panel. Every field comes from the shared IOperationsService, so the GraphQL response and the dashboard render exactly the same numbers.

dashboard

query {
  operations {
    metrics {
      dashboard(range: LAST_24_HOURS, hideAdminTrains: true) {
        kpis { executionsToday successRate currentlyRunning unresolvedDeadLetters }
        executionsOverTime { timestamp completed failed cancelled }
        topFailures { trainName count }
        topAverageDurations { trainName averageMilliseconds }
        throughputSeries {
          trainName
          buckets { timestamp count }
        }
      }
    }
  }
}
ParameterTypeDefaultDescription
rangeMetricsRangeLAST_24_HOURSGranularity of the executions-over-time chart. LAST_60_MINUTES returns 60 buckets (1 minute each); LAST_24_HOURS returns 24 buckets (1 hour each). The other series are always over the last 7 days
hideAdminTrainsBooleanfalseWhen true, framework admin trains (matching AdminTrains.FullNames) are excluded from every series

Returns: DashboardMetrics.

DashboardMetrics fields

FieldTypeDescription
kpisDashboardKpis!Today's headline counts
executionsOverTime[ExecutionsBucket!]!Per-bucket counts at the requested granularity
topFailures[TrainFailureCount!]!Top 10 trains by failure count over the last 7 days
topAverageDurations[TrainAverageDuration!]!Top 10 trains by average duration over the last 7 days (root-level executions only)
throughputSeries[ThroughputSeries!]!Top-3 trains plus an "Other" series, 28 6-hour buckets covering 7 days. Empty series are dropped

DashboardKpis fields

FieldTypeDescription
executionsTodayInt!Total executions started today (UTC)
successRateFloat!Completed / (Completed + Failed) as a percentage. Zero when no terminal executions exist today
currentlyRunningInt!Executions currently in InProgress
unresolvedDeadLettersInt!Dead letters in AwaitingIntervention

ExecutionsBucket fields

FieldTypeDescription
timestampDateTime!UTC start of the bucket
completedInt!Completed executions in the bucket
failedInt!Failed executions
cancelledInt!Cancelled executions

TrainFailureCount fields

FieldTypeDescription
trainNameString!Train interface FullName
countInt!Failures over the last 7 days

TrainAverageDuration fields

FieldTypeDescription
trainNameString!Train interface FullName
averageMillisecondsFloat!Mean execution time over completed root-level runs in the last 7 days

ThroughputSeries fields

FieldTypeDescription
trainNameString!Train interface FullName, or the literal string "Other" for the aggregated remainder series
buckets[ThroughputBucket!]!28 6-hour buckets, oldest first

ThroughputBucket fields

FieldTypeDescription
timestampDateTime!UTC start of the bucket
countInt!Completed executions in the bucket

server

Process-level snapshot. CPU% is intentionally not returned since it requires per-instance sampling state; consumers that need it can take two snapshots and compute it themselves.

query {
  operations {
    metrics {
      server { processStartTimeUtc uptimeSeconds workingSetBytes gcHeapBytes }
    }
  }
}

Returns: ServerMetrics.

FieldTypeDescription
processStartTimeUtcDateTime!When the host process started
uptimeSecondsFloat!Seconds since process start
workingSetBytesLong!Process.WorkingSet64
gcHeapBytesLong!GC.GetTotalMemory(false)

logs (nested under operations)

The operations.logs namespace returns paginated reads of trax.log, the framework's per-execution log table. The dashboard's Logs page is backed by this query. Logs are written by the framework, never by API consumers, so there are no log mutations.

query {
  operations {
    logs {
      logs(skip: 0, take: 50, minimumLevel: WARNING) {
        items { id metadataId eventId level category message exception stackTrace }
        totalCount
        isEstimatedCount
        nextCursor
      }
    }
  }
}
ParameterTypeDefaultDescription
skipInt0Number of records to skip (offset pagination). Ignored when afterId is provided.
takeInt25Number of records to return
metadataIdLongnullFilter to logs for a single execution
minimumLevelLogLevelnullIncludes the supplied level and anything more severe. LogLevel follows Microsoft.Extensions.Logging: TRACE, DEBUG, INFORMATION, WARNING, ERROR, CRITICAL, NONE
categoryStringnullExact-match filter on the logger category (e.g. Trax.Samples.GameServer.Trains.Combat.ResolveCombatTrain)
afterIdLongnullKeyset cursor. Returns records with id < afterId

Returns: PagedResult<LogEntry>.

When any filter or afterId is supplied, the count is exact (isEstimatedCount: false). Unfiltered first-page reads use the same pg_class.reltuples estimator as the other large-table queries because the log table grows quickly.

LogEntry fields

FieldTypeDescription
idLong!Database ID (monotonic, used as the keyset cursor)
metadataIdLong!The execution this log line belongs to
eventIdInt!EventId from the ILogger call site
levelLogLevel!Severity
categoryString!Logger category, typically the originating type name
messageString!Truncated to 4000 chars at write time
exceptionStringException message if any (truncated to 2000 chars)
stackTraceStringStack trace if any (truncated to 4000 chars)

manifestGroups (nested under operations)

The operations.manifestGroups namespace exposes every read scoped to manifest groups: the paged list, single-group lookup, and the cross-group dependency graph the dashboard renders as a DAG. The list lives here (rather than as a sibling of manifests at the operations root) because both the namespace and a sibling manifestGroups field would camelCase to the same name in the schema, and HotChocolate would silently drop one.

groups

Returns a paginated list of manifest groups, ordered by ID descending. Supports both offset-based and keyset cursor pagination.

query {
  operations {
    manifestGroups {
      groups(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 row was created
updatedAtDateTime!Last patch via updateManifestGroup

group

Single-group lookup by ID. Used by dashboards to pre-populate the group settings form before sending an updateManifestGroup patch.

query {
  operations {
    manifestGroups {
      group(id: 7) {
        id
        name
        maxActiveJobs
        priority
        isEnabled
      }
    }
  }
}
ParameterTypeRequiredDescription
idLong!YesManifest group database ID

Returns: ManifestGroupSummary (nullable; null when the group does not exist).

graph

Returns the 1-hop cross-group dependency neighborhood for a manifest group: every group containing a manifest the focal group's manifests depend on (upstream), every group containing a manifest depending on the focal group's manifests (downstream), and the focal group itself. Edges are directed parent → dependent.

query {
  operations {
    manifestGroups {
      graph(groupId: 7) {
        nodes { id name isHighlighted }
        edges { fromId toId }
      }
    }
  }
}
ParameterTypeRequiredDescription
groupIdLong!YesDatabase ID of the focal manifest group

Returns: ManifestGroupDependencyGraph (nullable). Returns null only when the group does not exist. Empty groups still return a single-node graph (focal group, no edges) so the UI can render the focal node.

ManifestGroupDependencyGraph fields

FieldTypeDescription
nodes[DependencyGraphNode!]!All groups in the neighborhood plus the focal group
edges[DependencyGraphEdge!]!Cross-group edges only. Same-group dependencies are excluded

DependencyGraphNode fields

FieldTypeDescription
idLong!Manifest group ID
nameString!Group name
isHighlightedBoolean!true for the focal group; the UI uses this to render it differently

DependencyGraphEdge fields

FieldTypeDescription
fromIdLong!Parent group ID (the group whose manifests are depended on)
toIdLong!Dependent group ID

workQueue (nested under operations)

The operations.workQueue namespace exposes paginated reads of the work queue. The work queue is the intermediary between scheduling and dispatch: every queued execution (manifest triggers, dashboard re-runs, dead-letter requeues, GraphQL queueTrain calls) lands here as a Queued row that the JobDispatcher picks up.

query {
  operations {
    workQueue {
      workQueues(skip: 0, take: 25, status: QUEUED) {
        items {
          id
          externalId
          trainName
          status
          createdAt
          dispatchedAt
          scheduledAt
          priority
          dispatchAttempts
          manifestId
          metadataId
          deadLetterId
          inputTypeName
        }
        totalCount
        isEstimatedCount
        nextCursor
      }
    }
  }
}

workQueues

ParameterTypeDefaultDescription
skipInt0Number of records to skip (offset pagination). Ignored when afterId is provided.
takeInt25Number of records to return
statusWorkQueueStatusnullFilter by lifecycle state (QUEUED, DISPATCHED, CANCELLED)
trainNameStringnullExact-match filter on the interface FullName (e.g. Trax.Samples.GameServer.Trains.Combat.IResolveCombatTrain)
afterIdLongnullKeyset cursor. Returns records with id < afterId. See Pagination

Returns: PagedResult<WorkQueueSummary>

When any filter or afterId is supplied, the count is exact and isEstimatedCount is false. Unfiltered first-page reads use the same fast estimator as the other large-table queries.

WorkQueueSummary fields

FieldTypeDescription
idLong!Database ID
externalIdString!GUID assigned at creation
trainNameString!Train interface FullName
statusWorkQueueStatus!QUEUED, DISPATCHED, or CANCELLED
createdAtDateTime!When the entry was queued
dispatchedAtDateTimeWhen the dispatcher picked it up (null while queued or if cancelled before dispatch)
scheduledAtDateTimeEarliest dispatch time. Null means dispatch immediately
priorityInt!Dispatch priority 0-31
dispatchAttemptsInt!Number of times dispatch was attempted and failed
manifestIdLongSource manifest ID, if scheduled
metadataIdLongMetadata ID created at dispatch, if dispatched
deadLetterIdLongDead letter that triggered this requeue, if applicable
inputTypeNameStringFully qualified type name of the input, for deserialization

workQueue (single)

Returns a single entry by database ID.

query {
  operations {
    workQueue {
      workQueue(id: 42) { id status priority dispatchAttempts }
    }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe work queue entry's database ID

Returns: WorkQueueSummary (nullable, returns null if the ID does not exist).


deadLetters (nested under operations)

The operations.deadLetters namespace exposes paginated dead-letter reads (deadLetters, deadLetter). See scheduler/dead-letters-and-cleanup for the full surface and examples.