Architecture
This document provides a technical deep dive into the @hyperfrontend/state-machine library architecture.
System Overview
Data Flow
Module Layers
The library is organized into logical layers:
| Layer | Components |
|---|---|
| Core Primitives | Store, Actions, Reducer |
| Derived Computation | Selectors, StateChange |
| Reactive System | Events |
| High-level Abstractions | AsyncOperation, LifecycleAwareComponent |
Core Components
Store
The central state container implementing the observer pattern.
class Store {
private state = rootReducer(void 0, { type: '' })
private listeners = new Set<Listener>()
readonly dispatch = (action: Action): void => {
this.state = rootReducer(this.state, action)
this.listeners.forEach((listener) => listener(this.state, action))
}
readonly getState = (): State => ({ ...this.state })
readonly subscribe = (listener: Listener): (() => void) => {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
}
Design decisions:
getState()returns shallow copy to prevent external mutation- Set-based listeners for O(1) add/remove operations
- Subscribe returns cleanup function (React/RxJS convention)
- Initial state via
rootReducer(void 0, { type: '' })
Action Creators
Actions follow the Flux Standard Action pattern:
export const start = <T = void>(payload?: T) => ({ type: types.START, payload })
export const cancel = <T = void>(payload?: T) => ({ type: types.CANCEL, payload })
export const pause = <T = void>(payload?: T) => ({ type: types.PAUSE, payload })
export const success = <T = void>(payload?: T) => ({ type: types.SUCCESS, payload })
export const fail = (error?: Error | string | any) => ({ type: types.FAIL, error })
Action types:
'process started'- START'process completed successfully'- SUCCESS'process failed'- FAIL'process paused'- PAUSE'process cancelled'- CANCEL
Root Reducer
Pure function with handler lookup table:
const handlers: Handlers = {
[ActionTypes.START]: (state) => ({ ...state, inProgress: true }),
[ActionTypes.PAUSE]: (state) => ({ ...state, halt: true }),
[ActionTypes.CANCEL]: (state) => ({ ...state, inProgress: false, halt: true }),
[ActionTypes.SUCCESS]: (state) => ({
...state,
inProgress: false,
success: true,
fail: false,
halt: false,
}),
[ActionTypes.FAIL]: (state, action) => ({
...state,
inProgress: false,
success: false,
fail: true,
halt: false,
error: action.error,
}),
}
export const rootReducer = (state = createInitialState(), action: Action): State => {
const handler = handlers[action.type as keyof Handlers]
return handler ? handler(state, action as any) : state
}
State Model
Core State (4 boolean flags):
interface State {
inProgress: boolean
success: boolean
fail: boolean
halt: boolean
}
Initial State:
{
inProgress: false,
success: false,
fail: false,
halt: false
}
State Selectors
10 derived states computed from 4 core flags:
export const notStarted = (state) => !state.inProgress && !state.success && !state.fail
export const inProgress = (state) => state.inProgress
export const done = (state) => !state.inProgress && (state.success || state.fail)
export const successful = (state) => !state.inProgress && state.success && !state.fail
export const failed = (state) => !state.inProgress && !state.success && state.fail
export const retrying = (state) => state.inProgress && !state.success && state.fail
export const restarting = (state) => state.inProgress && state.success && !state.fail
export const halted = (state) => state.halt
export const paused = (state) => state.inProgress && state.halt
export const cancelled = (state) => !state.inProgress && state.halt && !state.success && !state.fail
Derived state logic table:
| State | inProgress | success | fail | halt | Description |
|---|---|---|---|---|---|
| notStarted | false | false | false | any | Never executed |
| inProgress | true | any | any | false | Currently executing |
| done | false | true/false | true/false | false | Completed (success or fail) |
| successful | false | true | false | false | Completed successfully |
| failed | false | false | true | false | Completed with failure |
| retrying | true | false | true | false | Re-executing after failure |
| restarting | true | true | false | false | Re-executing after success |
| halted | any | any | any | true | Halt flag set |
| paused | true | any | any | true | Mid-execution halt |
| cancelled | false | false | false | true | Aborted before completion |
Event System
Edge-triggered events that fire only on false → true transitions:
class Events {
private readonly store = new Store()
private readonly change = new StateChange()
private readonly eventHandlers = new Set<[Event, EventHandler]>()
constructor() {
this.store.subscribe((state) => {
this.change.addItem(derivedState(state)).triggerCallbacks()
})
this.change.onStateChange(this.onStateChange)
}
private readonly onStateChange = () => {
const isActivated = (selector: StateStatusDeriver): boolean => {
const prev = selector(this.change.previous as DerivedState)
const curr = selector(this.change.current as DerivedState)
return !prev && curr // Edge detection: false → true
}
const onActivated = (selector: StateStatusDeriver, eventName: Event) => {
if (!isActivated(selector)) return
this.eventHandlers.forEach(([event, handler]) => {
if (event === eventName) handler(event, this.change.current, this.change.previous)
})
}
// Trigger events for each derived state
onActivated((s) => s.notStarted, event.NotStarted)
onActivated((s) => s.inProgress, event.InProgress)
// ... etc for all events
}
}
State Transitions
State Machine Diagram
Transition Table
| Current State | Action | Next State | Core State Changes |
|---|---|---|---|
| notStarted | START | inProgress | inProgress: true |
| inProgress | SUCCESS | successful | inProgress: false, success: true, fail: false, halt: false |
| inProgress | FAIL | failed | inProgress: false, success: false, fail: true, halt: false |
| inProgress | PAUSE | paused | halt: true (keeps inProgress) |
| inProgress | CANCEL | cancelled | inProgress: false, halt: true |
| successful | START | restarting | inProgress: true (keeps success=true) |
| failed | START | retrying | inProgress: true (keeps fail=true) |
| paused | START | inProgress | halt: false (keeps inProgress) |
| paused | CANCEL | cancelled | inProgress: false, halt: true |
Advanced Abstractions
AsyncOperation
Wraps async functions with automatic state management:
class AsyncOperation {
private events: Events
constructor(private process: AsyncProcess) {
this.events = new Events()
}
readonly start = async (): Promise<void> => {
this.events.dispatch(actions.start())
try {
await this.process()
this.events.dispatch(actions.success())
} catch (error) {
this.events.dispatch(actions.fail(error))
}
}
readonly pause = () => this.events.dispatch(actions.pause())
readonly cancel = () => this.events.dispatch(actions.cancel())
readonly on = (event: Event, handler: EventHandler) => this.events.on(event, handler)
}
CoordinatedAsyncProcess
Manages multiple async operations:
class CoordinatedAsyncProcess {
private asyncOperations: AsyncOperation[] = []
readonly registerProcess = (process: AsyncProcess): this => {
this.asyncOperations.push(new AsyncOperation(process))
return this
}
readonly startAll = () => Promise.all(this.asyncOperations.map((op) => op.start()))
readonly cancelAll = () => this.asyncOperations.forEach((op) => op.cancel())
readonly pauseAll = () => this.asyncOperations.forEach((op) => op.pause())
}
LifecycleAwareComponent
Abstract class for components with initialization workflows:
abstract class LifecycleAwareComponent {
private _initializing = false
private _ready = false
private _starting = false
private _stopping = false
private _active = false
// Protected setters trigger callbacks only on actual state change
protected setInitializing(value: boolean) {
if (this._initializing === value) return
this._initializing = value
this.initializingCallbacks.call(value)
}
// Callback registration with immediate invocation if state already active
public onReadyStatusChange(callback: StatusCallback): this {
this.readyCallbacks.add(callback)
if (this._ready) callback(true)
return this
}
// Abstract methods for subclass implementation
protected abstract init: Process<any>
public abstract start: Process<any>
public abstract stop: Process<any>
}
Lifecycle states: initializing → ready → starting → active → stopping → inactive
Design Patterns
| Pattern | Implementation |
|---|---|
| Flux/Redux | Unidirectional data flow with Store, Actions, Reducers |
| Observer | Store subscribers, Event handlers, CallStack callbacks |
| Factory | createInitialState(), callStack() |
| Template Method | LifecycleAwareComponent abstract class |
| Facade | AsyncOperation wraps Events + Store |
| Strategy | Handler lookup table in reducer |
| Decorator | AsyncOperation wraps async functions |
| Sliding Window | StateChange tracks previous/current state |
| Functional Core, Imperative Shell | Pure reducer/selectors, imperative Store/Events |
Type System
Core Types
// Core State
interface State {
inProgress: boolean
success: boolean
fail: boolean
halt: boolean
}
// Derived State
interface DerivedState {
notStarted: boolean
inProgress: boolean
done: boolean
successful: boolean
failed: boolean
retrying: boolean
restarting: boolean
halted: boolean
paused: boolean
cancelled: boolean
}
// Actions
interface Action {
type: string
}
// Selectors
type StateStatusDeriver = (state: State) => boolean
type StateDeriver = (state: State) => DerivedState
// Events
const event = {
NotStarted: 'notStarted',
InProgress: 'inProgress',
Done: 'done',
Successful: 'successful',
Failed: 'failed',
Retrying: 'retrying',
Restarting: 'restarting',
Paused: 'paused',
Cancelled: 'cancelled',
} as const
type Event = (typeof event)[keyof typeof event]