Reference

Generic state-machine mutations

Source GenericContracts.cs, GenericTrains.cs, StateMachineOptions.cs

Six mutations under dispatch { stateMachine { ... } } that take the machine as a runtime argument (the machine field) and reach every registered user-owned machine. They are not in the schema unless the host opts in. A machine's own typed operations are the machines root; moving a client from these to it is covered in Generic State-Machine Mutations.

Opt-in

CallEffect
StateMachineOptions.ExposeGenericStateMachineMutations(o => o.Policy = "..."), in the configure argument of AddStateMachinesServes the six mutations, each behind GenericStateMachineMutationOptions.Policy.
The same call with no Policy, or a blank oneAddStateMachines throws InvalidOperationException.
No callNone of the six is in the schema. The trains behind them are still mediator routes, so host code and a machine's typed surface dispatch them.

While the opt-in is on, every user-owned machine is reachable through the mutations, so each must declare a rule for every event, its start and its reads, under the same startup check as a machine marked [TraxMachine].

Authorization

CheckRefusal
Each mutation's train carries [TraxAuthorize], and the opt-in's policy gates its fieldTRAX_AUTHORIZATION GraphQL error ("Not authorized."), before the mutation runs
ISnapshotPrincipal.CurrentUserKey is nullUNAUTHENTICATED problem
The machine's own rule for the event, start or readFORBIDDEN problem

Mutations

MutationTrainDoes
startSnapshotIStartSnapshotCreates an instance from the machine's declared start. The server mints the id and builds the first snapshot.
saveSnapshotISaveSnapshotSoft path: validates a whole client snapshot and stores it. Refused as SAVE_NOT_ALLOWED for a machine with server-side behaviour (a gated event or start, a timer, an invoked train, an effect).
advanceSnapshotIAdvanceSnapshotAuthoritative path: fires a trigger on the stored draft, server-side.
loadSnapshotILoadSnapshotReads the caller's stored draft, held to the machine's get rule. A missing draft, and one the rule does not let the caller read, answer the same problem.
sendSnapshotISendSnapshotRuns the machine's one irreversible effect exactly once and advances with its receipt.
discardSnapshotIDiscardSnapshotDeletes one of the caller's instances, cancelling a run its state invoked first.

All six are mutations, loadSnapshot included. Each takes one input argument and answers output { ... }. The snapshot crosses the wire as a string of canonical JSON in both directions. Each train withholds its output from the lifecycle hooks, so the broadcaster and SignalR never carry an instance's snapshot.

Inputs

! is required.

Input typeFields
StartSnapshotInputmachine: String!, requestId: String!, input: String (the start input as a JSON object), sensitiveInput: String, schemaHash: String
SaveSnapshotInputmachine: String!, id: UUID!, snapshot: String! (the whole snapshot as JSON), schemaHash: String
AdvanceSnapshotInputmachine: String!, id: UUID!, trigger: String! (a trigger name, "Coin"), input: String (the trigger input as JSON), sensitiveInput: String, requestId: String, schemaHash: String, clientResult: String
LoadSnapshotInputmachine: String!, id: UUID!, schemaHash: String
SendSnapshotInputmachine: String!, id: UUID!, requestId: String, schemaHash: String
DiscardSnapshotInputmachine: String!, id: UUID!, requestId: String!
FieldMeaning
sensitiveInputFields of the input recorded masked, as a JSON object merged into input before it is applied. Every copy of the run's input Trax keeps writes them as {"_redacted": true}. A field must not be in both.
schemaHashThe client's machine schema hash. A mismatch is refused as SCHEMA_MISMATCH before anything runs. Absent: no check. See runtime integrity.
clientResultThe snapshot the client's twin computed for the advance. A different server result is refused as CLIENT_DIVERGENCE and nothing is written. Absent: no check.

Outputs

OutputFields
startSnapshotid, created (false on a retry of the start that created it), snapshot, revision, dueAt, problem
saveSnapshot, loadSnapshotsnapshot, revision, rowId, dueAt, problem
advanceSnapshot, sendSnapshotsnapshot, revision, rowId, dueAt, dueAtKnown, problem
discardSnapshotdiscarded (true when the instance is gone, already or by this request; another user's id answers the same), problem

Exactly one of snapshot and problem is set (discarded and problem for a discard). When problem is set, nothing was written. revision orders the writes of one rowId, which names one incarnation of the instance.

Request ids

MutationrequestId
startSnapshotrequired. The same id and input return the instance the first request created; the same id with another input is REQUEST_ID_REUSED.
advanceSnapshotoptional. The same id with the same trigger replays the result instead of firing again; with another trigger, REQUEST_ID_REUSED.
sendSnapshotoptional; send:{id} when absent. Shares one id space with advanceSnapshot, so an id an advance used is REQUEST_ID_REUSED, before the effect runs.
discardSnapshotrequired.

A blank id, one holding a control character, or one longer than 200 characters is INVALID_REQUEST_ID. The full matching rule is under persistence ports.

Limits

A snapshot, a trigger or start input, a clientResult, or an advanced snapshot larger than 64 KiB (SnapshotLimits.MaxSnapshotBytes) is TOO_LARGE, and nothing is written.

sendSnapshot

sendSnapshot takes no trigger. It fires the one transition the machine binds its effect to, and only from that transition's source state; from any other state it is NO_TRANSITION. The receipt the effect returns reaches the transition's reducer as input["receipt"]. An effect that throws is DELIVERY_FAILED, and the draft is not advanced, so the send can be retried.

Example

mutation Advance($i: AdvanceSnapshotInput!) {
  dispatch { stateMachine { advanceSnapshot(input: $i) {
    output { snapshot problem { code message } }
  } } }
}
{ "i": { "machine": "turnstile", "id": "33333333-3333-3333-3333-333333333333",
         "trigger": "Coin", "input": "{\"coin\":\"quarter\"}" } }
{ "data": { "dispatch": { "stateMachine": { "advanceSnapshot": { "output": {
  "snapshot": "{\"machine\":\"turnstile\",\"version\":1,\"state\":\"Unlocked\",\"context\":{\"paidWith\":\"quarter\"}}",
  "problem": null } } } } } }

SDK Reference

AddStateMachines | Persistence ports | Runtime integrity

See also