Skip to main content

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 tournamentRecords held 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 tournamentRecords held in state.
  • asyncEngine: used to asynchronously mutate tournamentRecords held in state.
note

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 execute method
  • 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:

ResultRecord stateQueued notices
All directives succeedWrites retainedDelivered after the queue
Refusal, rollback disabledEarlier writes may remainNot delivered by this call; they stay buffered and the next successful call delivers them
Refusal, rollback enabledRestored from the rollback snapshotQueued 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 (true or any object), the try {} 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:

  1. _middleware: false — params are passed through untouched, and the method receives the supplied record.
  2. params.tournamentId, if given, must be the supplied record's id; otherwise MISSING_TOURNAMENT_RECORD.
  3. drawId / eventId are resolved against the supplied record only (an unknown eventId is EVENT_NOT_FOUND).
  4. The effective tournamentId is params.tournamentId (or the one found via drawId / eventId), else the engine's active tournamentId. If that id is not the supplied record's, the call fails with MISSING_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.