remove typescript generics

they were getting in the way; it can be developed after the lib is
more mature
main
Brian Sakal 2 years ago
parent e328533629
commit 26383090ba

@ -1 +1,112 @@
(()=>{var x=function(...t){return{states:t}},i=function(t,...e){return{name:t,ons:e}},o=function(t,...e){return{eventName:t,reactions:e}},s=function(t){return{type:"Do",fn:t}},S=function(t){return{type:"Goto",targetStateName:t}},l=function(t){return{doFunctions:t}};function u(t,e){let{state:n,context:C}=e;typeof n>"u"&&(n=t.states[0].name);let E={machine:t,state:n,context:C,tickQueues:[]};return c(E,["entry",null]),E}function m(t){return t.machine.states.find(e=>e.name===t.state)}function v(t,e){return t.ons.filter(n=>n.eventName===e[0])}function h(){}function c(t,e){let n=m(t),E=v(n,e).map(r=>r.reactions).flat(),_=E.findIndex(r=>r.type==="Goto"),f=_===-1?E.length-1:_,y=E.slice(0,f+1).map(r=>r.type==="Do"?r.fn:r.type==="Goto"?(k,d,p)=>{p.state=r.targetStateName,c(p,["entry",d])}:h);l(y).doFunctions.forEach(r=>{r(t.context,e,t)})}var a=(t,e,n)=>{setTimeout(()=>{c(n,["timer-finished",null])},800)},T=(t,e,n)=>{console.log(n.state)},D=x(i("green",o("entry",s(a),s(T)),o("timer-finished",S("yellow"))),i("yellow",o("entry",s(a),s(T)),o("timer-finished",S("red"))),i("red",o("entry",s(a),s(T)),o("timer-finished",S("green")))),M=u(D,{context:{}});})();
(() => {
// src/index.ts
var Machine = function(...states) {
return { states };
};
var State = function(name, ...ons) {
return { name, ons };
};
var On = function(eventName, ...reactions) {
return { eventName, reactions };
};
var Do = function(fn) {
return { type: "Do", fn };
};
var Goto = function(targetStateName) {
return { type: "Goto", targetStateName };
};
var Tick = function(doFunctions) {
return { doFunctions };
};
function interpret(machine2, options) {
let { state, context } = options;
if (typeof state === "undefined") {
state = machine2.states[0].name;
}
const interpreter = { machine: machine2, state, context, tickQueues: [] };
console.log(interpreter);
send(interpreter, ["entry", null]);
return interpreter;
}
function getState(interpreter) {
return interpreter.machine.states.find((state) => state.name === interpreter.state);
}
function getOns(state, event) {
return state.ons.filter((on) => on.eventName === event[0]);
}
function noop() {
}
function send(interpreter, event) {
const state = getState(interpreter);
const onsTree = getOns(state, event);
const reactions = onsTree.map((on) => on.reactions).flat();
const indexOfFirstGoto = reactions.findIndex((reaction) => reaction.type === "Goto");
const indexOfFinalReaction = indexOfFirstGoto === -1 ? reactions.length - 1 : indexOfFirstGoto;
const reactionsUntilFirstGoto = reactions.slice(0, indexOfFinalReaction + 1);
const functionsToRunInTick = reactionsUntilFirstGoto.map((reaction) => {
if (reaction.type === "Do") {
return reaction.fn;
} else if (reaction.type === "Goto") {
return (ctx, e, self) => {
self.state = reaction.targetStateName;
send(self, ["entry", e]);
};
} else {
return noop;
}
});
const tick = Tick(functionsToRunInTick);
tick.doFunctions.forEach((fn) => {
fn(interpreter.context, event, interpreter);
});
}
// src/tests/00-basic.ts
var beginTimer = (ctx, e, self) => {
setTimeout(() => {
send(self, ["timer-finished", null]);
}, 800);
};
var log = (ctx, e, self) => {
console.log(self.state);
};
var machine = Machine(
State(
"green",
On(
"entry",
Do(beginTimer),
Do(log)
),
On(
"timer-finished",
Goto("yellow")
)
),
State(
"yellow",
On(
"entry",
Do(beginTimer),
Do(log)
),
On(
"timer-finished",
Goto("red")
)
),
State(
"red",
On(
"entry",
Do(beginTimer),
Do(log)
),
On(
"timer-finished",
Goto("green")
)
)
);
var actor = interpret(machine, { context: {} });
})();

@ -0,0 +1,152 @@
(() => {
// src/index.ts
var Machine = function(...states) {
return { states };
};
var State = function(name, ...ons) {
return { name, ons };
};
var On = function(eventName, ...reactions) {
return { eventName, reactions };
};
var Do = function(fn) {
return { type: "Do", fn };
};
var Goto = function(targetStateName) {
return { type: "Goto", targetStateName };
};
var Tick = function(doFunctions) {
return { doFunctions };
};
function interpret(machine, options) {
let { state, context } = options;
if (typeof state === "undefined") {
state = machine.states[0].name;
}
const interpreter = { machine, state, context, tickQueues: [] };
console.log(interpreter);
send(interpreter, ["entry", null]);
return interpreter;
}
function getState(interpreter) {
return interpreter.machine.states.find((state) => state.name === interpreter.state);
}
function getOns(state, event) {
return state.ons.filter((on) => on.eventName === event[0]);
}
function noop() {
}
function send(interpreter, event) {
const state = getState(interpreter);
const onsTree = getOns(state, event);
const reactions = onsTree.map((on) => on.reactions).flat();
const indexOfFirstGoto = reactions.findIndex((reaction) => reaction.type === "Goto");
const indexOfFinalReaction = indexOfFirstGoto === -1 ? reactions.length - 1 : indexOfFirstGoto;
const reactionsUntilFirstGoto = reactions.slice(0, indexOfFinalReaction + 1);
const functionsToRunInTick = reactionsUntilFirstGoto.map((reaction) => {
if (reaction.type === "Do") {
return reaction.fn;
} else if (reaction.type === "Goto") {
return (ctx, e, self) => {
self.state = reaction.targetStateName;
send(self, ["entry", e]);
};
} else {
return noop;
}
});
const tick = Tick(functionsToRunInTick);
tick.doFunctions.forEach((fn) => {
fn(interpreter.context, event, interpreter);
});
}
// 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(500);
send(self, ["timer-finished", null]);
};
var log = (ctx, e, self) => {
console.log(self.state);
};
var client = Machine(
State(
"idle",
On(
"entry",
Do(log)
),
On(
"server-created",
Do((_ctx, [_eventName, serverActor2], self) => {
self.context.serverActor = serverActor2;
}),
Goto("making-request")
)
),
State(
"making-request",
On(
"entry",
Do(log),
Do(makeRequest),
Goto("awaiting-response")
)
),
State(
"awaiting-response",
On(
"entry",
Do(log)
),
On(
"received-response",
Do(log),
Goto("making-request")
)
)
);
var server = Machine(
State(
"awaiting-request",
On(
"entry",
Do(log)
),
On(
"received-request",
Do((_ctx, [_eventName, clientActor2], self) => {
self.context.clientActor = clientActor2;
}),
Goto("sending-response")
)
),
State(
"sending-response",
On(
"entry",
Do(log),
Do(startTimer)
),
On(
"timer-finished",
Do(sendResponse),
Goto("awaiting-request")
)
)
);
var clientActor = interpret(client, { context: {} });
var serverActor = interpret(server, { context: {} });
send(clientActor, ["server-created", serverActor]);
})();

@ -10,7 +10,7 @@
},
"scripts": {
"build": "esbuild --bundle --format=esm --minify --sourcemap --outdir=dist src/index.ts",
"build-tests": "esbuild --bundle --minify --outdir=dist/tests src/tests/*.ts"
"build-tests": "esbuild --bundle --outdir=dist/tests src/tests/*.ts"
},
"devDependencies": {
"esbuild": "^0.17.18"

@ -1,48 +1,49 @@
type Event_T = [name:string, payload:any] | ['entry', any];
export interface Machine_T<S,E extends Event_T,C> {
states: Array<State_T<S,E,C>>
export type Event_T = [name:string, payload:any];
export interface Machine_T {
states: Array<State_T>
}
export interface State_T<S,E extends Event_T,C> {
name: S;
ons: Array<On_T<S,E,C>>;
export interface State_T {
name: string;
ons: Array<On_T>;
}
export interface On_T<S,E extends Event_T,C> {
eventName: E[0];
reactions: Array<Do_T<S,E,C> | Goto_T<S>>;
export interface On_T {
eventName: string;
reactions: Array<Do_T | Goto_T>;
};
export interface Do_T<S,E extends Event_T, C> {
export interface Do_T {
type: 'Do';
fn: DoFn_T<S,E,C>;
fn: DoFn_T;
};
export type DoFn_T<S,E extends Event_T, C> = (ctx:C,e:E,self:Interpreter_T<S,E,C>)=>any;
export interface Goto_T<S> {
export type DoFn_T = (ctx:any,e:Event_T,self:Interpreter_T)=>void;
export interface Goto_T {
type: 'Goto';
targetStateName: S;
targetStateName: string;
};
export const Machine = function<S,E extends Event_T,C>(...states:Array<State_T<S,E,C>>) : Machine_T<S,E,C> { return {states}; };
export const State = function<S,E extends Event_T,C>(name:S, ...ons:Array<On_T<S,E,C>>) : State_T<S,E,C>{ return {name, ons}; };
export const On = function<S,E extends Event_T,C>(eventName:E[0], ...reactions:Array<Do_T<S,E,C> | Goto_T<S>>) : On_T<S,E,C>{ return {eventName, reactions}; };
export const Do = function<S,E extends Event_T, C>(fn:DoFn_T<S,E,C>) : Do_T<S,E,C>{ return {type:'Do', fn}; };
export const Goto = function<S>(targetStateName:S) : Goto_T<S> { return {type:'Goto', targetStateName} };
export const Machine = function(...states:Array<State_T>) : Machine_T { return {states}; };
export const State = function(name:string, ...ons:Array<On_T>) : State_T{ return {name, ons}; };
export const On = function(eventName:string, ...reactions:Array<Do_T | Goto_T>) : On_T{ return {eventName, reactions}; };
export const Do = function<S,E extends Event_T, C>(fn:DoFn_T) : Do_T{ return {type:'Do', fn}; };
export const Goto = function(targetStateName:string) : Goto_T { return {type:'Goto', targetStateName} };
interface Tick_T<S,E extends Event_T, C> {
doFunctions: Array<DoFn_T<S,E,C>>
interface Tick_T {
doFunctions: Array<DoFn_T>
};
const Tick = function<S,E extends Event_T,C>(doFunctions : Array<DoFn_T<S,E,C>>) : Tick_T<S,E,C>{ return {doFunctions}; };
const Tick = function(doFunctions : Array<DoFn_T>) : Tick_T{ return {doFunctions}; };
export interface Interpreter_T<S,E extends Event_T,C> {
machine: Machine_T<S,E,C>;
state: S;
context: C;
tickQueues:Array<TickQueue_T<S,E,C>>
export interface Interpreter_T {
machine: Machine_T;
state: string;
context: any;
tickQueues:Array<TickQueue_T>
}
type TickQueue_T<S,E extends Event_T,C> = Array<Tick_T<S,E,C>>;
export function interpret<S,E extends Event_T,C>(machine:Machine_T<S,E,C>, options:{state?:S, context:C}) : Interpreter_T<S,E,C>{
type TickQueue_T = Array<Tick_T>;
export function interpret(machine:Machine_T, options:{state?:string, context:any}) : Interpreter_T{
let {state, context} = options;
if(typeof state === 'undefined'){ state = machine.states[0].name; }
const interpreter = {machine, state, context, tickQueues:[]}
console.log(interpreter);
//@ts-ignore
send(interpreter, ['entry', null] );
return interpreter;
@ -50,12 +51,12 @@ export function interpret<S,E extends Event_T,C>(machine:Machine_T<S,E,C>, optio
/** Helper function for `send()`
*/
function getState<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>) : State_T<S,E,C>{
return interpreter.machine.states.find((state)=>state.name===interpreter.state) as unknown as State_T<S,E,C>;
function getState(interpreter : Interpreter_T) : State_T{
return interpreter.machine.states.find((state)=>state.name===interpreter.state) as unknown as State_T;
}
/** Helper function for `send()`
*/
function getOns<S,E extends Event_T,C>(state : State_T<S,E,C>, event:E) : Array<On_T<S,E,C>>{
function getOns(state : State_T, event:Event_T) : Array<On_T>{
return state.ons.filter((on)=>on.eventName===event[0]);
}
/** Helper function for `send()`
@ -71,7 +72,7 @@ function noop(){}
* whether to run a reaction at all. If an Event is received, and is specified to be applied on a past
* Tick, it is discarded.
*/
export function send<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>, event:E){
export function send(interpreter : Interpreter_T, event:Event_T){
const state = getState(interpreter);
const onsTree = getOns(state, event);
const reactions = onsTree
@ -86,7 +87,7 @@ export function send<S,E extends Event_T,C>(interpreter : Interpreter_T<S,E,C>,
return reaction.fn;
}
else if(reaction.type === 'Goto'){
return (ctx:C,e:E,self:Interpreter_T<S,E,C>)=>{
return (ctx:any,e:Event_T,self:Interpreter_T)=>{
self.state = reaction.targetStateName;
//@ts-ignore
send(self, ['entry', e] );

@ -1,38 +1,43 @@
import { Machine, State, On, Do, Goto, Spawn, Unspawn, interpret, Interpreter_T, send } from '../index';
import { Machine, State, On, Do, Goto, Spawn, Unspawn, interpret, Interpreter_T, send, Event_T } from '../index';
const beginTimer = (ctx:C, e:E, self:Interpreter_T<S,E,C>)=>{ setTimeout(()=>{ send(self, ['timer-finished',null]); }, 800); };
const log = (ctx:C, e:E, self:Interpreter_T<S,E,C>)=>{ console.log(self.state); };
const beginTimer = (ctx:C, e:Event_T, self:Interpreter_T)=>{ setTimeout(()=>{ send(self, ['timer-finished',null]); }, 800); };
const log = (ctx:C, e:Event_T, self:Interpreter_T)=>{ console.log(self.state); };
type S = 'green' | 'yellow' | 'red';
type E = ['entry',null] | ['timer-finished',null];
type S =
| 'green'
| 'yellow'
| 'red';
type E =
| ['entry',null]
| ['timer-finished',null];
type C = {};
const machine =
Machine<S,E,C>(
Machine(
State('green',
On<S,E,C>('entry',
On('entry',
Do(beginTimer),
Do(log)
),
On<S,E,C>('timer-finished',
On('timer-finished',
Goto('yellow')
)
),
State('yellow',
On<S,E,C>('entry',
On('entry',
Do(beginTimer),
Do(log)
),
On<S,E,C>('timer-finished',
On('timer-finished',
Goto('red')
)
),
State('red',
On<S,E,C>('entry',
On('entry',
Do(beginTimer),
Do(log)
),
On<S,E,C>('timer-finished',
On('timer-finished',
Goto('green')
)
),

@ -0,0 +1,84 @@
import { Machine, State, On, Do, Goto, Spawn, Unspawn, interpret, Interpreter_T, send, Event_T } from '../index';
const wait = (ms:number)=>new Promise((resolve)=>{ setTimeout(()=>{ resolve(1); }, ms); });
const makeRequest = (ctx,e,self)=>{ send(ctx.serverActor, ['received-request',self]); };
const sendResponse = (ctx,e,self)=>{ send(ctx.clientActor, ['received-response',self]); };
const startTimer = async (ctx,e,self)=>{ await wait(500); send(self, ['timer-finished',null]); }
const log = (ctx, e, self:Interpreter_T)=>{ console.log(self.state); };
type Sc =
| 'idle'
| 'making-request'
| 'awaiting-response';
type Ec =
| ['entry',null]
| ['received-response',Interpreter_T]
| ['server-created', Interpreter_T];
type Cc =
| {serverActor?:Interpreter_T};
const client =
Machine(
State('idle',
On('entry',
Do(log),
),
On('server-created',
Do((_ctx,[_eventName,serverActor],self)=>{ self.context.serverActor=serverActor; }),
Goto('making-request')
)
),
State('making-request',
On('entry',
Do(log),
Do(makeRequest),
Goto('awaiting-response')
),
),
State('awaiting-response',
On('entry',
Do(log),
),
On('received-response',
Do(log),
Goto('making-request')
),
),
);
type Ss =
| 'awaiting-request'
| 'sending-response';
type Es =
| ['entry',null]
| ['received-request',Interpreter_T]
| ['timer-finished', null];
type Cs = {clientActor?:Interpreter_T};
const server =
Machine(
State('awaiting-request',
On('entry',
Do(log),
),
On('received-request',
Do((_ctx,[_eventName,clientActor],self)=>{ self.context.clientActor=clientActor; }),
Goto('sending-response')
),
),
State('sending-response',
On('entry',
Do(log),
Do(startTimer)
),
On('timer-finished',
Do(sendResponse),
Goto('awaiting-request')
)
),
);
const clientActor = interpret(client, {context:{}});
const serverActor = interpret(server, {context:{}});
send(clientActor, ['server-created', serverActor]);
Loading…
Cancel
Save