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 nesteddeadLettersnamespace for requeue/acknowledge). Off by default, opt in withExposeOperationMutations()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.RunandGraphQLOperation.Queueare passed), the mutation accepts an optionalmode: ExecutionModeparameter (RUNorQUEUE, defaultRUN) and an optionalpriority: Int. - Run only: the mutation always runs synchronously. No
modeorpriorityparameters. - Queue only: the mutation always queues. Has
prioritybut nomodeparameter.
Naming Convention
The mutation name is derived from the train's service interface name (or overridden via [TraxMutation(Name = "...")]):
- Strip the
Iprefix - Strip the
Trainsuffix - 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
}| Field | Type | When Populated |
|---|---|---|
externalId | String! | Always present. Identifies the execution or work queue entry |
metadataId | Long | RUN mode. Metadata ID of the completed execution |
output | {OutputType} | RUN mode, only for trains with non-Unit output |
workQueueId | Long | QUEUE 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:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
input | {TrainName}Input! | Yes | N/A | Strongly-typed input matching the train's input record |
mode | ExecutionMode | No | RUN | Whether to run synchronously (RUN) or queue for async execution (QUEUE) |
priority | Int | No | 0 | Dispatch 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
input | {TrainName}Input! | Yes | Strongly-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.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
input | {TrainName}Input! | Yes | N/A | Strongly-typed input matching the train's input record |
priority | Int | No | 0 | Dispatch 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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
externalId | String! | Yes | The 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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
externalId | String! | Yes | The manifest's external ID |
delay | TimeSpan! | Yes | How 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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
externalId | String! | Yes | The manifest's external ID |
Returns: OperationResponse
enableManifest
Re-enables a previously disabled manifest.
mutation {
operations {
enableManifest(externalId: "order-processing-daily") {
success
message
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
externalId | String! | Yes | The 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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
externalId | String! | Yes | The 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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | Long! | Yes | The 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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | Long! | Yes | The 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 }
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
id | Long! | Yes | The 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 }
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
ids | [Long!]! | Yes | 1 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 }
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
ids | [Long!]! | Yes | 1 to 1000 manifest database ids |
enabled | Boolean! | Yes | The 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 }
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
id | Long! | Yes | The 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 }
}
}| Field | Type | Description |
|---|---|---|
isEnabled | Boolean | Enable/disable the manifest |
maxRetries | Int | Retry budget |
priority | Int | Dispatch priority |
timeoutSeconds / clearTimeout | Int / Boolean | Per-execution timeout; clearTimeout: true removes it |
scheduleType | ScheduleType | Schedule type |
cronExpression | String | Cron expression |
intervalSeconds | Int | Interval |
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
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
fullName | String! | Yes | The effect factory's full type name, as operations.effects reports it in fullName. Matched exactly. |
enabled | Boolean! | Yes | true 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 }
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
input | UpdateSchedulerConfigInput! | Yes | Patch 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.
| Field | Type | Description |
|---|---|---|
manifestManagerEnabled | Boolean | |
jobDispatcherEnabled | Boolean | |
manifestManagerPollingInterval | TimeSpan | 1 second to 30 days |
jobDispatcherPollingInterval | TimeSpan | 1 second to 30 days |
maxActiveJobs | Int | At least 1 |
clearMaxActiveJobs | Boolean | When true, sets maxActiveJobs to null |
defaultMaxRetries | Int | Zero or more |
failureCountWindow | TimeSpan | 1 second to ten years. How far back failed runs count toward retry backoff and MaxRetries |
defaultRetryDelay | TimeSpan | Zero to ten years |
retryBackoffMultiplier | Float | At least 1 |
maxRetryDelay | TimeSpan | Zero to ten years |
defaultJobTimeout | TimeSpan | 1 second to ten years |
stalePendingTimeout | TimeSpan | 1 second to ten years |
recoverStuckJobsOnStartup | Boolean | |
deadLetterRetentionPeriod | TimeSpan | Zero to ten years |
autoPurgeDeadLetters | Boolean | |
localWorkerCount | Int | 1 to 256. Ignored when this process runs no local worker pool (see ConfigureLocalWorkers). Applies when the worker pool next starts |
clearLocalWorkerCount | Boolean | Resets localWorkerCount to Environment.ProcessorCount |
metadataCleanupInterval | TimeSpan | 1 second to 30 days. Ignored when metadata cleanup is not configured |
metadataCleanupRetention | TimeSpan | 1 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 }
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
id | Long! | Yes | The manifest group's database ID |
input | UpdateManifestGroupInput! | Yes | Patch payload (fields below) |
UpdateManifestGroupInput fields
| Field | Type | Default | Description |
|---|---|---|---|
maxActiveJobs | Int | null | New per-group concurrency limit, at least 1. null means "no change". To clear the limit, use clearMaxActiveJobs instead |
clearMaxActiveJobs | Boolean | false | When true, sets maxActiveJobs to null (removes the per-group limit). Takes precedence if both this and maxActiveJobs are set |
priority | Int | null | New priority, 0 to 31. null = no change |
isEnabled | Boolean | null | Whether 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 }
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
ids | [Long!]! | Yes | 1 to 1000 manifest group ids |
enabled | Boolean! | Yes | The 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 }
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
enabled | Boolean! | Yes | The 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
}
}
}
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
trainName | String! | Yes | N/A | Train interface FullName, the fullName that operations.trains returns (not its serviceTypeName, which is a display name) |
inputJson | String | No | null | JSON 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 |
priority | Int | No | 0 | Dispatch priority 0-31. Values outside that range are clamped |
scheduledAt | DateTime | No | null | Earliest 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: falsewith amessage, and no execution row is written. An unknowntrainName, 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 beforeinputJsonis read. A caller who may not run the train gets a GraphQL error with codeTRAX_AUTHORIZATIONand message"Not authorized.", even when the input is malformed. - Server failure: when the submitter fails, the execution row is marked
FAILEDwith 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
}
}
}
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
trainName | String! | Yes | N/A | Train interface FullName, as for queueTrain |
inputJson | String | No | null | JSON 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 }
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
id | Long! | Yes | The 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 }
}
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
ids | [Long!]! | Yes | 1 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.
| Field | Type | Description |
|---|---|---|
success | Boolean! | Whether the operation succeeded |
count | Int | Number of affected records, for a mutation that acts on a set or patches fields; null otherwise |
id | Long | The 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 |
message | String | Human-readable status message |