Skip to content

Commit

Permalink
feat(fsm): rewrite with signal power
Browse files Browse the repository at this point in the history
Special thanks to

Co-authored-by: David Khourshid <davidkpiano@gmail.com>
  • Loading branch information
AliMD and davidkpiano committed Feb 24, 2023
1 parent de42522 commit 01a1651
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 27 deletions.
1 change: 1 addition & 0 deletions core/fsm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"dependencies": {
"@alwatr/logger": "^0.29.0",
"@alwatr/type": "^0.29.0",
"@alwatr/signal": "^0.29.0",
"tslib": "^2.5.0"
}
}
79 changes: 52 additions & 27 deletions core/fsm/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,99 @@
import {createLogger, globalAlwatr, type AlwatrLogger} from '@alwatr/logger';
import {contextConsumer, type DispatchOptions} from '@alwatr/signal';
import {dispatch} from '@alwatr/signal/core.js';

import type {StringifyableRecord} from '@alwatr/type';
import type {Stringifyable, StringifyableRecord} from '@alwatr/type';

globalAlwatr.registeredList.push({
name: '@alwatr/fsm',
version: _ALWATR_VERSION_,
});

export interface StateConfig extends StringifyableRecord {
export interface StateConfig<TEventId extends string, TState extends string> extends StringifyableRecord {
/**
* An object mapping event name (keys) to state name
* An object mapping eventId (keys) to state.
*/
on?: Record<string, string | undefined>;
on?: Record<TEventId, TState | undefined>;
}

export interface MachineConfig extends StringifyableRecord {
export interface MachineConfig<TState extends string, TEventId extends string, TContext extends Stringifyable>
extends StringifyableRecord {
/**
* Machine ID.
* Machine ID (It is used in the state change signal identifier, so it must be unique).
*/
id: string;

/**
* Initial state name.
* Initial state.
*/
initial: string;
initial: TState;

/**
* Initial context.
*/
context: TContext;

/**
* States list
*/
states: Record<string, StateConfig | undefined>;
states: Record<TState | '_', StateConfig<TEventId, TState> | undefined>;
}

export class FiniteStateMachine {
currentState;
export class FiniteStateMachine<
TState extends string = string,
TEventId extends string = string,
TContext extends Stringifyable = StringifyableRecord
> {
stateConsumer;
context: TContext;

protected _logger: AlwatrLogger;

constructor(public config: MachineConfig) {
get gotState(): TState {
return this.stateConsumer.getValue() ?? this.config.initial;
}

protected setState(value: TState, options?: DispatchOptions): void {
dispatch(this.stateConsumer.id, value, options);
}

constructor(public readonly config: Readonly<MachineConfig<TState, TEventId, TContext>>) {
this._logger = createLogger(`alwatr/fsm:${config.id}`);
this._logger.logMethodArgs('constructor', config);
this.currentState = config.initial;
if (this.currentState in config.states === false) {
this.context = config.context;
this.stateConsumer = contextConsumer.bind<TState>('finite-state-machine-' + this.config.id);
this.setState(config.initial);
if (!config.states[config.initial]) {
this._logger.error('constructor', 'invalid_initial_state', config);
}
}

/**
*
* @param eventName
* Machine transition.
*/
transition(eventName: string): string {
const nextState = this.config.states[this.currentState]?.on?.[eventName] ??
this.config.states._?.on?.[eventName];
transition(toEventId: TEventId, newContext?: TContext, options?: DispatchOptions): TState | null {
const state = this.gotState;
const nextState = this.config.states[state]?.on?.[toEventId] ?? this.config.states._?.on?.[toEventId];

this._logger.logMethodFull('transition', eventName, nextState);
this._logger.logMethodFull('transition', {toEventId, newContext}, nextState);

if (nextState) {
this.currentState = nextState;
if (newContext !== undefined) {
this.context = newContext;
}
else {

if (nextState == null) {
this._logger.incident(
'transition',
'invalid_target_state',
'Defined target state for this event not found in state config',
{
eventName,
[this.currentState]: {...this.config.states._?.on, ...this.config.states[this.currentState]?.on},
eventName: toEventId,
[state]: {...this.config.states._?.on, ...this.config.states[state]?.on},
},
);
return null;
}

return this.currentState;
}
this.setState(nextState, options);
return nextState;
}
1 change: 1 addition & 0 deletions core/fsm/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"references": [
{"path": "../logger"},
{"path": "../type"},
{"path": "../signal"},
]
}

0 comments on commit 01a1651

Please sign in to comment.