jssm
Version:
A Javascript finite state machine (FSM) with a terse DSL and a simple API. Most FSMs are one-liners. Fast, easy, powerful, well tested, typed with TypeScript, and visualizations. MIT License.
1,231 lines • 78.4 kB
TypeScript
declare type StateType = string;
import { JssmGenericState, JssmGenericConfig, JssmStateConfig, JssmTransition, JssmTransitionList, // JssmTransitionRule,
JssmMachineInternalState, JssmAllowsOverride, JssmStateDeclaration, JssmStateStyleKeyList, JssmLayout, JssmHistory, JssmSerialization, FslDirection, FslDirections, FslTheme, HookDescription, HookHandler, HookContext, HookResult, HookComplexResult, EverythingHookContext, EverythingHookHandler, PostEverythingHookHandler, JssmRng } from './jssm_types';
import { arrow_direction, arrow_left_kind, arrow_right_kind } from './jssm_arrow';
import { compile, make, wrap_parse } from './jssm_compiler';
import { seq, unique, find_repeated, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, gen_splitmix32, sleep } from './jssm_util';
import * as constants from './jssm_constants';
declare const shapes: string[], gviz_shapes: string[], named_colors: string[], state_name_chars: readonly {
from: string;
to: string;
}[], state_name_first_chars: readonly {
from: string;
to: string;
}[], action_label_chars: readonly {
from: string;
to: string;
}[];
import { version, build_time } from './version';
/*********
*
* An internal method meant to take a series of declarations and fold them into
* a single multi-faceted declaration, in the process of building a state. Not
* generally meant for external use.
*
* @internal
*
*/
declare function transfer_state_properties(state_decl: JssmStateDeclaration): JssmStateDeclaration;
/**
*
* Collapse a list of individual state-style key/value pairs into a single
* {@link JssmStateConfig} object, remapping FSL-style kebab-case keys to the
* camelCase field names the runtime uses.
*
* The parser emits state styling as a flat array like
* `[{ key: 'color', value: 'red' }, { key: 'line-style', value: 'dashed' }]`
* because that is the most natural shape for the grammar to produce. This
* helper runs once per style bucket during `Machine` construction to turn
* those arrays into the compact `{ color, lineStyle, ... }` objects the
* graph-rendering code expects.
*
* ```typescript
* state_style_condense([
* { key: 'color', value: 'red' },
* { key: 'shape', value: 'oval' },
* { key: 'line-style', value: 'dashed' }
* ]);
* // => { color: 'red', shape: 'oval', lineStyle: 'dashed' }
*
* state_style_condense(undefined);
* // => {}
* ```
*
* @param jssk The list of style keys to condense. `undefined` is accepted
* and yields an empty config.
*
* @param machine Optional `Machine` reference, used only so that any
* {@link JssmError} thrown can point at the offending machine in its
* diagnostic message.
*
* @returns A `JssmStateConfig` object containing every key from `jssk`
* remapped into its camelCase field.
*
* @throws {JssmError} If `jssk` is neither an array nor `undefined`, if any
* element is not an object, if the same key appears more than once, or if a
* key is not one of the recognized style names.
*
* @internal
*
*/
declare function state_style_condense(jssk: JssmStateStyleKeyList, machine?: any): JssmStateConfig;
/*******
*
* Core finite state machine class. Holds the full graph of states and
* transitions, the current state, hooks, data, properties, and all runtime
* behavior. Typically created via the {@link sm} tagged template literal
* rather than constructed directly.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`Red 'next' => Green 'next' => Yellow 'next' => Red;`;
* light.state(); // 'Red'
* light.action('next'); // true
* light.state(); // 'Green'
* ```
*
* @typeparam mDT The machine data type — the type of the value stored in
* `.data()`. Defaults to `undefined` when no data is used.
*
*/
declare class Machine<mDT> {
_state: StateType;
_states: Map<StateType, JssmGenericState>;
_edges: Array<JssmTransition<StateType, mDT>>;
_edge_map: Map<StateType, Map<StateType, number>>;
_named_transitions: Map<StateType, number>;
_actions: Map<StateType, Map<StateType, number>>;
_reverse_actions: Map<StateType, Map<StateType, number>>;
_reverse_action_targets: Map<StateType, Map<StateType, number>>;
_start_states: Set<StateType>;
_end_states: Set<StateType>;
_machine_author?: Array<string>;
_machine_comment?: string;
_machine_contributor?: Array<string>;
_machine_definition?: string;
_machine_language?: string;
_machine_license?: string;
_machine_name?: string;
_machine_version?: string;
_fsl_version?: string;
_raw_state_declaration?: Array<Object>;
_state_declarations: Map<StateType, JssmStateDeclaration>;
_data?: mDT;
_instance_name: string;
_rng_seed: number;
_rng: JssmRng;
_graph_layout: JssmLayout;
_dot_preamble: string;
_arrange_declaration: Array<Array<StateType>>;
_arrange_start_declaration: Array<Array<StateType>>;
_arrange_end_declaration: Array<Array<StateType>>;
_themes: FslTheme[];
_flow: FslDirection;
_has_hooks: boolean;
_has_basic_hooks: boolean;
_has_named_hooks: boolean;
_has_entry_hooks: boolean;
_has_exit_hooks: boolean;
_has_after_hooks: boolean;
_has_global_action_hooks: boolean;
_has_transition_hooks: boolean;
_has_forced_transitions: boolean;
_hooks: Map<string, HookHandler<mDT>>;
_named_hooks: Map<string, HookHandler<mDT>>;
_entry_hooks: Map<string, HookHandler<mDT>>;
_exit_hooks: Map<string, HookHandler<mDT>>;
_after_hooks: Map<string, HookHandler<mDT>>;
_global_action_hooks: Map<string, HookHandler<mDT>>;
_any_action_hook: HookHandler<mDT> | undefined;
_standard_transition_hook: HookHandler<mDT> | undefined;
_main_transition_hook: HookHandler<mDT> | undefined;
_forced_transition_hook: HookHandler<mDT> | undefined;
_any_transition_hook: HookHandler<mDT> | undefined;
_has_post_hooks: boolean;
_has_post_basic_hooks: boolean;
_has_post_named_hooks: boolean;
_has_post_entry_hooks: boolean;
_has_post_exit_hooks: boolean;
_has_post_global_action_hooks: boolean;
_has_post_transition_hooks: boolean;
_code_allows_override: JssmAllowsOverride;
_config_allows_override: JssmAllowsOverride;
_post_hooks: Map<string, HookHandler<mDT>>;
_post_named_hooks: Map<string, HookHandler<mDT>>;
_post_entry_hooks: Map<string, HookHandler<mDT>>;
_post_exit_hooks: Map<string, HookHandler<mDT>>;
_post_global_action_hooks: Map<string, HookHandler<mDT>>;
_post_any_action_hook: HookHandler<mDT> | undefined;
_post_standard_transition_hook: HookHandler<mDT> | undefined;
_post_main_transition_hook: HookHandler<mDT> | undefined;
_post_forced_transition_hook: HookHandler<mDT> | undefined;
_post_any_transition_hook: HookHandler<mDT> | undefined;
_pre_everything_hook: EverythingHookHandler<mDT> | undefined;
_everything_hook: EverythingHookHandler<mDT> | undefined;
_pre_post_everything_hook: PostEverythingHookHandler<mDT> | undefined;
_post_everything_hook: PostEverythingHookHandler<mDT> | undefined;
_property_keys: Set<string>;
_default_properties: Map<string, any>;
_state_properties: Map<string, any>;
_required_properties: Set<string>;
_history: JssmHistory<mDT>;
_history_length: number;
_state_style: JssmStateConfig;
_active_state_style: JssmStateConfig;
_hooked_state_style: JssmStateConfig;
_terminal_state_style: JssmStateConfig;
_start_state_style: JssmStateConfig;
_end_state_style: JssmStateConfig;
_state_labels: Map<string, string>;
_time_source: () => number;
_create_started: number;
_created: number;
_after_mapping: Map<string, [string, number]>;
_timeout_source: (Function: any, number: any) => number;
_clear_timeout_source: (h: any) => void;
_timeout_handle: number | undefined;
_timeout_target: string | undefined;
_timeout_target_time: number | undefined;
constructor({ start_states, end_states, initial_state, start_states_no_enforce, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, property_definition, state_property, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, history, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, allows_override, config_allows_override, rng_seed, time_source, timeout_source, clear_timeout_source }: JssmGenericConfig<StateType, mDT>);
/********
*
* Internal method for fabricating states. Not meant for external use.
*
* @internal
*
*/
_new_state(state_config: JssmGenericState): StateType;
/*********
*
* Get the current state of a machine.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const lswitch = jssm.from('on <=> off;');
* console.log( lswitch.state() ); // 'on'
*
* lswitch.transition('off');
* console.log( lswitch.state() ); // 'off'
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @returns The current state name.
*
*/
state(): StateType;
/*********
*
* Get the label for a given state, if any; return `undefined` otherwise.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const lswitch = jssm.from('a -> b; state a: { label: "Foo!"; };');
* console.log( lswitch.label_for('a') ); // 'Foo!'
* console.log( lswitch.label_for('b') ); // undefined
* ```
*
* See also {@link display_text}.
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param state The state to get the label for.
*
* @returns The label string, or `undefined` if no label is set.
*
*/
label_for(state: StateType): string;
/*********
*
* Get whatever the node should show as text.
*
* Currently, this means to get the label for a given state, if any;
* otherwise to return the node's name. However, this definition is expected
* to grow with time, and it is currently considered ill-advised to manually
* parse this text.
*
* See also {@link label_for}.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const lswitch = jssm.from('a -> b; state a: { label: "Foo!"; };');
* console.log( lswitch.display_text('a') ); // 'Foo!'
* console.log( lswitch.display_text('b') ); // 'b'
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param state The state to get display text for.
*
* @returns The label if one exists, otherwise the state's name.
*
*/
display_text(state: StateType): string;
/*********
*
* Get the current data of a machine.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const lswitch = jssm.from('on <=> off;', {data: 1});
* console.log( lswitch.data() ); // 1
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @returns A deep clone of the machine's current data value.
*
*/
data(): mDT;
/*********
*
* Get the current value of a given property name. Checks the current
* state's properties first, then falls back to the global default.
* Returns `undefined` if neither exists. For a throwing variant, see
* {@link strict_prop}.
*
* ```typescript
* const m = sm`property color default "grey"; a -> b;
* state b: { property color "blue"; };`;
*
* m.prop('color'); // 'grey' (default, because state is 'a')
* m.go('b');
* m.prop('color'); // 'blue' (state 'b' overrides the default)
* m.prop('size'); // undefined (no such property)
* ```
*
* @param name The relevant property name to look up.
*
* @returns The value behind the prop name, or `undefined` if not defined.
*
*/
prop(name: string): any;
/*********
*
* Get the current value of a given property name. If missing on the state
* and without a global default, throws a {@link JssmError}, unlike
* {@link prop}, which would return `undefined` instead.
*
* ```typescript
* const m = sm`property color default "grey"; a -> b;`;
*
* m.strict_prop('color'); // 'grey'
* m.strict_prop('size'); // throws JssmError
* ```
*
* @param name The relevant property name to look up.
*
* @returns The value behind the prop name.
*
* @throws {JssmError} If the property is not defined on the current state
* and has no default.
*
*/
strict_prop(name: string): any;
/*********
*
* Get the current value of every prop, as an object. If no current definition
* exists for a prop — that is, if the prop was defined without a default and
* the current state also doesn't define the prop — then that prop will be listed
* in the returned object with a value of `undefined`.
*
* ```typescript
* const traffic_light = sm`
*
* property can_go default true;
* property hesitate default true;
* property stop_first default false;
*
* Off -> Red => Green => Yellow => Red;
* [Red Yellow Green] ~> [Off FlashingRed];
* FlashingRed -> Red;
*
* state Red: { property stop_first true; property can_go false; };
* state Off: { property stop_first true; };
* state FlashingRed: { property stop_first true; };
* state Green: { property hesitate false; };
*
* `;
*
* traffic_light.state(); // Off
* traffic_light.props(); // { can_go: true, hesitate: true, stop_first: true; }
*
* traffic_light.go('Red');
* traffic_light.props(); // { can_go: false, hesitate: true, stop_first: true; }
*
* traffic_light.go('Green');
* traffic_light.props(); // { can_go: true, hesitate: false, stop_first: false; }
* ```
*
* @returns An object mapping every known property name to its current value
* (or `undefined` if the property has no default and the current state
* doesn't define it).
*
*/
props(): object;
/*********
*
* Check whether a given string is a known property's name.
*
* ```typescript
* const example = sm`property foo default 1; a->b;`;
*
* example.known_prop('foo'); // true
* example.known_prop('bar'); // false
* ```
*
* @param prop_name The relevant property name to look up
*
*/
known_prop(prop_name: string): boolean;
/*********
*
* List all known property names. If you'd also like values, use
* {@link props} instead. The order of the properties is not defined, and
* the properties generally will not be sorted.
*
* ```typescript
* const m = sm`property color default "grey"; property size default 1; a -> b;`;
*
* m.known_props(); // ['color', 'size']
* ```
*
* @returns An array of all property name strings defined on this machine.
*
*/
known_props(): string[];
/********
*
* Check whether a given state is a valid start state (either because it was
* explicitly named as such, or because it was the first mentioned state.)
*
* ```typescript
* import { sm, is_start_state } from 'jssm';
*
* const example = sm`a -> b;`;
*
* console.log( final_test.is_start_state('a') ); // true
* console.log( final_test.is_start_state('b') ); // false
*
* const example = sm`start_states: [a b]; a -> b;`;
*
* console.log( final_test.is_start_state('a') ); // true
* console.log( final_test.is_start_state('b') ); // true
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The name of the state to check
*
*/
is_start_state(whichState: StateType): boolean;
/********
*
* Check whether a given state is a valid start state (either because it was
* explicitly named as such, or because it was the first mentioned state.)
*
* ```typescript
* import { sm, is_end_state } from 'jssm';
*
* const example = sm`a -> b;`;
*
* console.log( final_test.is_start_state('a') ); // false
* console.log( final_test.is_start_state('b') ); // true
*
* const example = sm`end_states: [a b]; a -> b;`;
*
* console.log( final_test.is_start_state('a') ); // true
* console.log( final_test.is_start_state('b') ); // true
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The name of the state to check
*
*/
is_end_state(whichState: StateType): boolean;
/********
*
* Check whether a given state is final (either has no exits or is marked
* `complete`.)
*
* ```typescript
* import { sm, state_is_final } from 'jssm';
*
* const final_test = sm`first -> second;`;
*
* console.log( final_test.state_is_final('first') ); // false
* console.log( final_test.state_is_final('second') ); // true
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The name of the state to check for finality
*
*/
state_is_final(whichState: StateType): boolean;
/********
*
* Check whether the current state is final (either has no exits or is marked
* `complete`.)
*
* ```typescript
* import { sm, is_final } from 'jssm';
*
* const final_test = sm`first -> second;`;
*
* console.log( final_test.is_final() ); // false
* state.transition('second');
* console.log( final_test.is_final() ); // true
* ```
*
*/
is_final(): boolean;
/********
*
* Serialize the current machine, including all defining state but not the
* machine string, to a structure. This means you will need the machine
* string to recreate (to not waste repeated space;) if you want the machine
* string embedded, call {@link serialize_with_string} instead.
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param comment An optional comment string to embed in the serialized
* output for identification or debugging.
*
* @returns A {@link JssmSerialization} object containing the machine's
* current state, data, and timestamp.
*
*/
serialize(comment?: string | undefined): JssmSerialization<mDT>;
/** Get the graph layout direction (e.g. `'LR'`, `'TB'`). Set via the
* FSL `graph_layout` directive.
* @returns The layout string, or the default if not set.
*/
graph_layout(): string;
/** Get the Graphviz DOT preamble string, injected before the graph body
* during visualization. Set via the FSL `dot_preamble` directive.
* @returns The preamble string.
*/
dot_preamble(): string;
/** Get the machine's author list. Set via the FSL `machine_author` directive.
* @returns An array of author name strings.
*/
machine_author(): Array<string>;
/** Get the machine's comment string. Set via the FSL `machine_comment` directive.
* @returns The comment string.
*/
machine_comment(): string;
/** Get the machine's contributor list. Set via the FSL `machine_contributor` directive.
* @returns An array of contributor name strings.
*/
machine_contributor(): Array<string>;
/** Get the machine's definition string. Set via the FSL `machine_definition` directive.
* @returns The definition string.
*/
machine_definition(): string;
/** Get the machine's language (ISO 639-1). Set via the FSL `machine_language` directive.
* @returns The language code string.
*/
machine_language(): string;
/** Get the machine's license string. Set via the FSL `machine_license` directive.
* @returns The license string.
*/
machine_license(): string;
/** Get the machine's name. Set via the FSL `machine_name` directive.
* @returns The machine name string.
*/
machine_name(): string;
/** Get the machine's version string. Set via the FSL `machine_version` directive.
* @returns The version string.
*/
machine_version(): string;
/** Get the raw state declaration objects as parsed from the FSL source.
* @returns An array of raw state declaration objects.
*/
raw_state_declarations(): Array<Object>;
/** Get the processed state declaration for a specific state.
* @param which - The state to look up.
* @returns The {@link JssmStateDeclaration} for the given state.
*/
state_declaration(which: StateType): JssmStateDeclaration;
/** Get all processed state declarations as a Map.
* @returns A `Map` from state name to {@link JssmStateDeclaration}.
*/
state_declarations(): Map<StateType, JssmStateDeclaration>;
/** Get the FSL language version this machine was compiled under.
* @returns The FSL version string.
*/
fsl_version(): string;
/** Get the complete internal state of the machine as a serializable
* structure. Includes actions, edges, edge map, named transitions,
* reverse actions, current state, and states map.
* @returns A {@link JssmMachineInternalState} snapshot.
*/
machine_state(): JssmMachineInternalState<mDT>;
/*********
*
* List all the states known by the machine. Please note that the order of
* these states is not guaranteed.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const lswitch = jssm.from('on <=> off;');
* console.log( lswitch.states() ); // ['on', 'off']
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @returns An array of all state names in the machine.
*
*/
states(): Array<StateType>;
/** Get the internal state descriptor for a given state name.
* @param whichState - The state to look up.
* @returns The {@link JssmGenericState} descriptor.
* @throws {JssmError} If the state does not exist.
*/
state_for(whichState: StateType): JssmGenericState;
/*********
*
* Check whether the machine knows a given state.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const lswitch = jssm.from('on <=> off;');
*
* console.log( lswitch.has_state('off') ); // true
* console.log( lswitch.has_state('dance') ); // false
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state to be checked for existence.
*
* @returns `true` if the state exists, `false` otherwise.
*
*/
has_state(whichState: StateType): boolean;
/*********
*
* Lists all edges of a machine.
*
* ```typescript
* import { sm } from 'jssm';
*
* const lswitch = sm`on 'toggle' <=> 'toggle' off;`;
*
* lswitch.list_edges();
* [
* {
* from: 'on',
* to: 'off',
* kind: 'main',
* forced_only: false,
* main_path: true,
* action: 'toggle'
* },
* {
* from: 'off',
* to: 'on',
* kind: 'main',
* forced_only: false,
* main_path: true,
* action: 'toggle'
* }
* ]
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @returns An array of all {@link JssmTransition} edge objects.
*
*/
list_edges(): Array<JssmTransition<StateType, mDT>>;
/** Get the map of named transitions (transitions with explicit names).
* @returns A `Map` from transition name to edge index.
*/
list_named_transitions(): Map<StateType, number>;
/** List all distinct action names defined anywhere in the machine.
* @returns An array of action name strings.
*/
list_actions(): Array<StateType>;
/** Whether any actions are defined on this machine.
* @returns `true` if the machine has at least one action.
*/
get uses_actions(): boolean;
/** Whether any forced (`~>`) transitions exist in this machine.
* @returns `true` if at least one forced transition is defined.
*/
get uses_forced_transitions(): boolean;
/*********
*
* Check if the code that built the machine allows overriding state and data.
*
* @returns The override permission from the FSL source code.
*
*/
get code_allows_override(): JssmAllowsOverride;
/*********
*
* Check if the machine config allows overriding state and data.
*
* @returns The override permission from the runtime config.
*
*/
get config_allows_override(): JssmAllowsOverride;
/*********
*
* Check if a machine allows overriding state and data. Resolves the
* combined effect of code and config permissions — config may not be
* less strict than code.
*
* @returns The effective override permission.
*
*/
get allows_override(): JssmAllowsOverride;
/** List all available theme names.
* @returns An array of theme name strings.
*/
all_themes(): FslTheme[];
/** List the character ranges accepted by the FSL grammar in any but the
* first position of a state name (atom). Each entry is an inclusive
* `{from, to}` range of single Unicode characters.
*
* @returns An array of `{from, to}` inclusive character ranges.
*
* @example
* const m = sm`a -> b;`;
* m.all_state_name_chars().some(r => '+' >= r.from && '+' <= r.to); // true
*/
all_state_name_chars(): ReadonlyArray<{
from: string;
to: string;
}>;
/** List the character ranges accepted by the FSL grammar in the first
* position of a state name (atom). Narrower than
* {@link all_state_name_chars}: notably omits `+`, `(`, `)`, `&`, `#`, `@`.
*
* @returns An array of `{from, to}` inclusive character ranges.
*
* @example
* const m = sm`a -> b;`;
* m.all_state_name_first_chars().some(r => '+' >= r.from && '+' <= r.to); // false
*/
all_state_name_first_chars(): ReadonlyArray<{
from: string;
to: string;
}>;
/** List the character ranges accepted inside a single-quoted FSL action
* label without escaping. Space is allowed; the apostrophe `'` is
* explicitly excluded since it terminates the label.
*
* @returns An array of `{from, to}` inclusive character ranges.
*
* @example
* const m = sm`a -> b;`;
* m.all_action_label_chars().some(r => ' ' >= r.from && ' ' <= r.to); // true
* m.all_action_label_chars().some(r => "'" >= r.from && "'" <= r.to); // false
*/
all_action_label_chars(): ReadonlyArray<{
from: string;
to: string;
}>;
/** Get the active theme(s) for this machine. Always stored as an array
* internally; the union return type exists for setter compatibility.
* @returns The current theme or array of themes.
*/
get themes(): FslTheme | FslTheme[];
/** Set the active theme(s). Accepts a single theme name or an array.
* @param to - A theme name or array of theme names to apply.
*/
set themes(to: FslTheme | FslTheme[]);
/** Get the flow direction for graph layout (e.g. `'right'`, `'down'`).
* Set via the FSL `flow` directive.
* @returns The current flow direction.
*/
flow(): FslDirection;
/** Look up a transition's edge index by source and target state names.
* @param from - Source state name.
* @param to - Target state name.
* @returns The edge index in the edges array, or `undefined` if no
* such transition exists.
*/
get_transition_by_state_names(from: StateType, to: StateType): number;
/** Look up the full transition object for a given source→target pair.
* @param from - Source state name.
* @param to - Target state name.
* @returns The {@link JssmTransition} object, or `undefined` if none exists.
*/
lookup_transition_for(from: StateType, to: StateType): JssmTransition<StateType, mDT>;
/********
*
* List all transitions attached to the current state, sorted by entrance and
* exit. The order of each sublist is not defined. A node could appear in
* both lists.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.list_transitions(); // { entrances: [ 'yellow', 'off' ], exits: [ 'green', 'off' ] }
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose transitions to have listed
*
*/
list_transitions(whichState?: StateType): JssmTransitionList;
/********
*
* List all entrances attached to the current state. Please note that the
* order of the list is not defined. This list includes both unforced and
* forced entrances; if this isn't desired, consider
* {@link list_unforced_entrances} or {@link list_forced_entrances} as
* appropriate.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.list_entrances(); // [ 'yellow', 'off' ]
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose entrances to have listed
*
*/
list_entrances(whichState?: StateType): Array<StateType>;
/********
*
* List all exits attached to the current state. Please note that the order
* of the list is not defined. This list includes both unforced and forced
* exits; if this isn't desired, consider {@link list_unforced_exits} or
* {@link list_forced_exits} as appropriate.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.list_exits(); // [ 'green', 'off' ]
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose exits to have listed
*
*/
list_exits(whichState?: StateType): Array<StateType>;
/** Get the transitions available from a state for use by the probabilistic
* walk system.
*
* If any exit declares a `probability`, only those probability-bearing
* exits are returned, so that non-probability peers cannot dilute the
* declared distribution. If no exit declares a `probability`, every
* legal (non-forced) exit is returned, which `weighted_rand_select`
* treats as equal weight. Forced-only exits (`~>`) are always excluded,
* since they cannot be taken by an ordinary `transition()` call.
*
* Fixes StoneCypher/fsl#1325, in which the function previously returned
* every exit unconditionally — including forced-only exits and exits
* with no `probability`, which distorted the weighted distribution.
*
* @param whichState - The state to inspect.
* @returns An array of {@link JssmTransition} edges exiting the state,
* filtered as described above. May be empty.
* @throws {JssmError} If the state does not exist.
*/
probable_exits_for(whichState: StateType): Array<JssmTransition<StateType, mDT>>;
/** Take a single random transition from the current state, weighted by
* edge probabilities.
* @returns `true` if a transition was taken, `false` otherwise.
*/
probabilistic_transition(): boolean;
/** Take `n` consecutive probabilistic transitions and return the sequence
* of states visited (before each transition).
* @param n - Number of steps to walk.
* @returns An array of state names visited during the walk.
*/
probabilistic_walk(n: number): Array<StateType>;
/** Take `n` probabilistic steps and return a histograph of how many times
* each state was visited.
* @param n - Number of steps to walk.
* @returns A `Map` from state name to visit count.
*/
probabilistic_histo_walk(n: number): Map<StateType, number>;
/********
*
* List all actions available from this state. Please note that the order of
* the actions is not guaranteed.
*
* ```typescript
* import { sm } from 'jssm';
*
* const machine = sm`
* red 'next' -> green 'next' -> yellow 'next' -> red;
* [red yellow green] 'shutdown' ~> off 'start' -> red;
* `;
*
* console.log( machine.state() ); // logs 'red'
* console.log( machine.actions() ); // logs ['next', 'shutdown']
*
* machine.action('next'); // true
* console.log( machine.state() ); // logs 'green'
* console.log( machine.actions() ); // logs ['next', 'shutdown']
*
* machine.action('shutdown'); // true
* console.log( machine.state() ); // logs 'off'
* console.log( machine.actions() ); // logs ['start']
*
* machine.action('start'); // true
* console.log( machine.state() ); // logs 'red'
* console.log( machine.actions() ); // logs ['next', 'shutdown']
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose actions to list. Defaults to the
* current state.
*
* @returns An array of action names available from the given state.
*
*/
actions(whichState?: StateType): Array<StateType>;
/********
*
* List all states that have a specific action attached. Please note that
* the order of the states is not guaranteed.
*
* ```typescript
* import { sm } from 'jssm';
*
* const machine = sm`
* red 'next' -> green 'next' -> yellow 'next' -> red;
* [red yellow green] 'shutdown' ~> off 'start' -> red;
* `;
*
* console.log( machine.list_states_having_action('next') ); // ['red', 'green', 'yellow']
* console.log( machine.list_states_having_action('start') ); // ['off']
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The action to be checked for associated states
*
*/
list_states_having_action(whichState: StateType): Array<StateType>;
/** List all action names available as exits from a given state.
* @param whichState - The state to inspect. Defaults to the current state.
* @returns An array of action name strings.
* @throws {JssmError} If the state does not exist.
*/
list_exit_actions(whichState?: StateType): Array<StateType>;
/** List all action exits from a state with their probabilities.
* @param whichState - The state to inspect. Defaults to the current state.
* @returns An array of `{ action, probability }` objects.
* @throws {JssmError} If the state does not exist.
*/
probable_action_exits(whichState?: StateType): Array<any>;
/** Check whether a state has no incoming transitions (unreachable after start).
* @param whichState - The state to check.
* @returns `true` if the state has zero entrances.
* @throws {JssmError} If the state does not exist.
*/
is_unenterable(whichState: StateType): boolean;
/** Check whether any state in the machine is unenterable.
* @returns `true` if at least one state has no incoming transitions.
*/
has_unenterables(): boolean;
/** Check whether the current state is terminal (has no exits).
* @returns `true` if the current state has zero exits.
*/
is_terminal(): boolean;
/** Check whether a specific state is terminal (has no exits).
* @param whichState - The state to check.
* @returns `true` if the state has zero exits.
* @throws {JssmError} If the state does not exist.
*/
state_is_terminal(whichState: StateType): boolean;
/** Check whether any state in the machine is terminal.
* @returns `true` if at least one state has no exits.
*/
has_terminals(): boolean;
/** Check whether the current state is complete (every exit has an action).
* @returns `true` if the current state is complete.
*/
is_complete(): boolean;
/** Check whether a specific state is complete (every exit has an action).
* @param whichState - The state to check.
* @returns `true` if the state is complete.
* @throws {JssmError} If the state does not exist.
*/
state_is_complete(whichState: StateType): boolean;
/** Check whether any state in the machine is complete.
* @returns `true` if at least one state is complete.
*/
has_completes(): boolean;
/** Low-level hook registration. Installs a handler described by a
* {@link HookDescription} into the appropriate internal map. Prefer the
* convenience wrappers ({@link hook}, {@link hook_entry}, etc.) over
* calling this directly.
* @param HookDesc - A hook descriptor specifying kind, states, and handler.
*/
set_hook(HookDesc: HookDescription<mDT>): void;
/** Register a pre-transition hook on a specific edge. Fires before
* transitioning from `from` to `to`. If the handler returns `false`, the
* transition is blocked.
*
* ```typescript
* const m = sm`a -> b -> c;`;
* m.hook('a', 'b', () => console.log('a->b'));
* ```
*
* @param from - Source state name.
* @param to - Target state name.
* @param handler - Callback invoked before the transition.
* @returns `this` for chaining.
*/
hook(from: string, to: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on a specific action-labeled edge.
* @param from - Source state name.
* @param to - Target state name.
* @param action - The action label that triggers this hook.
* @param handler - Callback invoked before the transition.
* @returns `this` for chaining.
*/
hook_action(from: string, to: string, action: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on any edge triggered by a specific action.
* @param action - The action name to hook.
* @param handler - Callback invoked before any transition with this action.
* @returns `this` for chaining.
*/
hook_global_action(action: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on any action-driven transition.
* @param handler - Callback invoked before any action transition.
* @returns `this` for chaining.
*/
hook_any_action(handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on any standard (`->`) transition.
* @param handler - Callback invoked before any legal transition.
* @returns `this` for chaining.
*/
hook_standard_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on any main-path (`=>`) transition.
* @param handler - Callback invoked before any main transition.
* @returns `this` for chaining.
*/
hook_main_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on any forced (`~>`) transition.
* @param handler - Callback invoked before any forced transition.
* @returns `this` for chaining.
*/
hook_forced_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook on any transition regardless of kind.
* @param handler - Callback invoked before every transition.
* @returns `this` for chaining.
*/
hook_any_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Register a hook that fires when entering a specific state.
* @param to - The state being entered.
* @param handler - Callback invoked on entry.
* @returns `this` for chaining.
*/
hook_entry(to: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Register a hook that fires when leaving a specific state.
* @param from - The state being exited.
* @param handler - Callback invoked on exit.
* @returns `this` for chaining.
*/
hook_exit(from: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Register a hook that fires after leaving a specific state (post-exit).
* @param from - The state that was exited.
* @param handler - Callback invoked after exit completes.
* @returns `this` for chaining.
*/
hook_after(from: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on a specific edge. Fires after the transition
* from `from` to `to` has completed. Cannot block the transition.
* @param from - Source state name.
* @param to - Target state name.
* @param handler - Callback invoked after the transition.
* @returns `this` for chaining.
*/
post_hook(from: string, to: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on a specific action-labeled edge.
* @param from - Source state name.
* @param to - Target state name.
* @param action - The action label.
* @param handler - Callback invoked after the transition.
* @returns `this` for chaining.
*/
post_hook_action(from: string, to: string, action: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on any edge triggered by a specific action.
* @param action - The action name.
* @param handler - Callback invoked after any transition with this action.
* @returns `this` for chaining.
*/
post_hook_global_action(action: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on any action-driven transition.
* @param handler - Callback invoked after any action transition.
* @returns `this` for chaining.
*/
post_hook_any_action(handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on any standard (`->`) transition.
* @param handler - Callback invoked after any legal transition.
* @returns `this` for chaining.
*/
post_hook_standard_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on any main-path (`=>`) transition.
* @param handler - Callback invoked after any main transition.
* @returns `this` for chaining.
*/
post_hook_main_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on any forced (`~>`) transition.
* @param handler - Callback invoked after any forced transition.
* @returns `this` for chaining.
*/
post_hook_forced_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook on any transition regardless of kind.
* @param handler - Callback invoked after every transition.
* @returns `this` for chaining.
*/
post_hook_any_transition(handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook that fires after entering a specific state.
* @param to - The state that was entered.
* @param handler - Callback invoked after entry.
* @returns `this` for chaining.
*/
post_hook_entry(to: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Post-transition hook that fires after leaving a specific state.
* @param from - The state that was exited.
* @param handler - Callback invoked after exit.
* @returns `this` for chaining.
*/
post_hook_exit(from: string, handler: HookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook that fires **before** all other pre-hooks
* on every transition. If the handler returns `false`, the transition is
* blocked. The handler receives an {@link EverythingHookContext} whose
* `hook_name` is `'pre everything'`.
*
* ```typescript
* const m = sm`a -> b -> c;`;
* m.hook_pre_everything(({ hook_name }) => {
* console.log(`${hook_name} fired`);
* return true;
* });
* ```
*
* @param handler - Callback invoked before all other pre-hooks.
* @returns `this` for chaining.
*/
hook_pre_everything(handler: EverythingHookHandler<mDT>): Machine<mDT>;
/** Register a pre-transition hook that fires **after** all other pre-hooks
* on every transition. If the handler returns `false`, the transition is
* blocked. The handler receives an {@link EverythingHookContext} whose
* `hook_name` is `'everything'`.
*
* ```typescript
* const m = sm`a -> b -> c;`;
* m.hook_everything(({ hook_name }) => {
* console.log(`${hook_name} fired`);
* return true;
* });
* ```
*
* @param handler - Callback invoked after all other pre-hooks.
* @returns `this` for chaining.
*/
hook_everything(handler: EverythingHookHandler<mDT>): Machine<mDT>;
/** Register a post-transition hook that fires **after** all other
* post-hooks on every transition. Cannot block the transition. The
* handler receives an {@link EverythingHookContext} whose `hook_name` is
* `'post everything'`.
*
* ```typescript
* const m = sm`a -> b -> c;`;
* m.hook_post_everything(({ hook_name }) => {
* console.log(`${hook_name} fired`);
* });
* ```
*
* @param handler - Callback invoked after all other post-hooks.
* @returns `this` for chaining.
*/
hook_post_everything(handler: PostEverythingHookHandler<mDT>): Machine<mDT>;
/** Register a post-transition hook that fires **before** all other
* post-hooks on every transition. Cannot block the transition. The
* handler receives an {@link EverythingHookContext} whose `hook_name` is
* `'pre post everything'`.
*
* ```typescript
* const m = sm`a -> b -> c;`;
* m.hook_pre_post_everything(({ hook_name }) => {
* console.log(`${hook_name} fired`);
* });
* ```
*
* @param handler - Callback invoked before all other post-hooks.
* @returns `this` for chaining.
*/
hook_pre_post_everything(handler: PostEverythingHookHandler<mDT>): Machine<mDT>;
/** Get the current RNG seed used for probabilistic transitions.
* @returns The numeric seed value.
*/
get rng_seed(): number;
/** Set the RNG seed. Pass `undefined` to reseed from the current time.
* Resets the internal PRNG so subsequent probabilistic operations use the
* new seed.
* @param to - The seed value, or `undefined` for time-based seeding.
*/
set rng_seed(to: number | undefined);
/** Get all edges between two states (there can be multiple with
* different actions).
* @param from - Source state name.
* @param to - Target state name.
* @returns An array of matching {@link JssmTransition} objects.
*/
edges_between(from: string, to: string): JssmTransition<StateType, mDT>[];
/*********
*
* Replace the current state and data with no regard to the graph.
*
* ```typescript
* import { sm } from 'jssm';
*
* const machine = sm`a -> b -> c;`;
* console.log( machine.state() ); // 'a'
*
* machine.go('b');
* machine.go('c');
* console.log( machine.state() ); // 'c'
*
* machine.override('a');
* console.log( machine.state() ); // 'a'
* ```
*
*/
override(newState: StateType, newData?: mDT | undefined): void;
/*********
*
* Shared transition core used by {@link transition}, {@link force_transition},
* and {@link action}. Runs validation, fires the full hook pipeline (pre-
* everything, any-action, after, any-transition, exit, named, basic,
* edge-type, entry, everything), commits the new state if nothing
* rejected, and returns whether the transition succeeded.
*
* Not meant for external use. Call one of the public