Situation: My objective is to devise a strategy for building scalable state machines in TypeScript using the TypeState library. TypeState offers a typesafe state machine for Typescript, which while not directly related to my current issue, serves as a good example of what I am aiming for.
Challenge: I am facing difficulties in creating a flexible pattern for extending enums in TypeScript and implementing them within interface and class declarations.
Objective: The following pseudo code demonstrates the blueprint of the pattern that I want to achieve.
1) Establish a base enum called States
2) Expand on enum States
by adding more states to create enum ExtendedStates
3) Define ParentInterface
utilizing States
and a typed state machine
4) Extend ParentInterface
with ChildInterface
, overriding States
with ExtendedStates
5) Implement ParentInterface
in class Parent
6) Enhance class Parent
into class Child
implementing ChildInterface
7) Ensure that broadcastState()
can be called from either class to retrieve the current state.
I have successfully utilized this pattern in other programming languages, and I would appreciate guidance on understanding TypeScript limitations and possible alternative patterns to achieve the same outcome.
import {TypeState} from "typestate";
enum States {
initialState
}
// finding an alternative to extend since it's not available on enum
enum ExtendedStates extends States {
AdditionalState
}
/////////////////////////////////////////
// works without any issues
interface ParentInterface {
fsm: TypeState.FiniteStateMachine<States>;
states: typeof States;
message: string;
}
// incorrectly extends ParentInterface, mismatching types of fsm/states
interface ChildInterface extends ParentInterface {
fsm: TypeState.FiniteStateMachine<ExtendedStates>;
states: typeof ExtendedStates;
}
/////////////////////////////////////////
class Parent implements ParentInterface {
public fsm: TypeState.FiniteStateMachine<States>;
public states: typeof States;
public message: string = "The current state is: ";
constructor(state: States | undefined) {
state = state ? state : this.states.initialState;
this.fsm = new TypeState.FiniteStateMachine(state);
this.broadcastCurrentState();
}
public broadcastCurrentState(): void {
console.log(this.message + this.fsm.currentState);
}
}
class Child extends Parent implements ChildInterface {
public fsm: TypeState.FiniteStateMachine<ExtendedStates>;
public states: typeof ExtendedStates;
constructor(state: ExtendedStates | undefined) {
state = state ? state : this.states.initialState;
this.fsm = new TypeState.FiniteStateMachine(ExtendedStates);
this.broadcastCurrentState();
}
}
Best Attempt So Far
import {TypeState} from "typestate";
enum States {
initialState
}
enum ExtendedStates {
initialState,
extendedState
}
class Parent {
public fsm: TypeState.FiniteStateMachine<States>;
public states: typeof States;
public message: string = "The current state is: ";
// T is declared but never used
constructor(state: <T> | undefined) {
state = state ? state : this.states.initialState;
// cannot find name T
this.fsm = new TypeState.FiniteStateMachine<T>(state);
this.broadcastCurrentState();
}
public broadcastCurrentState(): void {
console.log(this.message + this.fsm.currentState);
}
}
// incompatible types for fsm
class Child extends Parent {
public fsm: TypeState.FiniteStateMachine<ExtendedStates>;
public states: typeof ExtendedStates;
constructor(state: ExtendedStates | undefined) {
// Parameter not assignable to type <T>
super(state);
}
}
This attempt comes close to the desired outcome but fails to compile, resulting in excessive enum duplication. It also lacks interfaces, which are optional but offer additional safety measures.
I am eager to hear your insights. I believe this approach holds considerable potential, and there may be a simple solution that I am overlooking.