Hone logo
Hone
Problems

Type-Safe State Machine in TypeScript

Building state machines is a common pattern in software development, particularly for managing complex workflows or user interfaces. This challenge asks you to create a type-safe state machine in TypeScript, ensuring that state transitions are valid and preventing runtime errors related to incorrect state changes. This will involve defining states and transitions with strong typing, making your code more robust and maintainable.

Problem Description

You are tasked with creating a generic StateMachine class in TypeScript that enforces type safety for states and transitions. The state machine should be configurable with a set of states and a set of valid transitions between those states. The class should provide methods for transitioning between states, and it should throw an error if an invalid transition is attempted.

Key Requirements:

  • Type-Safe States: The state machine must use a union type to define the possible states.
  • Type-Safe Transitions: Transitions between states must be defined with a type that specifies the source and destination states.
  • Error Handling: Attempting an invalid transition should result in a clear error message.
  • Current State Tracking: The state machine must maintain and expose the current state.
  • Generic: The state machine should be generic, allowing it to be used with different state types.

Expected Behavior:

  • The StateMachine class should be instantiated with an initial state and a set of valid transitions.
  • The transition(newState: T) method should update the current state to newState if the transition is valid.
  • If the transition is invalid (i.e., there's no defined transition from the current state to newState), the transition method should throw an error with a descriptive message.
  • The getCurrentState() method should return the current state of the state machine.

Edge Cases to Consider:

  • What happens if the initial state is not a valid state? (Consider throwing an error during instantiation).
  • What happens if the transition function is not defined for a particular state?
  • How to handle transitions that lead back to the same state? (Should be allowed by default, but consider if this needs to be configurable).

Examples

Example 1:

// Define states
type TrafficLightState = 'red' | 'yellow' | 'green';

// Define transitions
type TrafficLightTransition =
  | { from: 'red'; to: 'green' }
  | { from: 'green'; to: 'yellow' }
  | { from: 'yellow'; to: 'red' };

// Usage
const trafficLight = new StateMachine<TrafficLightState>(
  'red',
  [
    { from: 'red', to: 'green' },
    { from: 'green', to: 'yellow' },
    { from: 'yellow', to: 'red' },
  ]
);

console.log(trafficLight.getCurrentState()); // Output: red

trafficLight.transition('green');
console.log(trafficLight.getCurrentState()); // Output: green

try {
  trafficLight.transition('red'); // Invalid transition
} catch (error) {
  console.error(error.message); // Output: Invalid transition from green to red
}

Explanation: This example demonstrates a simple traffic light state machine. The states are 'red', 'yellow', and 'green'. The transitions are defined as objects specifying the source and destination states. The code shows a valid transition and then attempts an invalid transition, which results in an error.

Example 2:

// Define states
type OrderState = 'pending' | 'processing' | 'shipped' | 'delivered';

// Define transitions
type OrderTransition =
  | { from: 'pending'; to: 'processing' }
  | { from: 'processing'; to: 'shipped' }
  | { from: 'shipped'; to: 'delivered' };

// Usage
const orderMachine = new StateMachine<OrderState>(
  'pending',
  [
    { from: 'pending', to: 'processing' },
    { from: 'processing', to: 'shipped' },
    { from: 'shipped', to: 'delivered' },
  ]
);

console.log(orderMachine.getCurrentState()); // Output: pending

orderMachine.transition('processing');
console.log(orderMachine.getCurrentState()); // Output: processing

try {
  orderMachine.transition('pending'); // Invalid transition
} catch (error) {
  console.error(error.message); // Output: Invalid transition from processing to pending
}

Explanation: This example showcases an order processing state machine. The states represent different stages of an order. The transitions are defined to reflect the typical flow of an order.

Constraints

  • Time Limit: The solution should be completed within 60 minutes.
  • Code Quality: The code should be well-structured, readable, and follow TypeScript best practices.
  • Error Messages: Error messages should be clear and informative, indicating the current state and the attempted destination state.
  • No External Libraries: You are not allowed to use any external libraries for this challenge. The solution must be implemented using only built-in TypeScript features.
  • Transition Definition: The transition definition must be an array of objects, each object having from and to properties.

Notes

  • Consider using a type guard to ensure that the newState is a valid state.
  • Think about how to make the StateMachine class more flexible, perhaps by allowing for configurable error handling.
  • The transition method should not allow transitions to states that are not explicitly defined in the transition list.
  • Focus on type safety and clear error handling. A working, but not type-safe, solution will not be considered complete.
  • The from and to properties in the transition definition should match the state type.
Loading editor...
typescript