(() => { // src/index.ts var Machine = function(...states) { return { states }; }; var State = function(name, ...eventReactionCouplings) { return { name, eventReactionCouplings }; }; var On = function(eventName, ...reactions) { return { eventName, reactions }; }; var SideEffect = function(fn) { return { type: "SideEffect", fn }; }; var Goto = function(targetStateName) { return { type: "Goto", targetStateName }; }; var Context = function(fn) { return { type: "ContextMutation", fn }; }; function interpret(machine, options) { let { state, context } = options; if (typeof state === "undefined") { state = machine.states[0].name; } const interpreter = { machine, state, context, eventQueue: [], isTransitioning: false }; send(interpreter, ["entry", null]); return interpreter; } function getState(interpreter) { return interpreter.machine.states.find((state) => state.name === interpreter.state); } function getMatchingEventReactionCouplings(state, event) { return state.eventReactionCouplings.filter((eventReactionCoupling) => eventReactionCoupling.eventName === event[0]); } function send(interpreter, event) { interpreter.eventQueue.push(event); if (interpreter.isTransitioning === false) { interpreter.isTransitioning = true; while (interpreter.eventQueue.length > 0) { processNextEvent(interpreter); } interpreter.isTransitioning = false; } } function processNextEvent(interpreter) { const nextEvent = interpreter.eventQueue.shift(); if (typeof nextEvent !== "undefined") { const state = getState(interpreter); const eventReactionCouplings = getMatchingEventReactionCouplings(state, nextEvent); const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat(); const { sideEffects, contextMutations, goto_ } = categorizeReactions(reactions); sideEffects.forEach((sideEffect) => { sideEffect.fn(interpreter.context, nextEvent, interpreter); }); contextMutations.forEach((contextMutation) => { interpreter.context = contextMutation.fn(interpreter.context, nextEvent, interpreter); }); if (goto_ !== null) { interpreter.state = goto_.targetStateName; send(interpreter, ["entry", null]); } } } function categorizeReactions(reactions) { let sideEffects = [], contextMutations = [], goto_ = null; reactions.forEach((reaction) => { if (reaction.type === "SideEffect") { sideEffects.push(reaction); } else if (reaction.type === "ContextMutation") { contextMutations.push(reaction); } else if (reaction.type === "Goto") { goto_ = reaction; } }); return { sideEffects, contextMutations, goto_ }; } // src/tests/01-ping-pong.ts var wait = (ms) => new Promise((resolve) => { setTimeout(() => { resolve(1); }, ms); }); var makeRequest = (ctx, e, self) => { send(ctx.serverActor, ["received-request", self]); }; var sendResponse = (ctx, e, self) => { send(ctx.clientActor, ["received-response", self]); }; var startTimer = async (ctx, e, self) => { await wait(1500); send(self, ["timer-finished", null]); }; var log = (ctx, e, self) => { console.log(self.state, ctx); }; var client = Machine( State( "idle", On( "entry", SideEffect(log) ), On( "server-created", SideEffect((_ctx, [_eventName, serverActor2], self) => { self.context.serverActor = serverActor2; }), Goto("making-request") ) ), State( "making-request", On( "entry", SideEffect(log), SideEffect(makeRequest), Context((ctx) => ({ ...ctx, requestsMade: ctx.requestsMade + 1 })), Goto("awaiting-response") ) ), State( "awaiting-response", On( "entry", SideEffect(log) ), On( "received-response", SideEffect(log), Context((ctx) => ({ ...ctx, responsesReceived: ctx.responsesReceived + 1 })), Goto("making-request") ) ) ); var server = Machine( State( "awaiting-request", On( "entry", SideEffect(log) ), On( "received-request", SideEffect((_ctx, [_eventName, clientActor2], self) => { self.context.clientActor = clientActor2; }), Goto("sending-response") ) ), State( "sending-response", On( "entry", SideEffect(log), SideEffect(startTimer) ), On( "timer-finished", SideEffect(sendResponse), Goto("awaiting-request") ) ) ); var clientActor = interpret(client, { context: { requestsMade: 0, responsesReceived: 0 } }); var serverActor = interpret(server, { context: {} }); send(clientActor, ["server-created", serverActor]); })();