You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

170 lines
4.9 KiB
JavaScript

(() => {
// 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]);
})();