Lab
State machine
type Phase = 'stored' | 'testing' | 'held' | 'released';
type LotEvent =
| { type: 'start' }
| { type: 'abandon' }
| { type: 'pass' | 'hold'; runId: number };
type Lot = { phase: Phase; lastRun: number; activeRun: number | null };
type Decision = { lot: Lot; reason: string; effect: string };
// Which events each phase allows, and the phase each one leads to.
const transitions: Record<Phase, Partial<Record<LotEvent['type'], Phase>>> = {
stored: { start: 'testing' },
testing: { abandon: 'stored', pass: 'released', hold: 'held' },
held: { start: 'testing' },
released: {},
};
const storedLot = (): Lot => ({ phase: 'stored', lastRun: 0, activeRun: null });
const accept = (lot: Lot, effect = 'none'): Decision =>
({ lot, reason: 'accepted', effect });
const refuse = (lot: Lot, reason: string): Decision =>
({ lot, reason, effect: 'none' });
// Decides and returns; it does no work. Its caller commits the lot.
function transition(lot: Lot, event: LotEvent): Decision {
const next = transitions[lot.phase][event.type];
if (next === undefined) return refuse(lot, 'not-allowed');
if (event.type === 'start') {
const run = lot.lastRun + 1;
return accept({ phase: next, lastRun: run, activeRun: run }, 'start-test');
}
if (event.type === 'abandon') {
return accept({ ...lot, phase: next, activeRun: null });
}
// A result counts only if it comes from the active run.
if (event.runId !== lot.activeRun) return refuse(lot, 'stale-run');
return accept({ ...lot, phase: next, activeRun: null });
}
// The owner: one place holds the lot and commits each decision.
let lot = storedLot();
function dispatch(event: LotEvent): string {
const decision = transition(lot, event);
lot = decision.lot;
const reason = decision.reason;
console.log(`${event.type}: ${lot.phase}, run ${lot.lastRun}, ${reason}`);
return reason;
}
dispatch({ type: 'start' });
const abandoned = lot.activeRun!;
dispatch({ type: 'abandon' });
dispatch({ type: 'start' });
const current = lot.activeRun!;
const late = dispatch({ type: 'pass', runId: abandoned });
dispatch({ type: 'pass', runId: current });
console.log(`late pass: ${late}; the lot ends ${lot.phase}`);
TODO
Visual to come