matthewp / robot

🤖 A functional, immutable Finite State Machine library
https://thisrobot.life
BSD 2-Clause "Simplified" License
1.92k stars 88 forks source link

feat: improve TS type definitions #173

Closed acd02 closed 2 years ago

acd02 commented 2 years ago

This PR aims to improve type definitions regarding nested states, and also adds a missing "child" field. However, this is a naive improvement, far from perfect 😅 , see example below 👇

Considering this wizard machine:

type StepDetailMachineState = { idle: {}; validate: {}; finished: {} }

type WizardMachineState = {
  step1: StepDetailMachineState
  step2: StepDetailMachineState
  end: {}
}

const firstStepMachine = createMachine<StepDetailMachineState, {}>({
  idle: state(transition('validate', 'validate')),
  validate: state(transition('finished', 'finished')),
  finished: final(),
})

const secondStepMachine = createMachine<StepDetailMachineState, {}>({
  idle: state(transition('validate', 'validate')),
  validate: state(transition('finished', 'finished')),
  finished: final(),
})

const wizardMachine = createMachine<WizardMachineState>({
  step1: invokeMachine(firstStepMachine, transition('done', 'step2')),
  step2: invokeMachine(secondStepMachine, transition('done', 'end')),
  end: final(),
})

Nested states:

// now

import { interpret } from 'robot3'
import { wizardMachine } from './wizardMachine'

const service = interpret(wizardMachine)
// ⬆️  TS will infer that service.machine.current can be ➡️ 'end' | 'step1' | 'step2'
const child = service.child // Property 'child' does not exist...
// after PR

import { interpret } from 'robot3'
import { wizardMachine } from './wizardMachine'

const service = interpret(wizardMachine)
// ⬆️  TS will infer that service.machine.current can be ➡️ 'end' | 'step1' | 'step2' | 'idle' | 'validate' | 'finished'
const child = service.child
// ⬆️  TS will infer that child?.machine.current can be ➡️ 'end' | 'step1' | 'step2' | 'idle' | 'validate' | 'finished'

// But it won't be able to infer that service.machine.current can only be ➡️ 'end' | 'step1' | 'step2'
// and that child?.machine.current can only be ➡️ 'idle' | 'validate' | 'finished'