Mutations

Mutations are organized into two groups under the root Mutation type:

type Mutation {
  dispatch: DispatchMutations!     # only when [TraxMutation] trains exist
  operations: OperationsMutations! # only when ExposeOperationMutations() is set
}
  • dispatch: auto-generated typed mutations for trains annotated with [TraxMutation]
  • operations: scheduler management operations (trigger, disable, enable, cancel manifests and groups, plus the nested deadLetters namespace for requeue/acknowledge). Off by default, opt in with ExposeOperationMutations() on the builder. The Trax scheduler is reachable through these mutations, so leaving them open lets any caller disrupt scheduled work.

The root Mutation type is omitted entirely when no source contributes to it (no [TraxMutation] train and ExposeOperationMutations() not called).

Dispatch Mutations (Auto-Generated)

Trax auto-generates strongly-typed mutations for trains that opt in with [TraxMutation]. Only trains with this attribute appear under dispatch. Trains annotated with [TraxQuery] appear under query { discover { ... } } instead; see Queries.

Each whitelisted train gets a single mutation field named after the train (no prefix). Trains with Namespace set are grouped under a sub-namespace (e.g. dispatch { alerts { createAlert } }). The mutation's parameters and behavior depend on the operations passed to the attribute constructor:

  • Run + Queue (default): when no operations are specified (or both GraphQLOperation.Run and GraphQLOperation.Queue are passed), the mutation accepts an optional mode: ExecutionMode parameter (RUN or QUEUE, default RUN) and an optional priority: Int.
  • Run only: the mutation always runs synchronously. No mode or priority parameters.
  • Queue only: the mutation always queues. Has priority but no mode parameter.

Naming Convention

The mutation name is derived from the train's service interface name (or overridden via [TraxMutation(Name = "...")]):

  1. Strip the I prefix
  2. Strip the Train suffix
  3. Use the result as the field name (camelCase)

For example, IBanPlayerTrain produces banPlayer.

Example

Given a train annotated with [TraxMutation]:

public record BanPlayerInput : IManifestProperties
{
    public required string PlayerId { get; init; }
    public required string Reason { get; init; }
}

The schema exposes:

input BanPlayerInput {
  playerId: String!
  reason: String!
}
 
# Run synchronously (default mode)
mutation {
  dispatch {
    banPlayer(input: { playerId: "player-42", reason: "cheating" }) {
      externalId
      metadataId
      output { ... }
    }
  }
}
 
# Queue for async execution
mutation {
  dispatch {
    banPlayer(
      input: { playerId: "player-42", reason: "cheating" }
      mode: QUEUE
      priority: 10
    ) {
      externalId
      workQueueId
    }
  }
}

Unified Response Type

Every dispatch mutation returns a single per-train response type with nullable fields. Which fields are populated depends on the execution mode:

type BanPlayerResponse {
  externalId: String!       # always present
  metadataId: Long          # present for RUN, null for QUEUE
  output: BanPlayerOutput   # present for RUN (typed trains only), null for QUEUE
  workQueueId: Long         # present for QUEUE, null for RUN
}
FieldTypeWhen Populated
externalIdString!Always present. Identifies the execution or work queue entry
metadataIdLongRUN mode. Metadata ID of the completed execution
output{OutputType}RUN mode, only for trains with non-Unit output
workQueueIdLongQUEUE mode. Database ID of the created WorkQueue entry

The wrapper is named {TrainName}Response by default. If the train's output CLR class is also named {TrainName}Response (for example IAddressValidationTrain returning AddressValidationResponse), the wrapper falls back to {TrainName}MutationResponse so the schema can build without a name collision. Trains whose output type follows a different naming convention are unaffected.

Run + Queue Mode (Default)

When no operations are specified (or both GraphQLOperation.Run and GraphQLOperation.Queue are passed), the mutation includes a mode parameter:

ParameterTypeRequiredDefaultDescription
input{TrainName}Input!YesN/AStrongly-typed input matching the train's input record
modeExecutionModeNoRUNWhether to run synchronously (RUN) or queue for async execution (QUEUE)
priorityIntNo0Dispatch priority (0-31, higher runs first). Silently ignored for RUN mode.

The ExecutionMode enum is automatically registered in the GraphQL schema when any train uses both Run and Queue operations:

enum ExecutionMode {
  RUN
  QUEUE
}

Example: Run + Queue train with typed output

A train ServiceTrain<LookupPlayerInput, LookupPlayerOutput> annotated with [TraxMutation] (default, both modes) produces:

type LookupPlayerResponse {
  externalId: String!
  metadataId: Long
  output: LookupPlayerOutput
  workQueueId: Long
}
 
type LookupPlayerOutput {
  playerId: String!
  rank: Int!
  wins: Int!
  losses: Int!
  rating: Int!
}
 
# Run synchronously (default)
mutation {
  dispatch {
    lookupPlayer(input: { playerId: "player-42" }) {
      externalId
      metadataId
      output {
        playerId
        rank
        wins
        losses
        rating
      }
    }
  }
}
 
# Queue for async execution
mutation {
  dispatch {
    lookupPlayer(
      input: { playerId: "player-42" }
      mode: QUEUE
      priority: 5
    ) {
      externalId
      workQueueId
    }
  }
}

The output type is automatically registered as a GraphQL ObjectType and deduplicated. If multiple trains share the same output type, only one GraphQL type is generated.

Run-Only Mode

When GraphQLOperation.Run is the only operation passed (e.g. [TraxMutation(GraphQLOperation.Run)]), the mutation always runs synchronously. No mode or priority parameters are generated.

ParameterTypeRequiredDescription
input{TrainName}Input!YesStrongly-typed input matching the train's input record

The response type still uses the unified format, but workQueueId will always be null.

Queue-Only Mode

When GraphQLOperation.Queue is the only operation passed (e.g. [TraxMutation(GraphQLOperation.Queue)]), the mutation always queues. No mode parameter is generated, but priority is available.

ParameterTypeRequiredDefaultDescription
input{TrainName}Input!YesN/AStrongly-typed input matching the train's input record
priorityIntNo0Dispatch priority (0-31, higher runs first)

The response type still uses the unified format, but metadataId and output will always be null.


Operations Mutations

The whole namespace sits behind the operations gate (GateOperations, RequireAuthorization or AllowAnonymousOperations; see AddTraxGraphQL). Mutations that enqueue a train and input the caller chose (requeueExecution, workQueue.queueTrain) also apply that train's [TraxAuthorize] requirements. Mutations that enqueue what a manifest fixed (triggerManifest, triggerManifestDelayed, triggerGroup, dead-letter requeues) are governed by the gate alone. See Authorization: The Operations Surface.

triggerManifest

Triggers an immediate execution of a manifest, bypassing its normal schedule. A manifest holds at most one queued work queue entry, so when it already has one, nothing more is queued and that entry becomes the triggered run: it runs even if the manifest is disabled, and an entry due later (a retry waiting out its backoff) is brought forward to now. The mutation still succeeds.

mutation {
  operations {
    triggerManifest(externalId: "order-processing-daily") {
      success
      message
    }
  }
}
ParameterTypeRequiredDescription
externalIdString!YesThe manifest's external ID

Returns: OperationResponse


triggerManifestDelayed

Triggers a manifest execution after a specified delay.

mutation {
  operations {
    triggerManifestDelayed(
      externalId: "order-processing-daily"
      delay: "00:05:00"
    ) {
      success
      message
    }
  }
}
ParameterTypeRequiredDescription
externalIdString!YesThe manifest's external ID
delayTimeSpan!YesHow long to wait before triggering (e.g. "00:05:00" for 5 minutes)

Returns: OperationResponse


disableManifest

Disables a manifest. Disabled manifests are skipped during scheduling cycles.

mutation {
  operations {
    disableManifest(externalId: "order-processing-daily") {
      success
      message
    }
  }
}
ParameterTypeRequiredDescription
externalIdString!YesThe manifest's external ID

Returns: OperationResponse


enableManifest

Re-enables a previously disabled manifest.

mutation {
  operations {
    enableManifest(externalId: "order-processing-daily") {
      success
      message
    }
  }
}
ParameterTypeRequiredDescription
externalIdString!YesThe manifest's external ID

Returns: OperationResponse


cancelManifest

Requests cancellation of every pending and running execution of a manifest. Sets CancellationRequested on each, which the run observes at its next junction boundary on any host; a run on the host that received the mutation is also cancelled at once.

mutation {
  operations {
    cancelManifest(externalId: "order-processing-daily") {
      success
      count
      message
    }
  }
}
ParameterTypeRequiredDescription
externalIdString!YesThe manifest's external ID

Returns: OperationResponse (includes count, the number of executions marked for cancellation)


triggerGroup

Triggers immediate execution of all enabled manifests in a group. A manifest that already has a queued work queue entry is not queued again and does not stop the others being queued; its entry is brought forward to now if it was due later. Every entry the trigger touches runs even if its manifest is disabled afterwards.

mutation {
  operations {
    triggerGroup(groupId: 1) {
      success
      count
      message
    }
  }
}
ParameterTypeRequiredDescription
groupIdLong!YesThe manifest group's database ID

Returns: OperationResponse (includes count, the number of manifests queued, which leaves out those skipped as already queued; the server logs the skipped count)


cancelGroup

Requests cancellation of every pending and running execution across all manifests in a group, by the same rule as cancelManifest.

mutation {
  operations {
    cancelGroup(groupId: 1) {
      success
      count
      message
    }
  }
}
ParameterTypeRequiredDescription
groupIdLong!YesThe manifest group's database ID

Returns: OperationResponse (includes count, the number of executions marked for cancellation)


cancelExecution

Requests cancellation of a single execution by metadata id. Sets the durable cancel_requested flag on the row when it is still PENDING or IN_PROGRESS; the process running the train observes it at its next junction boundary and records the run as CANCELLED, and a run on the host that received the mutation is cancelled at once. It goes through the same IOperationsService.CancelExecutionsAsync call as the dashboard's Cancel button.

mutation {
  operations {
    cancelExecution(id: 100) { success count message }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe execution's metadata id

Returns: OperationResponse. count is 1 when the execution was flagged. An execution that is missing or already terminal returns success: false with count 0.


cancelExecutions

Requests cancellation of many executions at once, as cancelExecution does for one. Every id still PENDING or IN_PROGRESS is flagged in one statement; terminal and unknown ids are skipped. Backs the dashboard's bulk cancel.

mutation {
  operations {
    cancelExecutions(ids: [100, 101, 102]) { success count message }
  }
}
ParameterTypeRequiredDescription
ids[Long!]!Yes1 to 1000 execution metadata ids

Returns: OperationResponse. count is the number flagged, zero included. An empty list, or more than 1000 ids, returns success: false and flags nothing.


setManifestsEnabled

Enables or disables many manifests at once. Only manifests whose flag differs are written. Backs the dashboard's bulk enable and disable.

mutation {
  operations {
    setManifestsEnabled(ids: [3, 4, 5], enabled: false) { success count message }
  }
}
ParameterTypeRequiredDescription
ids[Long!]!Yes1 to 1000 manifest database ids
enabledBoolean!YesThe flag to set

Returns: OperationResponse. count is the number of manifests changed, zero included. An empty list, or more than 1000 ids, returns success: false and changes nothing.


requeueExecution

Re-queues an execution: reads its train name + input from the metadata row and enqueues a fresh work queue entry for the dispatcher (the GraphQL counterpart of the dashboard's Re-queue button). Both call IOperationsService.RequeueExecutionAsync, so they refuse the same runs with the same messages. The enqueue goes through the same path as queueTrain, so a caller who may not run the train gets a GraphQL error with code TRAX_AUTHORIZATION ("Not authorized.") rather than success: false.

The new run replays the decisions the execution recorded with AddDecisionRecording, so it takes the tracks the execution took instead of asking its deciders again. The replay link is set only here, to the execution being re-queued, and only when it has decisions to replay (it recorded a decision, or was itself a replaying requeue, so re-queueing a re-queue replays too); any other execution is re-queued as an ordinary enqueue. queueTrain has no way to set it. See Re-queued runs replay their decisions.

An execution with no saved input is refused with success: false and a message saying inputs are saved only when SaveTrainParameters() is on. So is one whose input was too large to save in full and was stored as the truncation placeholder ({"_truncated": true, ...}), one stored as the _unserializable or _disposed placeholder because it could not be saved, and one whose recorded input has a [TraxSensitive] member masked as {"_redacted": true}: re-queueing it would run the train with the mask in place of the value. An execution whose train is no longer registered is refused with "Train {name} is no longer registered, so execution {id} cannot be re-queued.", and one whose saved input no longer reads as the train's input type, because the type changed shape since, with "The saved input of run {id} no longer reads as {InputType.FullName}: " and the parser's message, never as an invalid InputJson the caller did not send. Enqueue refusals (a throwing OnQueue, an unusable subject key, a deferred entry cancelled before confirmation) come back as success: false, with the message rule queueTrain describes, and an infrastructure failure is a masked GraphQL error, as for queueTrain. An enqueue reads a missing input as {}, so re-queueing it would re-run the train with defaults rather than with what it ran with. This check runs before authorization, so it answers the same for every caller; a missing execution id also returns success: false.

mutation {
  operations {
    requeueExecution(id: 100) { success message }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe execution's metadata id

Returns: OperationResponse.


updateManifest

Patches mutable settings on a single manifest. Each field on input is independent; a null value leaves it unchanged. Set clearTimeout: true to remove the per-execution timeout.

mutation {
  operations {
    updateManifest(id: 42, input: {
      isEnabled: false
      maxRetries: 5
      priority: 10
      scheduleType: CRON
      cronExpression: "0 3 * * *"
    }) { success message }
  }
}
FieldTypeDescription
isEnabledBooleanEnable/disable the manifest
maxRetriesIntRetry budget
priorityIntDispatch priority
timeoutSeconds / clearTimeoutInt / BooleanPer-execution timeout; clearTimeout: true removes it
scheduleTypeScheduleTypeSchedule type
cronExpressionStringCron expression
intervalSecondsIntInterval

Returns: OperationResponse (success: false when the manifest id does not exist).


setEffectEnabled

Turns an observational effect on or off in the API process, through the effect registry, the same calls the dashboard's Effects page makes. The change is in memory: it does not reach the scheduler or worker processes where trains usually run, and a restart restores the configured state.

mutation {
  operations {
    setEffectEnabled(
      fullName: "Trax.Effect.Provider.Json.Services.JsonEffectFactory.JsonEffectProviderFactory"
      enabled: false
    ) {
      success
      count
      message
    }
  }
}
ParameterTypeRequiredDescription
fullNameString!YesThe effect factory's full type name, as operations.effects reports it in fullName. Matched exactly.
enabledBoolean!Yestrue to turn the effect on, false to turn it off

Returns: OperationResponse. success is false, and nothing changes, when no effect has that name or the effect was registered as not toggleable. On success count is 1.


config (nested namespace)

The operations.config namespace patches scheduler runtime settings. A save writes only the fields it sets to the persisted trax.scheduler_config row, so it never rewrites a setting it did not name, and applies them to the host that received it at once. Every running scheduler host reads the row every few seconds and applies a new or changed one without a restart, so a save made on an API-only host, or on one of several scheduler hosts, reaches all of them. A scheduler applies a change from its next polling cycle, including a new polling or cleanup interval; localWorkerCount is the exception and applies when the worker pool next starts. The row also survives restarts: each scheduler applies it at startup over the settings configured in code.

The row records which settings a save named, in its overrides. A setting no save has named is not stored, so each scheduler keeps the value configured in code for it, and a later change in code applies to it. That is why any host can make a save, the first one included, whether or not it runs the scheduler: an API-only host built with AddTraxJobRunner() never has to supply values for settings it did not name. Two hosts making the first save at the same time both succeed; the one that loses the race applies its patch to the row the other created.

Which fields a save stores depends on the host. A field whose setting the row already names is stored when it differs from the stored value. For a setting the row does not name, a scheduler host stores the field only when it differs from the value that host runs with, so a save from the dashboard on a scheduler host leaves unchanged fields out. A host that does not run the scheduler cannot know that value, so it stores every field the patch sets, and each field it sends becomes a saved value that replaces the code value on every scheduler. count counts the fields stored.

A stored value takes precedence over the value in code until it is changed or the row is deleted, and deleting the row returns every running scheduler to its configured settings. Each scheduler logs a warning when a saved value replaces a different value configured in code, because a deploy that changes that setting in code then has no effect. The two dead-letter purge settings are the exception to "the saved value wins": when code states them too, the purge runs only if both autoPurgeDeadLetters values allow it, and the longer of the two deadLetterRetentionPeriod values applies (see Dead Letter Auto-Purge).

A scheduler that cannot read the row when it starts (the database is briefly unreachable, or the table is not migrated yet) logs a warning, runs with its code values, and applies the row at its first successful read.

updateScheduler

Patches one or more fields. Fields left out of input are unchanged. The persisted row's updatedAt only moves on real changes (no-op patches are ignored at the DB layer).

mutation {
  operations {
    config {
      updateScheduler(input: {
        defaultMaxRetries: 5
        defaultJobTimeout: "00:30:00"
      }) { success count message }
    }
  }
}
ParameterTypeRequiredDescription
inputUpdateSchedulerConfigInput!YesPatch payload (fields below)

UpdateSchedulerConfigInput fields

Every field defaults to null and means "no change". To clear maxActiveJobs (set to "no per-group cap") or localWorkerCount (reset to Environment.ProcessorCount), set the corresponding clear* flag to true.

FieldTypeDescription
manifestManagerEnabledBoolean
jobDispatcherEnabledBoolean
manifestManagerPollingIntervalTimeSpan1 second to 30 days
jobDispatcherPollingIntervalTimeSpan1 second to 30 days
maxActiveJobsIntAt least 1
clearMaxActiveJobsBooleanWhen true, sets maxActiveJobs to null
defaultMaxRetriesIntZero or more
failureCountWindowTimeSpan1 second to ten years. How far back failed runs count toward retry backoff and MaxRetries
defaultRetryDelayTimeSpanZero to ten years
retryBackoffMultiplierFloatAt least 1
maxRetryDelayTimeSpanZero to ten years
defaultJobTimeoutTimeSpan1 second to ten years
stalePendingTimeoutTimeSpan1 second to ten years
recoverStuckJobsOnStartupBoolean
deadLetterRetentionPeriodTimeSpanZero to ten years
autoPurgeDeadLettersBoolean
localWorkerCountInt1 to 256. Ignored when this process runs no local worker pool (see ConfigureLocalWorkers). Applies when the worker pool next starts
clearLocalWorkerCountBooleanResets localWorkerCount to Environment.ProcessorCount
metadataCleanupIntervalTimeSpan1 second to 30 days. Ignored when metadata cleanup is not configured
metadataCleanupRetentionTimeSpan1 second to ten years. Ignored when metadata cleanup is not configured

Returns: OperationResponse. count is the number of fields actually changed (zero if every supplied value already matched). A value outside its range makes success false, with a message naming each offending field, and nothing in the patch is applied or persisted; so does a first save on a host that does not run the scheduler. The ranges are what the scheduler can run with: an interval is the wait between two polling cycles, which a timer caps at about 49 days, and polling the database more often than once a second is load rather than responsiveness. When a scheduler applies the row, a persisted value outside its range (from a row written before these checks, or edited by hand) is skipped with a warning, and the configured value stays in effect.


manifestGroups (nested namespace)

The operations.manifestGroups namespace patches mutable fields on a manifest group and enables or disables groups in bulk. The dashboard calls the same underlying service, so a save from either surface produces an identical write.

updateManifestGroup

Patches one or more fields on a manifest group. Fields left out of input are unchanged; updatedAt is bumped only when at least one field actually changed.

mutation {
  operations {
    manifestGroups {
      updateManifestGroup(id: 7, input: {
        priority: 5
        isEnabled: false
      }) { success count message }
    }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe manifest group's database ID
inputUpdateManifestGroupInput!YesPatch payload (fields below)

UpdateManifestGroupInput fields

FieldTypeDefaultDescription
maxActiveJobsIntnullNew per-group concurrency limit, at least 1. null means "no change". To clear the limit, use clearMaxActiveJobs instead
clearMaxActiveJobsBooleanfalseWhen true, sets maxActiveJobs to null (removes the per-group limit). Takes precedence if both this and maxActiveJobs are set
priorityIntnullNew priority, 0 to 31. null = no change
isEnabledBooleannullWhether the group is active. null = no change

Returns: OperationResponse. On success, count is the number of fields actually changed (zero if every supplied value already matched the persisted row). On failure (the group is not found, or a value is out of range), success is false, message explains, and no field is written.

setManifestGroupsEnabled

Enables or disables the listed groups. Only groups whose flag differs are written, with updatedAt bumped.

mutation {
  operations {
    manifestGroups {
      setManifestGroupsEnabled(ids: [1, 2], enabled: false) { success count message }
    }
  }
}
ParameterTypeRequiredDescription
ids[Long!]!Yes1 to 1000 manifest group ids
enabledBoolean!YesThe flag to set

Returns: OperationResponse. count is the number of groups changed, zero included. An empty list, or more than 1000 ids, returns success: false and changes nothing: an empty list never means "every group".

setAllManifestGroupsEnabled

Enables or disables every manifest group. It is a field of its own so that "all" is something a caller asks for by name.

mutation {
  operations {
    manifestGroups {
      setAllManifestGroupsEnabled(enabled: true) { success count message }
    }
  }
}
ParameterTypeRequiredDescription
enabledBoolean!YesThe flag to set

Returns: OperationResponse. count is the number of groups changed, zero included.


workQueue (nested namespace)

The operations.workQueue namespace lets the dashboard (and other API clients) put work into the queue and cancel it. Reads live under operations.workQueue in queries.md.

queueTrain

Creates a new work queue entry. The dispatcher picks it up on its next poll. An unknown trainName, malformed or oversized inputJson, or JSON that deserializes to null returns OperationResponse(success: false, message: ...) and inserts nothing. For most trains nothing is written until every check has passed. A train that sets DeferQueuePromotion is the exception: its entry is committed unconfirmed (never dispatched) before its OnQueue hook runs, and removed again if the hook throws, so a refused enqueue of such a train does briefly write a row.

Upgrading from a version where this mutation wrote the row itself: it now authorizes, fires OnQueue and stamps the subject key. See Enqueue and Outcome Changes.

The entry is created through ITrainExecutionService.QueueAsync, so the train's [TraxAuthorize] requirements apply on top of the operations gate. Authorization runs before inputJson is read: a caller who may not run the train gets a GraphQL error with code TRAX_AUTHORIZATION and message "Not authorized.", not success: false, even when the input is malformed, and nothing is inserted. See Authorization: The Operations Surface.

Four kinds of exception propagate out of the mutation rather than becoming success: false. Authorization (an UnauthorizedAccessException, which TrainAuthorizationException is) surfaces as the TRAX_AUTHORIZATION error above. Cancellation of the request ends it. An infrastructure failure, meaning a database, EF Core, network, I/O or timeout exception anywhere in the exception's chain (a DbException such as NpgsqlException, DbUpdateException, TimeoutException, SocketException, HttpRequestException or IOException), is logged on the server and arrives as a GraphQL error with HotChocolate's masked "Unexpected Execution Error" message, so nothing about the server reaches the caller. That includes a data-layer exception caused by the train's own OnQueue hook; a hook that means to refuse throws its own exception. The mediator's TrainAuthorizationNotConfiguredException, for a [TraxAuthorize] train on a host with no ITrainAuthorizationService registered, is a host misconfiguration, so it is logged and masked the same way.

Every other exception from the enqueue is a refusal and becomes success: false: invalid JSON as "Invalid InputJson: " followed by the parser's message, an oversized input as the generic "The train input failed validation." (neither the cap nor the input's size is echoed), and anything else as a refusal. That last group covers the train's OnQueue hook throwing, QueueSubjectKey throwing or returning an empty key, one that is only whitespace, or one longer than 512 Unicode characters, and a deferred entry being cancelled before it was confirmed (in which case the hook's side-effect may already have landed). A refusal's message is "The enqueue was refused: " followed by the exception's message only when that message was written for the caller: a plain TrainException (not a type derived from it), whose message is the train author's, or the mediator's QueuedWorkCancelledException and QueueHookTimeoutException. For any other exception it is the fixed "The enqueue was refused.", and the exception is logged at Warning on the server. A hook that refuses with a reason the caller should read throws TrainException. Trax.Scheduler/docs/adr/0004 records the split.

mutation {
  operations {
    workQueue {
      queueTrain(input: {
        trainName: "Trax.Samples.GameServer.Trains.Combat.IResolveCombatTrain"
        inputJson: "{\"attackerId\":\"player-1\",\"defenderId\":\"player-2\"}"
        priority: 10
      }) {
        success
        count
        message
      }
    }
  }
}
FieldTypeRequiredDefaultDescription
trainNameString!YesN/ATrain interface FullName, the fullName that operations.trains returns (not its serviceTypeName, which is a display name)
inputJsonStringNonullJSON payload that deserializes to the train's input type. null or blank is read as {}, so a train whose input needs no values can be queued without one; an input type that needs values (a positional record whose parameters have no defaults, or a required member) is refused. The JSON literal null is refused
priorityIntNo0Dispatch priority 0-31. Values outside that range are clamped
scheduledAtDateTimeNonullEarliest UTC time the entry should be picked up. Null means dispatch immediately

Returns: OperationResponse. On success, count is 1 and id is the new work queue entry's id, which message also names.

runTrain

Runs a train now, through the same IOperationsService.RunTrainAsync call as the dashboard's Run dialog. The run's PENDING execution row is written and handed straight to the job submitter the train is routed to (its [TraxRemote] or builder route, otherwise the host's default), so it skips the work queue: dispatch ordering, group limits and the subject lock do not apply. Use queueTrain when they should.

What the caller sees follows the operations payload convention:

  • Refused: success: false with a message, and no execution row is written. An unknown trainName, invalid JSON ("Invalid InputJson: " and the parser's message), an oversized input (the generic "The train input failed validation."), or a refusal the train itself makes.
  • Not allowed: the train's [TraxAuthorize] requirements apply on top of the operations gate and are checked before inputJson is read. A caller who may not run the train gets a GraphQL error with code TRAX_AUTHORIZATION and message "Not authorized.", even when the input is malformed.
  • Server failure: when the submitter fails, the execution row is marked FAILED with that exception and the caller gets HotChocolate's masked "Unexpected Execution Error". A database failure writing the row is reported the same way.
mutation {
  operations {
    workQueue {
      runTrain(input: {
        trainName: "Trax.Samples.GameServer.Trains.Combat.IResolveCombatTrain"
        inputJson: "{\"attackerId\":\"player-1\",\"defenderId\":\"player-2\"}"
      }) {
        success
        id
        message
      }
    }
  }
}
FieldTypeRequiredDefaultDescription
trainNameString!YesN/ATrain interface FullName, as for queueTrain
inputJsonStringNonullJSON payload that deserializes to the train's input type. Property names match whatever their case and a property given twice is refused. null or blank is read as {}

Returns: OperationResponse. On success, id is the execution's metadata id (pass it to operations.execution or executionDetail), not a work queue id, and count is 1.

A host that exposes the operations mutations must register an IJobSubmitter, or it refuses to start; see AddTraxGraphQL.

cancelWorkQueueEntry

Cancels a queued entry. Only entries with status: QUEUED can be cancelled. Already-dispatched or already-cancelled entries return OperationResponse(success: false, ...) without modifying the row.

mutation {
  operations {
    workQueue {
      cancelWorkQueueEntry(id: 1234) { success message }
    }
  }
}
ParameterTypeRequiredDescription
idLong!YesThe work queue entry's database ID

Returns: OperationResponse.

cancelWorkQueueEntries

Cancels many queued entries in one round-trip (a single set-based UPDATE through the same IOperationsService.CancelWorkQueueEntriesAsync call as the dashboard). Only entries still QUEUED are affected; already-dispatched or already-cancelled ids in the list are skipped. Backs the dashboard's bulk-cancel selection.

mutation {
  operations {
    workQueue {
      cancelWorkQueueEntries(ids: [1234, 1235, 1236]) { success count message }
    }
  }
}
ParameterTypeRequiredDescription
ids[Long!]!Yes1 to 1000 work queue entry ids to cancel

Returns: OperationResponse. count is the number actually cancelled, zero included. An empty list, or more than 1000 ids, returns success: false ("No ids were given." for an empty one) and cancels nothing.


deadLetters (nested namespace)

The operations.deadLetters namespace exposes dead-letter requeue and acknowledge mutations: requeueDeadLetter, acknowledgeDeadLetter, batch variants (requeueDeadLetters, acknowledgeDeadLetters), and "all" variants (requeueAllDeadLetters, acknowledgeAllDeadLetters). The batch variants take 1 to 1000 ids; an empty or longer list returns success: false and changes nothing. See scheduler/dead-letters-and-cleanup for full details and examples.


OperationResponse

Shared response type for operations mutations.

FieldTypeDescription
successBoolean!Whether the operation succeeded
countIntNumber of affected records, for a mutation that acts on a set or patches fields; null otherwise
idLongThe one row the operation acted on, when there is one: the work queue entry queueTrain and requeueExecution created, the execution runTrain started, the group updateManifestGroup patched. null otherwise
messageStringHuman-readable status message