State Engines
Competition Factory engines share a state which contains one or more tournamentRecords and any factory functions which have been imported from Governors.
All factory engines provide logging and middleware services which resolve parameters such as events and drawDefinitions from identifiers.
Mutation engines provide subscription/notification functionality.
- askEngine: used to synchronously query
tournamentRecordsheld in state. It does not notify subscribers, but it does not enforce read-only use either: it runs whatever method it is given. - syncEngine: used to synchronously mutate
tournamentRecordsheld in state. - asyncEngine: used to asynchronously mutate
tournamentRecordsheld in state.
All engine methods which make a mutation return either { success: true } or { error }
Engine state
By default, a "deep copy" of tournamentRecords is made as they are loaded into shared state.
engine.setState(tournamentRecord)engine.setState(tournamentRecords)
This behavior can be overridden such that engines operate directly on loaded tournamentRecords.
engine.setState(records, deepCopyOption?: boolean, deepCopyAttributes?: object)
deepCopyOption is a boolean; false loads the caller's own objects without copying. It is also
written to the process-wide deep-copy setting, so it affects later copies too — see
Global State. deepCopyAttributes configures how copies are
made (threshold, ignore, toJSON, stringify).
Invoking Factory Functions
All state engines can be used to invoke factory functions in multiple ways:
- execute: invoke a factory function directly, parameters passed into
executemethod - import and execute: invoke an imported function as an engine attribute
- import and execute by name: invoke an imported function by reference
Mutation engines can submit a queue of functions to be executed in sequence.
- executionQueue: invoke an imported function as part of a queue of functions
Examples
For each of the following examples askEngine and the getParticipants method are first imported from the factory.
import { askEngine, participantGovernor } from 'tods-competition-factory';
const { getParticipants } = participantGovernor;
// load a tournamentRecord into state
askEngine.setState(tournamentRecord);
Execute
Function is invoked directly, parameters passed into execute method.
const { participants } = askEngine.execute({ getParticipants, ...params });
Import and Execute
Function is imported and invoked as an engine attribute.
askEngine.importMethods({ getParticipants });
const { participants } = askEngine.getParticipants({ withIndividualParticipants: true });
Import and Execute by Name
Function is imported and invoked by reference.
askEngine.importMethods({ getParticipants });
const { participants } = askEngine.execute({
params: { participantFilters: { participantTypes: [PAIR] } },
method: 'getParticipants',
});
Execution Queue
The executionQueue method accepts an array of imported governor methods and associated parameters,
allowing for multiple queries or mutations in a single API call, which is significant if a client is making a
request of a server and the server needs to prepare context by loading a tournament record.
Queue notifications are deferred until successful completion. The rollback option controls whether an error also restores earlier writes:
| Result | Record state | Queued notices |
|---|---|---|
| All directives succeed | Writes retained | Delivered after the queue |
| Refusal, rollback disabled | Earlier writes may remain | Not delivered by this call; they stay buffered and the next successful call delivers them |
| Refusal, rollback enabled | Restored from the rollback snapshot | Queued notices discarded |
Subscribers run synchronously when dispatched; batching does not make slow callbacks nonblocking. Restoring records cannot undo external work already performed by subscribers or other host code. A single rollback-enabled queue is one transaction boundary; separate calls do not share it. See Mutation Engines for the canonical rollback and ownership contract.
syncEngine.importMethods({ getParticipants });
const result = syncEngine.executionQueue([
{ method: 'getParticipants', params: { participantFilters: { participantTypes: [INDIVIDUAL] } } },
]);
// { success, results } — or an error object if any directive failed
if (result.error) throw new Error(result.error.message);
const { participants } = result.results[0];
devContext
devContext is a property of engine state which is used to store any data which is useful for development purposes.
- The devContext value can be either a boolean or an object. It is unset by default.
- When the devContext value is truthy (
trueor any object), thetry {} catch {}block is NOT used in method invocation: an exception thrown inside a method propagates to the caller. - When devContext is unset or
false, an exception is caught and returned as an{ error }result. - When devContext is an object it is also used to configure engine logging.
askEngine.devContext(true); // errors are THROWN
askEngine.devContext(false); // default behaviour: errors are caught and returned
Bypassing State
Passing tournamentRecord as a parameter makes the passed record the only record in scope for that call: a temporary
{ [tournamentId]: tournamentRecord } replaces the loaded tournamentRecords, which are not referenced.
askEngine.getParticipants({ tournamentRecord });
It is not an unconditional bypass. The parameter middleware then resolves the call in this order:
_middleware: false— params are passed through untouched, and the method receives the supplied record.params.tournamentId, if given, must be the supplied record's id; otherwiseMISSING_TOURNAMENT_RECORD.drawId/eventIdare resolved against the supplied record only (an unknowneventIdisEVENT_NOT_FOUND).- The effective tournamentId is
params.tournamentId(or the one found viadrawId/eventId), else the engine's active tournamentId. If that id is not the supplied record's, the call fails withMISSING_TOURNAMENT_RECORD. If there is no effective tournamentId, or it is the supplied record's, the method receives the supplied record.
So with a different tournament active in the engine, pass tournamentId (or a drawId / eventId inside the supplied
record) alongside tournamentRecord.