Comparing version 0.19.0 to 0.19.1
@@ -1,2 +0,2 @@ | ||
const CONTRACT_MODEL_UPDATE_FN_RETURN_VALUE="Model update function must return valid update operations!",SEP=".",TRANSITION_SYMBOL="--\x3e",TRANSITION_LABEL_START_SYMBOL=":",HISTORY_STATE_NAME="H",HISTORY_PREFIX="history.",INIT_STATE="nok",INIT_EVENT="init",AUTO_EVENT="auto",STATE_PROTOTYPE_NAME="State",NO_STATE_UPDATE=[],NO_OUTPUT=null,ACTION_IDENTITY=function(){return{outputs:NO_OUTPUT,updates:NO_STATE_UPDATE}},history_symbol={},SHALLOW="shallow",DEEP="deep",WRONG_EVENT_FORMAT_ERROR="The machine received an event which does not have the proper format. Expecting an object whose unique key is the event name, and value is the event data.",FUNCTION_THREW_ERROR=(t,e)=>`Exception thrown when executing ${e} ${t||""}`,INVALID_ACTION_FACTORY_EXECUTED=(t,e)=>`${FUNCTION_THREW_ERROR(t,e)}\nThe ${e} returned a value which is not an action.`,INVALID_PREDICATE_EXECUTED=(t,e)=>`${FUNCTION_THREW_ERROR(t,e)}\nThe ${e} returned a value which is not a boolean.`,ACTION_FACTORY_DESC="action factory",ENTRY_ACTION_FACTORY_DESC="(decorating) entry action",UPDATE_STATE_FN_DESC="update state function",PREDICATE_DESC="predicate",COMMAND_RENDER="render",PATH_ROOT=[0],PRE_ORDER="PRE_ORDER";function clone(t){return void 0===t?void 0:JSON.parse(JSON.stringify(t))}function merge(t,e){return Object.assign({},t,e)}function updatePathInTraversalState(t,e,n){n.forEach((n,s)=>{const i=t.get(e),a=t.get(n),o=a&&a.path;t.set(n,merge(a,{isAdded:!0,isVisited:!1,path:o||i.path.concat(s)}))})}function updateVisitInTraversalState(t,e){t.set(e,merge(t.get(e),{isVisited:!0}))}function visitTree(t,e){const{store:n,lenses:s,traverse:i}=t,{empty:a,add:o,takeAndRemoveOne:r,isEmpty:c}=n,{getChildren:u}=s,{visit:l,seed:d}=i,f=new Map,T="function"==typeof d?new(d()):clone(d);let p="function"==typeof a?new(a()):clone(a),m=T;for(o([e],p),f.set(e,{isAdded:!0,isVisited:!1,path:PATH_ROOT});!c(p);){const t=r(p),e=u(f,t);o(e,p),updatePathInTraversalState(f,t,e),m=l(m,f,t),updateVisitInTraversalState(f,t)}return f.clear(),m}function breadthFirstTraverseTree(t,e,n){const{getChildren:s}=t;return visitTree({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.push.apply(e,t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function preorderTraverseTree(t,e,n){const{getChildren:s}=t;return visitTree({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function postOrderTraverseTree(t,e,n){const{getChildren:s}=t,{seed:i,visit:a}=e,o=(t,e)=>e.get(t).isVisited||((t,e)=>0===s(t,e).length)(t,e);return visitTree({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>o(e,t)?[]:s(e,t).concat([e])},traverse:{seed:i,visit:(t,e,n)=>o(n,e)?a(t,e,n):t}},n)}function isLeafLabel(t){return 0===objectTreeLenses.getChildren(t).length}const objectTreeLenses={isLeafLabel:isLeafLabel,getLabel:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getLabel > unexpected object tree value";return t},getChildren:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getChildren > unexpected value";{let e=Object.values(t)[0];return e&&"object"==typeof e&&!Array.isArray(e)?Object.keys(e).map(t=>({[t]:e[t]})):[]}},constructTree:(t,e)=>{const n=t&&Object.keys(t)&&Object.keys(t)[0];return 0===e.length?t:{[n]:Object.assign.apply(null,e)}}};function traverseObj(t,e){const n={root:e},{strategy:s,seed:i,visit:a}=t;return({BFS:breadthFirstTraverseTree,PRE_ORDER:preorderTraverseTree,POST_ORDER:postOrderTraverseTree}[s]||preorderTraverseTree)(objectTreeLenses,{seed:i,visit:function(t,e,n){const{path:s}=e.get(n);return JSON.stringify(s)===JSON.stringify(PATH_ROOT)?t:a(t,e,n)}},n)}const arrayTreeLenses={getLabel:t=>Array.isArray(t)?t[0]:t,getChildren:t=>Array.isArray(t)?t[1]:[],constructTree:(t,e)=>e&&Array.isArray(e)&&e.length>0?[t,e]:t},noop=()=>{},emptyConsole={log:noop,warn:noop,info:noop,debug:noop,error:noop,trace:noop};function isBoolean(t){return"boolean"==typeof t}function isFunction(t){return"function"==typeof t}function isControlState(t){return t&&"string"==typeof t||isHistoryControlState(t)}function isEvent(t){return t&&"string"==typeof t}function isActionFactory(t){return t&&"function"==typeof t}function make_states(t){return t.reduce((t,e)=>(t[e]="",t),{})}function make_events(t){return t}function get_fn_name(t){return/^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/.exec(t.toString())[1]}function wrap(t){return["-",t,"-"].join("")}function times$1(t,e){return Array.apply(null,{length:e}).map(Number.call,Number).map(t)}function always(t){return t}function keys(t){return Object.keys(t)}function merge$1(t,e){return Object.assign({},t,e)}function is_history_transition(t){return t.to.startsWith(HISTORY_PREFIX)}function is_entry_transition(t){return t.event===INIT_EVENT}function is_from_control_state(t){return function(e){return e.from===t}}function is_to_history_control_state_of(t){return function(e){return is_history_control_state_of(t,e.to)}}function is_history_control_state_of(t,e){return e.substring(HISTORY_PREFIX.length)===t}function format_transition_label(t,e,n){const s=t||"";return e&&n?`${s} [${e.name}] / ${n.name}`:e?`${s} [${e.name}]}`:n?`${s} / ${n.name}`:`${s}`}function format_history_transition_state_name({from:t,to:e}){return`${t}.${e.substring(HISTORY_PREFIX.length)}.${HISTORY_STATE_NAME}`}function get_all_transitions(t){const{from:e,event:n,guards:s}=t;return s?s.map(({predicate:t,to:s,action:i})=>({from:e,event:n,predicate:t,to:s,action:i})):[t]}function getDisplayName(t){return t.replace(/_/g," ")}function mergeModelUpdates(t){return function(e,n,s){return{updates:t.reduce((t,i)=>{const a=i(e,n,s).updates;return a?t.concat(a):t},[]),outputs:NO_OUTPUT}}}function chainModelUpdates(t){return function(e,n,s){const{updateState:i}=s;return{updates:t.reduce((t,e)=>{const{extendedState:a,updates:o}=t,r=e(a,n,s).updates;return{extendedState:i(a,o),updates:r}},{extendedState:e,updates:[]}).updates||[],outputs:NO_OUTPUT}}}function mergeActionFactories(t,e){return function(n,s,i){const a=e.map(t=>t(n,s,i)),o=a.map(t=>t.updates||[]),r=a.map(t=>t.outputs||{});return{updates:[].concat(...o),outputs:t(r)}}}function identity(t,e,n){return{updates:[],outputs:NO_OUTPUT}}function lastOf(t){return t[t.length-1]}function formatActionName(t,e,n,s,i){const a=i?i.name:"",o=a?`[${a}]`:"",r=t?t.name:"identity";return`${r||"unnamed action"}:${e}-${n}->${s} ${o}`}function getFsmStateList(t){const{getLabel:e}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,n,s)=>{const i=e(s);return t[Object.keys(i)[0]]="",t}},t)}function getStatesType(t){const{getLabel:e,isLeafLabel:n}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,s,i)=>{const a=e(i),o=Object.keys(a)[0];return n(a)?(t[o]=!1,t):(t[o]=!0,t)}},t)}function getStatesPath(t){const{getLabel:e}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,n,s)=>{const i=n.get(s).path.join("."),a=e(s);return t[Object.keys(a)[0]]=i,t}},t)}function getStatesTransitionsMap(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return isHistoryControlState(n)?t:(t[n]=t[n]||{},t[n][s]=e,t)},{})||{}}function getStatesTransitionsMaps(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return isHistoryControlState(n)?t:(t[n]=t[n]||{},t[n][s]=t[n][s]?t[n][s].concat(e):[e],t)},{})||{}}function getEventTransitionsMaps(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return isHistoryControlState(n)?t:(t[s]=t[s]||{},t[s][n]=t[s][n]?t[s][n].concat(e):[e],t)},{})||{}}function getHistoryStatesMap(t){return reduceTransitions((t,e,n,s)=>{const{from:i,event:a,to:o,action:r,predicate:c,gen:u}=e;if(isHistoryControlState(i)){const n=getHistoryUnderlyingState(i);t.set(n,(t.get(n)||[]).concat([e]))}else if(isHistoryControlState(o)){const n=getHistoryUnderlyingState(o);t.set(n,(t.get(n)||[]).concat([e]))}return t},new Map,t)||{}}function getTargetStatesMap(t){return reduceTransitions((t,e,n,s)=>{const{to:i}=e;return t.set(i,(t.get(i)||[]).concat([e])),t},new Map,t)||{}}function getAncestorMap(t){const{getLabel:e,getChildren:n}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,s,i)=>{const a=e(i),o=Object.keys(a)[0];return n(i).map(t=>Object.keys(e(t))[0]).forEach(e=>{t[e]=t[e]||[],t[e]=t[e].concat(o)}),t}},t)}function computeHistoryMaps(t){if(0===Object.keys(t).length)throw"computeHistoryMaps : passed empty control states parameter?";const{getLabel:e,isLeafLabel:n}=objectTreeLenses,s={strategy:PRE_ORDER,seed:{stateList:[],stateAncestors:{[DEEP]:{},[SHALLOW]:{}}},visit:(t,n,s)=>{const i=e(s),a=Object.keys(i)[0];t.stateList=t.stateList.concat(a);const{path:o}=n.get(s);n.set(JSON.stringify(o),a);const r=o.slice(0,-1);if(1===r.length)n.set(JSON.stringify(r),INIT_STATE);else{const e=n.get(JSON.stringify(r));t.stateAncestors[SHALLOW][a]=[e];const{ancestors:s}=o.reduce((t,e)=>{const s=t.path.slice(0,-1);if(t.path=s,s.length>1){const e=n.get(JSON.stringify(s));t.ancestors=t.ancestors.concat(e)}return t},{ancestors:[],path:o});t.stateAncestors[DEEP][a]=s}return t}},{stateList:i,stateAncestors:a}=traverseObj(s,t);return{stateList:i,stateAncestors:a}}function mapOverTransitionsActions(t,e){return reduceTransitions(function(e,n,s,i){const{from:a,event:o,to:r,action:c,predicate:u}=n,l=t(c,n,s,i);return l.displayName=l.displayName||c&&(c.name||c.displayName||formatActionName(c,a,o,r,u)),void 0===u?e.push({from:a,event:o,to:r,action:l}):0===s?e.push({from:a,event:o,guards:[{to:r,predicate:u,action:l}]}):e[e.length-1].guards.push({to:r,predicate:u,action:l}),e},[],e)}function reduceTransitions(t,e,n){return n.reduce((e,n,s)=>{let{from:i,event:a,to:o,gen:r,action:c,guards:u}=n;return u||(u=r?[{to:o,action:c,gen:r,predicate:void 0}]:[{to:o,action:c,predicate:void 0}]),u.reduce((e,n,o)=>{const{to:r,action:c,gen:u,predicate:l}=n;return t(e,u?{from:i,event:a,to:r,action:c,predicate:l,gen:u}:{from:i,event:a,to:r,action:c,predicate:l},o,s)},e)},e)}function everyTransition(t,e){return reduceTransitions((e,n)=>e&&t(n),!0,[e])}function computeTimesCircledOn(t,e){return t.reduce((t,n)=>n===e?t+1:t,0)}function isInitState(t){return t===INIT_STATE}function isInitEvent(t){return t===INIT_EVENT}function isEventless(t){return void 0===t}function arrayizeOutput(t){return t===NO_OUTPUT?NO_OUTPUT:Array.isArray(t)?t:[t]}function isHistoryControlState(t){return"object"==typeof t&&(DEEP in t||SHALLOW in t)}function getHistoryParentState(t){return t[SHALLOW]||t[DEEP]}function isShallowHistory(t){return t[SHALLOW]}function isDeepHistory(t){return t[DEEP]}function getHistoryType(t){return t[DEEP]?DEEP:SHALLOW}function getHistoryUnderlyingState(t){return t[getHistoryType(t)]}function isHistoryStateEdge(t){return void 0!==t.history}function initHistoryDataStructure(t){const e=()=>t.reduce((t,e)=>(t[e]="",t),{});return{[DEEP]:e(),[SHALLOW]:e()}}function isCompoundState(t,e){const{statesAdjacencyList:n}=t;return n[e]&&0!==n[e].length}function isAtomicState(t,e){return!isCompoundState(t,e)}function updateHistory(t,e,n){return n===INIT_STATE?t:([SHALLOW,DEEP].forEach(s=>{(e[s][n]||[]).forEach(e=>{t[s][e]=n})}),t)}function computeHistoryState(t,e,n,s){const{stateList:i,stateAncestors:a}=computeHistoryMaps(t);let o=initHistoryDataStructure(i);return(o=e.reduce((t,e)=>updateHistory(t,a,e),o))[n][s]}function findInitTransition(t){return t.find(t=>t.from===INIT_STATE&&t.event===INIT_EVENT)}function tryCatch(t,e){return function(...n){try{return t.apply(t,n)}catch(t){return e(t,n)}}}function tryCatchMachineFn(t,e,n=[]){return tryCatch(e,(s,i)=>{const a=new Error(s),o=getFunctionName(e),r=FUNCTION_THREW_ERROR(o,t);a.probableCause=s.probableCause?[s.probableCause,r].join("\n"):r;const c={fnName:o,params:n.reduce((t,e,n)=>(t[e]=i[n],t),{})};return a.info=s.info?[].concat([s.info]).concat([c]):c,a})}function getFunctionName(t){return t.name||t.displayName||"anonymous"}function assert(t,e){const n=t.apply(null,e);if(!0!==n){const e=n.info;throw console.error(`ERROR: failed contract ${t.name||""}. ${e?"Error info:":""}`,n.info),n}}function notifyThrows(t,e){t.error(e),e.probableCause&&t.error(`Probable cause: ${e.probableCause}`),e.info&&t.error("ERROR: additional info",e.info)}function handleFnExecError(t,e,n,s,i,a){const{debug:o,console:r}=t;return o&&n instanceof Error?(i({debug:o,console:r},n,e),!0):!(!o||s(n))&&(a({debug:o,console:r},n,e),!0)}function notifyAndRethrow({debug:t,console:e},n){throw notifyThrows(e,n),n}function throwIfInvalidActionResult({debug:t,console:e},n,s){const{action:i,extendedState:a,eventData:o,settings:r}=s,c=getFunctionName(i),u=new Error(INVALID_ACTION_FACTORY_EXECUTED(c,ACTION_FACTORY_DESC));throw u.info={fnName:getFunctionName(i),params:{updatedExtendedState:a,eventData:o,settings:r},returned:n},notifyThrows(e,u),u}function throwIfInvalidGuardResult({debug:t,console:e},n,s){const i=getFunctionName(s.predicate),a=new Error(INVALID_PREDICATE_EXECUTED(i,PREDICATE_DESC));throw a.info={predicateName:i,params:s,returned:n},notifyThrows(e,a),a}function throwIfInvalidEntryActionResult({debug:t,console:e},n,s){const{action:i,extendedState:a,eventData:o,settings:r}=s,c=getFunctionName(i),u=new Error(INVALID_ACTION_FACTORY_EXECUTED(c,ENTRY_ACTION_FACTORY_DESC));throw u.info={fnName:getFunctionName(i),params:{updatedExtendedState:a,eventData:o,settings:r},returned:n},notifyThrows(e,u),u}function isActions(t){return t&&"updates"in t&&"outputs"in t&&(t.outputs===NO_OUTPUT||Array.isArray(t.outputs))&&Array.isArray(t.updates)}function isEventStruct(t){let e;return t&&"object"==typeof t?Object.keys(t).length>1?(e=new Error(WRONG_EVENT_FORMAT_ERROR)).info={event:t,cause:"Event objects must have only one key which is the event name!"}:e=!0:(e=new Error(WRONG_EVENT_FORMAT_ERROR)).info={event:t,cause:"not an object!"},e}const noDuplicatedStates={name:"noDuplicatedStates",shouldThrow:!1,predicate:(t,e)=>{const{getLabel:n}=objectTreeLenses,s={strategy:PRE_ORDER,seed:{duplicatedStates:[],statesHashMap:{}},visit:(t,e,s)=>{const{duplicatedStates:i,statesHashMap:a}=t,o=n(s),r=Object.keys(o)[0];return r in a?{duplicatedStates:i.concat(r),statesHashMap:a}:{duplicatedStates:i,statesHashMap:(a[r]="",a)}}},{duplicatedStates:i}=traverseObj(s,t.states);return{isFulfilled:0===i.length,blame:{message:"State names must be unique! Found duplicated state names. Cf. log",info:{duplicatedStates:i}}}}},noReservedStates={name:"noReservedStates",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:-1===Object.keys(n).indexOf(INIT_STATE),blame:{message:"You cannot use a reserved control state name for any of the configured control states for the machine! Cf. log",info:{reservedStates:[INIT_STATE],statesType:n}}})},atLeastOneState={name:"atLeastOneState",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:Object.keys(n).length>0,blame:{message:"Machine configuration must define at least one control state! Cf. log",info:{statesType:n}}})},isInitialControlStateDeclared={name:"isInitialControlStateDeclared",shouldThrow:!1,predicate:(t,e,{initTransition:n,statesType:s})=>{const{initialControlState:i,transitions:a}=t,o=Object.keys(s);return i?{isFulfilled:o.indexOf(i)>-1,blame:{message:"Configured initial control state must be a declared state. Cf. log",info:{initialControlState:i,declaredStates:o}}}:{isFulfilled:!0,blame:void 0}}},eventsAreStrings={name:"eventsAreStrings",shouldThrow:!1,predicate:(t,e)=>({isFulfilled:t.events.every(t=>"string"==typeof t),blame:{message:"Events must be an array of strings!",info:{events:t.events}}})},validInitialConfig={name:"validInitialConfig",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s}=t;return n&&s?{isFulfilled:!1,blame:{message:"Invalid machine configuration : defining an initial control state and an initial transition at the same time may lead to ambiguity and is forbidden!",info:{initialControlState:s,initTransition:n}}}:n||s?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Invalid machine configuration : you must define EITHER an initial control state OR an initial transition! Else in which state is the machine supposed to start?",info:{initialControlState:s,initTransition:n}}}}},validInitialTransition={name:"validInitialTransition",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s,transitions:i}=t,a=i.reduce((t,e)=>(e.from===INIT_STATE&&t.push(e),t),[]);return{isFulfilled:s&&!n||!s&&n&&1===a.length&&n.event===INIT_EVENT&&(isInconditionalTransition(n)||areCconditionalTransitions(n)),blame:{message:"Invalid configuration for initial transition! Cf. log",info:{initTransition:n,initTransitions:a,initialControlState:s}}}}},initEventOnlyInCompoundStates={name:"initEventOnlyInCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:i})=>{const a=Object.keys(s).filter(t=>!s[t]).map(t=>({[t]:n[t]&&n[t][INIT_EVENT]})).filter(t=>Object.values(t)[0]);return{isFulfilled:0===a.length,blame:{message:"Found at least one atomic state with an entry transition! That is forbidden! Cf. log",info:{initTransitions:a}}}}},validInitialTransitionForCompoundState={name:"validInitialTransitionForCompoundState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:i})=>{const a=Object.keys(s).filter(t=>s[t]),o=a.map(t=>n[t]&&n[t][INIT_EVENT]),r=o.every(Boolean);if(!r)return{isFulfilled:!1,blame:{message:"Found at least one compound state without an entry transition! Cf. log",info:{hasEntryTransitions:a.map(t=>({[t]:!(!n[t]||!n[t][INIT_EVENT])}))}}};const c=r&&o.every(t=>{const{guards:e,to:n}=t;return!e&&"string"==typeof n});return c?c&&o.every(t=>{const{from:e,to:n}=t;return e!==n&&i[n]&&i[n].startsWith(i[e])})?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must have a target state which is strictly below the compound state in the state hierarchy! ",info:{states:t.states,statesPath:i,entryTransitions:o}}}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must be inconditional and the associated target control state cannot be a history pseudo-state. Cf. log",info:{entryTransitions:o}}}}},validEventLessTransitions={name:"validEventLessTransitions",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:i})=>{const a=Object.keys(s).map(t=>({[t]:n[t]&&"undefined"in n[t]&&1!==Object.keys(n[t]).length})).filter(t=>void 0!==Object.values(t)[0]&&Object.values(t)[0]);return{isFulfilled:0===a.length,blame:{message:"Found at least one control state without both an eventless transition and a competing transition! Cf. log",info:{failingOriginControlStates:a}}}}},allStateTransitionsOnOneSingleRow={name:"allStateTransitionsOnOneSingleRow",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n})=>{const s=Object.keys(n).reduce((t,e)=>{const s=Object.keys(n[e]).filter(t=>n[e][t].length>1);return s.length>0&&(t[e]=s),t},{});return{isFulfilled:0===Object.keys(s).length,blame:{message:"Found at least one control state and one event for which the associated transition are not condensated under a unique row! Cf. log",info:{statesTransitionsInfo:s}}}}},noConflictingTransitionsWithAncestorState={name:"noConflictingTransitionsWithAncestorState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,eventTransitionsMaps:s,ancestorMap:i})=>{const a=Object.keys(s).reduce((t,e)=>{const n=Object.keys(s[e]),a=n.filter(t=>t!==INIT_STATE).map(t=>i[t]&&{[t]:i[t].find(t=>n.indexOf(t)>-1)}).filter(t=>t&&Object.values(t).filter(Boolean).length>0);return a.length>0&&(t[e]=a),t},{});return{isFulfilled:0===Object.keys(a).length,blame:{message:"Found two conflicting transitions! A -ev-> X, and B -ev-> Y leads to ambiguity if A < B or B < A. Cf. log",info:{eventTransitionsInfo:a}}}}},isHistoryStatesTargetStates={name:"isHistoryStatesTargetStates",shouldThrow:!1,predicate:(t,e,{})=>{const n=t.transitions.reduce((t,e)=>isHistoryControlState(e.from)?t.concat(e):t,[]);return{isFulfilled:0===Object.keys(n).length,blame:{message:"Found a history pseudo state configured as the origin control state for a transition. History pseudo states should only be target control states. Cf. log",info:{wrongHistoryStates:n}}}}},isHistoryStatesCompoundStates={name:"isHistoryStatesCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,statesType:s})=>{const i=Object.keys(n).map(t=>{if(t===INIT_STATE)return[];return Object.keys(n[t]).reduce((e,i)=>{const a=n[t][i][0],{guards:o,to:r}=a;return o?o.reduce((t,e)=>{const{to:n}=e;return isHistoryControlState(n)&&!s[getHistoryUnderlyingState(n)]?t.concat(a):t},e):isHistoryControlState(r)&&!s[getHistoryUnderlyingState(r)]?e.concat(a):e},[])}).reduce((t,e)=>t.concat(e),[]);return{isFulfilled:0===Object.keys(i).length,blame:{message:"Found a history pseudo state connected to an atomic state! History pseudo states only refer to compound states. Cf. log",info:{wrongHistoryStates:i,states:t.states}}}}},isHistoryStatesExisting={name:"isHistoryStatesExisting",shouldThrow:!1,predicate:(t,e,{historyStatesMap:n,statesType:s})=>{const i=Array.from(n.entries()).map(([t,e])=>!(t in s)&&{historyState:t,flatTransitions:e}).filter(Boolean),a=Object.keys(i).length;return{isFulfilled:0===a,blame:{message:`Found ${a} history pseudo state referring to a control state that is not declared! Check the states property of the state machine definition.`,info:{invalidTransitions:i,states:t.states}}}}};function isInconditionalTransition(t){const{from:e,event:n,guards:s,to:i,action:a}=t;return void 0===s&&i&&isControlState(e)&&isEvent(n)&&isControlState(i)&&isActionFactory(a)}function isValidGuard(t){const{to:e,predicate:n,action:s}=t;return e&&isControlState(e)&&isFunction(n)&&isActionFactory(s)}function areCconditionalTransitions(t){const{from:e,event:n,guards:s,to:i}=t;return s&&Array.isArray(s)&&s.length>0&&!i&&isControlState(e)&&isEvent(n)&&s.every(isValidGuard)}const isValidFsmDef={name:"isValidFsmDef",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n,states:s,events:i,initialExtendedState:a}=t,o=n&&Array.isArray(n),r=s&&"object"==typeof s,c=i&&Array.isArray(i);return o?r?c?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"The events property for a machine definition must be an array!",info:{events:i}}}:{isFulfilled:!1,blame:{message:"The states property for a machine definition must be an object!",info:{states:s}}}:{isFulfilled:!1,blame:{message:"The transitions property for a machine definition must be an array!",info:{transitions:n}}}}},haveTransitionsValidTypes={name:"haveTransitionsValidTypes",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n}=t,s=n.map((t,e)=>!isInconditionalTransition(t)&&!areCconditionalTransitions(t)&&{transition:t,index:e}).filter(Boolean),i=Object.keys(s).length;return{isFulfilled:0===i,blame:{message:`Found ${i} transitions with invalid format! Check logs for more details.`,info:{wrongTransitions:s,transitions:n}}}}},areEventsDeclared={name:"areEventsDeclared",shouldThrow:!1,predicate:(t,e,{eventTransitionsMaps:n})=>{const s=Object.keys(n),i=t.events,a=i.map(t=>-1===s.indexOf(t)&&t).filter(Boolean),o=s.map(t=>-1===i.indexOf(t)&&t).filter(Boolean).filter(t=>t!==INIT_EVENT);return{isFulfilled:0===a.length&&0===o.length,blame:{message:"All declared events must be used in transitions. All events used in transition must be declared! Cf. log",info:{eventsDeclaredButNotTriggeringTransitions:a,eventsNotDeclaredButTriggeringTransitions:o}}}}},areStatesDeclared={name:"areStatesDeclared",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,targetStatesMap:s,statesType:i})=>{const a=Object.keys(n),o=Array.from(s.keys()),r=Object.keys([a,o].reduce((t,e)=>(e.forEach(e=>t[e]=!0),t),{})),c=Object.keys(i),u=c.map(t=>-1===r.indexOf(t)&&t).filter(Boolean),l=r.map(t=>t!==INIT_STATE&&-1===c.indexOf(t)&&t).filter(Boolean);return{isFulfilled:0===u.length&&0===u.length,blame:{message:"All declared states must be used in transitions. All states used in transition must be declared! Cf. log",info:{statesDeclaredButNotTriggeringTransitions:u,statesNotDeclaredButTriggeringTransitions:l}}}}},isValidSettings={name:"isValidSettings",shouldThrow:!1,predicate:t=>({isFulfilled:!0,blame:void 0})},isInitialStateOriginState={name:"isInitialStateOriginState",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n})=>Array.from(n.keys()).indexOf(INIT_STATE)>-1?{isFulfilled:!1,blame:{message:"Found at least one transition with the initial state as target state! CF. log",info:{targetStates:Array.from(n.keys()),transitions:t.transitions}}}:{isFulfilled:!0,blame:void 0}},isValidSelfTransition={name:"isValidSelfTransition",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n,statesType:s})=>{const i=Array.from(n.keys()).map(t=>{return n.get(t).map(e=>{const{from:n,event:i}=e;if(t in s&&!s[t]&&n&&n===t&&!i)return{state:t,flatTransition:e}}).filter(Boolean)}).filter(t=>t.length>0);return{isFulfilled:0===i.length,blame:{message:"Found at least one eventless self-transition involving an atomic state! This is forbidden to avoid infinity loop! Cf. log",info:{wrongSelfTransitions:i}}}}},fsmContracts={computed:(t,e)=>({statesType:getStatesType(t.states),initTransition:findInitTransition(t.transitions),statesTransitionsMap:getStatesTransitionsMap(t.transitions),statesTransitionsMaps:getStatesTransitionsMaps(t.transitions),eventTransitionsMaps:getEventTransitionsMaps(t.transitions),ancestorMap:getAncestorMap(t.states),statesPath:getStatesPath(t.states),historyStatesMap:getHistoryStatesMap(t.transitions),targetStatesMap:getTargetStatesMap(t.transitions)}),description:"FSM structure",contracts:[isValidFsmDef,isValidSettings,isInitialControlStateDeclared,isInitialStateOriginState,eventsAreStrings,haveTransitionsValidTypes,noDuplicatedStates,noReservedStates,atLeastOneState,areEventsDeclared,areStatesDeclared,validInitialConfig,validInitialTransition,initEventOnlyInCompoundStates,validInitialTransitionForCompoundState,validEventLessTransitions,isValidSelfTransition,allStateTransitionsOnOneSingleRow,noConflictingTransitionsWithAncestorState,isHistoryStatesExisting,isHistoryStatesTargetStates,isHistoryStatesCompoundStates]};function makeContractHandler(t){const e=t.description;return function(...n){const s=[],i=t.computed.apply(null,n);return{isFulfilled:t.contracts.reduce((t,a)=>{const{name:o,predicate:r,shouldThrow:c}=a,u=n.concat(i),{isFulfilled:l,blame:d}=r.apply(null,u),f=`${e} FAILS ${o}!`,{message:T,info:p}=d||{};if(l)return t;if(s.push({name:o,message:T,info:p}),console.error(f),console.error([o,T].join(": ")),console.debug("Supporting error data:",p),c)throw new Error([f,"check console for information!"].join("\n"));return!1},!0),failingContracts:s}}}const fsmContractChecker=(t,e,n)=>makeContractHandler(n)(t,e),alwaysTrue=()=>!0;function build_event_enum(t){return(t=t.reduce?t:Array.prototype.slice.call(arguments)).push(INIT_EVENT),t.reduce(function(t,e){return t[e]=e,t},{})}function build_nested_state_structure(t){const e="State";let n={},s={};function i(){}return t={nok:t},i.prototype={current_state_name:INIT_STATE},n[INIT_STATE]=new i,n[STATE_PROTOTYPE_NAME]=new i,function t(i,a){keys(i).forEach(function(o){const r=i[o];if(n[o]=new a,n[o].name=o,n[o].parent_name=get_fn_name(a),n[o].root_name=e,"object"==typeof r){s[o]=!0;const e=function(){};e.displayName=o,e.prototype=n[o],t(r,e)}})}(t,i),{hash_states:n,is_group_state:s}}function build_state_enum(t){let e={history:{}};return e.NOK=INIT_STATE,function t(n){keys(n).forEach(function(s){const i=n[s];e[s]=s,"object"==typeof i&&t(i)})}(t),e}function normalizeTransitions(t){const{initialControlState:e,transitions:n}=t,s=findInitTransition(n);return e?n.concat([{from:INIT_STATE,event:INIT_EVENT,to:e,action:ACTION_IDENTITY}]):s?n:void 0}function normalizeFsmDef(t){return Object.assign({},t,{transitions:normalizeTransitions(t)})}function create_state_machine(t,e){return createStateMachine(t,e)}function createStateMachine(t,e){const{states:n,events:s,initialExtendedState:i,updateState:a}=t,{debug:o}=e||{},r=o&&o.checkContracts||void 0;let c=o&&o.console?o.console:emptyConsole;if(r){const{failingContracts:n}=fsmContractChecker(t,e,r);if(n.length>0)throw new Error("createStateMachine: called with wrong parameters! Cf. logs for failing contracts.")}const u=tryCatchMachineFn(UPDATE_STATE_FN_DESC,a,["extendedState, updates"]),l=build_event_enum(s),d=normalizeTransitions(t),f=build_nested_state_structure(n);let T=i;const{stateList:p,stateAncestors:m}=computeHistoryMaps(n);let h=initHistoryDataStructure(p),g={},y={};const _=f.is_group_state;let E=f.hash_states;function S(t,e){var n,s;c.debug("send event",t),n=isEventStruct,s=[t],r&&assert(n,s);const i=keys(t)[0],a=t[i],u=E[INIT_STATE].current_state_name;return e&&i===INIT_EVENT&&u!==INIT_STATE?(c.warn("The external event INIT_EVENT can only be sent when starting the machine!"),NO_OUTPUT):function(t,e,n,s){const i=t[INIT_STATE].current_state_name,a=t[i][e];if(a){c.log("found event handler!"),c.info("WHEN EVENT ",e);const{stop:r,outputs:u}=a(s,n,i);o&&!r&&c.warn("No guards have been fulfilled! We recommend to configure guards explicitly to cover the full state space!");const l=arrayizeOutput(u),d=t[INIT_STATE].current_state_name;if(y[d]&&d!==i){const t=g[d]?INIT_EVENT:AUTO_EVENT;return[].concat(l).concat(S({[t]:n},!1))}return l}return c.warn(`There is no transition associated to the event |${e}| in state |${i}|!`),NO_OUTPUT}(f.hash_states,i,a,T)}return d.forEach(function(t){let{from:n,to:s,action:i,event:r,guards:d}=t;d||(d=[{predicate:alwaysTrue,to:s,action:i}]),r===INIT_EVENT&&(g[n]=!0);let f=E[n];if(r&&!(r in l))throw`unknown event ${r} found in state machine definition!`;r||(r=AUTO_EVENT,y[n]=!0),_[n]&&g[n]&&(y[n]=!0),f[r]=d.reduce((t,s,i)=>{const r=s.action||ACTION_IDENTITY,l=r.name||r.displayName,d=function(t,e){let s="";const d=function(d,f,p){n=p||n;const y=t.predicate,_=y||alwaysTrue,S=!y||tryCatchMachineFn(PREDICATE_DESC,_,["extendedState","eventData","settings"])(d,f,e),O=t.to;if(s=_?"_checking_condition_"+i:"",handleFnExecError({debug:o,console:c},{predicate:t.predicate,extendedState:d,eventData:f,settings:e},S,isBoolean,notifyAndRethrow,throwIfInvalidGuardResult),S){c.info("IN STATE ",n),y&&c.info(`CASE: guard ${_.name} for transition is fulfilled`),!y&&c.info("CASE: unguarded transition"),c.info("THEN : we execute the action "+l);const t=tryCatchMachineFn(ACTION_FACTORY_DESC,r,["extendedState","eventData","settings"])(d,f,e);handleFnExecError({debug:o,console:c},{action:r,extendedState:d,eventData:f,settings:e},t,isActions,notifyAndRethrow,throwIfInvalidActionResult);const{updates:s,outputs:i}=t;!function(t,e,n){const s=n[t].name;h=updateHistory(h,m,s),c.info("left state",wrap(t))}(n,0,E);const p=u(d,s);handleFnExecError({debug:o,console:c},{updateStateFn:a,extendedState:d,updates:s},p,alwaysTrue,notifyAndRethrow,noop),T=p;const S=function(t,e,n){let s,i;if(isHistoryControlState(t)){const e=t.deep?DEEP:t.shallow?SHALLOW:void 0,a=t[e];o&&c&&!g[a]&&c.warn("Configured a history state which does not relate to a compound state! The behaviour of the machine is thus unspecified. Please review your machine configuration"),i=h[e][a]||a,s=n[i]}else{if(!t)throw"enter_state : unknown case! Not a state name, and not a history state to enter!";s=n[t],i=s.name}return n[INIT_STATE].current_state_name=i,o&&c.info("AND TRANSITION TO STATE",i),i}(O,0,E);return c.info("ENTERING NEXT STATE : ",S),{stop:!0,outputs:i}}return{stop:!1,outputs:NO_OUTPUT}};return d.displayName=n+s,d}(s,e);return function(e,n,s){const i=t(e,n,s);return i.stop?i:d(e,n,s)}},function(){return{stop:!1,outputs:NO_OUTPUT}})}),S({[INIT_EVENT]:i},!0),function(t){return S(t,!0)}}function makeWebComponentFromFsm({name:t,eventHandler:e,fsm:n,commandHandlers:s,effectHandlers:i,options:a}){return customElements.define(t,class extends HTMLElement{constructor(){if(t.split("-").length<=1)throw"makeWebComponentFromFsm : web component's name MUST include a dash! Please review the name property passed as parameter to the function!";super();const o=this;this.eventSubject=e,this.options=Object.assign({},a);const r=this.options.NO_ACTION||NO_OUTPUT;this.eventSubject.subscribe({next:t=>{const e=n(t);e!==r&&e.forEach(t=>{if(t===r)return;const{command:e,params:n}=t;s[e](this.eventSubject.next,n,i,o)})}})}static get observedAttributes(){return[]}connectedCallback(){this.options.initialEvent&&this.eventSubject.next(this.options.initialEvent)}disconnectedCallback(){this.options.terminalEvent&&this.eventSubject.next(this.options.terminalEvent),this.eventSubject.complete()}attributeChangedCallback(t,e,n){this.constructor(),this.connectedCallback()}})}function makeNamedActionsFactory(t){return Object.keys(t).reduce((e,n)=>{const s=t[n];return s.displayName=n,e[n]=s,e},{})}function mergeOutputsFn(t){return t.reduce((t,e)=>t.concat(e),[])}function decorateWithEntryActions(t,e,n){if(!e)return t;const{transitions:s,states:i,initialExtendedState:a,initialControlState:o,events:r,updateState:c,settings:u}=t,l=getFsmStateList(i),d=Object.keys(e).every(t=>null!=l[t]),f=n||mergeOutputsFn;if(d){return{initialExtendedState:a,initialControlState:o,states:i,events:r,transitions:mapOverTransitionsActions((t,n,s,i)=>{const{to:a}=n,o=e[a];return o?decorateWithExitAction(t,o,f,c):t},s),updateState:c,settings:u}}throw"decorateWithEntryActions : found control states for which entry actions are defined, and yet do not exist in the state machine!"}function decorateWithExitAction(t,e,n,s){const i=function(i,a,o){const{debug:r}=o,c=tryCatchMachineFn(ENTRY_ACTION_FACTORY_DESC,e,["extendedState","eventData","settings"]),u=t(i,a,o),l=u.updates,d=tryCatchMachineFn(UPDATE_STATE_FN_DESC,s,["extendedState, updates"])(i,l);handleFnExecError({debug:r,console:console},{updateStateFn:s,extendedState:i,actionUpdate:l},d,alwaysTrue,notifyAndRethrow,noop);const f=d,T=c(f,a,o);if(!handleFnExecError({debug:r,console:console},{action:e,extendedState:f,eventData:a,settings:o},T,isActions,notifyAndRethrow,throwIfInvalidEntryActionResult))return{updates:[].concat(l,T.updates),outputs:n([u.outputs,T.outputs])}};return i.displayName=`Entry_Action_After_${getFunctionName(t)}`,i}function traceFSM(t,e){const{initialExtendedState:n,initialControlState:s,events:i,states:a,transitions:o,updateState:r}=e;return{initialExtendedState:n,initialControlState:s,events:i,states:a,updateState:r,transitions:mapOverTransitionsActions((t,e,n,s)=>(function(i,a,o){const{from:c,event:u,to:l,predicate:d}=e,f=tryCatchMachineFn(ACTION_FACTORY_DESC,t,["extendedState","eventData","settings"])(i,a,o),{outputs:T,updates:p}=f;return{updates:p,outputs:[{outputs:T,updates:p,extendedState:i,newExtendedState:tryCatchMachineFn(UPDATE_STATE_FN_DESC,r,["extendedState, updates"])(i,p||[]),controlState:c,event:{eventLabel:u,eventData:a},settings:o,targetControlState:l,predicate:d,actionFactory:t,guardIndex:n,transitionIndex:s}]}}),o)}}function makeHistoryStates(t){const e=Object.keys(getFsmStateList(t));return(t,n)=>{if(!e.includes(n))throw"makeHistoryStates: the state for which a history state must be constructed is not a configured state for the state machine under implementation!!";return{[t]:n,type:history_symbol}}}function historyState(t,e){return{[t]:e}}function generateStatePlantUmlHeader(t,e){return e?`state "${e}" as ${t} <<NoContent>>`:`state "${getDisplayName(t)}" as ${t} <<NoContent>>`}function toPlantUml(t,e){const{states:n,transitions:s}=t,{getChildren:i,constructTree:a,getLabel:o}=objectTreeLenses,r=t=>t.join(SEP),c=postOrderTraverseTree(objectTreeLenses,{seed:()=>Map,visit:(t,e,n)=>{const{path:a}=e.get(n),c=o(n),u=stateToPlantUML(Object.keys(c)[0],times$1(e=>t.get(r(a.concat(e))),((t,e)=>i(t,e).length)(n,e)),s);return t.set(r(a),u),t}},{[INIT_STATE]:n}),u=c.get("0");return c.clear(),u}function stateToPlantUML(t,e,n){return[`${generateStatePlantUmlHeader(t,"")} {`,e.join("\n"),format_history_states(t,n),format_entry_transitions(t,n),"}",translate_transitions(t,n)].filter(t=>"\n"!==t&&""!==t).join("\n")}function format_history_states(t,e){const n=e.reduce((e,n)=>{return get_all_transitions(n).filter(is_history_transition).filter(is_to_history_control_state_of(t)).reduce((t,e)=>(t[format_history_transition_state_name(e)]=void 0,t),e)},{});return Object.keys(n).map(t=>`${generateStatePlantUmlHeader(t,HISTORY_STATE_NAME)}`).join("\n")}function translate_transitions(t,e){return[format_history_transitions(t,e),format_standard_transitions(t,e)].filter(Boolean).join("\n")}function format_standard_transitions(t,e){return t===INIT_STATE?"":e.map(e=>{return get_all_transitions(e).filter(is_from_control_state(t)).filter(t=>!is_entry_transition(t)).filter(t=>!is_history_transition(t)).map(({from:t,event:e,predicate:n,to:s,action:i})=>[t,TRANSITION_SYMBOL,s,TRANSITION_LABEL_START_SYMBOL,format_transition_label(e,n,i)].join(" ")).join("\n")}).filter(Boolean).join("\n")}function format_entry_transitions(t,e){return e.reduce((e,n)=>{return get_all_transitions(n).filter(is_entry_transition).filter(is_from_control_state(t)).reduce((t,e)=>{const{from:n,to:s,predicate:i,action:a}=e;return t.push(`[*] ${TRANSITION_SYMBOL} ${s} ${TRANSITION_LABEL_START_SYMBOL} ${format_transition_label("",i,a)}`),t},e)},[]).join("\n")}function format_history_transitions(t,e){return e.map(e=>{return get_all_transitions(e).filter(is_from_control_state(t)).filter(is_history_transition).map(({from:t,event:e,predicate:n,to:s,action:i})=>[t,TRANSITION_SYMBOL,format_history_transition_state_name({from:t,to:s}),TRANSITION_LABEL_START_SYMBOL,format_transition_label(e,n,i)].join(" ")).join("\n")}).filter(Boolean).join("\n")}function toDagreVisualizerFormat(t){const{states:e,transitions:n}=t,{getLabel:s,getChildren:i}=objectTreeLenses,{constructTree:a}=arrayTreeLenses,o=t=>t.join(SEP),r=postOrderTraverseTree(objectTreeLenses,{seed:()=>Map,visit:(t,e,n)=>{const{path:r}=e.get(n),c=s(n),u=Object.keys(c)[0],l=times$1(e=>t.get(o(r.concat(e))),((t,e)=>i(t,e).length)(n,e));return t.set(o(r),a(u,l)),t}},{[INIT_STATE]:e}).get("0"),c=n.map(t=>{const{from:e,to:n,event:s,guards:i,action:a}=t;if(i){return{from:e,event:s,guards:i.map(t=>{const{predicate:e,to:n,action:s}=t;return{predicate:e.name,to:n,action:s.name}})}}return{from:e,to:n,event:s,action:a.name||"no action name?"}});return JSON.stringify({states:r,transitions:c})}export{fsmContracts,build_state_enum,normalizeTransitions,normalizeFsmDef,create_state_machine,createStateMachine,makeWebComponentFromFsm,makeNamedActionsFactory,mergeOutputsFn,decorateWithEntryActions,traceFSM,makeHistoryStates,historyState,toPlantUml,toDagreVisualizerFormat,CONTRACT_MODEL_UPDATE_FN_RETURN_VALUE,SEP,TRANSITION_SYMBOL,TRANSITION_LABEL_START_SYMBOL,HISTORY_STATE_NAME,HISTORY_PREFIX,INIT_STATE,INIT_EVENT,AUTO_EVENT,STATE_PROTOTYPE_NAME,NO_STATE_UPDATE,NO_OUTPUT,ACTION_IDENTITY,history_symbol,SHALLOW,DEEP,WRONG_EVENT_FORMAT_ERROR,FUNCTION_THREW_ERROR,INVALID_ACTION_FACTORY_EXECUTED,INVALID_PREDICATE_EXECUTED,ACTION_FACTORY_DESC,ENTRY_ACTION_FACTORY_DESC,UPDATE_STATE_FN_DESC,PREDICATE_DESC,COMMAND_RENDER,noop,emptyConsole,isBoolean,isFunction,isControlState,isEvent,isActionFactory,make_states,make_events,get_fn_name,wrap,times$1 as times,always,keys,merge$1 as merge,is_history_transition,is_entry_transition,is_from_control_state,is_to_history_control_state_of,is_history_control_state_of,format_transition_label,format_history_transition_state_name,get_all_transitions,getDisplayName,mergeModelUpdates,chainModelUpdates,mergeActionFactories,identity,lastOf,getFsmStateList,getStatesType,getStatesPath,getStatesTransitionsMap,getStatesTransitionsMaps,getEventTransitionsMaps,getHistoryStatesMap,getTargetStatesMap,getAncestorMap,computeHistoryMaps,mapOverTransitionsActions,reduceTransitions,everyTransition,computeTimesCircledOn,isInitState,isInitEvent,isEventless,arrayizeOutput,isHistoryControlState,getHistoryParentState,isShallowHistory,isDeepHistory,getHistoryType,getHistoryUnderlyingState,isHistoryStateEdge,initHistoryDataStructure,isCompoundState,isAtomicState,updateHistory,computeHistoryState,findInitTransition,tryCatch,tryCatchMachineFn,getFunctionName,assert,notifyThrows,handleFnExecError,notifyAndRethrow,throwIfInvalidActionResult,throwIfInvalidGuardResult,throwIfInvalidEntryActionResult,isActions,isEventStruct}; | ||
const CONTRACT_MODEL_UPDATE_FN_RETURN_VALUE="Model update function must return valid update operations!",SEP=".",TRANSITION_SYMBOL="--\x3e",TRANSITION_LABEL_START_SYMBOL=":",HISTORY_STATE_NAME="H",HISTORY_PREFIX="history.",INIT_STATE="nok",INIT_EVENT="init",AUTO_EVENT="auto",STATE_PROTOTYPE_NAME="State",NO_STATE_UPDATE=[],NO_OUTPUT=null,ACTION_IDENTITY=function(){return{outputs:NO_OUTPUT,updates:NO_STATE_UPDATE}},history_symbol={},SHALLOW="shallow",DEEP="deep",WRONG_EVENT_FORMAT_ERROR="The machine received an event which does not have the proper format. Expecting an object whose unique key is the event name, and value is the event data.",FUNCTION_THREW_ERROR=(t,e)=>`Exception thrown when executing ${e} ${t||""}`,INVALID_ACTION_FACTORY_EXECUTED=(t,e)=>`${FUNCTION_THREW_ERROR(t,e)}\nThe ${e} returned a value which is not an action.`,INVALID_PREDICATE_EXECUTED=(t,e)=>`${FUNCTION_THREW_ERROR(t,e)}\nThe ${e} returned a value which is not a boolean.`,ACTION_FACTORY_DESC="action factory",ENTRY_ACTION_FACTORY_DESC="(decorating) entry action",UPDATE_STATE_FN_DESC="update state function",PREDICATE_DESC="predicate",COMMAND_RENDER="render",PATH_ROOT=[0],PRE_ORDER="PRE_ORDER";function clone(t){return void 0===t?void 0:JSON.parse(JSON.stringify(t))}function merge(t,e){return Object.assign({},t,e)}function updatePathInTraversalState(t,e,n){n.forEach((n,s)=>{const i=t.get(e),a=t.get(n),o=a&&a.path;t.set(n,merge(a,{isAdded:!0,isVisited:!1,path:o||i.path.concat(s)}))})}function updateVisitInTraversalState(t,e){t.set(e,merge(t.get(e),{isVisited:!0}))}function visitTree(t,e){const{store:n,lenses:s,traverse:i}=t,{empty:a,add:o,takeAndRemoveOne:r,isEmpty:c}=n,{getChildren:u}=s,{visit:l,seed:d}=i,f=new Map,T="function"==typeof d?new(d()):clone(d);let p="function"==typeof a?new(a()):clone(a),m=T;for(o([e],p),f.set(e,{isAdded:!0,isVisited:!1,path:PATH_ROOT});!c(p);){const t=r(p),e=u(f,t);o(e,p),updatePathInTraversalState(f,t,e),m=l(m,f,t),updateVisitInTraversalState(f,t)}return f.clear(),m}function breadthFirstTraverseTree(t,e,n){const{getChildren:s}=t;return visitTree({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.push.apply(e,t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function preorderTraverseTree(t,e,n){const{getChildren:s}=t;return visitTree({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function postOrderTraverseTree(t,e,n){const{getChildren:s}=t,{seed:i,visit:a}=e,o=(t,e)=>e.get(t).isVisited||((t,e)=>0===s(t,e).length)(t,e);return visitTree({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>o(e,t)?[]:s(e,t).concat([e])},traverse:{seed:i,visit:(t,e,n)=>o(n,e)?a(t,e,n):t}},n)}function isLeafLabel(t){return 0===objectTreeLenses.getChildren(t).length}const objectTreeLenses={isLeafLabel:isLeafLabel,getLabel:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getLabel > unexpected object tree value";return t},getChildren:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getChildren > unexpected value";{let e=Object.values(t)[0];return e&&"object"==typeof e&&!Array.isArray(e)?Object.keys(e).map(t=>({[t]:e[t]})):[]}},constructTree:(t,e)=>{const n=t&&Object.keys(t)&&Object.keys(t)[0];return 0===e.length?t:{[n]:Object.assign.apply(null,e)}}};function traverseObj(t,e){const n={root:e},{strategy:s,seed:i,visit:a}=t;return({BFS:breadthFirstTraverseTree,PRE_ORDER:preorderTraverseTree,POST_ORDER:postOrderTraverseTree}[s]||preorderTraverseTree)(objectTreeLenses,{seed:i,visit:function(t,e,n){const{path:s}=e.get(n);return JSON.stringify(s)===JSON.stringify(PATH_ROOT)?t:a(t,e,n)}},n)}const arrayTreeLenses={getLabel:t=>Array.isArray(t)?t[0]:t,getChildren:t=>Array.isArray(t)?t[1]:[],constructTree:(t,e)=>e&&Array.isArray(e)&&e.length>0?[t,e]:t},noop=()=>{},emptyConsole={log:noop,warn:noop,info:noop,debug:noop,error:noop,trace:noop};function isBoolean(t){return"boolean"==typeof t}function isFunction(t){return"function"==typeof t}function isControlState(t){return t&&"string"==typeof t||isHistoryControlState(t)}function isEvent(t){return t&&"string"==typeof t}function isActionFactory(t){return t&&"function"==typeof t}function make_states(t){return t.reduce((t,e)=>(t[e]="",t),{})}function make_events(t){return t}function get_fn_name(t){return/^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/.exec(t.toString())[1]}function wrap(t){return["-",t,"-"].join("")}function times$1(t,e){return Array.apply(null,{length:e}).map(Number.call,Number).map(t)}function always(t){return t}function keys(t){return Object.keys(t)}function merge$1(t,e){return Object.assign({},t,e)}function is_history_transition(t){return t.to.startsWith(HISTORY_PREFIX)}function is_entry_transition(t){return t.event===INIT_EVENT}function is_from_control_state(t){return function(e){return e.from===t}}function is_to_history_control_state_of(t){return function(e){return is_history_control_state_of(t,e.to)}}function is_history_control_state_of(t,e){return e.substring(HISTORY_PREFIX.length)===t}function format_transition_label(t,e,n){const s=t||"";return e&&n?`${s} [${e.name}] / ${n.name}`:e?`${s} [${e.name}]}`:n?`${s} / ${n.name}`:`${s}`}function format_history_transition_state_name({from:t,to:e}){return`${t}.${e.substring(HISTORY_PREFIX.length)}.${HISTORY_STATE_NAME}`}function get_all_transitions(t){const{from:e,event:n,guards:s}=t;return s?s.map(({predicate:t,to:s,action:i})=>({from:e,event:n,predicate:t,to:s,action:i})):[t]}function getDisplayName(t){return t.replace(/_/g," ")}function mergeModelUpdates(t){return function(e,n,s){return{updates:t.reduce((t,i)=>{const a=i(e,n,s).updates;return a?t.concat(a):t},[]),outputs:NO_OUTPUT}}}function chainModelUpdates(t){return function(e,n,s){const{updateState:i}=s;return{updates:t.reduce((t,e)=>{const{extendedState:a,updates:o}=t,r=e(a,n,s).updates;return{extendedState:i(a,o),updates:r}},{extendedState:e,updates:[]}).updates||[],outputs:NO_OUTPUT}}}function mergeActionFactories(t,e){return function(n,s,i){const a=e.map(t=>t(n,s,i)),o=a.map(t=>t.updates||[]),r=a.map(t=>t.outputs||{});return{updates:[].concat(...o),outputs:t(r)}}}function identity(t,e,n){return{updates:[],outputs:NO_OUTPUT}}function lastOf(t){return t[t.length-1]}function formatActionName(t,e,n,s,i){const a=i?i.name:"",o=a?`[${a}]`:"",r=t?t.name:"identity";return`${r||"unnamed action"}:${e}-${n}->${s} ${o}`}function getFsmStateList(t){const{getLabel:e}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,n,s)=>{const i=e(s);return t[Object.keys(i)[0]]="",t}},t)}function getStatesType(t){const{getLabel:e,isLeafLabel:n}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,s,i)=>{const a=e(i),o=Object.keys(a)[0];return n(a)?(t[o]=!1,t):(t[o]=!0,t)}},t)}function getStatesPath(t){const{getLabel:e}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,n,s)=>{const i=n.get(s).path.join("."),a=e(s);return t[Object.keys(a)[0]]=i,t}},t)}function getStatesTransitionsMap(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return isHistoryControlState(n)?t:(t[n]=t[n]||{},t[n][s]=e,t)},{})||{}}function getStatesTransitionsMaps(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return isHistoryControlState(n)?t:(t[n]=t[n]||{},t[n][s]=t[n][s]?t[n][s].concat(e):[e],t)},{})||{}}function getEventTransitionsMaps(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return isHistoryControlState(n)?t:(t[s]=t[s]||{},t[s][n]=t[s][n]?t[s][n].concat(e):[e],t)},{})||{}}function getHistoryStatesMap(t){return reduceTransitions((t,e,n,s)=>{const{from:i,event:a,to:o,action:r,predicate:c,gen:u}=e;if(isHistoryControlState(i)){const n=getHistoryUnderlyingState(i);t.set(n,(t.get(n)||[]).concat([e]))}else if(isHistoryControlState(o)){const n=getHistoryUnderlyingState(o);t.set(n,(t.get(n)||[]).concat([e]))}return t},new Map,t)||{}}function getTargetStatesMap(t){return reduceTransitions((t,e,n,s)=>{const{to:i}=e;return t.set(i,(t.get(i)||[]).concat([e])),t},new Map,t)||{}}function getAncestorMap(t){const{getLabel:e,getChildren:n}=objectTreeLenses;return traverseObj({strategy:PRE_ORDER,seed:{},visit:(t,s,i)=>{const a=e(i),o=Object.keys(a)[0];return n(i).map(t=>Object.keys(e(t))[0]).forEach(e=>{t[e]=t[e]||[],t[e]=t[e].concat(o)}),t}},t)}function computeHistoryMaps(t){if(0===Object.keys(t).length)throw"computeHistoryMaps : passed empty control states parameter?";const{getLabel:e,isLeafLabel:n}=objectTreeLenses,s={strategy:PRE_ORDER,seed:{stateList:[],stateAncestors:{[DEEP]:{},[SHALLOW]:{}}},visit:(t,n,s)=>{const i=e(s),a=Object.keys(i)[0];t.stateList=t.stateList.concat(a);const{path:o}=n.get(s);n.set(JSON.stringify(o),a);const r=o.slice(0,-1);if(1===r.length)n.set(JSON.stringify(r),INIT_STATE);else{const e=n.get(JSON.stringify(r));t.stateAncestors[SHALLOW][a]=[e];const{ancestors:s}=o.reduce((t,e)=>{const s=t.path.slice(0,-1);if(t.path=s,s.length>1){const e=n.get(JSON.stringify(s));t.ancestors=t.ancestors.concat(e)}return t},{ancestors:[],path:o});t.stateAncestors[DEEP][a]=s}return t}},{stateList:i,stateAncestors:a}=traverseObj(s,t);return{stateList:i,stateAncestors:a}}function mapOverTransitionsActions(t,e){return reduceTransitions(function(e,n,s,i){const{from:a,event:o,to:r,action:c,predicate:u}=n,l=t(c,n,s,i);return l.displayName=l.displayName||c&&(c.name||c.displayName||formatActionName(c,a,o,r,u)),void 0===u?e.push({from:a,event:o,to:r,action:l}):0===s?e.push({from:a,event:o,guards:[{to:r,predicate:u,action:l}]}):e[e.length-1].guards.push({to:r,predicate:u,action:l}),e},[],e)}function reduceTransitions(t,e,n){return n.reduce((e,n,s)=>{let{from:i,event:a,to:o,gen:r,action:c,guards:u}=n;return u||(u=r?[{to:o,action:c,gen:r,predicate:void 0}]:[{to:o,action:c,predicate:void 0}]),u.reduce((e,n,o)=>{const{to:r,action:c,gen:u,predicate:l}=n;return t(e,u?{from:i,event:a,to:r,action:c,predicate:l,gen:u}:{from:i,event:a,to:r,action:c,predicate:l},o,s)},e)},e)}function everyTransition(t,e){return reduceTransitions((e,n)=>e&&t(n),!0,[e])}function computeTimesCircledOn(t,e){return t.reduce((t,n)=>n===e?t+1:t,0)}function isInitState(t){return t===INIT_STATE}function isInitEvent(t){return t===INIT_EVENT}function isEventless(t){return void 0===t}function arrayizeOutput(t){return t===NO_OUTPUT?NO_OUTPUT:Array.isArray(t)?t:[t]}function isHistoryControlState(t){return"object"==typeof t&&(DEEP in t||SHALLOW in t)}function getHistoryParentState(t){return t[SHALLOW]||t[DEEP]}function isShallowHistory(t){return t[SHALLOW]}function isDeepHistory(t){return t[DEEP]}function getHistoryType(t){return t[DEEP]?DEEP:SHALLOW}function getHistoryUnderlyingState(t){return t[getHistoryType(t)]}function isHistoryStateEdge(t){return void 0!==t.history}function initHistoryDataStructure(t){const e=()=>t.reduce((t,e)=>(t[e]="",t),{});return{[DEEP]:e(),[SHALLOW]:e()}}function isCompoundState(t,e){const{statesAdjacencyList:n}=t;return n[e]&&0!==n[e].length}function isAtomicState(t,e){return!isCompoundState(t,e)}function updateHistory(t,e,n){return n===INIT_STATE?t:([SHALLOW,DEEP].forEach(s=>{(e[s][n]||[]).forEach(e=>{t[s][e]=n})}),t)}function computeHistoryState(t,e,n,s){const{stateList:i,stateAncestors:a}=computeHistoryMaps(t);let o=initHistoryDataStructure(i);return(o=e.reduce((t,e)=>updateHistory(t,a,e),o))[n][s]}function findInitTransition(t){return t.find(t=>t.from===INIT_STATE&&t.event===INIT_EVENT)}function tryCatch(t,e){return function(...n){try{return t.apply(t,n)}catch(t){return e(t,n)}}}function tryCatchMachineFn(t,e,n=[]){return tryCatch(e,(s,i)=>{const a=new Error(s),o=getFunctionName(e),r=FUNCTION_THREW_ERROR(o,t);a.probableCause=s.probableCause?[s.probableCause,r].join("\n"):r;const c={fnName:o,params:n.reduce((t,e,n)=>(t[e]=i[n],t),{})};return a.info=s.info?[].concat([s.info]).concat([c]):c,a})}function getFunctionName(t){return t.name||t.displayName||"anonymous"}function assert(t,e){const n=t.apply(null,e);if(!0!==n){const e=n.info;throw console.error(`ERROR: failed contract ${t.name||""}. ${e?"Error info:":""}`,n.info),n}}function notifyThrows(t,e){t.error(e),e.probableCause&&t.error(`Probable cause: ${e.probableCause}`),e.info&&t.error("ERROR: additional info",e.info)}function handleFnExecError(t,e,n,s,i,a){const{debug:o,console:r}=t;return o&&n instanceof Error?(i({debug:o,console:r},n,e),!0):!(!o||s(n))&&(a({debug:o,console:r},n,e),!0)}function notifyAndRethrow({debug:t,console:e},n){throw notifyThrows(e,n),n}function throwIfInvalidActionResult({debug:t,console:e},n,s){const{action:i,extendedState:a,eventData:o,settings:r}=s,c=getFunctionName(i),u=new Error(INVALID_ACTION_FACTORY_EXECUTED(c,ACTION_FACTORY_DESC));throw u.info={fnName:getFunctionName(i),params:{updatedExtendedState:a,eventData:o,settings:r},returned:n},notifyThrows(e,u),u}function throwIfInvalidGuardResult({debug:t,console:e},n,s){const i=getFunctionName(s.predicate),a=new Error(INVALID_PREDICATE_EXECUTED(i,PREDICATE_DESC));throw a.info={predicateName:i,params:s,returned:n},notifyThrows(e,a),a}function throwIfInvalidEntryActionResult({debug:t,console:e},n,s){const{action:i,extendedState:a,eventData:o,settings:r}=s,c=getFunctionName(i),u=new Error(INVALID_ACTION_FACTORY_EXECUTED(c,ENTRY_ACTION_FACTORY_DESC));throw u.info={fnName:getFunctionName(i),params:{updatedExtendedState:a,eventData:o,settings:r},returned:n},notifyThrows(e,u),u}function isActions(t){return t&&"updates"in t&&"outputs"in t&&(t.outputs===NO_OUTPUT||Array.isArray(t.outputs))&&Array.isArray(t.updates)}function isEventStruct(t){let e;return t&&"object"==typeof t?Object.keys(t).length>1?(e=new Error(WRONG_EVENT_FORMAT_ERROR)).info={event:t,cause:"Event objects must have only one key which is the event name!"}:e=!0:(e=new Error(WRONG_EVENT_FORMAT_ERROR)).info={event:t,cause:"not an object!"},e}function isError(t){return t instanceof Error}const noDuplicatedStates={name:"noDuplicatedStates",shouldThrow:!1,predicate:(t,e)=>{const{getLabel:n}=objectTreeLenses,s={strategy:PRE_ORDER,seed:{duplicatedStates:[],statesHashMap:{}},visit:(t,e,s)=>{const{duplicatedStates:i,statesHashMap:a}=t,o=n(s),r=Object.keys(o)[0];return r in a?{duplicatedStates:i.concat(r),statesHashMap:a}:{duplicatedStates:i,statesHashMap:(a[r]="",a)}}},{duplicatedStates:i}=traverseObj(s,t.states);return{isFulfilled:0===i.length,blame:{message:"State names must be unique! Found duplicated state names. Cf. log",info:{duplicatedStates:i}}}}},noReservedStates={name:"noReservedStates",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:-1===Object.keys(n).indexOf(INIT_STATE),blame:{message:"You cannot use a reserved control state name for any of the configured control states for the machine! Cf. log",info:{reservedStates:[INIT_STATE],statesType:n}}})},atLeastOneState={name:"atLeastOneState",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:Object.keys(n).length>0,blame:{message:"Machine configuration must define at least one control state! Cf. log",info:{statesType:n}}})},isInitialControlStateDeclared={name:"isInitialControlStateDeclared",shouldThrow:!1,predicate:(t,e,{initTransition:n,statesType:s})=>{const{initialControlState:i,transitions:a}=t,o=Object.keys(s);return i?{isFulfilled:o.indexOf(i)>-1,blame:{message:"Configured initial control state must be a declared state. Cf. log",info:{initialControlState:i,declaredStates:o}}}:{isFulfilled:!0,blame:void 0}}},eventsAreStrings={name:"eventsAreStrings",shouldThrow:!1,predicate:(t,e)=>({isFulfilled:t.events.every(t=>"string"==typeof t),blame:{message:"Events must be an array of strings!",info:{events:t.events}}})},validInitialConfig={name:"validInitialConfig",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s}=t;return n&&s?{isFulfilled:!1,blame:{message:"Invalid machine configuration : defining an initial control state and an initial transition at the same time may lead to ambiguity and is forbidden!",info:{initialControlState:s,initTransition:n}}}:n||s?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Invalid machine configuration : you must define EITHER an initial control state OR an initial transition! Else in which state is the machine supposed to start?",info:{initialControlState:s,initTransition:n}}}}},validInitialTransition={name:"validInitialTransition",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s,transitions:i}=t,a=i.reduce((t,e)=>(e.from===INIT_STATE&&t.push(e),t),[]);return{isFulfilled:s&&!n||!s&&n&&1===a.length&&n.event===INIT_EVENT&&(isInconditionalTransition(n)||areCconditionalTransitions(n)),blame:{message:"Invalid configuration for initial transition! Cf. log",info:{initTransition:n,initTransitions:a,initialControlState:s}}}}},initEventOnlyInCompoundStates={name:"initEventOnlyInCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:i})=>{const a=Object.keys(s).filter(t=>!s[t]).map(t=>({[t]:n[t]&&n[t][INIT_EVENT]})).filter(t=>Object.values(t)[0]);return{isFulfilled:0===a.length,blame:{message:"Found at least one atomic state with an entry transition! That is forbidden! Cf. log",info:{initTransitions:a}}}}},validInitialTransitionForCompoundState={name:"validInitialTransitionForCompoundState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:i})=>{const a=Object.keys(s).filter(t=>s[t]),o=a.map(t=>n[t]&&n[t][INIT_EVENT]),r=o.every(Boolean);if(!r)return{isFulfilled:!1,blame:{message:"Found at least one compound state without an entry transition! Cf. log",info:{hasEntryTransitions:a.map(t=>({[t]:!(!n[t]||!n[t][INIT_EVENT])}))}}};const c=r&&o.every(t=>{const{guards:e,to:n}=t;return!e&&"string"==typeof n});return c?c&&o.every(t=>{const{from:e,to:n}=t;return e!==n&&i[n]&&i[n].startsWith(i[e])})?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must have a target state which is strictly below the compound state in the state hierarchy! ",info:{states:t.states,statesPath:i,entryTransitions:o}}}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must be inconditional and the associated target control state cannot be a history pseudo-state. Cf. log",info:{entryTransitions:o}}}}},validEventLessTransitions={name:"validEventLessTransitions",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:i})=>{const a=Object.keys(s).map(t=>({[t]:n[t]&&"undefined"in n[t]&&1!==Object.keys(n[t]).length})).filter(t=>void 0!==Object.values(t)[0]&&Object.values(t)[0]);return{isFulfilled:0===a.length,blame:{message:"Found at least one control state without both an eventless transition and a competing transition! Cf. log",info:{failingOriginControlStates:a}}}}},allStateTransitionsOnOneSingleRow={name:"allStateTransitionsOnOneSingleRow",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n})=>{const s=Object.keys(n).reduce((t,e)=>{const s=Object.keys(n[e]).filter(t=>n[e][t].length>1);return s.length>0&&(t[e]=s),t},{});return{isFulfilled:0===Object.keys(s).length,blame:{message:"Found at least one control state and one event for which the associated transition are not condensated under a unique row! Cf. log",info:{statesTransitionsInfo:s}}}}},noConflictingTransitionsWithAncestorState={name:"noConflictingTransitionsWithAncestorState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,eventTransitionsMaps:s,ancestorMap:i})=>{const a=Object.keys(s).reduce((t,e)=>{const n=Object.keys(s[e]),a=n.filter(t=>t!==INIT_STATE).map(t=>i[t]&&{[t]:i[t].find(t=>n.indexOf(t)>-1)}).filter(t=>t&&Object.values(t).filter(Boolean).length>0);return a.length>0&&(t[e]=a),t},{});return{isFulfilled:0===Object.keys(a).length,blame:{message:"Found two conflicting transitions! A -ev-> X, and B -ev-> Y leads to ambiguity if A < B or B < A. Cf. log",info:{eventTransitionsInfo:a}}}}},isHistoryStatesTargetStates={name:"isHistoryStatesTargetStates",shouldThrow:!1,predicate:(t,e,{})=>{const n=t.transitions.reduce((t,e)=>isHistoryControlState(e.from)?t.concat(e):t,[]);return{isFulfilled:0===Object.keys(n).length,blame:{message:"Found a history pseudo state configured as the origin control state for a transition. History pseudo states should only be target control states. Cf. log",info:{wrongHistoryStates:n}}}}},isHistoryStatesCompoundStates={name:"isHistoryStatesCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,statesType:s})=>{const i=Object.keys(n).map(t=>{if(t===INIT_STATE)return[];return Object.keys(n[t]).reduce((e,i)=>{const a=n[t][i][0],{guards:o,to:r}=a;return o?o.reduce((t,e)=>{const{to:n}=e;return isHistoryControlState(n)&&!s[getHistoryUnderlyingState(n)]?t.concat(a):t},e):isHistoryControlState(r)&&!s[getHistoryUnderlyingState(r)]?e.concat(a):e},[])}).reduce((t,e)=>t.concat(e),[]);return{isFulfilled:0===Object.keys(i).length,blame:{message:"Found a history pseudo state connected to an atomic state! History pseudo states only refer to compound states. Cf. log",info:{wrongHistoryStates:i,states:t.states}}}}},isHistoryStatesExisting={name:"isHistoryStatesExisting",shouldThrow:!1,predicate:(t,e,{historyStatesMap:n,statesType:s})=>{const i=Array.from(n.entries()).map(([t,e])=>!(t in s)&&{historyState:t,flatTransitions:e}).filter(Boolean),a=Object.keys(i).length;return{isFulfilled:0===a,blame:{message:`Found ${a} history pseudo state referring to a control state that is not declared! Check the states property of the state machine definition.`,info:{invalidTransitions:i,states:t.states}}}}};function isInconditionalTransition(t){const{from:e,event:n,guards:s,to:i,action:a}=t;return void 0===s&&i&&isControlState(e)&&isEvent(n)&&isControlState(i)&&isActionFactory(a)}function isValidGuard(t){const{to:e,predicate:n,action:s}=t;return e&&isControlState(e)&&isFunction(n)&&isActionFactory(s)}function areCconditionalTransitions(t){const{from:e,event:n,guards:s,to:i}=t;return s&&Array.isArray(s)&&s.length>0&&!i&&isControlState(e)&&isEvent(n)&&s.every(isValidGuard)}const isValidFsmDef={name:"isValidFsmDef",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n,states:s,events:i,initialExtendedState:a}=t,o=n&&Array.isArray(n),r=s&&"object"==typeof s,c=i&&Array.isArray(i);return o?r?c?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"The events property for a machine definition must be an array!",info:{events:i}}}:{isFulfilled:!1,blame:{message:"The states property for a machine definition must be an object!",info:{states:s}}}:{isFulfilled:!1,blame:{message:"The transitions property for a machine definition must be an array!",info:{transitions:n}}}}},haveTransitionsValidTypes={name:"haveTransitionsValidTypes",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n}=t,s=n.map((t,e)=>!isInconditionalTransition(t)&&!areCconditionalTransitions(t)&&{transition:t,index:e}).filter(Boolean),i=Object.keys(s).length;return{isFulfilled:0===i,blame:{message:`Found ${i} transitions with invalid format! Check logs for more details.`,info:{wrongTransitions:s,transitions:n}}}}},areEventsDeclared={name:"areEventsDeclared",shouldThrow:!1,predicate:(t,e,{eventTransitionsMaps:n})=>{const s=Object.keys(n),i=t.events,a=i.map(t=>-1===s.indexOf(t)&&t).filter(Boolean),o=s.map(t=>-1===i.indexOf(t)&&t).filter(Boolean).filter(t=>t!==INIT_EVENT);return{isFulfilled:0===a.length&&0===o.length,blame:{message:"All declared events must be used in transitions. All events used in transition must be declared! Cf. log",info:{eventsDeclaredButNotTriggeringTransitions:a,eventsNotDeclaredButTriggeringTransitions:o}}}}},areStatesDeclared={name:"areStatesDeclared",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,targetStatesMap:s,statesType:i})=>{const a=Object.keys(n),o=Array.from(s.keys()),r=Object.keys([a,o].reduce((t,e)=>(e.forEach(e=>t[e]=!0),t),{})),c=Object.keys(i),u=c.map(t=>-1===r.indexOf(t)&&t).filter(Boolean),l=r.map(t=>t!==INIT_STATE&&-1===c.indexOf(t)&&t).filter(Boolean);return{isFulfilled:0===u.length&&0===u.length,blame:{message:"All declared states must be used in transitions. All states used in transition must be declared! Cf. log",info:{statesDeclaredButNotTriggeringTransitions:u,statesNotDeclaredButTriggeringTransitions:l}}}}},isValidSettings={name:"isValidSettings",shouldThrow:!1,predicate:t=>({isFulfilled:!0,blame:void 0})},isInitialStateOriginState={name:"isInitialStateOriginState",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n})=>Array.from(n.keys()).indexOf(INIT_STATE)>-1?{isFulfilled:!1,blame:{message:"Found at least one transition with the initial state as target state! CF. log",info:{targetStates:Array.from(n.keys()),transitions:t.transitions}}}:{isFulfilled:!0,blame:void 0}},isValidSelfTransition={name:"isValidSelfTransition",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n,statesType:s})=>{const i=Array.from(n.keys()).map(t=>{return n.get(t).map(e=>{const{from:n,event:i}=e;if(t in s&&!s[t]&&n&&n===t&&!i)return{state:t,flatTransition:e}}).filter(Boolean)}).filter(t=>t.length>0);return{isFulfilled:0===i.length,blame:{message:"Found at least one eventless self-transition involving an atomic state! This is forbidden to avoid infinity loop! Cf. log",info:{wrongSelfTransitions:i}}}}},fsmContracts={computed:(t,e)=>({statesType:getStatesType(t.states),initTransition:findInitTransition(t.transitions),statesTransitionsMap:getStatesTransitionsMap(t.transitions),statesTransitionsMaps:getStatesTransitionsMaps(t.transitions),eventTransitionsMaps:getEventTransitionsMaps(t.transitions),ancestorMap:getAncestorMap(t.states),statesPath:getStatesPath(t.states),historyStatesMap:getHistoryStatesMap(t.transitions),targetStatesMap:getTargetStatesMap(t.transitions)}),description:"FSM structure",contracts:[isValidFsmDef,isValidSettings,isInitialControlStateDeclared,isInitialStateOriginState,eventsAreStrings,haveTransitionsValidTypes,noDuplicatedStates,noReservedStates,atLeastOneState,areEventsDeclared,areStatesDeclared,validInitialConfig,validInitialTransition,initEventOnlyInCompoundStates,validInitialTransitionForCompoundState,validEventLessTransitions,isValidSelfTransition,allStateTransitionsOnOneSingleRow,noConflictingTransitionsWithAncestorState,isHistoryStatesExisting,isHistoryStatesTargetStates,isHistoryStatesCompoundStates]};function makeContractHandler(t){const e=t.description;return function(...n){const s=[],i=t.computed.apply(null,n);return{isFulfilled:t.contracts.reduce((t,a)=>{const{name:o,predicate:r,shouldThrow:c}=a,u=n.concat(i),{isFulfilled:l,blame:d}=r.apply(null,u),f=`${e} FAILS ${o}!`,{message:T,info:p}=d||{};if(l)return t;if(s.push({name:o,message:T,info:p}),console.error(f),console.error([o,T].join(": ")),console.debug("Supporting error data:",p),c)throw new Error([f,"check console for information!"].join("\n"));return!1},!0),failingContracts:s}}}const fsmContractChecker=(t,e,n)=>makeContractHandler(n)(t,e),alwaysTrue=()=>!0;function build_event_enum(t){return(t=t.reduce?t:Array.prototype.slice.call(arguments)).push(INIT_EVENT),t.reduce(function(t,e){return t[e]=e,t},{})}function build_nested_state_structure(t){const e="State";let n={},s={};function i(){}return t={nok:t},i.prototype={current_state_name:INIT_STATE},n[INIT_STATE]=new i,n[STATE_PROTOTYPE_NAME]=new i,function t(i,a){keys(i).forEach(function(o){const r=i[o];if(n[o]=new a,n[o].name=o,n[o].parent_name=get_fn_name(a),n[o].root_name=e,"object"==typeof r){s[o]=!0;const e=function(){};e.displayName=o,e.prototype=n[o],t(r,e)}})}(t,i),{hash_states:n,is_group_state:s}}function build_state_enum(t){let e={history:{}};return e.NOK=INIT_STATE,function t(n){keys(n).forEach(function(s){const i=n[s];e[s]=s,"object"==typeof i&&t(i)})}(t),e}function normalizeTransitions(t){const{initialControlState:e,transitions:n}=t,s=findInitTransition(n);return e?n.concat([{from:INIT_STATE,event:INIT_EVENT,to:e,action:ACTION_IDENTITY}]):s?n:void 0}function normalizeFsmDef(t){return Object.assign({},t,{transitions:normalizeTransitions(t)})}function create_state_machine(t,e){return createStateMachine(t,e)}function createStateMachine(t,e){const{states:n,events:s,initialExtendedState:i,updateState:a}=t,{debug:o}=e||{},r=o&&o.checkContracts||void 0;let c=o&&o.console?o.console:emptyConsole;if(r){const{failingContracts:n}=fsmContractChecker(t,e,r);if(n.length>0)throw new Error("createStateMachine: called with wrong parameters! Cf. logs for failing contracts.")}const u=tryCatchMachineFn(UPDATE_STATE_FN_DESC,a,["extendedState, updates"]),l=build_event_enum(s),d=normalizeTransitions(t),f=build_nested_state_structure(n);let T=i;const{stateList:p,stateAncestors:m}=computeHistoryMaps(n);let h=initHistoryDataStructure(p),g={},y={};const _=f.is_group_state;let E=f.hash_states;function S(t,e){var n,s;c.debug("send event",t),n=isEventStruct,s=[t],r&&assert(n,s);const i=keys(t)[0],a=t[i],u=E[INIT_STATE].current_state_name;return e&&i===INIT_EVENT&&u!==INIT_STATE?(c.warn("The external event INIT_EVENT can only be sent when starting the machine!"),NO_OUTPUT):function(t,e,n,s){const i=t[INIT_STATE].current_state_name,a=t[i][e];if(a){c.log("found event handler!"),c.info("WHEN EVENT ",e);const{stop:r,outputs:u}=a(s,n,i);o&&!r&&c.warn("No guards have been fulfilled! We recommend to configure guards explicitly to cover the full state space!");const l=arrayizeOutput(u),d=t[INIT_STATE].current_state_name;if(y[d]&&d!==i){const t=g[d]?INIT_EVENT:AUTO_EVENT;return[].concat(l).concat(S({[t]:n},!1))}return l}return c.warn(`There is no transition associated to the event |${e}| in state |${i}|!`),NO_OUTPUT}(f.hash_states,i,a,T)}return d.forEach(function(t){let{from:n,to:s,action:i,event:r,guards:d}=t;d||(d=[{predicate:alwaysTrue,to:s,action:i}]),r===INIT_EVENT&&(g[n]=!0);let f=E[n];if(r&&!(r in l))throw`unknown event ${r} found in state machine definition!`;r||(r=AUTO_EVENT,y[n]=!0),_[n]&&g[n]&&(y[n]=!0),f[r]=d.reduce((t,s,i)=>{const r=s.action||ACTION_IDENTITY,l=r.name||r.displayName,d=function(t,e){let s="";const d=function(d,f,p){n=p||n;const y=t.predicate,_=y||alwaysTrue,S=!y||tryCatchMachineFn(PREDICATE_DESC,_,["extendedState","eventData","settings"])(d,f,e),O=t.to;if(s=_?"_checking_condition_"+i:"",handleFnExecError({debug:o,console:c},{predicate:t.predicate,extendedState:d,eventData:f,settings:e},S,isBoolean,notifyAndRethrow,throwIfInvalidGuardResult),S){c.info("IN STATE ",n),y&&c.info(`CASE: guard ${_.name} for transition is fulfilled`),!y&&c.info("CASE: unguarded transition"),c.info("THEN : we execute the action "+l);const t=tryCatchMachineFn(ACTION_FACTORY_DESC,r,["extendedState","eventData","settings"])(d,f,e);handleFnExecError({debug:o,console:c},{action:r,extendedState:d,eventData:f,settings:e},t,isActions,notifyAndRethrow,throwIfInvalidActionResult);const{updates:s,outputs:i}=t;!function(t,e,n){const s=n[t].name;h=updateHistory(h,m,s),c.info("left state",wrap(t))}(n,0,E);const p=u(d,s);handleFnExecError({debug:o,console:c},{updateStateFn:a,extendedState:d,updates:s},p,alwaysTrue,notifyAndRethrow,noop),T=p;const S=function(t,e,n){let s,i;if(isHistoryControlState(t)){const e=t.deep?DEEP:t.shallow?SHALLOW:void 0,a=t[e];o&&c&&!g[a]&&c.warn("Configured a history state which does not relate to a compound state! The behaviour of the machine is thus unspecified. Please review your machine configuration"),i=h[e][a]||a,s=n[i]}else{if(!t)throw"enter_state : unknown case! Not a state name, and not a history state to enter!";s=n[t],i=s.name}return n[INIT_STATE].current_state_name=i,o&&c.info("AND TRANSITION TO STATE",i),i}(O,0,E);return c.info("ENTERING NEXT STATE: ",S),isError(p)&&c.error("with error: ",p),isError(p)||c.info("with extended state: ",T),{stop:!0,outputs:i}}return{stop:!1,outputs:NO_OUTPUT}};return d.displayName=n+s,d}(s,e);return function(e,n,s){const i=t(e,n,s);return i.stop?i:d(e,n,s)}},function(){return{stop:!1,outputs:NO_OUTPUT}})}),S({[INIT_EVENT]:i},!0),function(t){return S(t,!0)}}function makeWebComponentFromFsm({name:t,eventHandler:e,fsm:n,commandHandlers:s,effectHandlers:i,options:a}){return customElements.define(t,class extends HTMLElement{constructor(){if(t.split("-").length<=1)throw"makeWebComponentFromFsm : web component's name MUST include a dash! Please review the name property passed as parameter to the function!";super();const o=this;this.eventSubject=e,this.options=Object.assign({},a);const r=this.options.NO_ACTION||NO_OUTPUT;this.eventSubject.subscribe({next:t=>{const e=n(t);e!==r&&e.forEach(t=>{if(t===r)return;const{command:e,params:n}=t;s[e](this.eventSubject.next,n,i,o)})}})}static get observedAttributes(){return[]}connectedCallback(){this.options.initialEvent&&this.eventSubject.next(this.options.initialEvent)}disconnectedCallback(){this.options.terminalEvent&&this.eventSubject.next(this.options.terminalEvent),this.eventSubject.complete()}attributeChangedCallback(t,e,n){this.constructor(),this.connectedCallback()}})}function makeNamedActionsFactory(t){return Object.keys(t).reduce((e,n)=>{const s=t[n];return s.displayName=n,e[n]=s,e},{})}function mergeOutputsFn(t){return t.reduce((t,e)=>t.concat(e),[])}function decorateWithEntryActions(t,e,n){if(!e)return t;const{transitions:s,states:i,initialExtendedState:a,initialControlState:o,events:r,updateState:c,settings:u}=t,l=getFsmStateList(i),d=Object.keys(e).every(t=>null!=l[t]),f=n||mergeOutputsFn;if(d){return{initialExtendedState:a,initialControlState:o,states:i,events:r,transitions:mapOverTransitionsActions((t,n,s,i)=>{const{to:a}=n,o=e[a];return o?decorateWithExitAction(t,o,f,c):t},s),updateState:c,settings:u}}throw"decorateWithEntryActions : found control states for which entry actions are defined, and yet do not exist in the state machine!"}function decorateWithExitAction(t,e,n,s){const i=function(i,a,o){const{debug:r}=o,c=tryCatchMachineFn(ENTRY_ACTION_FACTORY_DESC,e,["extendedState","eventData","settings"]),u=t(i,a,o),l=u.updates,d=tryCatchMachineFn(UPDATE_STATE_FN_DESC,s,["extendedState, updates"])(i,l);handleFnExecError({debug:r,console:console},{updateStateFn:s,extendedState:i,actionUpdate:l},d,alwaysTrue,notifyAndRethrow,noop);const f=d,T=c(f,a,o);if(!handleFnExecError({debug:r,console:console},{action:e,extendedState:f,eventData:a,settings:o},T,isActions,notifyAndRethrow,throwIfInvalidEntryActionResult))return{updates:[].concat(l,T.updates),outputs:n([u.outputs,T.outputs])}};return i.displayName=`Entry_Action_After_${getFunctionName(t)}`,i}function traceFSM(t,e){const{initialExtendedState:n,initialControlState:s,events:i,states:a,transitions:o,updateState:r}=e;return{initialExtendedState:n,initialControlState:s,events:i,states:a,updateState:r,transitions:mapOverTransitionsActions((t,e,n,s)=>(function(i,a,o){const{from:c,event:u,to:l,predicate:d}=e,f=tryCatchMachineFn(ACTION_FACTORY_DESC,t,["extendedState","eventData","settings"])(i,a,o),{outputs:T,updates:p}=f;return{updates:p,outputs:[{outputs:T,updates:p,extendedState:i,newExtendedState:tryCatchMachineFn(UPDATE_STATE_FN_DESC,r,["extendedState, updates"])(i,p||[]),controlState:c,event:{eventLabel:u,eventData:a},settings:o,targetControlState:l,predicate:d,actionFactory:t,guardIndex:n,transitionIndex:s}]}}),o)}}function makeHistoryStates(t){const e=Object.keys(getFsmStateList(t));return(t,n)=>{if(!e.includes(n))throw"makeHistoryStates: the state for which a history state must be constructed is not a configured state for the state machine under implementation!!";return{[t]:n,type:history_symbol}}}function historyState(t,e){return{[t]:e}}function generateStatePlantUmlHeader(t,e){return e?`state "${e}" as ${t} <<NoContent>>`:`state "${getDisplayName(t)}" as ${t} <<NoContent>>`}function toPlantUml(t,e){const{states:n,transitions:s}=t,{getChildren:i,constructTree:a,getLabel:o}=objectTreeLenses,r=t=>t.join(SEP),c=postOrderTraverseTree(objectTreeLenses,{seed:()=>Map,visit:(t,e,n)=>{const{path:a}=e.get(n),c=o(n),u=stateToPlantUML(Object.keys(c)[0],times$1(e=>t.get(r(a.concat(e))),((t,e)=>i(t,e).length)(n,e)),s);return t.set(r(a),u),t}},{[INIT_STATE]:n}),u=c.get("0");return c.clear(),u}function stateToPlantUML(t,e,n){return[`${generateStatePlantUmlHeader(t,"")} {`,e.join("\n"),format_history_states(t,n),format_entry_transitions(t,n),"}",translate_transitions(t,n)].filter(t=>"\n"!==t&&""!==t).join("\n")}function format_history_states(t,e){const n=e.reduce((e,n)=>{return get_all_transitions(n).filter(is_history_transition).filter(is_to_history_control_state_of(t)).reduce((t,e)=>(t[format_history_transition_state_name(e)]=void 0,t),e)},{});return Object.keys(n).map(t=>`${generateStatePlantUmlHeader(t,HISTORY_STATE_NAME)}`).join("\n")}function translate_transitions(t,e){return[format_history_transitions(t,e),format_standard_transitions(t,e)].filter(Boolean).join("\n")}function format_standard_transitions(t,e){return t===INIT_STATE?"":e.map(e=>{return get_all_transitions(e).filter(is_from_control_state(t)).filter(t=>!is_entry_transition(t)).filter(t=>!is_history_transition(t)).map(({from:t,event:e,predicate:n,to:s,action:i})=>[t,TRANSITION_SYMBOL,s,TRANSITION_LABEL_START_SYMBOL,format_transition_label(e,n,i)].join(" ")).join("\n")}).filter(Boolean).join("\n")}function format_entry_transitions(t,e){return e.reduce((e,n)=>{return get_all_transitions(n).filter(is_entry_transition).filter(is_from_control_state(t)).reduce((t,e)=>{const{from:n,to:s,predicate:i,action:a}=e;return t.push(`[*] ${TRANSITION_SYMBOL} ${s} ${TRANSITION_LABEL_START_SYMBOL} ${format_transition_label("",i,a)}`),t},e)},[]).join("\n")}function format_history_transitions(t,e){return e.map(e=>{return get_all_transitions(e).filter(is_from_control_state(t)).filter(is_history_transition).map(({from:t,event:e,predicate:n,to:s,action:i})=>[t,TRANSITION_SYMBOL,format_history_transition_state_name({from:t,to:s}),TRANSITION_LABEL_START_SYMBOL,format_transition_label(e,n,i)].join(" ")).join("\n")}).filter(Boolean).join("\n")}function toDagreVisualizerFormat(t){const{states:e,transitions:n}=t,{getLabel:s,getChildren:i}=objectTreeLenses,{constructTree:a}=arrayTreeLenses,o=t=>t.join(SEP),r=postOrderTraverseTree(objectTreeLenses,{seed:()=>Map,visit:(t,e,n)=>{const{path:r}=e.get(n),c=s(n),u=Object.keys(c)[0],l=times$1(e=>t.get(o(r.concat(e))),((t,e)=>i(t,e).length)(n,e));return t.set(o(r),a(u,l)),t}},{[INIT_STATE]:e}).get("0"),c=n.map(t=>{const{from:e,to:n,event:s,guards:i,action:a}=t;if(i){return{from:e,event:s,guards:i.map(t=>{const{predicate:e,to:n,action:s}=t;return{predicate:e.name,to:n,action:s.name}})}}return{from:e,to:n,event:s,action:a.name||"no action name?"}});return JSON.stringify({states:r,transitions:c})}export{fsmContracts,build_state_enum,normalizeTransitions,normalizeFsmDef,create_state_machine,createStateMachine,makeWebComponentFromFsm,makeNamedActionsFactory,mergeOutputsFn,decorateWithEntryActions,traceFSM,makeHistoryStates,historyState,toPlantUml,toDagreVisualizerFormat,CONTRACT_MODEL_UPDATE_FN_RETURN_VALUE,SEP,TRANSITION_SYMBOL,TRANSITION_LABEL_START_SYMBOL,HISTORY_STATE_NAME,HISTORY_PREFIX,INIT_STATE,INIT_EVENT,AUTO_EVENT,STATE_PROTOTYPE_NAME,NO_STATE_UPDATE,NO_OUTPUT,ACTION_IDENTITY,history_symbol,SHALLOW,DEEP,WRONG_EVENT_FORMAT_ERROR,FUNCTION_THREW_ERROR,INVALID_ACTION_FACTORY_EXECUTED,INVALID_PREDICATE_EXECUTED,ACTION_FACTORY_DESC,ENTRY_ACTION_FACTORY_DESC,UPDATE_STATE_FN_DESC,PREDICATE_DESC,COMMAND_RENDER,noop,emptyConsole,isBoolean,isFunction,isControlState,isEvent,isActionFactory,make_states,make_events,get_fn_name,wrap,times$1 as times,always,keys,merge$1 as merge,is_history_transition,is_entry_transition,is_from_control_state,is_to_history_control_state_of,is_history_control_state_of,format_transition_label,format_history_transition_state_name,get_all_transitions,getDisplayName,mergeModelUpdates,chainModelUpdates,mergeActionFactories,identity,lastOf,getFsmStateList,getStatesType,getStatesPath,getStatesTransitionsMap,getStatesTransitionsMaps,getEventTransitionsMaps,getHistoryStatesMap,getTargetStatesMap,getAncestorMap,computeHistoryMaps,mapOverTransitionsActions,reduceTransitions,everyTransition,computeTimesCircledOn,isInitState,isInitEvent,isEventless,arrayizeOutput,isHistoryControlState,getHistoryParentState,isShallowHistory,isDeepHistory,getHistoryType,getHistoryUnderlyingState,isHistoryStateEdge,initHistoryDataStructure,isCompoundState,isAtomicState,updateHistory,computeHistoryState,findInitTransition,tryCatch,tryCatchMachineFn,getFunctionName,assert,notifyThrows,handleFnExecError,notifyAndRethrow,throwIfInvalidActionResult,throwIfInvalidGuardResult,throwIfInvalidEntryActionResult,isActions,isEventStruct,isError}; | ||
//# sourceMappingURL=kingly.es.min.js.map |
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.Kingly={})}(this,function(t){"use strict";const e=".",n="--\x3e",s=":",o="H",i="history.",a="nok",r="init",c="auto",u="State",l=[],d=null,f=function(){return{outputs:d,updates:l}},p={},m="shallow",h="deep",g="The machine received an event which does not have the proper format. Expecting an object whose unique key is the event name, and value is the event data.",y=(t,e)=>`Exception thrown when executing ${e} ${t||""}`,b=(t,e)=>`${y(t,e)}\nThe ${e} returned a value which is not an action.`,v=(t,e)=>`${y(t,e)}\nThe ${e} returned a value which is not a boolean.`,T="action factory",S="(decorating) entry action",E="update state function",O="predicate",A=[0],_="PRE_ORDER";function w(t){return void 0===t?void 0:JSON.parse(JSON.stringify(t))}function C(t,e){return Object.assign({},t,e)}function j(t,e,n){n.forEach((n,s)=>{const o=t.get(e),i=t.get(n),a=i&&i.path;t.set(n,C(i,{isAdded:!0,isVisited:!1,path:a||o.path.concat(s)}))})}function F(t,e){t.set(e,C(t.get(e),{isVisited:!0}))}function N(t,e){const{store:n,lenses:s,traverse:o}=t,{empty:i,add:a,takeAndRemoveOne:r,isEmpty:c}=n,{getChildren:u}=s,{visit:l,seed:d}=o,f=new Map,p="function"==typeof d?new(d()):w(d);let m="function"==typeof i?new(i()):w(i),h=p;for(a([e],m),f.set(e,{isAdded:!0,isVisited:!1,path:A});!c(m);){const t=r(m),e=u(f,t);a(e,m),j(f,t,e),h=l(h,f,t),F(f,t)}return f.clear(),h}function k(t,e,n){const{getChildren:s}=t;return N({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.push.apply(e,t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function x(t,e,n){const{getChildren:s}=t;return N({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function M(t,e,n){const{getChildren:s}=t,{seed:o,visit:i}=e,a=(t,e)=>e.get(t).isVisited||((t,e)=>0===s(t,e).length)(t,e);return N({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>a(e,t)?[]:s(e,t).concat([e])},traverse:{seed:o,visit:(t,e,n)=>a(n,e)?i(t,e,n):t}},n)}const I={isLeafLabel:function(t){return 0===I.getChildren(t).length},getLabel:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getLabel > unexpected object tree value";return t},getChildren:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getChildren > unexpected value";{let e=Object.values(t)[0];return e&&"object"==typeof e&&!Array.isArray(e)?Object.keys(e).map(t=>({[t]:e[t]})):[]}},constructTree:(t,e)=>{const n=t&&Object.keys(t)&&Object.keys(t)[0];return 0===e.length?t:{[n]:Object.assign.apply(null,e)}}};function R(t,e){const n={root:e},{strategy:s,seed:o,visit:i}=t;return({BFS:k,PRE_ORDER:x,POST_ORDER:M}[s]||x)(I,{seed:o,visit:function(t,e,n){const{path:s}=e.get(n);return JSON.stringify(s)===JSON.stringify(A)?t:i(t,e,n)}},n)}const D={getLabel:t=>Array.isArray(t)?t[0]:t,getChildren:t=>Array.isArray(t)?t[1]:[],constructTree:(t,e)=>e&&Array.isArray(e)&&e.length>0?[t,e]:t},$=()=>{},L={log:$,warn:$,info:$,debug:$,error:$,trace:$};function H(t){return"boolean"==typeof t}function P(t){return"function"==typeof t}function B(t){return t&&"string"==typeof t||gt(t)}function U(t){return t&&"string"==typeof t}function V(t){return t&&"function"==typeof t}function W(t){return/^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/.exec(t.toString())[1]}function Y(t){return["-",t,"-"].join("")}function J(t,e){return Array.apply(null,{length:e}).map(Number.call,Number).map(t)}function X(t){return Object.keys(t)}function z(t){return t.to.startsWith(i)}function q(t){return t.event===r}function G(t){return function(e){return e.from===t}}function K(t){return function(e){return Q(t,e.to)}}function Q(t,e){return e.substring(i.length)===t}function Z(t,e,n){const s=t||"";return e&&n?`${s} [${e.name}] / ${n.name}`:e?`${s} [${e.name}]}`:n?`${s} / ${n.name}`:`${s}`}function tt({from:t,to:e}){return`${t}.${e.substring(i.length)}.${o}`}function et(t){const{from:e,event:n,guards:s}=t;return s?s.map(({predicate:t,to:s,action:o})=>({from:e,event:n,predicate:t,to:s,action:o})):[t]}function nt(t){return t.replace(/_/g," ")}function st(t){const{getLabel:e}=I;return R({strategy:_,seed:{},visit:(t,n,s)=>{const o=e(s);return t[Object.keys(o)[0]]="",t}},t)}function ot(t){const{getLabel:e,isLeafLabel:n}=I;return R({strategy:_,seed:{},visit:(t,s,o)=>{const i=e(o),a=Object.keys(i)[0];return n(i)?(t[a]=!1,t):(t[a]=!0,t)}},t)}function it(t){const{getLabel:e}=I;return R({strategy:_,seed:{},visit:(t,n,s)=>{const o=n.get(s).path.join("."),i=e(s);return t[Object.keys(i)[0]]=o,t}},t)}function at(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return gt(n)?t:(t[n]=t[n]||{},t[n][s]=e,t)},{})||{}}function rt(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return gt(n)?t:(t[n]=t[n]||{},t[n][s]=t[n][s]?t[n][s].concat(e):[e],t)},{})||{}}function ct(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return gt(n)?t:(t[s]=t[s]||{},t[s][n]=t[s][n]?t[s][n].concat(e):[e],t)},{})||{}}function ut(t){return mt((t,e,n,s)=>{const{from:o,event:i,to:a,action:r,predicate:c,gen:u}=e;if(gt(o)){const n=bt(o);t.set(n,(t.get(n)||[]).concat([e]))}else if(gt(a)){const n=bt(a);t.set(n,(t.get(n)||[]).concat([e]))}return t},new Map,t)||{}}function lt(t){return mt((t,e,n,s)=>{const{to:o}=e;return t.set(o,(t.get(o)||[]).concat([e])),t},new Map,t)||{}}function dt(t){const{getLabel:e,getChildren:n}=I;return R({strategy:_,seed:{},visit:(t,s,o)=>{const i=e(o),a=Object.keys(i)[0];return n(o).map(t=>Object.keys(e(t))[0]).forEach(e=>{t[e]=t[e]||[],t[e]=t[e].concat(a)}),t}},t)}function ft(t){if(0===Object.keys(t).length)throw"computeHistoryMaps : passed empty control states parameter?";const{getLabel:e,isLeafLabel:n}=I,s={strategy:_,seed:{stateList:[],stateAncestors:{[h]:{},[m]:{}}},visit:(t,n,s)=>{const o=e(s),i=Object.keys(o)[0];t.stateList=t.stateList.concat(i);const{path:r}=n.get(s);n.set(JSON.stringify(r),i);const c=r.slice(0,-1);if(1===c.length)n.set(JSON.stringify(c),a);else{const e=n.get(JSON.stringify(c));t.stateAncestors[m][i]=[e];const{ancestors:s}=r.reduce((t,e)=>{const s=t.path.slice(0,-1);if(t.path=s,s.length>1){const e=n.get(JSON.stringify(s));t.ancestors=t.ancestors.concat(e)}return t},{ancestors:[],path:r});t.stateAncestors[h][i]=s}return t}},{stateList:o,stateAncestors:i}=R(s,t);return{stateList:o,stateAncestors:i}}function pt(t,e){return mt(function(e,n,s,o){const{from:i,event:a,to:r,action:c,predicate:u}=n,l=t(c,n,s,o);return l.displayName=l.displayName||c&&(c.name||c.displayName||function(t,e,n,s,o){const i=o?o.name:"",a=i?`[${i}]`:"",r=t?t.name:"identity";return`${r||"unnamed action"}:${e}-${n}->${s} ${a}`}(c,i,a,r,u)),void 0===u?e.push({from:i,event:a,to:r,action:l}):0===s?e.push({from:i,event:a,guards:[{to:r,predicate:u,action:l}]}):e[e.length-1].guards.push({to:r,predicate:u,action:l}),e},[],e)}function mt(t,e,n){return n.reduce((e,n,s)=>{let{from:o,event:i,to:a,gen:r,action:c,guards:u}=n;return u||(u=r?[{to:a,action:c,gen:r,predicate:void 0}]:[{to:a,action:c,predicate:void 0}]),u.reduce((e,n,a)=>{const{to:r,action:c,gen:u,predicate:l}=n;return t(e,u?{from:o,event:i,to:r,action:c,predicate:l,gen:u}:{from:o,event:i,to:r,action:c,predicate:l},a,s)},e)},e)}function ht(t){return t===d?d:Array.isArray(t)?t:[t]}function gt(t){return"object"==typeof t&&(h in t||m in t)}function yt(t){return t[h]?h:m}function bt(t){return t[yt(t)]}function vt(t){const e=()=>t.reduce((t,e)=>(t[e]="",t),{});return{[h]:e(),[m]:e()}}function Tt(t,e){const{statesAdjacencyList:n}=t;return n[e]&&0!==n[e].length}function St(t,e,n){return n===a?t:([m,h].forEach(s=>{(e[s][n]||[]).forEach(e=>{t[s][e]=n})}),t)}function Et(t){return t.find(t=>t.from===a&&t.event===r)}function Ot(t,e){return function(...n){try{return t.apply(t,n)}catch(t){return e(t,n)}}}function At(t,e,n=[]){return Ot(e,(s,o)=>{const i=new Error(s),a=_t(e),r=y(a,t);i.probableCause=s.probableCause?[s.probableCause,r].join("\n"):r;const c={fnName:a,params:n.reduce((t,e,n)=>(t[e]=o[n],t),{})};return i.info=s.info?[].concat([s.info]).concat([c]):c,i})}function _t(t){return t.name||t.displayName||"anonymous"}function wt(t,e){const n=t.apply(null,e);if(!0!==n){const e=n.info;throw console.error(`ERROR: failed contract ${t.name||""}. ${e?"Error info:":""}`,n.info),n}}function Ct(t,e){t.error(e),e.probableCause&&t.error(`Probable cause: ${e.probableCause}`),e.info&&t.error("ERROR: additional info",e.info)}function jt(t,e,n,s,o,i){const{debug:a,console:r}=t;return a&&n instanceof Error?(o({debug:a,console:r},n,e),!0):!(!a||s(n))&&(i({debug:a,console:r},n,e),!0)}function Ft({debug:t,console:e},n){throw Ct(e,n),n}function Nt({debug:t,console:e},n,s){const{action:o,extendedState:i,eventData:a,settings:r}=s,c=_t(o),u=new Error(b(c,T));throw u.info={fnName:_t(o),params:{updatedExtendedState:i,eventData:a,settings:r},returned:n},Ct(e,u),u}function kt({debug:t,console:e},n,s){const o=_t(s.predicate),i=new Error(v(o,O));throw i.info={predicateName:o,params:s,returned:n},Ct(e,i),i}function xt({debug:t,console:e},n,s){const{action:o,extendedState:i,eventData:a,settings:r}=s,c=_t(o),u=new Error(b(c,S));throw u.info={fnName:_t(o),params:{updatedExtendedState:i,eventData:a,settings:r},returned:n},Ct(e,u),u}function Mt(t){return t&&"updates"in t&&"outputs"in t&&(t.outputs===d||Array.isArray(t.outputs))&&Array.isArray(t.updates)}function It(t){let e;return t&&"object"==typeof t?Object.keys(t).length>1?(e=new Error(g)).info={event:t,cause:"Event objects must have only one key which is the event name!"}:e=!0:(e=new Error(g)).info={event:t,cause:"not an object!"},e}function Rt(t){const{from:e,event:n,guards:s,to:o,action:i}=t;return void 0===s&&o&&B(e)&&U(n)&&B(o)&&V(i)}function Dt(t){const{to:e,predicate:n,action:s}=t;return e&&B(e)&&P(n)&&V(s)}function $t(t){const{from:e,event:n,guards:s,to:o}=t;return s&&Array.isArray(s)&&s.length>0&&!o&&B(e)&&U(n)&&s.every(Dt)}const Lt={computed:(t,e)=>({statesType:ot(t.states),initTransition:Et(t.transitions),statesTransitionsMap:at(t.transitions),statesTransitionsMaps:rt(t.transitions),eventTransitionsMaps:ct(t.transitions),ancestorMap:dt(t.states),statesPath:it(t.states),historyStatesMap:ut(t.transitions),targetStatesMap:lt(t.transitions)}),description:"FSM structure",contracts:[{name:"isValidFsmDef",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n,states:s,events:o,initialExtendedState:i}=t,a=n&&Array.isArray(n),r=s&&"object"==typeof s,c=o&&Array.isArray(o);return a?r?c?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"The events property for a machine definition must be an array!",info:{events:o}}}:{isFulfilled:!1,blame:{message:"The states property for a machine definition must be an object!",info:{states:s}}}:{isFulfilled:!1,blame:{message:"The transitions property for a machine definition must be an array!",info:{transitions:n}}}}},{name:"isValidSettings",shouldThrow:!1,predicate:t=>({isFulfilled:!0,blame:void 0})},{name:"isInitialControlStateDeclared",shouldThrow:!1,predicate:(t,e,{initTransition:n,statesType:s})=>{const{initialControlState:o,transitions:i}=t,a=Object.keys(s);return o?{isFulfilled:a.indexOf(o)>-1,blame:{message:"Configured initial control state must be a declared state. Cf. log",info:{initialControlState:o,declaredStates:a}}}:{isFulfilled:!0,blame:void 0}}},{name:"isInitialStateOriginState",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n})=>Array.from(n.keys()).indexOf(a)>-1?{isFulfilled:!1,blame:{message:"Found at least one transition with the initial state as target state! CF. log",info:{targetStates:Array.from(n.keys()),transitions:t.transitions}}}:{isFulfilled:!0,blame:void 0}},{name:"eventsAreStrings",shouldThrow:!1,predicate:(t,e)=>({isFulfilled:t.events.every(t=>"string"==typeof t),blame:{message:"Events must be an array of strings!",info:{events:t.events}}})},{name:"haveTransitionsValidTypes",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n}=t,s=n.map((t,e)=>!Rt(t)&&!$t(t)&&{transition:t,index:e}).filter(Boolean),o=Object.keys(s).length;return{isFulfilled:0===o,blame:{message:`Found ${o} transitions with invalid format! Check logs for more details.`,info:{wrongTransitions:s,transitions:n}}}}},{name:"noDuplicatedStates",shouldThrow:!1,predicate:(t,e)=>{const{getLabel:n}=I,s={strategy:_,seed:{duplicatedStates:[],statesHashMap:{}},visit:(t,e,s)=>{const{duplicatedStates:o,statesHashMap:i}=t,a=n(s),r=Object.keys(a)[0];return r in i?{duplicatedStates:o.concat(r),statesHashMap:i}:{duplicatedStates:o,statesHashMap:(i[r]="",i)}}},{duplicatedStates:o}=R(s,t.states);return{isFulfilled:0===o.length,blame:{message:"State names must be unique! Found duplicated state names. Cf. log",info:{duplicatedStates:o}}}}},{name:"noReservedStates",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:-1===Object.keys(n).indexOf(a),blame:{message:"You cannot use a reserved control state name for any of the configured control states for the machine! Cf. log",info:{reservedStates:[a],statesType:n}}})},{name:"atLeastOneState",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:Object.keys(n).length>0,blame:{message:"Machine configuration must define at least one control state! Cf. log",info:{statesType:n}}})},{name:"areEventsDeclared",shouldThrow:!1,predicate:(t,e,{eventTransitionsMaps:n})=>{const s=Object.keys(n),o=t.events,i=o.map(t=>-1===s.indexOf(t)&&t).filter(Boolean),a=s.map(t=>-1===o.indexOf(t)&&t).filter(Boolean).filter(t=>t!==r);return{isFulfilled:0===i.length&&0===a.length,blame:{message:"All declared events must be used in transitions. All events used in transition must be declared! Cf. log",info:{eventsDeclaredButNotTriggeringTransitions:i,eventsNotDeclaredButTriggeringTransitions:a}}}}},{name:"areStatesDeclared",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,targetStatesMap:s,statesType:o})=>{const i=Object.keys(n),r=Array.from(s.keys()),c=Object.keys([i,r].reduce((t,e)=>(e.forEach(e=>t[e]=!0),t),{})),u=Object.keys(o),l=u.map(t=>-1===c.indexOf(t)&&t).filter(Boolean),d=c.map(t=>t!==a&&-1===u.indexOf(t)&&t).filter(Boolean);return{isFulfilled:0===l.length&&0===l.length,blame:{message:"All declared states must be used in transitions. All states used in transition must be declared! Cf. log",info:{statesDeclaredButNotTriggeringTransitions:l,statesNotDeclaredButTriggeringTransitions:d}}}}},{name:"validInitialConfig",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s}=t;return n&&s?{isFulfilled:!1,blame:{message:"Invalid machine configuration : defining an initial control state and an initial transition at the same time may lead to ambiguity and is forbidden!",info:{initialControlState:s,initTransition:n}}}:n||s?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Invalid machine configuration : you must define EITHER an initial control state OR an initial transition! Else in which state is the machine supposed to start?",info:{initialControlState:s,initTransition:n}}}}},{name:"validInitialTransition",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s,transitions:o}=t,i=o.reduce((t,e)=>(e.from===a&&t.push(e),t),[]);return{isFulfilled:s&&!n||!s&&n&&1===i.length&&n.event===r&&(Rt(n)||$t(n)),blame:{message:"Invalid configuration for initial transition! Cf. log",info:{initTransition:n,initTransitions:i,initialControlState:s}}}}},{name:"initEventOnlyInCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:o})=>{const i=Object.keys(s).filter(t=>!s[t]).map(t=>({[t]:n[t]&&n[t][r]})).filter(t=>Object.values(t)[0]);return{isFulfilled:0===i.length,blame:{message:"Found at least one atomic state with an entry transition! That is forbidden! Cf. log",info:{initTransitions:i}}}}},{name:"validInitialTransitionForCompoundState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:o})=>{const i=Object.keys(s).filter(t=>s[t]),a=i.map(t=>n[t]&&n[t][r]),c=a.every(Boolean);if(!c)return{isFulfilled:!1,blame:{message:"Found at least one compound state without an entry transition! Cf. log",info:{hasEntryTransitions:i.map(t=>({[t]:!(!n[t]||!n[t][r])}))}}};const u=c&&a.every(t=>{const{guards:e,to:n}=t;return!e&&"string"==typeof n});return u?u&&a.every(t=>{const{from:e,to:n}=t;return e!==n&&o[n]&&o[n].startsWith(o[e])})?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must have a target state which is strictly below the compound state in the state hierarchy! ",info:{states:t.states,statesPath:o,entryTransitions:a}}}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must be inconditional and the associated target control state cannot be a history pseudo-state. Cf. log",info:{entryTransitions:a}}}}},{name:"validEventLessTransitions",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:o})=>{const i=Object.keys(s).map(t=>({[t]:n[t]&&"undefined"in n[t]&&1!==Object.keys(n[t]).length})).filter(t=>void 0!==Object.values(t)[0]&&Object.values(t)[0]);return{isFulfilled:0===i.length,blame:{message:"Found at least one control state without both an eventless transition and a competing transition! Cf. log",info:{failingOriginControlStates:i}}}}},{name:"isValidSelfTransition",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n,statesType:s})=>{const o=Array.from(n.keys()).map(t=>{return n.get(t).map(e=>{const{from:n,event:o}=e;if(t in s&&!s[t]&&n&&n===t&&!o)return{state:t,flatTransition:e}}).filter(Boolean)}).filter(t=>t.length>0);return{isFulfilled:0===o.length,blame:{message:"Found at least one eventless self-transition involving an atomic state! This is forbidden to avoid infinity loop! Cf. log",info:{wrongSelfTransitions:o}}}}},{name:"allStateTransitionsOnOneSingleRow",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n})=>{const s=Object.keys(n).reduce((t,e)=>{const s=Object.keys(n[e]).filter(t=>n[e][t].length>1);return s.length>0&&(t[e]=s),t},{});return{isFulfilled:0===Object.keys(s).length,blame:{message:"Found at least one control state and one event for which the associated transition are not condensated under a unique row! Cf. log",info:{statesTransitionsInfo:s}}}}},{name:"noConflictingTransitionsWithAncestorState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,eventTransitionsMaps:s,ancestorMap:o})=>{const i=Object.keys(s).reduce((t,e)=>{const n=Object.keys(s[e]),i=n.filter(t=>t!==a).map(t=>o[t]&&{[t]:o[t].find(t=>n.indexOf(t)>-1)}).filter(t=>t&&Object.values(t).filter(Boolean).length>0);return i.length>0&&(t[e]=i),t},{});return{isFulfilled:0===Object.keys(i).length,blame:{message:"Found two conflicting transitions! A -ev-> X, and B -ev-> Y leads to ambiguity if A < B or B < A. Cf. log",info:{eventTransitionsInfo:i}}}}},{name:"isHistoryStatesExisting",shouldThrow:!1,predicate:(t,e,{historyStatesMap:n,statesType:s})=>{const o=Array.from(n.entries()).map(([t,e])=>!(t in s)&&{historyState:t,flatTransitions:e}).filter(Boolean),i=Object.keys(o).length;return{isFulfilled:0===i,blame:{message:`Found ${i} history pseudo state referring to a control state that is not declared! Check the states property of the state machine definition.`,info:{invalidTransitions:o,states:t.states}}}}},{name:"isHistoryStatesTargetStates",shouldThrow:!1,predicate:(t,e,{})=>{const n=t.transitions.reduce((t,e)=>gt(e.from)?t.concat(e):t,[]);return{isFulfilled:0===Object.keys(n).length,blame:{message:"Found a history pseudo state configured as the origin control state for a transition. History pseudo states should only be target control states. Cf. log",info:{wrongHistoryStates:n}}}}},{name:"isHistoryStatesCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,statesType:s})=>{const o=Object.keys(n).map(t=>{if(t===a)return[];return Object.keys(n[t]).reduce((e,o)=>{const i=n[t][o][0],{guards:a,to:r}=i;return a?a.reduce((t,e)=>{const{to:n}=e;return gt(n)&&!s[bt(n)]?t.concat(i):t},e):gt(r)&&!s[bt(r)]?e.concat(i):e},[])}).reduce((t,e)=>t.concat(e),[]);return{isFulfilled:0===Object.keys(o).length,blame:{message:"Found a history pseudo state connected to an atomic state! History pseudo states only refer to compound states. Cf. log",info:{wrongHistoryStates:o,states:t.states}}}}}]};const Ht=(t,e,n)=>(function(t){const e=t.description;return function(...n){const s=[],o=t.computed.apply(null,n);return{isFulfilled:t.contracts.reduce((t,i)=>{const{name:a,predicate:r,shouldThrow:c}=i,u=n.concat(o),{isFulfilled:l,blame:d}=r.apply(null,u),f=`${e} FAILS ${a}!`,{message:p,info:m}=d||{};if(l)return t;if(s.push({name:a,message:p,info:m}),console.error(f),console.error([a,p].join(": ")),console.debug("Supporting error data:",m),c)throw new Error([f,"check console for information!"].join("\n"));return!1},!0),failingContracts:s}}})(n)(t,e),Pt=()=>!0;function Bt(t){const{initialControlState:e,transitions:n}=t,s=Et(n);return e?n.concat([{from:a,event:r,to:e,action:f}]):s?n:void 0}function Ut(t,e){const{states:n,events:s,initialExtendedState:o,updateState:i}=t,{debug:l}=e||{},p=l&&l.checkContracts||void 0;let g=l&&l.console?l.console:L;if(p){const{failingContracts:n}=Ht(t,e,p);if(n.length>0)throw new Error("createStateMachine: called with wrong parameters! Cf. logs for failing contracts.")}const y=At(E,i,["extendedState, updates"]),b=function(t){return(t=t.reduce?t:Array.prototype.slice.call(arguments)).push(r),t.reduce(function(t,e){return t[e]=e,t},{})}(s),v=Bt(t),S=function(t){const e="State";let n={},s={};function o(){}return t={nok:t},o.prototype={current_state_name:a},n[a]=new o,n[u]=new o,function t(o,i){X(o).forEach(function(a){const r=o[a];if(n[a]=new i,n[a].name=a,n[a].parent_name=W(i),n[a].root_name=e,"object"==typeof r){s[a]=!0;const e=function(){};e.displayName=a,e.prototype=n[a],t(r,e)}})}(t,o),{hash_states:n,is_group_state:s}}(n);let A=o;const{stateList:_,stateAncestors:w}=ft(n);let C=vt(_),j={},F={};const N=S.is_group_state;let k=S.hash_states;function x(t,e){var n,s;g.debug("send event",t),n=It,s=[t],p&&wt(n,s);const o=X(t)[0],i=t[o],u=k[a].current_state_name;return e&&o===r&&u!==a?(g.warn("The external event INIT_EVENT can only be sent when starting the machine!"),d):function(t,e,n,s){const o=t[a].current_state_name,i=t[o][e];if(i){g.log("found event handler!"),g.info("WHEN EVENT ",e);const{stop:u,outputs:d}=i(s,n,o);l&&!u&&g.warn("No guards have been fulfilled! We recommend to configure guards explicitly to cover the full state space!");const f=ht(d),p=t[a].current_state_name;if(F[p]&&p!==o){const t=j[p]?r:c;return[].concat(f).concat(x({[t]:n},!1))}return f}return g.warn(`There is no transition associated to the event |${e}| in state |${o}|!`),d}(S.hash_states,o,i,A)}return v.forEach(function(t){let{from:n,to:s,action:o,event:u,guards:p}=t;p||(p=[{predicate:Pt,to:s,action:o}]),u===r&&(j[n]=!0);let v=k[n];if(u&&!(u in b))throw`unknown event ${u} found in state machine definition!`;u||(u=c,F[n]=!0),N[n]&&j[n]&&(F[n]=!0),v[u]=p.reduce((t,s,o)=>{const r=s.action||f,c=r.name||r.displayName,u=function(t,e){let s="";const u=function(u,f,p){n=p||n;const b=t.predicate,v=b||Pt,S=!b||At(O,v,["extendedState","eventData","settings"])(u,f,e),E=t.to;if(s=v?"_checking_condition_"+o:"",jt({debug:l,console:g},{predicate:t.predicate,extendedState:u,eventData:f,settings:e},S,H,Ft,kt),S){g.info("IN STATE ",n),b&&g.info(`CASE: guard ${v.name} for transition is fulfilled`),!b&&g.info("CASE: unguarded transition"),g.info("THEN : we execute the action "+c);const t=At(T,r,["extendedState","eventData","settings"])(u,f,e);jt({debug:l,console:g},{action:r,extendedState:u,eventData:f,settings:e},t,Mt,Ft,Nt);const{updates:s,outputs:o}=t;!function(t,e,n){const s=n[t].name;C=St(C,w,s),g.info("left state",Y(t))}(n,0,k);const d=y(u,s);jt({debug:l,console:g},{updateStateFn:i,extendedState:u,updates:s},d,Pt,Ft,$),A=d;const p=function(t,e,n){let s,o;if(gt(t)){const e=t.deep?h:t.shallow?m:void 0,i=t[e];l&&g&&!j[i]&&g.warn("Configured a history state which does not relate to a compound state! The behaviour of the machine is thus unspecified. Please review your machine configuration"),o=C[e][i]||i,s=n[o]}else{if(!t)throw"enter_state : unknown case! Not a state name, and not a history state to enter!";s=n[t],o=s.name}return n[a].current_state_name=o,l&&g.info("AND TRANSITION TO STATE",o),o}(E,0,k);return g.info("ENTERING NEXT STATE : ",p),{stop:!0,outputs:o}}return{stop:!1,outputs:d}};return u.displayName=n+s,u}(s,e);return function(e,n,s){const o=t(e,n,s);return o.stop?o:u(e,n,s)}},function(){return{stop:!1,outputs:d}})}),x({[r]:o},!0),function(t){return x(t,!0)}}function Vt(t){return t.reduce((t,e)=>t.concat(e),[])}function Wt(t,e){return e?`state "${e}" as ${t} <<NoContent>>`:`state "${nt(t)}" as ${t} <<NoContent>>`}t.fsmContracts=Lt,t.build_state_enum=function(t){let e={history:{}};return e.NOK=a,function t(n){X(n).forEach(function(s){const o=n[s];e[s]=s,"object"==typeof o&&t(o)})}(t),e},t.normalizeTransitions=Bt,t.normalizeFsmDef=function(t){return Object.assign({},t,{transitions:Bt(t)})},t.create_state_machine=function(t,e){return Ut(t,e)},t.createStateMachine=Ut,t.makeWebComponentFromFsm=function({name:t,eventHandler:e,fsm:n,commandHandlers:s,effectHandlers:o,options:i}){return customElements.define(t,class extends HTMLElement{constructor(){if(t.split("-").length<=1)throw"makeWebComponentFromFsm : web component's name MUST include a dash! Please review the name property passed as parameter to the function!";super();const a=this;this.eventSubject=e,this.options=Object.assign({},i);const r=this.options.NO_ACTION||d;this.eventSubject.subscribe({next:t=>{const e=n(t);e!==r&&e.forEach(t=>{if(t===r)return;const{command:e,params:n}=t;s[e](this.eventSubject.next,n,o,a)})}})}static get observedAttributes(){return[]}connectedCallback(){this.options.initialEvent&&this.eventSubject.next(this.options.initialEvent)}disconnectedCallback(){this.options.terminalEvent&&this.eventSubject.next(this.options.terminalEvent),this.eventSubject.complete()}attributeChangedCallback(t,e,n){this.constructor(),this.connectedCallback()}})},t.makeNamedActionsFactory=function(t){return Object.keys(t).reduce((e,n)=>{const s=t[n];return s.displayName=n,e[n]=s,e},{})},t.mergeOutputsFn=Vt,t.decorateWithEntryActions=function(t,e,n){if(!e)return t;const{transitions:s,states:o,initialExtendedState:i,initialControlState:a,events:r,updateState:c,settings:u}=t,l=st(o),d=Object.keys(e).every(t=>null!=l[t]),f=n||Vt;if(d)return{initialExtendedState:i,initialControlState:a,states:o,events:r,transitions:pt((t,n,s,o)=>{const{to:i}=n,a=e[i];return a?function(t,e,n,s){const o=function(o,i,a){const{debug:r}=a,c=At(S,e,["extendedState","eventData","settings"]),u=t(o,i,a),l=u.updates,d=At(E,s,["extendedState, updates"]),f=d(o,l);jt({debug:r,console:console},{updateStateFn:s,extendedState:o,actionUpdate:l},f,Pt,Ft,$);const p=f,m=c(p,i,a),h=jt({debug:r,console:console},{action:e,extendedState:p,eventData:i,settings:a},m,Mt,Ft,xt);if(!h)return{updates:[].concat(l,m.updates),outputs:n([u.outputs,m.outputs])}};return o.displayName=`Entry_Action_After_${_t(t)}`,o}(t,a,f,c):t},s),updateState:c,settings:u};throw"decorateWithEntryActions : found control states for which entry actions are defined, and yet do not exist in the state machine!"},t.traceFSM=function(t,e){const{initialExtendedState:n,initialControlState:s,events:o,states:i,transitions:a,updateState:r}=e;return{initialExtendedState:n,initialControlState:s,events:o,states:i,updateState:r,transitions:pt((t,e,n,s)=>(function(o,i,a){const{from:c,event:u,to:l,predicate:d}=e,f=At(T,t,["extendedState","eventData","settings"])(o,i,a),{outputs:p,updates:m}=f;return{updates:m,outputs:[{outputs:p,updates:m,extendedState:o,newExtendedState:At(E,r,["extendedState, updates"])(o,m||[]),controlState:c,event:{eventLabel:u,eventData:i},settings:a,targetControlState:l,predicate:d,actionFactory:t,guardIndex:n,transitionIndex:s}]}}),a)}},t.makeHistoryStates=function(t){const e=Object.keys(st(t));return(t,n)=>{if(!e.includes(n))throw"makeHistoryStates: the state for which a history state must be constructed is not a configured state for the state machine under implementation!!";return{[t]:n,type:p}}},t.historyState=function(t,e){return{[t]:e}},t.toPlantUml=function(t,i){const{states:r,transitions:c}=t,{getChildren:u,constructTree:l,getLabel:d}=I,f=t=>t.join(e),p=M(I,{seed:()=>Map,visit:(t,e,i)=>{const{path:r}=e.get(i),l=d(i),p=function(t,e,i){return[`${Wt(t,"")} {`,e.join("\n"),function(t,e){const n=e.reduce((e,n)=>{const s=et(n);return s.filter(z).filter(K(t)).reduce((t,e)=>(t[tt(e)]=void 0,t),e)},{});return Object.keys(n).map(t=>`${Wt(t,o)}`).join("\n")}(t,i),function(t,e){return e.reduce((e,o)=>{const i=et(o);return i.filter(q).filter(G(t)).reduce((t,e)=>{const{from:o,to:i,predicate:a,action:r}=e;return t.push(`[*] ${n} ${i} ${s} ${Z("",a,r)}`),t},e)},[]).join("\n")}(t,i),"}",function(t,e){const o=function(t,e){return e.map(e=>{const o=et(e);return o.filter(G(t)).filter(z).map(({from:t,event:e,predicate:o,to:i,action:a})=>[t,n,tt({from:t,to:i}),s,Z(e,o,a)].join(" ")).join("\n")}).filter(Boolean).join("\n")}(t,e),i=function(t,e){return t===a?"":e.map(e=>{const o=et(e);return o.filter(G(t)).filter(t=>!q(t)).filter(t=>!z(t)).map(({from:t,event:e,predicate:o,to:i,action:a})=>[t,n,i,s,Z(e,o,a)].join(" ")).join("\n")}).filter(Boolean).join("\n")}(t,e);return[o,i].filter(Boolean).join("\n")}(t,i)].filter(t=>"\n"!==t&&""!==t).join("\n")}(Object.keys(l)[0],J(e=>t.get(f(r.concat(e))),((t,e)=>u(t,e).length)(i,e)),c);return t.set(f(r),p),t}},{[a]:r}),m=p.get("0");return p.clear(),m},t.toDagreVisualizerFormat=function(t){const{states:n,transitions:s}=t,{getLabel:o,getChildren:i}=I,{constructTree:r}=D,c=t=>t.join(e),u=M(I,{seed:()=>Map,visit:(t,e,n)=>{const{path:s}=e.get(n),a=o(n),u=Object.keys(a)[0],l=J(e=>t.get(c(s.concat(e))),((t,e)=>i(t,e).length)(n,e));return t.set(c(s),r(u,l)),t}},{[a]:n}).get("0"),l=s.map(t=>{const{from:e,to:n,event:s,guards:o,action:i}=t;return o?{from:e,event:s,guards:o.map(t=>{const{predicate:e,to:n,action:s}=t;return{predicate:e.name,to:n,action:s.name}})}:{from:e,to:n,event:s,action:i.name||"no action name?"}});return JSON.stringify({states:u,transitions:l})},t.CONTRACT_MODEL_UPDATE_FN_RETURN_VALUE="Model update function must return valid update operations!",t.SEP=e,t.TRANSITION_SYMBOL=n,t.TRANSITION_LABEL_START_SYMBOL=s,t.HISTORY_STATE_NAME=o,t.HISTORY_PREFIX=i,t.INIT_STATE=a,t.INIT_EVENT=r,t.AUTO_EVENT=c,t.STATE_PROTOTYPE_NAME=u,t.NO_STATE_UPDATE=l,t.NO_OUTPUT=d,t.ACTION_IDENTITY=f,t.history_symbol=p,t.SHALLOW=m,t.DEEP=h,t.WRONG_EVENT_FORMAT_ERROR=g,t.FUNCTION_THREW_ERROR=y,t.INVALID_ACTION_FACTORY_EXECUTED=b,t.INVALID_PREDICATE_EXECUTED=v,t.ACTION_FACTORY_DESC=T,t.ENTRY_ACTION_FACTORY_DESC=S,t.UPDATE_STATE_FN_DESC=E,t.PREDICATE_DESC=O,t.COMMAND_RENDER="render",t.noop=$,t.emptyConsole=L,t.isBoolean=H,t.isFunction=P,t.isControlState=B,t.isEvent=U,t.isActionFactory=V,t.make_states=function(t){return t.reduce((t,e)=>(t[e]="",t),{})},t.make_events=function(t){return t},t.get_fn_name=W,t.wrap=Y,t.times=J,t.always=function(t){return t},t.keys=X,t.merge=function(t,e){return Object.assign({},t,e)},t.is_history_transition=z,t.is_entry_transition=q,t.is_from_control_state=G,t.is_to_history_control_state_of=K,t.is_history_control_state_of=Q,t.format_transition_label=Z,t.format_history_transition_state_name=tt,t.get_all_transitions=et,t.getDisplayName=nt,t.mergeModelUpdates=function(t){return function(e,n,s){return{updates:t.reduce((t,o)=>{const i=o(e,n,s).updates;return i?t.concat(i):t},[]),outputs:d}}},t.chainModelUpdates=function(t){return function(e,n,s){const{updateState:o}=s;return{updates:t.reduce((t,e)=>{const{extendedState:i,updates:a}=t,r=e(i,n,s).updates;return{extendedState:o(i,a),updates:r}},{extendedState:e,updates:[]}).updates||[],outputs:d}}},t.mergeActionFactories=function(t,e){return function(n,s,o){const i=e.map(t=>t(n,s,o)),a=i.map(t=>t.updates||[]),r=i.map(t=>t.outputs||{});return{updates:[].concat(...a),outputs:t(r)}}},t.identity=function(t,e,n){return{updates:[],outputs:d}},t.lastOf=function(t){return t[t.length-1]},t.getFsmStateList=st,t.getStatesType=ot,t.getStatesPath=it,t.getStatesTransitionsMap=at,t.getStatesTransitionsMaps=rt,t.getEventTransitionsMaps=ct,t.getHistoryStatesMap=ut,t.getTargetStatesMap=lt,t.getAncestorMap=dt,t.computeHistoryMaps=ft,t.mapOverTransitionsActions=pt,t.reduceTransitions=mt,t.everyTransition=function(t,e){return mt((e,n)=>e&&t(n),!0,[e])},t.computeTimesCircledOn=function(t,e){return t.reduce((t,n)=>n===e?t+1:t,0)},t.isInitState=function(t){return t===a},t.isInitEvent=function(t){return t===r},t.isEventless=function(t){return void 0===t},t.arrayizeOutput=ht,t.isHistoryControlState=gt,t.getHistoryParentState=function(t){return t[m]||t[h]},t.isShallowHistory=function(t){return t[m]},t.isDeepHistory=function(t){return t[h]},t.getHistoryType=yt,t.getHistoryUnderlyingState=bt,t.isHistoryStateEdge=function(t){return void 0!==t.history},t.initHistoryDataStructure=vt,t.isCompoundState=Tt,t.isAtomicState=function(t,e){return!Tt(t,e)},t.updateHistory=St,t.computeHistoryState=function(t,e,n,s){const{stateList:o,stateAncestors:i}=ft(t);let a=vt(o);return(a=e.reduce((t,e)=>St(t,i,e),a))[n][s]},t.findInitTransition=Et,t.tryCatch=Ot,t.tryCatchMachineFn=At,t.getFunctionName=_t,t.assert=wt,t.notifyThrows=Ct,t.handleFnExecError=jt,t.notifyAndRethrow=Ft,t.throwIfInvalidActionResult=Nt,t.throwIfInvalidGuardResult=kt,t.throwIfInvalidEntryActionResult=xt,t.isActions=Mt,t.isEventStruct=It,Object.defineProperty(t,"__esModule",{value:!0})}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.Kingly={})}(this,function(t){"use strict";const e=".",n="--\x3e",s=":",o="H",i="history.",a="nok",r="init",c="auto",u="State",l=[],d=null,f=function(){return{outputs:d,updates:l}},p={},m="shallow",h="deep",g="The machine received an event which does not have the proper format. Expecting an object whose unique key is the event name, and value is the event data.",y=(t,e)=>`Exception thrown when executing ${e} ${t||""}`,b=(t,e)=>`${y(t,e)}\nThe ${e} returned a value which is not an action.`,v=(t,e)=>`${y(t,e)}\nThe ${e} returned a value which is not a boolean.`,T="action factory",S="(decorating) entry action",E="update state function",O="predicate",w=[0],A="PRE_ORDER";function _(t){return void 0===t?void 0:JSON.parse(JSON.stringify(t))}function C(t,e){return Object.assign({},t,e)}function j(t,e,n){n.forEach((n,s)=>{const o=t.get(e),i=t.get(n),a=i&&i.path;t.set(n,C(i,{isAdded:!0,isVisited:!1,path:a||o.path.concat(s)}))})}function F(t,e){t.set(e,C(t.get(e),{isVisited:!0}))}function N(t,e){const{store:n,lenses:s,traverse:o}=t,{empty:i,add:a,takeAndRemoveOne:r,isEmpty:c}=n,{getChildren:u}=s,{visit:l,seed:d}=o,f=new Map,p="function"==typeof d?new(d()):_(d);let m="function"==typeof i?new(i()):_(i),h=p;for(a([e],m),f.set(e,{isAdded:!0,isVisited:!1,path:w});!c(m);){const t=r(m),e=u(f,t);a(e,m),j(f,t,e),h=l(h,f,t),F(f,t)}return f.clear(),h}function k(t,e,n){const{getChildren:s}=t;return N({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.push.apply(e,t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function x(t,e,n){const{getChildren:s}=t;return N({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>s(e)},traverse:e},n)}function M(t,e,n){const{getChildren:s}=t,{seed:o,visit:i}=e,a=(t,e)=>e.get(t).isVisited||((t,e)=>0===s(t,e).length)(t,e);return N({store:{empty:[],takeAndRemoveOne:t=>t.shift(),isEmpty:t=>0===t.length,add:(t,e)=>e.unshift(...t)},lenses:{getChildren:(t,e)=>a(e,t)?[]:s(e,t).concat([e])},traverse:{seed:o,visit:(t,e,n)=>a(n,e)?i(t,e,n):t}},n)}const I={isLeafLabel:function(t){return 0===I.getChildren(t).length},getLabel:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getLabel > unexpected object tree value";return t},getChildren:t=>{if("object"!=typeof t||Array.isArray(t)||1!==Object.keys(t).length)throw"getChildren > unexpected value";{let e=Object.values(t)[0];return e&&"object"==typeof e&&!Array.isArray(e)?Object.keys(e).map(t=>({[t]:e[t]})):[]}},constructTree:(t,e)=>{const n=t&&Object.keys(t)&&Object.keys(t)[0];return 0===e.length?t:{[n]:Object.assign.apply(null,e)}}};function R(t,e){const n={root:e},{strategy:s,seed:o,visit:i}=t;return({BFS:k,PRE_ORDER:x,POST_ORDER:M}[s]||x)(I,{seed:o,visit:function(t,e,n){const{path:s}=e.get(n);return JSON.stringify(s)===JSON.stringify(w)?t:i(t,e,n)}},n)}const D={getLabel:t=>Array.isArray(t)?t[0]:t,getChildren:t=>Array.isArray(t)?t[1]:[],constructTree:(t,e)=>e&&Array.isArray(e)&&e.length>0?[t,e]:t},$=()=>{},L={log:$,warn:$,info:$,debug:$,error:$,trace:$};function H(t){return"boolean"==typeof t}function P(t){return"function"==typeof t}function B(t){return t&&"string"==typeof t||gt(t)}function U(t){return t&&"string"==typeof t}function V(t){return t&&"function"==typeof t}function W(t){return/^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/.exec(t.toString())[1]}function Y(t){return["-",t,"-"].join("")}function J(t,e){return Array.apply(null,{length:e}).map(Number.call,Number).map(t)}function X(t){return Object.keys(t)}function z(t){return t.to.startsWith(i)}function q(t){return t.event===r}function G(t){return function(e){return e.from===t}}function K(t){return function(e){return Q(t,e.to)}}function Q(t,e){return e.substring(i.length)===t}function Z(t,e,n){const s=t||"";return e&&n?`${s} [${e.name}] / ${n.name}`:e?`${s} [${e.name}]}`:n?`${s} / ${n.name}`:`${s}`}function tt({from:t,to:e}){return`${t}.${e.substring(i.length)}.${o}`}function et(t){const{from:e,event:n,guards:s}=t;return s?s.map(({predicate:t,to:s,action:o})=>({from:e,event:n,predicate:t,to:s,action:o})):[t]}function nt(t){return t.replace(/_/g," ")}function st(t){const{getLabel:e}=I;return R({strategy:A,seed:{},visit:(t,n,s)=>{const o=e(s);return t[Object.keys(o)[0]]="",t}},t)}function ot(t){const{getLabel:e,isLeafLabel:n}=I;return R({strategy:A,seed:{},visit:(t,s,o)=>{const i=e(o),a=Object.keys(i)[0];return n(i)?(t[a]=!1,t):(t[a]=!0,t)}},t)}function it(t){const{getLabel:e}=I;return R({strategy:A,seed:{},visit:(t,n,s)=>{const o=n.get(s).path.join("."),i=e(s);return t[Object.keys(i)[0]]=o,t}},t)}function at(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return gt(n)?t:(t[n]=t[n]||{},t[n][s]=e,t)},{})||{}}function rt(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return gt(n)?t:(t[n]=t[n]||{},t[n][s]=t[n][s]?t[n][s].concat(e):[e],t)},{})||{}}function ct(t){return t.reduce((t,e)=>{const{from:n,event:s}=e;return gt(n)?t:(t[s]=t[s]||{},t[s][n]=t[s][n]?t[s][n].concat(e):[e],t)},{})||{}}function ut(t){return mt((t,e,n,s)=>{const{from:o,event:i,to:a,action:r,predicate:c,gen:u}=e;if(gt(o)){const n=bt(o);t.set(n,(t.get(n)||[]).concat([e]))}else if(gt(a)){const n=bt(a);t.set(n,(t.get(n)||[]).concat([e]))}return t},new Map,t)||{}}function lt(t){return mt((t,e,n,s)=>{const{to:o}=e;return t.set(o,(t.get(o)||[]).concat([e])),t},new Map,t)||{}}function dt(t){const{getLabel:e,getChildren:n}=I;return R({strategy:A,seed:{},visit:(t,s,o)=>{const i=e(o),a=Object.keys(i)[0];return n(o).map(t=>Object.keys(e(t))[0]).forEach(e=>{t[e]=t[e]||[],t[e]=t[e].concat(a)}),t}},t)}function ft(t){if(0===Object.keys(t).length)throw"computeHistoryMaps : passed empty control states parameter?";const{getLabel:e,isLeafLabel:n}=I,s={strategy:A,seed:{stateList:[],stateAncestors:{[h]:{},[m]:{}}},visit:(t,n,s)=>{const o=e(s),i=Object.keys(o)[0];t.stateList=t.stateList.concat(i);const{path:r}=n.get(s);n.set(JSON.stringify(r),i);const c=r.slice(0,-1);if(1===c.length)n.set(JSON.stringify(c),a);else{const e=n.get(JSON.stringify(c));t.stateAncestors[m][i]=[e];const{ancestors:s}=r.reduce((t,e)=>{const s=t.path.slice(0,-1);if(t.path=s,s.length>1){const e=n.get(JSON.stringify(s));t.ancestors=t.ancestors.concat(e)}return t},{ancestors:[],path:r});t.stateAncestors[h][i]=s}return t}},{stateList:o,stateAncestors:i}=R(s,t);return{stateList:o,stateAncestors:i}}function pt(t,e){return mt(function(e,n,s,o){const{from:i,event:a,to:r,action:c,predicate:u}=n,l=t(c,n,s,o);return l.displayName=l.displayName||c&&(c.name||c.displayName||function(t,e,n,s,o){const i=o?o.name:"",a=i?`[${i}]`:"",r=t?t.name:"identity";return`${r||"unnamed action"}:${e}-${n}->${s} ${a}`}(c,i,a,r,u)),void 0===u?e.push({from:i,event:a,to:r,action:l}):0===s?e.push({from:i,event:a,guards:[{to:r,predicate:u,action:l}]}):e[e.length-1].guards.push({to:r,predicate:u,action:l}),e},[],e)}function mt(t,e,n){return n.reduce((e,n,s)=>{let{from:o,event:i,to:a,gen:r,action:c,guards:u}=n;return u||(u=r?[{to:a,action:c,gen:r,predicate:void 0}]:[{to:a,action:c,predicate:void 0}]),u.reduce((e,n,a)=>{const{to:r,action:c,gen:u,predicate:l}=n;return t(e,u?{from:o,event:i,to:r,action:c,predicate:l,gen:u}:{from:o,event:i,to:r,action:c,predicate:l},a,s)},e)},e)}function ht(t){return t===d?d:Array.isArray(t)?t:[t]}function gt(t){return"object"==typeof t&&(h in t||m in t)}function yt(t){return t[h]?h:m}function bt(t){return t[yt(t)]}function vt(t){const e=()=>t.reduce((t,e)=>(t[e]="",t),{});return{[h]:e(),[m]:e()}}function Tt(t,e){const{statesAdjacencyList:n}=t;return n[e]&&0!==n[e].length}function St(t,e,n){return n===a?t:([m,h].forEach(s=>{(e[s][n]||[]).forEach(e=>{t[s][e]=n})}),t)}function Et(t){return t.find(t=>t.from===a&&t.event===r)}function Ot(t,e){return function(...n){try{return t.apply(t,n)}catch(t){return e(t,n)}}}function wt(t,e,n=[]){return Ot(e,(s,o)=>{const i=new Error(s),a=At(e),r=y(a,t);i.probableCause=s.probableCause?[s.probableCause,r].join("\n"):r;const c={fnName:a,params:n.reduce((t,e,n)=>(t[e]=o[n],t),{})};return i.info=s.info?[].concat([s.info]).concat([c]):c,i})}function At(t){return t.name||t.displayName||"anonymous"}function _t(t,e){const n=t.apply(null,e);if(!0!==n){const e=n.info;throw console.error(`ERROR: failed contract ${t.name||""}. ${e?"Error info:":""}`,n.info),n}}function Ct(t,e){t.error(e),e.probableCause&&t.error(`Probable cause: ${e.probableCause}`),e.info&&t.error("ERROR: additional info",e.info)}function jt(t,e,n,s,o,i){const{debug:a,console:r}=t;return a&&n instanceof Error?(o({debug:a,console:r},n,e),!0):!(!a||s(n))&&(i({debug:a,console:r},n,e),!0)}function Ft({debug:t,console:e},n){throw Ct(e,n),n}function Nt({debug:t,console:e},n,s){const{action:o,extendedState:i,eventData:a,settings:r}=s,c=At(o),u=new Error(b(c,T));throw u.info={fnName:At(o),params:{updatedExtendedState:i,eventData:a,settings:r},returned:n},Ct(e,u),u}function kt({debug:t,console:e},n,s){const o=At(s.predicate),i=new Error(v(o,O));throw i.info={predicateName:o,params:s,returned:n},Ct(e,i),i}function xt({debug:t,console:e},n,s){const{action:o,extendedState:i,eventData:a,settings:r}=s,c=At(o),u=new Error(b(c,S));throw u.info={fnName:At(o),params:{updatedExtendedState:i,eventData:a,settings:r},returned:n},Ct(e,u),u}function Mt(t){return t&&"updates"in t&&"outputs"in t&&(t.outputs===d||Array.isArray(t.outputs))&&Array.isArray(t.updates)}function It(t){let e;return t&&"object"==typeof t?Object.keys(t).length>1?(e=new Error(g)).info={event:t,cause:"Event objects must have only one key which is the event name!"}:e=!0:(e=new Error(g)).info={event:t,cause:"not an object!"},e}function Rt(t){return t instanceof Error}function Dt(t){const{from:e,event:n,guards:s,to:o,action:i}=t;return void 0===s&&o&&B(e)&&U(n)&&B(o)&&V(i)}function $t(t){const{to:e,predicate:n,action:s}=t;return e&&B(e)&&P(n)&&V(s)}function Lt(t){const{from:e,event:n,guards:s,to:o}=t;return s&&Array.isArray(s)&&s.length>0&&!o&&B(e)&&U(n)&&s.every($t)}const Ht={computed:(t,e)=>({statesType:ot(t.states),initTransition:Et(t.transitions),statesTransitionsMap:at(t.transitions),statesTransitionsMaps:rt(t.transitions),eventTransitionsMaps:ct(t.transitions),ancestorMap:dt(t.states),statesPath:it(t.states),historyStatesMap:ut(t.transitions),targetStatesMap:lt(t.transitions)}),description:"FSM structure",contracts:[{name:"isValidFsmDef",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n,states:s,events:o,initialExtendedState:i}=t,a=n&&Array.isArray(n),r=s&&"object"==typeof s,c=o&&Array.isArray(o);return a?r?c?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"The events property for a machine definition must be an array!",info:{events:o}}}:{isFulfilled:!1,blame:{message:"The states property for a machine definition must be an object!",info:{states:s}}}:{isFulfilled:!1,blame:{message:"The transitions property for a machine definition must be an array!",info:{transitions:n}}}}},{name:"isValidSettings",shouldThrow:!1,predicate:t=>({isFulfilled:!0,blame:void 0})},{name:"isInitialControlStateDeclared",shouldThrow:!1,predicate:(t,e,{initTransition:n,statesType:s})=>{const{initialControlState:o,transitions:i}=t,a=Object.keys(s);return o?{isFulfilled:a.indexOf(o)>-1,blame:{message:"Configured initial control state must be a declared state. Cf. log",info:{initialControlState:o,declaredStates:a}}}:{isFulfilled:!0,blame:void 0}}},{name:"isInitialStateOriginState",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n})=>Array.from(n.keys()).indexOf(a)>-1?{isFulfilled:!1,blame:{message:"Found at least one transition with the initial state as target state! CF. log",info:{targetStates:Array.from(n.keys()),transitions:t.transitions}}}:{isFulfilled:!0,blame:void 0}},{name:"eventsAreStrings",shouldThrow:!1,predicate:(t,e)=>({isFulfilled:t.events.every(t=>"string"==typeof t),blame:{message:"Events must be an array of strings!",info:{events:t.events}}})},{name:"haveTransitionsValidTypes",shouldThrow:!1,predicate:(t,e)=>{const{transitions:n}=t,s=n.map((t,e)=>!Dt(t)&&!Lt(t)&&{transition:t,index:e}).filter(Boolean),o=Object.keys(s).length;return{isFulfilled:0===o,blame:{message:`Found ${o} transitions with invalid format! Check logs for more details.`,info:{wrongTransitions:s,transitions:n}}}}},{name:"noDuplicatedStates",shouldThrow:!1,predicate:(t,e)=>{const{getLabel:n}=I,s={strategy:A,seed:{duplicatedStates:[],statesHashMap:{}},visit:(t,e,s)=>{const{duplicatedStates:o,statesHashMap:i}=t,a=n(s),r=Object.keys(a)[0];return r in i?{duplicatedStates:o.concat(r),statesHashMap:i}:{duplicatedStates:o,statesHashMap:(i[r]="",i)}}},{duplicatedStates:o}=R(s,t.states);return{isFulfilled:0===o.length,blame:{message:"State names must be unique! Found duplicated state names. Cf. log",info:{duplicatedStates:o}}}}},{name:"noReservedStates",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:-1===Object.keys(n).indexOf(a),blame:{message:"You cannot use a reserved control state name for any of the configured control states for the machine! Cf. log",info:{reservedStates:[a],statesType:n}}})},{name:"atLeastOneState",shouldThrow:!1,predicate:(t,e,{statesType:n})=>({isFulfilled:Object.keys(n).length>0,blame:{message:"Machine configuration must define at least one control state! Cf. log",info:{statesType:n}}})},{name:"areEventsDeclared",shouldThrow:!1,predicate:(t,e,{eventTransitionsMaps:n})=>{const s=Object.keys(n),o=t.events,i=o.map(t=>-1===s.indexOf(t)&&t).filter(Boolean),a=s.map(t=>-1===o.indexOf(t)&&t).filter(Boolean).filter(t=>t!==r);return{isFulfilled:0===i.length&&0===a.length,blame:{message:"All declared events must be used in transitions. All events used in transition must be declared! Cf. log",info:{eventsDeclaredButNotTriggeringTransitions:i,eventsNotDeclaredButTriggeringTransitions:a}}}}},{name:"areStatesDeclared",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,targetStatesMap:s,statesType:o})=>{const i=Object.keys(n),r=Array.from(s.keys()),c=Object.keys([i,r].reduce((t,e)=>(e.forEach(e=>t[e]=!0),t),{})),u=Object.keys(o),l=u.map(t=>-1===c.indexOf(t)&&t).filter(Boolean),d=c.map(t=>t!==a&&-1===u.indexOf(t)&&t).filter(Boolean);return{isFulfilled:0===l.length&&0===l.length,blame:{message:"All declared states must be used in transitions. All states used in transition must be declared! Cf. log",info:{statesDeclaredButNotTriggeringTransitions:l,statesNotDeclaredButTriggeringTransitions:d}}}}},{name:"validInitialConfig",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s}=t;return n&&s?{isFulfilled:!1,blame:{message:"Invalid machine configuration : defining an initial control state and an initial transition at the same time may lead to ambiguity and is forbidden!",info:{initialControlState:s,initTransition:n}}}:n||s?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Invalid machine configuration : you must define EITHER an initial control state OR an initial transition! Else in which state is the machine supposed to start?",info:{initialControlState:s,initTransition:n}}}}},{name:"validInitialTransition",shouldThrow:!1,predicate:(t,e,{initTransition:n})=>{const{initialControlState:s,transitions:o}=t,i=o.reduce((t,e)=>(e.from===a&&t.push(e),t),[]);return{isFulfilled:s&&!n||!s&&n&&1===i.length&&n.event===r&&(Dt(n)||Lt(n)),blame:{message:"Invalid configuration for initial transition! Cf. log",info:{initTransition:n,initTransitions:i,initialControlState:s}}}}},{name:"initEventOnlyInCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:o})=>{const i=Object.keys(s).filter(t=>!s[t]).map(t=>({[t]:n[t]&&n[t][r]})).filter(t=>Object.values(t)[0]);return{isFulfilled:0===i.length,blame:{message:"Found at least one atomic state with an entry transition! That is forbidden! Cf. log",info:{initTransitions:i}}}}},{name:"validInitialTransitionForCompoundState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:o})=>{const i=Object.keys(s).filter(t=>s[t]),a=i.map(t=>n[t]&&n[t][r]),c=a.every(Boolean);if(!c)return{isFulfilled:!1,blame:{message:"Found at least one compound state without an entry transition! Cf. log",info:{hasEntryTransitions:i.map(t=>({[t]:!(!n[t]||!n[t][r])}))}}};const u=c&&a.every(t=>{const{guards:e,to:n}=t;return!e&&"string"==typeof n});return u?u&&a.every(t=>{const{from:e,to:n}=t;return e!==n&&o[n]&&o[n].startsWith(o[e])})?{isFulfilled:!0,blame:void 0}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must have a target state which is strictly below the compound state in the state hierarchy! ",info:{states:t.states,statesPath:o,entryTransitions:a}}}:{isFulfilled:!1,blame:{message:"Found at least one compound state with an invalid entry transition! Entry transitions for compound states must be inconditional and the associated target control state cannot be a history pseudo-state. Cf. log",info:{entryTransitions:a}}}}},{name:"validEventLessTransitions",shouldThrow:!1,predicate:(t,e,{statesTransitionsMap:n,statesType:s,statesPath:o})=>{const i=Object.keys(s).map(t=>({[t]:n[t]&&"undefined"in n[t]&&1!==Object.keys(n[t]).length})).filter(t=>void 0!==Object.values(t)[0]&&Object.values(t)[0]);return{isFulfilled:0===i.length,blame:{message:"Found at least one control state without both an eventless transition and a competing transition! Cf. log",info:{failingOriginControlStates:i}}}}},{name:"isValidSelfTransition",shouldThrow:!1,predicate:(t,e,{targetStatesMap:n,statesType:s})=>{const o=Array.from(n.keys()).map(t=>{return n.get(t).map(e=>{const{from:n,event:o}=e;if(t in s&&!s[t]&&n&&n===t&&!o)return{state:t,flatTransition:e}}).filter(Boolean)}).filter(t=>t.length>0);return{isFulfilled:0===o.length,blame:{message:"Found at least one eventless self-transition involving an atomic state! This is forbidden to avoid infinity loop! Cf. log",info:{wrongSelfTransitions:o}}}}},{name:"allStateTransitionsOnOneSingleRow",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n})=>{const s=Object.keys(n).reduce((t,e)=>{const s=Object.keys(n[e]).filter(t=>n[e][t].length>1);return s.length>0&&(t[e]=s),t},{});return{isFulfilled:0===Object.keys(s).length,blame:{message:"Found at least one control state and one event for which the associated transition are not condensated under a unique row! Cf. log",info:{statesTransitionsInfo:s}}}}},{name:"noConflictingTransitionsWithAncestorState",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,eventTransitionsMaps:s,ancestorMap:o})=>{const i=Object.keys(s).reduce((t,e)=>{const n=Object.keys(s[e]),i=n.filter(t=>t!==a).map(t=>o[t]&&{[t]:o[t].find(t=>n.indexOf(t)>-1)}).filter(t=>t&&Object.values(t).filter(Boolean).length>0);return i.length>0&&(t[e]=i),t},{});return{isFulfilled:0===Object.keys(i).length,blame:{message:"Found two conflicting transitions! A -ev-> X, and B -ev-> Y leads to ambiguity if A < B or B < A. Cf. log",info:{eventTransitionsInfo:i}}}}},{name:"isHistoryStatesExisting",shouldThrow:!1,predicate:(t,e,{historyStatesMap:n,statesType:s})=>{const o=Array.from(n.entries()).map(([t,e])=>!(t in s)&&{historyState:t,flatTransitions:e}).filter(Boolean),i=Object.keys(o).length;return{isFulfilled:0===i,blame:{message:`Found ${i} history pseudo state referring to a control state that is not declared! Check the states property of the state machine definition.`,info:{invalidTransitions:o,states:t.states}}}}},{name:"isHistoryStatesTargetStates",shouldThrow:!1,predicate:(t,e,{})=>{const n=t.transitions.reduce((t,e)=>gt(e.from)?t.concat(e):t,[]);return{isFulfilled:0===Object.keys(n).length,blame:{message:"Found a history pseudo state configured as the origin control state for a transition. History pseudo states should only be target control states. Cf. log",info:{wrongHistoryStates:n}}}}},{name:"isHistoryStatesCompoundStates",shouldThrow:!1,predicate:(t,e,{statesTransitionsMaps:n,statesType:s})=>{const o=Object.keys(n).map(t=>{if(t===a)return[];return Object.keys(n[t]).reduce((e,o)=>{const i=n[t][o][0],{guards:a,to:r}=i;return a?a.reduce((t,e)=>{const{to:n}=e;return gt(n)&&!s[bt(n)]?t.concat(i):t},e):gt(r)&&!s[bt(r)]?e.concat(i):e},[])}).reduce((t,e)=>t.concat(e),[]);return{isFulfilled:0===Object.keys(o).length,blame:{message:"Found a history pseudo state connected to an atomic state! History pseudo states only refer to compound states. Cf. log",info:{wrongHistoryStates:o,states:t.states}}}}}]};const Pt=(t,e,n)=>(function(t){const e=t.description;return function(...n){const s=[],o=t.computed.apply(null,n);return{isFulfilled:t.contracts.reduce((t,i)=>{const{name:a,predicate:r,shouldThrow:c}=i,u=n.concat(o),{isFulfilled:l,blame:d}=r.apply(null,u),f=`${e} FAILS ${a}!`,{message:p,info:m}=d||{};if(l)return t;if(s.push({name:a,message:p,info:m}),console.error(f),console.error([a,p].join(": ")),console.debug("Supporting error data:",m),c)throw new Error([f,"check console for information!"].join("\n"));return!1},!0),failingContracts:s}}})(n)(t,e),Bt=()=>!0;function Ut(t){const{initialControlState:e,transitions:n}=t,s=Et(n);return e?n.concat([{from:a,event:r,to:e,action:f}]):s?n:void 0}function Vt(t,e){const{states:n,events:s,initialExtendedState:o,updateState:i}=t,{debug:l}=e||{},p=l&&l.checkContracts||void 0;let g=l&&l.console?l.console:L;if(p){const{failingContracts:n}=Pt(t,e,p);if(n.length>0)throw new Error("createStateMachine: called with wrong parameters! Cf. logs for failing contracts.")}const y=wt(E,i,["extendedState, updates"]),b=function(t){return(t=t.reduce?t:Array.prototype.slice.call(arguments)).push(r),t.reduce(function(t,e){return t[e]=e,t},{})}(s),v=Ut(t),S=function(t){const e="State";let n={},s={};function o(){}return t={nok:t},o.prototype={current_state_name:a},n[a]=new o,n[u]=new o,function t(o,i){X(o).forEach(function(a){const r=o[a];if(n[a]=new i,n[a].name=a,n[a].parent_name=W(i),n[a].root_name=e,"object"==typeof r){s[a]=!0;const e=function(){};e.displayName=a,e.prototype=n[a],t(r,e)}})}(t,o),{hash_states:n,is_group_state:s}}(n);let w=o;const{stateList:A,stateAncestors:_}=ft(n);let C=vt(A),j={},F={};const N=S.is_group_state;let k=S.hash_states;function x(t,e){var n,s;g.debug("send event",t),n=It,s=[t],p&&_t(n,s);const o=X(t)[0],i=t[o],u=k[a].current_state_name;return e&&o===r&&u!==a?(g.warn("The external event INIT_EVENT can only be sent when starting the machine!"),d):function(t,e,n,s){const o=t[a].current_state_name,i=t[o][e];if(i){g.log("found event handler!"),g.info("WHEN EVENT ",e);const{stop:u,outputs:d}=i(s,n,o);l&&!u&&g.warn("No guards have been fulfilled! We recommend to configure guards explicitly to cover the full state space!");const f=ht(d),p=t[a].current_state_name;if(F[p]&&p!==o){const t=j[p]?r:c;return[].concat(f).concat(x({[t]:n},!1))}return f}return g.warn(`There is no transition associated to the event |${e}| in state |${o}|!`),d}(S.hash_states,o,i,w)}return v.forEach(function(t){let{from:n,to:s,action:o,event:u,guards:p}=t;p||(p=[{predicate:Bt,to:s,action:o}]),u===r&&(j[n]=!0);let v=k[n];if(u&&!(u in b))throw`unknown event ${u} found in state machine definition!`;u||(u=c,F[n]=!0),N[n]&&j[n]&&(F[n]=!0),v[u]=p.reduce((t,s,o)=>{const r=s.action||f,c=r.name||r.displayName,u=function(t,e){let s="";const u=function(u,f,p){n=p||n;const b=t.predicate,v=b||Bt,S=!b||wt(O,v,["extendedState","eventData","settings"])(u,f,e),E=t.to;if(s=v?"_checking_condition_"+o:"",jt({debug:l,console:g},{predicate:t.predicate,extendedState:u,eventData:f,settings:e},S,H,Ft,kt),S){g.info("IN STATE ",n),b&&g.info(`CASE: guard ${v.name} for transition is fulfilled`),!b&&g.info("CASE: unguarded transition"),g.info("THEN : we execute the action "+c);const t=wt(T,r,["extendedState","eventData","settings"])(u,f,e);jt({debug:l,console:g},{action:r,extendedState:u,eventData:f,settings:e},t,Mt,Ft,Nt);const{updates:s,outputs:o}=t;!function(t,e,n){const s=n[t].name;C=St(C,_,s),g.info("left state",Y(t))}(n,0,k);const d=y(u,s);jt({debug:l,console:g},{updateStateFn:i,extendedState:u,updates:s},d,Bt,Ft,$),w=d;const p=function(t,e,n){let s,o;if(gt(t)){const e=t.deep?h:t.shallow?m:void 0,i=t[e];l&&g&&!j[i]&&g.warn("Configured a history state which does not relate to a compound state! The behaviour of the machine is thus unspecified. Please review your machine configuration"),o=C[e][i]||i,s=n[o]}else{if(!t)throw"enter_state : unknown case! Not a state name, and not a history state to enter!";s=n[t],o=s.name}return n[a].current_state_name=o,l&&g.info("AND TRANSITION TO STATE",o),o}(E,0,k);return g.info("ENTERING NEXT STATE: ",p),Rt(d)&&g.error("with error: ",d),Rt(d)||g.info("with extended state: ",w),{stop:!0,outputs:o}}return{stop:!1,outputs:d}};return u.displayName=n+s,u}(s,e);return function(e,n,s){const o=t(e,n,s);return o.stop?o:u(e,n,s)}},function(){return{stop:!1,outputs:d}})}),x({[r]:o},!0),function(t){return x(t,!0)}}function Wt(t){return t.reduce((t,e)=>t.concat(e),[])}function Yt(t,e){return e?`state "${e}" as ${t} <<NoContent>>`:`state "${nt(t)}" as ${t} <<NoContent>>`}t.fsmContracts=Ht,t.build_state_enum=function(t){let e={history:{}};return e.NOK=a,function t(n){X(n).forEach(function(s){const o=n[s];e[s]=s,"object"==typeof o&&t(o)})}(t),e},t.normalizeTransitions=Ut,t.normalizeFsmDef=function(t){return Object.assign({},t,{transitions:Ut(t)})},t.create_state_machine=function(t,e){return Vt(t,e)},t.createStateMachine=Vt,t.makeWebComponentFromFsm=function({name:t,eventHandler:e,fsm:n,commandHandlers:s,effectHandlers:o,options:i}){return customElements.define(t,class extends HTMLElement{constructor(){if(t.split("-").length<=1)throw"makeWebComponentFromFsm : web component's name MUST include a dash! Please review the name property passed as parameter to the function!";super();const a=this;this.eventSubject=e,this.options=Object.assign({},i);const r=this.options.NO_ACTION||d;this.eventSubject.subscribe({next:t=>{const e=n(t);e!==r&&e.forEach(t=>{if(t===r)return;const{command:e,params:n}=t;s[e](this.eventSubject.next,n,o,a)})}})}static get observedAttributes(){return[]}connectedCallback(){this.options.initialEvent&&this.eventSubject.next(this.options.initialEvent)}disconnectedCallback(){this.options.terminalEvent&&this.eventSubject.next(this.options.terminalEvent),this.eventSubject.complete()}attributeChangedCallback(t,e,n){this.constructor(),this.connectedCallback()}})},t.makeNamedActionsFactory=function(t){return Object.keys(t).reduce((e,n)=>{const s=t[n];return s.displayName=n,e[n]=s,e},{})},t.mergeOutputsFn=Wt,t.decorateWithEntryActions=function(t,e,n){if(!e)return t;const{transitions:s,states:o,initialExtendedState:i,initialControlState:a,events:r,updateState:c,settings:u}=t,l=st(o),d=Object.keys(e).every(t=>null!=l[t]),f=n||Wt;if(d)return{initialExtendedState:i,initialControlState:a,states:o,events:r,transitions:pt((t,n,s,o)=>{const{to:i}=n,a=e[i];return a?function(t,e,n,s){const o=function(o,i,a){const{debug:r}=a,c=wt(S,e,["extendedState","eventData","settings"]),u=t(o,i,a),l=u.updates,d=wt(E,s,["extendedState, updates"]),f=d(o,l);jt({debug:r,console:console},{updateStateFn:s,extendedState:o,actionUpdate:l},f,Bt,Ft,$);const p=f,m=c(p,i,a),h=jt({debug:r,console:console},{action:e,extendedState:p,eventData:i,settings:a},m,Mt,Ft,xt);if(!h)return{updates:[].concat(l,m.updates),outputs:n([u.outputs,m.outputs])}};return o.displayName=`Entry_Action_After_${At(t)}`,o}(t,a,f,c):t},s),updateState:c,settings:u};throw"decorateWithEntryActions : found control states for which entry actions are defined, and yet do not exist in the state machine!"},t.traceFSM=function(t,e){const{initialExtendedState:n,initialControlState:s,events:o,states:i,transitions:a,updateState:r}=e;return{initialExtendedState:n,initialControlState:s,events:o,states:i,updateState:r,transitions:pt((t,e,n,s)=>(function(o,i,a){const{from:c,event:u,to:l,predicate:d}=e,f=wt(T,t,["extendedState","eventData","settings"])(o,i,a),{outputs:p,updates:m}=f;return{updates:m,outputs:[{outputs:p,updates:m,extendedState:o,newExtendedState:wt(E,r,["extendedState, updates"])(o,m||[]),controlState:c,event:{eventLabel:u,eventData:i},settings:a,targetControlState:l,predicate:d,actionFactory:t,guardIndex:n,transitionIndex:s}]}}),a)}},t.makeHistoryStates=function(t){const e=Object.keys(st(t));return(t,n)=>{if(!e.includes(n))throw"makeHistoryStates: the state for which a history state must be constructed is not a configured state for the state machine under implementation!!";return{[t]:n,type:p}}},t.historyState=function(t,e){return{[t]:e}},t.toPlantUml=function(t,i){const{states:r,transitions:c}=t,{getChildren:u,constructTree:l,getLabel:d}=I,f=t=>t.join(e),p=M(I,{seed:()=>Map,visit:(t,e,i)=>{const{path:r}=e.get(i),l=d(i),p=function(t,e,i){return[`${Yt(t,"")} {`,e.join("\n"),function(t,e){const n=e.reduce((e,n)=>{const s=et(n);return s.filter(z).filter(K(t)).reduce((t,e)=>(t[tt(e)]=void 0,t),e)},{});return Object.keys(n).map(t=>`${Yt(t,o)}`).join("\n")}(t,i),function(t,e){return e.reduce((e,o)=>{const i=et(o);return i.filter(q).filter(G(t)).reduce((t,e)=>{const{from:o,to:i,predicate:a,action:r}=e;return t.push(`[*] ${n} ${i} ${s} ${Z("",a,r)}`),t},e)},[]).join("\n")}(t,i),"}",function(t,e){const o=function(t,e){return e.map(e=>{const o=et(e);return o.filter(G(t)).filter(z).map(({from:t,event:e,predicate:o,to:i,action:a})=>[t,n,tt({from:t,to:i}),s,Z(e,o,a)].join(" ")).join("\n")}).filter(Boolean).join("\n")}(t,e),i=function(t,e){return t===a?"":e.map(e=>{const o=et(e);return o.filter(G(t)).filter(t=>!q(t)).filter(t=>!z(t)).map(({from:t,event:e,predicate:o,to:i,action:a})=>[t,n,i,s,Z(e,o,a)].join(" ")).join("\n")}).filter(Boolean).join("\n")}(t,e);return[o,i].filter(Boolean).join("\n")}(t,i)].filter(t=>"\n"!==t&&""!==t).join("\n")}(Object.keys(l)[0],J(e=>t.get(f(r.concat(e))),((t,e)=>u(t,e).length)(i,e)),c);return t.set(f(r),p),t}},{[a]:r}),m=p.get("0");return p.clear(),m},t.toDagreVisualizerFormat=function(t){const{states:n,transitions:s}=t,{getLabel:o,getChildren:i}=I,{constructTree:r}=D,c=t=>t.join(e),u=M(I,{seed:()=>Map,visit:(t,e,n)=>{const{path:s}=e.get(n),a=o(n),u=Object.keys(a)[0],l=J(e=>t.get(c(s.concat(e))),((t,e)=>i(t,e).length)(n,e));return t.set(c(s),r(u,l)),t}},{[a]:n}).get("0"),l=s.map(t=>{const{from:e,to:n,event:s,guards:o,action:i}=t;return o?{from:e,event:s,guards:o.map(t=>{const{predicate:e,to:n,action:s}=t;return{predicate:e.name,to:n,action:s.name}})}:{from:e,to:n,event:s,action:i.name||"no action name?"}});return JSON.stringify({states:u,transitions:l})},t.CONTRACT_MODEL_UPDATE_FN_RETURN_VALUE="Model update function must return valid update operations!",t.SEP=e,t.TRANSITION_SYMBOL=n,t.TRANSITION_LABEL_START_SYMBOL=s,t.HISTORY_STATE_NAME=o,t.HISTORY_PREFIX=i,t.INIT_STATE=a,t.INIT_EVENT=r,t.AUTO_EVENT=c,t.STATE_PROTOTYPE_NAME=u,t.NO_STATE_UPDATE=l,t.NO_OUTPUT=d,t.ACTION_IDENTITY=f,t.history_symbol=p,t.SHALLOW=m,t.DEEP=h,t.WRONG_EVENT_FORMAT_ERROR=g,t.FUNCTION_THREW_ERROR=y,t.INVALID_ACTION_FACTORY_EXECUTED=b,t.INVALID_PREDICATE_EXECUTED=v,t.ACTION_FACTORY_DESC=T,t.ENTRY_ACTION_FACTORY_DESC=S,t.UPDATE_STATE_FN_DESC=E,t.PREDICATE_DESC=O,t.COMMAND_RENDER="render",t.noop=$,t.emptyConsole=L,t.isBoolean=H,t.isFunction=P,t.isControlState=B,t.isEvent=U,t.isActionFactory=V,t.make_states=function(t){return t.reduce((t,e)=>(t[e]="",t),{})},t.make_events=function(t){return t},t.get_fn_name=W,t.wrap=Y,t.times=J,t.always=function(t){return t},t.keys=X,t.merge=function(t,e){return Object.assign({},t,e)},t.is_history_transition=z,t.is_entry_transition=q,t.is_from_control_state=G,t.is_to_history_control_state_of=K,t.is_history_control_state_of=Q,t.format_transition_label=Z,t.format_history_transition_state_name=tt,t.get_all_transitions=et,t.getDisplayName=nt,t.mergeModelUpdates=function(t){return function(e,n,s){return{updates:t.reduce((t,o)=>{const i=o(e,n,s).updates;return i?t.concat(i):t},[]),outputs:d}}},t.chainModelUpdates=function(t){return function(e,n,s){const{updateState:o}=s;return{updates:t.reduce((t,e)=>{const{extendedState:i,updates:a}=t,r=e(i,n,s).updates;return{extendedState:o(i,a),updates:r}},{extendedState:e,updates:[]}).updates||[],outputs:d}}},t.mergeActionFactories=function(t,e){return function(n,s,o){const i=e.map(t=>t(n,s,o)),a=i.map(t=>t.updates||[]),r=i.map(t=>t.outputs||{});return{updates:[].concat(...a),outputs:t(r)}}},t.identity=function(t,e,n){return{updates:[],outputs:d}},t.lastOf=function(t){return t[t.length-1]},t.getFsmStateList=st,t.getStatesType=ot,t.getStatesPath=it,t.getStatesTransitionsMap=at,t.getStatesTransitionsMaps=rt,t.getEventTransitionsMaps=ct,t.getHistoryStatesMap=ut,t.getTargetStatesMap=lt,t.getAncestorMap=dt,t.computeHistoryMaps=ft,t.mapOverTransitionsActions=pt,t.reduceTransitions=mt,t.everyTransition=function(t,e){return mt((e,n)=>e&&t(n),!0,[e])},t.computeTimesCircledOn=function(t,e){return t.reduce((t,n)=>n===e?t+1:t,0)},t.isInitState=function(t){return t===a},t.isInitEvent=function(t){return t===r},t.isEventless=function(t){return void 0===t},t.arrayizeOutput=ht,t.isHistoryControlState=gt,t.getHistoryParentState=function(t){return t[m]||t[h]},t.isShallowHistory=function(t){return t[m]},t.isDeepHistory=function(t){return t[h]},t.getHistoryType=yt,t.getHistoryUnderlyingState=bt,t.isHistoryStateEdge=function(t){return void 0!==t.history},t.initHistoryDataStructure=vt,t.isCompoundState=Tt,t.isAtomicState=function(t,e){return!Tt(t,e)},t.updateHistory=St,t.computeHistoryState=function(t,e,n,s){const{stateList:o,stateAncestors:i}=ft(t);let a=vt(o);return(a=e.reduce((t,e)=>St(t,i,e),a))[n][s]},t.findInitTransition=Et,t.tryCatch=Ot,t.tryCatchMachineFn=wt,t.getFunctionName=At,t.assert=_t,t.notifyThrows=Ct,t.handleFnExecError=jt,t.notifyAndRethrow=Ft,t.throwIfInvalidActionResult=Nt,t.throwIfInvalidGuardResult=kt,t.throwIfInvalidEntryActionResult=xt,t.isActions=Mt,t.isEventStruct=It,t.isError=Rt,Object.defineProperty(t,"__esModule",{value:!0})}); | ||
//# sourceMappingURL=kingly.umd.min.js.map |
@@ -206,3 +206,3 @@ // modules are defined as an array | ||
var protocol = location.protocol === 'https:' ? 'wss' : 'ws'; | ||
var ws = new WebSocket(protocol + '://' + hostname + ':' + "58903" + '/'); | ||
var ws = new WebSocket(protocol + '://' + hostname + ':' + "60427" + '/'); | ||
@@ -209,0 +209,0 @@ ws.onmessage = function (event) { |
{ | ||
"name": "kingly", | ||
"sideEffects": false, | ||
"version": "0.19.0", | ||
"version": "0.19.1", | ||
"description": "Extended Hierarchical State Transducer library", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/brucou/state-transducer" | ||
"url": "https://github.com/brucou/kingly" | ||
}, | ||
@@ -10,0 +10,0 @@ "keywords": [ |
699
README.md
@@ -5,5 +5,5 @@ |Modelize your interface | Encode the graph | Run the generated machine! | | ||
[](https://badge.fury.io/js/state-transducer) | ||
 | ||
 | ||
[](https://badge.fury.io/js/kingly) | ||
 | ||
 | ||
 | ||
@@ -13,16 +13,9 @@ | ||
- [Features](#features) | ||
- [Documentation](#documentation) | ||
- [Examples](#examples) | ||
- [Motivation](#motivation) | ||
- [The link between state machines and user interfaces](#the-link-between-state-machines-and-user-interfaces) | ||
- [Install](#install) | ||
- [Tests](#tests) | ||
- [Integration with UI libraries](#integration-with-ui-libraries) | ||
- [API](#api) | ||
* [API design](#api-design) | ||
* [General concepts](#general-concepts) | ||
* [Transducer semantics](#transducer-semantics) | ||
* [`createStateMachine :: FSM_Def -> Settings -> FSM`](#-createstatemachine----fsm-def----settings----fsm-) | ||
* [`traceFSM :: Env -> FSM_Def -> FSM_Def`](#-tracefsm----env----fsm-def----fsm-def-) | ||
* [`makeWebComponentFromFsm :: ComponentDef -> FSM`](#-makewebcomponentfromfsm----componentdef----fsm-) | ||
- [Possible API extensions](#possible-api-extensions) | ||
- [API design](#api-design) | ||
- [Visualization tools](#visualization-tools) | ||
@@ -37,18 +30,25 @@ - [Credits](#credits) | ||
# Features | ||
This library enables you to write user interfaces as state machines. You specify the machine as a | ||
graph. The library computes a function which implements that machine. You use that to drive | ||
your interface. It integrates easily with any or no framework. Tests can be automatically generated. | ||
This library enables you to implement the **behaviour** of your user interfaces or components as | ||
state machines. The behaviour is the relation between the actions performed on the user | ||
interface (button clicks, etc.) and the actions (commands) to perform on the interfaced systems | ||
(fetch data, display screens, etc.). You specify the machine as a graph. The library computes a function which | ||
implements the state machine which specifies the behaviour. You **drive** your interface with | ||
that function, you **display** the UI with the UI framework of your choice. Tests can be automatically generated. | ||
Salient features : | ||
Salient features: | ||
- **small size** : treeshakeable implementation, down from 8kB | ||
- **small API** : one function for the state machine, one function for tracing (and one function | ||
- **small size**: treeshakeable implementation, down from 11kB | ||
- **small API**: one function for the state machine, one function for tracing (and one function | ||
for the [test generation](https://github.com/brucou/state-transducer-testing) available in a | ||
separate package) | ||
- **just a function!** : easy to integrate into any framework | ||
- **[automatic test generation!](https://github.com/brucou/state-transducer-testing)** : write the machine, how to progress from one state to another, and let the computer generate hundreds of tests for you | ||
- **just a function!**: easy to integrate into any front-end framework | ||
- **[automatic test generation!](https://github.com/brucou/state-transducer-testing)**: write the machine, how to progress from one state to another, and let the computer generate hundreds of tests for you | ||
# Documentation | ||
All documentation can be accessed in the [dedicated web site](https://brucou.github.io/documentation/). | ||
# Examples | ||
|Code playground | Demo | State Machine | | ||
|---|---|---| | ||
|[trivial counter](https://codesandbox.io/s/w6x42521n7)|| | ||
|[password meter component](https://codesandbox.io/s/73wy8jwk86)|| | ||
@@ -62,157 +62,42 @@ |[movie database search interface](https://codesandbox.io/s/mo2p97k7m8)|| | ||
Now, the whole thing can sound very abstract but the major motivation for this library has been the | ||
specification and implementation of user interfaces. As a matter of fact, to [every user | ||
interface can be associated a computation](https://brucou.github.io/posts/user-interfaces-as-reactive-systems/#reactive-systems-as-automata) | ||
relating inputs to the user interface to an action to be performed on the interfaced systems. That | ||
computation often has a logic [organized around a limited set of control states](#base-example), | ||
and can be advantageously modelized by a state machine. | ||
The major motivation for this library is the specification and implementation of user interfaces. | ||
As a matter of fact, to [every user interface can be associated a computation](http://brucou.github.io/documentation/v1/contributed/User%20interfaces%20as%20reactive%20systems.html) relating | ||
inputs to the user interface to an action to be performed on the interfaced systems. That | ||
computation often has a logic [organized around a limited set of control states](http://brucou.github.io/documentation/v1/tutorials/password-meter.html), and can be advantageously modelized by a state machine. | ||
[**Jump to the examples**](#general-concepts). | ||
[**Jump to the tutorials**](https://brucou.github.io/documentation/v1/tutorials/). | ||
This library is born from : | ||
This library was born in early 2016 from: | ||
- the desire to apply such state machines for both specification and implementation of user | ||
interfaces | ||
- the absence of existing javascript libraries which satisfy our [design criteria](https://github.com/brucou/state-transducer#api-design) | ||
- mostly, we want the state machine library API design to be as close as possible from the | ||
mathematical object denoting it. This should allow us to reason about it, compose and reuse | ||
it easily. | ||
- most libraries we found either do not feature hierarchy in their state machines, or use a | ||
rather imperative API, or impose a concurrency model on top of the state machine's control flow | ||
- mostly, we want the state machine library API design to be as close as possible from the mathematical object denoting it. This should allow us to reason about it, compose and reuse it easily. | ||
- most libraries we found either do not feature hierarchy in their state machines, or use a rather imperative API, or impose a concurrency model on top of the state machine's control flow | ||
This is a [work in progress](#roadmap), however the main API for the v1.0 should be relatively | ||
stable. | ||
In the three years of existence and use of this library, we reached an API which should be fairly stable. It has been used succesfully for user-interfaces as well as in other contexts: | ||
It works nicely and have already been used succesfully for user-interfaces as well as in other | ||
contexts: | ||
- in multi-steps workflows: see an example [here](https://github.com/brucou/component-combinators/tree/master/examples/volunteerApplication), a constant feature of enterprise software today | ||
- for ['smart' synchronous streams](https://github.com/brucou/partial-synchronous-streams), which | ||
tracks computation state to avoid useless re-computations | ||
- for ['smart' synchronous streams](https://github.com/brucou/partial-synchronous-streams), which tracks computation state to avoid useless re-computations | ||
- to implement cross-domain communication protocols, to coordinate iframes with a main window | ||
In such cases, we were able to modelize our computation with an Extended Hierarchical State Transducer | ||
in a way that : | ||
In such cases, we were able to modelize our computation with an Extended Hierarchical State Transducer in a way that: | ||
- is economical (complexity of the transducer proportional to complexity of the computation) | ||
- is reasonably easy to reason about and communicate (the transducer can | ||
be visually represented, supporting both internal and external communication, and design | ||
specification and documentation) | ||
- supports step-wise refinement and iterative development (control states can be refined into a | ||
hierarchy of nested states) | ||
# The link between state machines and user interfaces | ||
In short : | ||
- a user interface can be specified by a relation between events received by the user | ||
interfaces and actions to be performed as a result on the interfaced system. | ||
- Because to the same triggering event, there may be different actions to perform on the | ||
interfaced system (depending for instance on when the event did occur, or which other events | ||
occured before), we use state to represent that variability, and specify the user interface with | ||
a function `f` such that `actions = f(state, event)`. We call here `f` the reactive function for the user interface. | ||
- The previous expression suffices to specify the user interface's behaviour, but is not enough | ||
to deduce an implementation. We then use a function `g` such that `(actions_n, state_{n+1} = g | ||
(state_n, event_n)`. That is, we explicitly include the modification of the state triggered by | ||
events. Depending on the choice that is made for `state_n`, there is an infinite number of ways | ||
to specify the user interface. | ||
- a state machine specification is one of those ways with some nice properties (concise | ||
specification, formal reasoning, easy visualization). It divides the state into control states and | ||
extended state. For each control state, it specifies a reactive sub-function which returns an | ||
updated state (i.e. a new control state, and a new extended state) and the actions to perform on | ||
the interfaced system. | ||
Let's take a very simple example to illustrate these equations. The user interface to | ||
specify is a [password selector](https://cdn.dribbble.com/users/522131/screenshots/4467712/password_strength.png). Visually, the user interface consists of a password input field | ||
and a submit password button. Its behaviour is the following : | ||
- the user types | ||
- for each new value of the password input, the input is displayed in green if the password is | ||
strong (that will be, to remain simple if there are both letters and numbers in the password), and | ||
in red otherwise | ||
- if the password is not strong, the user click on `set password` button is ignored, otherwise | ||
the password is set to the value of the password input | ||
- is reasonably easy to reason about and communicate (the transducer can be visually represented) | ||
- supports step-wise refinement and iterative development (control states can be refined into a hierarchy of nested states) | ||
A `f` partial formulation : | ||
# Install | ||
`npm install kingly --save` | ||
|State|Event|Actions| | ||
|---|---|---| | ||
|`{input: ""}`|*typed `a`*|display input in red| | ||
|`{input: "a"}`|*typed `2`*|display input in green| | ||
|`{input: "a2"}`|*clicked submit*|submit `a2` password| | ||
|`{input: "a"}`|*typed `b`*|display input in red| | ||
|`{input: "ab"}`|*clicked submit*|---| | ||
Cf [documentation](http://brucou.github.io/documentation/v1/tutorials/installation.html) | ||
A `g` partial formulation : | ||
|state_n|event|actions_n|state_{n+1}| | ||
|---|---|---|---| | ||
|`{input: ""}`|*typed `a`*|display input in red|`{input: "a"}`| | ||
|`{input: "a"}`|*typed `2`*|display input in green|`{input: "a2"}`| | ||
|`{input: "a2"}`|*clicked submit*|submit `a2` password|`{input: "a2"}`| | ||
|`{input: "a"}`|*typed `b`*|display input in red|`{input: "ab"}`| | ||
|`{input: "ab"}`|*clicked submit*|---|`{input: "ab"`}| | ||
A state machine partial formulation : | ||
|Control state|Extended state|Event|Actions|New control state|New extended state| | ||
|---|---|---|---|---|---| | ||
|**Weak**|`input: ""`|typed `a`|display input in red|**Weak**|`input: "a"`| | ||
|**Weak**|`input: "a"`|typed `2`|display input in green|**Strong**|`input: "a2"`| | ||
|**Strong**|`input: "a2"`|clicked submit|submit `a2` password|**Done**|`input: "a2"`| | ||
|**Weak**|`input: "a"`|typed `b`|display input in red|**Weak**|`input: "ab"`| | ||
|**Weak**|`input: "ab"`|clicked submit| - |**Weak**| `input: "ab"` | | ||
The corresponding implementation is by a function `fsm` with an encapsulated initial internal state | ||
of `{control state : weak, extended state: {input : ''}}` such that, if the user types 'a2' and | ||
clicks submit : | ||
``` | ||
fsm(typed 'a') = nothing | ||
fsm(typed '2') = nothing | ||
fsm(clicked submit) = submit `a2` password | ||
``` | ||
The corresponding visualization (actions are not represented) : | ||
 | ||
Note that we wrote only partial formulations in our table, as the sequence of inputs by the user | ||
is potentially infinite (while this article is not). Our tables do not for instance give a | ||
mapping for the following sequence of events : `[typed 'a', typed '2', typed | ||
<backspace>]`. Conversely, our state machine concisely represents the fact that whatever input | ||
we receive in the `Weak` control state, it will only go to the `Strong` control state if some | ||
pre-configured condition are fulfilled (both numbers and letters in the password). It will | ||
only submit the password if the `clicked submit` event is received while it is in the `Strong` | ||
state. | ||
The starting state and these two assertions can be combined into a theorem : the machine will only submit a password if the password is strong. In short, we are able to reason formally about the machine and extract properties from its definition. This is just one of the many attractive properties of state machines which makes it a tool of choice for **robust** and testable user interface's implementation. | ||
For the modelization of a [much more complex user interface](https://sarimarton.github.io/tmdb-ui-cyclejs/dist/#/), and more details on the benefits of state machine, I'll refer the reader to a [detailed article](https://www.infoq.com/articles/robust-user-interfaces-with-state-machines) on the subject. | ||
# Install | ||
`npm install state-transducer --save` | ||
# Tests | ||
To run the current automated tests : `npm run test` | ||
To run the current automated tests: `npm run test` | ||
# Integration with UI libraries | ||
The machine implementation is just a function. As such it is pretty easy to integrate in any | ||
framework. In fact, we have implemented the same interface behaviour over [React](https://codesandbox.io/s/ym8vpqm7m9), [Vue](https://codesandbox.io/s/p7xv6r1moq), [Svelte](https://github.com/brucou/movie-search-app-svelte), [Inferno](https://codesandbox.io/s/9zjo5yx8po), [Nerv](https://codesandbox.io/s/o4vkwmw7y), [Ivi](https://codesandbox.io/s/3x9x5v4kq5), and [Dojo](https://codesandbox.io/s/jnvylz9jkw) with the exact same fsm. By isolating your component behaviour in a fsm, you can delay the UI library choice to the last moment. | ||
framework. In fact, we have implemented the same interface behaviour over [React](https://codesandbox.io/s/ym8vpqm7m9), [Vue](https://codesandbox.io/s/p7xv6r1moq), [Svelte](https://github.com/brucou/movie-search-app-svelte), [Inferno](https://codesandbox.io/s/9zjo5yx8po), [Nerv](https://codesandbox.io/s/o4vkwmw7y), [Ivi](https://codesandbox.io/s/3x9x5v4kq5) with the exact same fsm. By isolating your component behaviour in a fsm, you can delay the UI library choice to the last moment. | ||
As of April 2019, we officially provide the following integrations : | ||
# API design | ||
The key objectives for the API was: | ||
- [integration with React](https://github.com/brucou/react-state-driven) | ||
- using state machines allows to use React mostly as a DOM library and eliminates the need for | ||
state management, hooks and other react paraphernalia. | ||
- [integration with Vue](https://github.com/brucou/vue-state-driven) | ||
- using state machines allows to use Vue mostly as a DOM library and eliminates the need for | ||
state management, hooks and other Vue advanced concepts. | ||
- integration with framework supporting webcomponents (only supported in [browsers which support | ||
custom elements v1](https://caniuse.com/#feat=custom-elementsv1)) | ||
- provided by the factory function `makeWebComponentFromFsm` | ||
- I am investigating whether the dependency on custom elements could be removed with the | ||
excellent [wicked elements](https://github.com/WebReflection/wicked-elements/tree/master/esm) | ||
# API | ||
## API design | ||
The key objectives for the API was : | ||
- **generality**, **reusability** and **simplicity** | ||
@@ -225,12 +110,9 @@ - there is no explicit provision made to accommodate specific use cases or frameworks | ||
As a result of this, the following choices were made : | ||
As a result of this, the following choices were made: | ||
- **functional interface** : the transducer is just a function. As such, the | ||
transducer is a black-box, and only its computed outputs can be observed | ||
- **functional interface**: the transducer is just a function. As such, the transducer is a black-box, and only its computed outputs can be observed | ||
- **complete encapsulation** of the state of the transducer | ||
- **no effects** performed by the machine | ||
- no exit and entry actions, or activities as in other state machine formalisms | ||
- there is no loss of generality as both entry and exit actions can be implemented with our | ||
state transducer. There is simply no syntactic support for it in the core API. This can however be | ||
provided through standard functional programming patterns (higher-order functions, etc.) | ||
- there is no loss of generality as both entry and exit actions can be implemented with our state transducer. There is simply no syntactic support for it in the core API. This can however be provided through standard functional programming patterns (higher-order functions, etc.) | ||
- every computation performed is synchronous (asynchrony is an effect) | ||
@@ -247,4 +129,3 @@ - action factories return the **updates** to the extended state to avoid any | ||
Concretely, our state transducer will be created by the factory function `createStateMachine`, | ||
which returns a state transducer which : | ||
Concretely, our state transducer will be created by the factory function `createStateMachine`, which returns a state transducer which: | ||
@@ -257,374 +138,17 @@ - immediately positions itself in its configured initial state (as defined by its initial control | ||
its inputs. However, a given output of the transducer depends exclusively on the sequence of inputs | ||
it has received so far ([causality property](https://en.wikipedia.org/wiki/Causal_system)). This means that it is possible to associate to a state transducer another function which takes a sequence of inputs into a | ||
sequence of outputs, in a way that **that** function is pure. This is what enables | ||
simple and automated testing. | ||
it has received so far ([causality property](https://en.wikipedia.org/wiki/Causal_system)). This means that it is possible to associate to a state transducer another function which takes a sequence of inputs into a sequence of outputs, in a way that **that** function is pure. This is what enables simple and automated testing. | ||
## General concepts | ||
There are a few things to be acquainted with : | ||
- the basic state machine formalism | ||
- its extension, including hierarchy (compound states), and history states | ||
- the library API | ||
To familiarize the reader with these, we will be leveraging two examples. The first example is | ||
the aforementioned password selector. This pretty simple example will serve to showcase the API | ||
of the library, and standard state machine terminology. The second example modelizes the | ||
behaviour of a CD player. It is more complex, and will feature a hierarchical state machine. For | ||
this example, we will show a run of the machine, and by doing so, illustrate advanced concepts | ||
such as compound states, and history states. We will not indigate into the implementation however. For a very advanced example, I invite the reader to refer to [the wizard form demo](https://github.com/brucou/cycle-state-machine-demo). | ||
We then present into more details the semantics of a state transducer and how it relates to its | ||
configuration. Finally we present our API whose documentation relies on all previously introduced | ||
concepts. | ||
### Base example | ||
We will be using as our base example the password selector we discussed previously. As a | ||
reminder, its behaviour was described by the following state machine : | ||
 | ||
To specify our machine, we need : | ||
- a list of control states the machine can be in | ||
- a list of events accepted by the machine | ||
- to describe transitions from a control state to another | ||
- the initial state of the machine (initial control state, initial extended state) | ||
The first three are clear from the graph. The initial control state can also be deduced from the | ||
graph. The initial exxtended state can be derived from the mapping table [above](#the-link-between-state-machines-and-user-interfaces) describing the | ||
behaviour of the password selector. | ||
The fsm ends up being defined by: | ||
```javascript | ||
const initialExtendedState = { | ||
input: "" | ||
}; | ||
const states = { | ||
[INIT]: "", | ||
[STRONG]: "", | ||
[WEAK]: "", | ||
[DONE]: "" | ||
}; | ||
const initialControlState = INIT; | ||
const events = [TYPED_CHAR, CLICKED_SUBMIT, START]; | ||
const transitions = [ | ||
{ from: INIT, event: START, to: WEAK, action: displayInitScreen }, | ||
{ from: WEAK, event: CLICKED_SUBMIT, to: WEAK, action: NO_ACTIONS }, | ||
{ | ||
from: WEAK, | ||
event: TYPED_CHAR, | ||
guards: [ | ||
{ predicate: isPasswordWeak, to: WEAK, action: displayInputInRed }, | ||
{ predicate: isPasswordStrong, to: STRONG, action: displayInputInGreen } | ||
] | ||
}, | ||
{ | ||
from: STRONG, | ||
event: TYPED_CHAR, | ||
guards: [ | ||
{ predicate: isPasswordWeak, to: WEAK, action: displayInputInRed }, | ||
{ predicate: isPasswordStrong, to: STRONG, action: displayInputInGreen } | ||
] | ||
}, | ||
{ | ||
from: STRONG, | ||
event: CLICKED_SUBMIT, | ||
to: DONE, | ||
action: displaySubmittedPassword | ||
} | ||
]; | ||
const pwdFsmDef = { | ||
initialControlState, | ||
initialExtendedState, | ||
states, | ||
events, | ||
transitions | ||
}; | ||
``` | ||
where action factories mapped to a transition compute two things : | ||
- a list of updates to apply internally to the extended state | ||
- an external output for the consumer of the state transducer | ||
For instance : | ||
```javascript | ||
function displayInitScreen() { | ||
return { | ||
updates: NO_STATE_UPDATE, | ||
outputs: [ | ||
{ command: RENDER, params: { screen: INIT_SCREEN, props: void 0 } } | ||
] | ||
}; | ||
} | ||
``` | ||
The full runnable code is [available here](https://codesandbox.io/s/73wy8jwk86). | ||
### CD drawer example | ||
This example is taken from Ian Horrock's seminal book on statecharts and is the specification of | ||
a CD player. The behaviour of the CD player is pretty straight forward and understandable | ||
immediately from the visualization. From a didactical point of view, the example serves to feature | ||
advanced characteristics of hierarchical state machines, including history states, composite states, | ||
transient states, automatic transitions, and entry points. For a deeper understanding of how the | ||
transitions work in the case of a hierarchical machine, you can have a look at the | ||
[terminology](https://github.com/brucou/state-transducer#terminology) and | ||
[sample run](https://github.com/brucou/state-transducer#example-run) for the CD player machine. | ||
 | ||
The salient facts are : | ||
- `NO Cd loaded`, `CD_Paused` are control states which are composite states : they are themselves state machines comprised on control states. | ||
- The control state `H` is a pseudo-control state called shallow history state | ||
- All composite states feature an entry point and an automatic transition. For instance | ||
`CD_Paused` has the sixth control state as entry point, and the transition from `CD_Paused` into | ||
that control state is called an automatic transition. Entering the `CD_Paused` control state | ||
automatically triggers that transition. | ||
- `Closing CD drawer` is a transient state. The machine will automatically transition away from | ||
it, picking a path according to the guards configured on the available exiting transitions | ||
### Example run | ||
To illustrate the previously described transducer semantics, let's run the CD player example. | ||
| Control state | Internal event | External event| | ||
|--------------------|:-----------:|------------------| | ||
| INIT_STATE | INIT_EVENT | | | ||
| No Cd Loaded | INIT | | | ||
| CD Drawer Closed | -- | | | ||
| CD Drawer Closed | | Eject | | ||
| CD Drawer Open | | Eject (put a CD) | | ||
| Closing CD Drawer | eventless | | | ||
| CD Loaded | INIT | | | ||
| CD Loaded subgroup | INIT | | | ||
| CD Stopped | -- | | | ||
| CD stopped | | Play | | ||
| CD playing | | Forward down | | ||
| Stepping forwards | | Forward up | | ||
| **CD playing** | -- | | | ||
Note : | ||
- the state entry semantics -- entering `No Cd Loaded` leads to enter `CD Drawer Closed` | ||
- the guard -- because we put a CD in the drawer, the machine transitions from `Closing CD Drawer` to `CD Loaded` | ||
- the eventless transition -- the latter is an eventless transition : the guards are | ||
automatically evaluated to select a transition to progress the state machine (by contract, there | ||
must be one) | ||
- the hierarchy of states -- the `Forward down` event transitions the state machines to `Stepping | ||
forwards`, as it applies to all atomic states nested in the `CD Loaded subgroup` control state | ||
- the history semantics -- releasing the forward key on the CD player returns to `CD Playing` the | ||
last atomic state for compound state `CD Loaded subgroup`. | ||
## Transducer semantics | ||
We give here a quick summary of the behaviour of the state transducer : | ||
**Preconditions** | ||
- the machine is configured with a set of control states, an initial extended state, | ||
transitions, guards, action factories, and user settings. | ||
- the machine configuration is valid (cf. contracts) | ||
- Input events have the shape `{{[event_label]: event_data}}` | ||
**Event processing** | ||
- Calling the machine factory creates a machine according to specifications and triggers the | ||
reserved `INIT_EVENT` event which advances the state machine out of the reserved **internal** | ||
initial control state towards the relevant **user-configured** initial control state | ||
- the `INIT_EVENT` event carries the initial extended state as data | ||
- if there is no initial transition, it is required to pass an initial control state | ||
- if there is no initial control state, it is required to configure an initial transition | ||
- an initial transition is a transition from the reserved `INIT_STATE` initial control state, | ||
triggered by the reserved initial event `INIT_EVENT` | ||
- **Loop** | ||
- Search for a feasible transition in the configured transitions | ||
- a feasible transition is a transition which is configured to deal with the received event, and | ||
for which there is a fulfilled guard | ||
- If there is no feasible transition : | ||
- issue memorized output (`NO_OUTPUT` if none), extended state and control state do not change. | ||
**Break** away from the loop | ||
- If there is a feasible transition, select the first transition according to what follows : | ||
- if there is an INIT transition, select that | ||
- if there is an eventless transition, select that | ||
- otherwise select the first transition whose guard is fulfilled (as ordered per array index) | ||
- evaluate the selected transition | ||
- if the target control state is an history state, replace it by the control state it | ||
references (i.e. the last seen nested state for that compound state) | ||
- **update the extended state** (with the updates produced by the action factory) | ||
- aggregate and memorize the outputs (produced by the action factory) | ||
- update the control state to the target control state | ||
- update the history for the control state (applies only if control state is compound state) | ||
- iterate on **Loop** | ||
- **_THE END_** | ||
A few interesting points : | ||
- a machine always transitions towards an atomic state at the end of event processing | ||
- on that path towards an atomic target state, all intermediary extended state updates are | ||
performed. Guards and action factories on that path are thus receiving a possibly evolving extended | ||
state. The computed outputs will be aggregated in an array of outputs. | ||
The aforedescribed behaviour is loosely summarized here : | ||
 | ||
**History states semantics** | ||
An history state relates to the past configuration a compound state. There | ||
are two kinds of history states : shallow history states (H), and deep history states (H*). A | ||
picture being worth more than words, thereafter follows an illustration of both history states : | ||
 | ||
Assuming the corresponding machine has had the following run `[INIT, EVENT1, EVENT3, EVENT5, | ||
EVENT4]`: | ||
- the configurations for the `OUTER` control state will have been `[OUTER.A, INNER, INNER.S, INNER.T]` | ||
- the shallow history state for the `OUTER` control state will correspond to the `INNER` control | ||
state (the last direct substate of `OUTER`), leading to an automatic transition to INNER_S | ||
- the deep history state for the `OUTER` control state will correspond to the `INNER.T` control | ||
state (the last substate of `OUTER` before exiting it) | ||
In short the history state allows to short-circuit the default entry behaviour for a compound | ||
state, which is to follow the transition triggered by the INIT event. When transitioning to the | ||
history state, transition is towards the last seen state for the entered compound state. | ||
### Contracts | ||
#### Format | ||
- state names (from `fsmDef.states`) must be unique and be JavaScript strings | ||
- event names (from `fsmDef.events`) must be unique and be JavaScript strings | ||
- reserved states (like `INIT_STATE`) cannot be used when defining transitions | ||
- at least one control state must be declared in `fsmDef.states` | ||
- all transitions must be valid : | ||
- the transition syntax must be followed (cf. types) | ||
- all states referenced in the `transitions` data structure must be defined in the `states` data | ||
structure | ||
- all transitions must define an action (even if that action does not modify the extended state | ||
or returns `NO_OUTPUT`) | ||
- all action factories must fill in the `updates` and `outputs` property (no syntax sugar) (**NOT | ||
ENFORCED**) | ||
- NO_OUTPUT must be used to indicate the absence of outputs | ||
- all transitions for a given origin control state and triggering event must be defined in one | ||
row of `fsmDef.transitions` | ||
- `fsmDef.settings` must include a `updateState` function covering the state machine's extended | ||
state update concern. | ||
#### Initial event and initial state | ||
By initial transition, we mean the transition with origin the machine's default initial state. | ||
- An initial transition must be configured : | ||
- by way of a starting control state defined at configuration time | ||
- by way of a initial transition at configuration time | ||
- ~~the init event has the initial extended state as event data~~ | ||
- ~~The machine cannot stay blocked in the initial control state. This means that at least one | ||
transition must be configured and be executed between the initial control state and another state | ||
. This is turn means :~~ | ||
- ~~at least one non-reserved control state must be configured~~ | ||
- ~~at least one transition out of the initial control state must be configured~~ | ||
- ~~of all guards for such transitions, if any, at least one must be fulfilled to enable a | ||
transition away from the initial control state~~ | ||
- there is exactly one initial transition, whose only effect is to determine the starting | ||
control state for the machine | ||
- the action on any such transitions is the *identity* action | ||
- the control state resulting from the initial transition may be guarded by configuring | ||
`guards` for the initial transition | ||
- there are no incoming transitions to the reserved initial state | ||
Additionally the following applies : | ||
- the initial event can only be sent internally (external initial events will be ignored, and the | ||
machine will return `NO_OUTPUT`) | ||
- the state machine starts in the reserved initial state | ||
#### Coherence | ||
- the initial control state (`fsmDef.initialControlState`) must be a state declared in `fsmDef. | ||
states` | ||
- transitions featuring the initial event (`INIT_EVENT`) are only allowed for transitions involving | ||
compound states | ||
- e.g. A -INIT_EVENT-> B iff A is a compound state or A is the initial state | ||
- all states declared in `fsmDef.states` must be used as target or origin of transitions in | ||
`fsmDef.transitions` | ||
- all events declared in `fsmDef.events` must be used as triggering events of transitions in | ||
`fsmDef.transitions` | ||
- history pseudo states must be target states and refer to a given declared compound state | ||
- there cannot be two transitions with the same `(from, event, predicate)` - sameness defined for | ||
predicate by referential equality (**NOT ENFORCED**) | ||
#### Semantical contracts | ||
- The machine behaviour is as explicit as possible | ||
- if a transition is taken, and has guards configured, one of those guards must be fulfilled, i | ||
.e. guards must cover the entire state space when they exist | ||
- A transition evaluation must end | ||
- eventless transitions must progress the state machine | ||
- at least one guard must be fulfilled, otherwise we would remain forever in the same state | ||
- eventless self-transitions are forbidden (while theoretically possible, the feature is of | ||
little practical value, though being a possible source of ambiguity or infinite loops) | ||
- ~~eventless self-transitions must modify the extended state~~ | ||
- ~~lest we loop forever (a real blocking infinite loop)~~ | ||
- ~~note that there is not really a strong rationale for eventless self-transition, I recommend | ||
just staying away from it~~ | ||
- the machine is deterministic and unambiguous | ||
- to a (from, event) couple, there can only correspond one row in the `transitions` array of the | ||
state machine (but there can be several guards in that row) | ||
- (particular case) eventless transitions must not be contradicted by event-ful transitions | ||
- e.g. if there is an eventless transition `A -eventless-> B`, there cannot be a competing | ||
`A -ev-> X` | ||
- A -ev> B and A < OUTER_A with OUTER_A -ev>C !! : there are two valid transitions triggered by | ||
`ev`. Such transitions would unduely complicate the input testing generation, and decrease | ||
the readability of the machine so we forbid such transitions[^x] | ||
- no transitions from the history state (history state is only a target state) | ||
- A transition evaluation must always end (!), and end in an atomic state | ||
- Every compound state must have eactly one inconditional (unguarded) INIT transition, i.e. a | ||
transition whose triggering event is `INIT_EVENT`. That transition must have a target state | ||
which is a substate of the compound state (no hierarchy crossing), and which is not a history | ||
pseudo state | ||
- Compound states must not have eventless transitions defined on them (would introduce | ||
ambiguity with the INIT transition) | ||
- (the previous conditions ensure that there is always a way down the hierarchy for compound | ||
states, and that way is always taken when entering the compound state, and the descent | ||
process always terminate) | ||
- the machine does not perform any effects | ||
- guards, action factories are pure functions | ||
- as such exceptions while running those functions are fatal, and will not be caught | ||
- `updateState :: ExtendedState -> ExtendedStateUpdates -> ExtendedState` must be a pure function | ||
(this is important in particular for the tracing mechanism which triggers two execution of this | ||
function with the same parameters) | ||
[^x]: There are however semantics which allow such transitions, thus possibilitating event bubbling. | ||
Those contracts ensure a good behaviour of the state machine. and we recommend that they all be | ||
observed. However, some of them are not easily enforcable : | ||
- we can only check at runtime that transition with guards fulfill at least one of those guards. | ||
In these cases, we only issue a warning, as this is not a fatal error. This leaves some | ||
flexibility to have a shorter machine configuration. Note that we recommend explicitness and | ||
disambiguity vs. conciseness. | ||
- purity of functions cannot be checked, even at runtime | ||
Contracts enforcement can be parameterized with `settings.debug.checkContracts`. | ||
# Visualization tools | ||
We have included two helpers for visualization of the state transducer : | ||
We have included two helpers for visualization of the state transducer: | ||
- conversion to plantUML : `toPlantUml :: FSM_Def -> PlantUml`. | ||
- the resulting chain of characters can be pasted in [plantText](`https://www.planttext.com/`) | ||
or [plantUML previewer](http://sujoyu.github.io/plantuml-previewer/) to get an automated graph | ||
representation. Both will produce the exact same visual representation. | ||
- conversion to [online visualizer](https://github.com/brucou/state-transducer-visualizer) | ||
format (dagre layout engine) : for instructions, cf. github directory : `toDagreVisualizerFormat | ||
:: FSM_Def -> JSON` | ||
- conversion to plantUML: `toPlantUml :: FSM_Def -> PlantUml` | ||
- the resulting chain of characters can be pasted in [plantText](`https://www.planttext.com/`) or [plantUML previewer](http://sujoyu.github.io/plantuml-previewer/) to get an automated graph representation. Both will produce the exact same visual representation | ||
- conversion to [online visualizer](https://github.com/brucou/state-transducer-visualizer) format (dagre layout engine): for instructions, cf. github directory: `toDagreVisualizerFormat :: FSM_Def -> JSON` | ||
 | ||
Automated visualization works well with simple graphs, but seems to encounter trouble to generate | ||
optimally satisfying complex graphs. The Dagre layout seems to be a least worse option. I | ||
believe the best option for visualization is to use professional specialized tooling such as | ||
`yed`. In a future version, we will provide a conversion to `yed` graph format to facilitate | ||
such workflow. The [`yed`](https://www.yworks.com/products/yed) orthogonal and flowchart layout | ||
seem to give pretty good results. | ||
Automated visualization works well with simple graphs, but seems to encounter trouble to generate optimally satisfying complex graphs. The Dagre layout seems to be a least worse option. I believe the best option for visualization is to use professional specialized tooling such as `yed`. In a future version, we will provide a conversion to `yed` graph format to facilitate such workflow. The [`yed`](https://www.yworks.com/products/yed) orthogonal and flowchart layout seem to give pretty good results. | ||
# Credits | ||
- Credit to [Pankaj Parashar](https://css-tricks.com/password-strength-meter/) for the password | ||
selector | ||
- Credit to [Pankaj Parashar](https://css-tricks.com/password-strength-meter/) for the password selector | ||
- Credit to [Sari Marton](https://github.com/sarimarton/) for the [original version](https://github.com/sarimarton/tmdb-ui-cyclejs) of the movie search app | ||
@@ -664,9 +188,4 @@ | ||
- [ ] add and document exit actions | ||
- [ ] turn the test generation into an iterator(ES6 generator) : this allows it to be composed with | ||
transducers and manipulate the test cases one by one as soon as they are produced. Will be useful | ||
for both example-based and property-based testing. When the generators runs through thousands of | ||
test cases, we often have to wait a long time before seeing any result, which is pretty | ||
damageable when a failure is located toward the ends of the generated input sequences. | ||
- [ ] add other searches that DFS, BFS (add probability to transitions, exclude some transitions, | ||
etc.). HINT : `store.pickOne` can be used to select the next transition | ||
- [ ] turn the test generation into an iterator(ES6 generator): this allows it to be composed with transducers and manipulate the test cases one by one as soon as they are produced. Will be useful for both example-based and property-based testing. When the generators runs through thousands of test cases, we often have to wait a long time before seeing any result, which is pretty damageable when a failure is located toward the ends of the generated input sequences. | ||
- [ ] add other searches that DFS, BFS (add probability to transitions, exclude some transitions, etc.). HINT: `store.pickOne` can be used to select the next transition | ||
- pick a random transition | ||
@@ -676,40 +195,22 @@ - pick next transition according to ranking (probability-based, prefix-based or else) | ||
# Who else uses state machines | ||
The use of state machines is not unusual for safety-critical software for embedded systems. | ||
Nearly all safety-critical code on the Airbus A380 is implemented with a [suite of tools](https://www.ansys.com/products/embedded-software/ansys-scade-suite/scade-suite-capabilities#cap1) which | ||
produces state machines both as [specification](https://www.youtube.com/watch?list=PL0lZXwHtV6Ok5s-iSkBjHirM1fu53_Phv&v=EHP_spl5xU0) and [implementation](https://www.youtube.com/watch?v=523bJ1vZZmw&index=5&list=PL0lZXwHtV6Ok5s-iSkBjHirM1fu53_Phv) | ||
target. The driver here is two-fold. On the one hand is productivity : writing highly reliable code | ||
by hand can be done but it is painstakingly slow, while state machines allow to **generate the code** | ||
automatically. On the other hand is reliability. Quoting Gerard Berry, founder of Esterel | ||
technologies, [<< low-level programming techniques will not remain acceptable for large | ||
safety-critical programs, since they make behavior understanding and analysis almost | ||
impracticable >>](https://ptolemy.berkeley.edu/projects/chess/design/2010/discussions/Pdf/synclang.pdf), in a harsh regulatory context | ||
which may require that every single system requirement | ||
be traced to the code that implements it (!). Requirements modeled by state-machines are amenable | ||
to formal verification and validation. | ||
The use of state machines is not unusual for safety-critical software for embedded systems. Nearly all safety-critical code on the Airbus A380 is implemented with a [suite of tools](https://www.ansys.com/products/embedded-software/ansys-scade-suite/scade-suite-capabilities#cap1) which produces state machines both as [specification](https://www.youtube.com/watch?list=PL0lZXwHtV6Ok5s-iSkBjHirM1fu53_Phv&v=EHP_spl5xU0) and [implementation](https://www.youtube.com/watch?v=523bJ1vZZmw&index=5&list=PL0lZXwHtV6Ok5s-iSkBjHirM1fu53_Phv) target. The driver here is two-fold. On the one hand is productivity: writing highly reliable code by hand can be done but it is painstakingly slow, while state machines allow to **generate the code** automatically. On the other hand is reliability. Quoting Gerard Berry, founder of Esterel technologies, [<< low-level programming techniques will not remain acceptable for large safety-critical programs, since they make behavior understanding and analysis almost impracticable >>](https://ptolemy.berkeley.edu/projects/chess/design/2010/discussions/Pdf/synclang.pdf), in a harsh regulatory context which may require that every single system requirement be traced to the code that implements it (!). Requirements modeled by state-machines are amenable to formal verification and validation. | ||
State machines have also been used extensively in [games of reasonable complexity](http://howtomakeanrpg.com/a/state-machines.html), and [tutorials](https://www.gamedev.net/articles/programming/general-and-gameplay-programming/state-machines-in-games-r2982/) abound | ||
on the subject. Fu and Houlette, in | ||
[AI Game Programming Wisdom 2](https://www.researchgate.net/publication/284383920_The_Ultimate_Guide_to_FSMs_in_Games) | ||
summarized the rationale : "Behavior modeling techniques based on state-machines are very | ||
popular in the gaming industry because they are easy to implement, computationally efficient, | ||
an intuitive representation of behavior, accessible to subject matter experts in addition to programmers, relatively easy to maintain, and can be developed in a number of commercial integrated development environments". | ||
State machines have also been used extensively in [games of reasonable complexity](http://howtomakeanrpg.com/a/state-machines.html), and [tutorials](https://www.gamedev.net/articles/programming/general-and-gameplay-programming/state-machines-in-games-r2982/) abound on the subject. Fu and Houlette, in [AI Game Programming Wisdom 2](https://www.researchgate.net/publication/284383920_The_Ultimate_Guide_to_FSMs_in_Games) summarized the rationale: "Behavior modeling techniques based on state-machines are very popular in the gaming industry because they are easy to implement, computationally efficient, an intuitive representation of behavior, accessible to subject matter experts in addition to programmers, relatively easy to maintain, and can be developed in a number of commercial integrated development environments". | ||
More prosaically, did you know that ES6 generators compile down to ES5 state machines where no | ||
native option is available? Facebook's [`regenerator`](https://github.com/facebook/regenerator) | ||
is a good example of such. | ||
More prosaically, did you know that ES6 generators compile down to ES5 state machines where no native option is available? Facebook's [`regenerator`](https://github.com/facebook/regenerator) is a good example of such. | ||
So state machines are nothing like a new, experimental tool, but rather one with a fairly extended | ||
and proven track in both industrial and consumer applications. | ||
So state machines are nothing like a new, experimental tool, but rather one with a fairly extended and proven track in both industrial and consumer applications. | ||
# About the name | ||
We call this library "Kingly" to express it allows developers to rule their UI -- like a king. | ||
Developers define rules in the machine, in the form of control states and guards, those rules | ||
define what is possible and what is not, and what should happen in response to events. | ||
And also, the other names I wanted were already taken :-). | ||
# Acknowledgments | ||
This library is old and went through several redesigns and a large refactoring as I grew as a | ||
programmer and accumulated experience using it. I actually started after toiling with the cyclejs | ||
framework and complex state orchestration. I was not an expert in functional programming, and | ||
the original design was quite tangled (streams, asynchrony, etc.) and hardly reusable out of | ||
cyclejs. The current design resulting from my increased understanding and awareness of | ||
architecture, and functional design. | ||
This library is old and went through several redesigns and a large refactoring as I grew as a programmer and accumulated experience using it. I actually started after toiling with the cyclejs framework and complex state orchestration. I was not an expert in functional programming, and the original design was quite tangled (streams, asynchrony, etc.) and hardly reusable out of cyclejs. The current design resulting from my increased understanding and awareness of architecture, and functional design. | ||
The key influences I want to quote thus are: | ||
- cyclejs, but of course from which I started to understand the benefits of the separation of | ||
effects from logic | ||
- cyclejs, but of course, from which I started to understand the benefits of the separation of effects from logic | ||
- elm - who led me to the equational thinking behind Kingly | ||
@@ -720,65 +221,31 @@ - erlang - for forcing me to learn much more about concurrency. | ||
## So what is an Extended Hierarchical State Transducer ? | ||
Not like it matters so much but anyways. Feel free to skip that section if you have little | ||
interest in computer science. | ||
Not like it matters so much but anyways. Feel free to skip that section if you have little interest in computer science. | ||
Alright, let's build the concept progressively. | ||
An [automaton](https://en.wikipedia.org/wiki/Automata_theory) is a construct made of states | ||
designed to determine if a sequence of inputs should be accepted or rejected. It looks a lot like a | ||
basic board game where each space on the board represents a state. Each state has information about what to do when an input is received by the machine (again, rather like what to do when you land on the Jail spot in a popular board game). As the machine receives a new input, it looks at the state and picks a new spot based on the information on what to do when it receives that input at that state. When there are no more inputs, the automaton stops and the space it is on when it completes determines whether the automaton accepts or rejects that particular set of inputs. | ||
An [automaton](https://en.wikipedia.org/wiki/Automata_theory) is a construct made of states designed to determine if a sequence of inputs should be accepted or rejected. It looks a lot like a basic board game where each space on the board represents a state. Each state has information about what to do when an input is received by the machine (again, rather like what to do when you land on the Jail spot in a popular board game). As the machine receives a new input, it looks at the state and picks a new spot based on the information on what to do when it receives that input at that state. When there are no more inputs, the automaton stops and the space it is on when it completes determines whether the automaton accepts or rejects that particular set of inputs. | ||
State machines and automata are essentially interchangeable terms. Automata is the favored term | ||
when connoting automata theory, while state machines is more often used in the context of the | ||
actual or practical usage of automata. | ||
State machines and automata are essentially interchangeable terms. Automata is the favored term when connoting automata theory, while state machines is more often used in the context of the actual or practical usage of automata. | ||
An extended state machine is a state machine endowed with a set of variables, predicates (guards) | ||
and instructions governing the update of the mentioned set of variables. To any extended state | ||
machines it corresponds a standard state machine (albeit often one with a far greater number of | ||
states) with the same semantics. | ||
An extended state machine is a state machine endowed with a set of variables, predicates (guards) and instructions governing the update of the mentioned set of variables. To any extended state machines it corresponds a standard state machine (albeit often one with a far greater number of states) with the same semantics. | ||
A hierarchical state machine is a state machine whose states can be themselves state machines. | ||
Thus instead of having a set of states as in standard state machines, we have a hierarchy (tree) of | ||
states describing the system under study. | ||
A hierarchical state machine is a state machine whose states can be themselves state machines. Thus instead of having a set of states as in standard state machines, we have a hierarchy (tree) of states describing the system under study. | ||
A [state transducer](https://en.wikipedia.org/wiki/Finite-state_transducer) is a state | ||
machine, which in addition to accepting inputs, and modifying its state accordingly, may also | ||
generate outputs. | ||
A [state transducer](https://en.wikipedia.org/wiki/Finite-state_transducer) is a state machine, which in addition to accepting inputs, and modifying its state accordingly, may also generate outputs. | ||
We propose here a library dealing with extended hierarchical state transducers, i.e. a state machine | ||
whose states can be other state machines (hierarchical part), which (may) associate an output to an | ||
input (transducer part), and whose input/output relation follows a logic guided by | ||
predefined control states (state machine part), and an encapsulated memory which can be | ||
modified through actions guarded by predicates (extended part). | ||
We propose here a library dealing with extended hierarchical state transducers, i.e. a state machine whose states can be other state machines (hierarchical part), which (may) associate an output to an input (transducer part), and whose input/output relation follows a logic guided by predefined control states (state machine part), and an encapsulated memory which can be modified through actions guarded by predicates (extended part). | ||
Note that if we add concurrency and messaging to extended hierarchical state transducers, we get | ||
a statechart. We made the design decision to remain at the present level, and not to incorporate | ||
any concurrency mechanism.[^2] | ||
Note that if we add concurrency and messaging to extended hierarchical state transducers, we get a statechart. We made the design decision to remain at the present level, and not to incorporate any concurrency mechanism.[^2] | ||
[^2]: Our rationale is as follows : | ||
- statecharts include activities and actions which may produce effects, and concurrency. We are | ||
seeking an purely computational approach (i.e effect-less) to facilitate **composition, reuse and | ||
testing**. | ||
- In the absence of concurrency (i.e. absence of parallel regions), a statechart can be turned | ||
into a hierarchical state transducer. That is often enough! | ||
- there is no difference in terms of | ||
expressive power between statecharts and hierarchical transducers[^4], just as there is no | ||
difference in expressive power between extended state machines and regular state machines. The | ||
difference lies in naturalness and convenience : a 5-state extended state machine is | ||
easier to read and maintain than the equivalent 50-state regular state machine. | ||
- we argue that convenience here is on the side of being able to freely plug in any [concurrent | ||
or communication model](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.92.6145&rep=rep1&type=pdf) fitting the problem space. In highly concurrent systems, programmers may have it hard to elaborate a mental model of the statecharts solely from the visualization of | ||
concurrent statecharts. | ||
- some [statecharts practitioners](http://sismic.readthedocs.io/en/master/communication.html#) | ||
favor having separate state charts communicating[^5] in an ad-hoc way rather than an integrated | ||
statechart model where concurrent state charts are gathered in nested states of a single | ||
statechart. We agree. | ||
[^2]: Our rationale is as follows: | ||
- statecharts include activities and actions which may produce effects, and concurrency. We are seeking an purely computational approach (i.e effect-less) to facilitate **composition, reuse and testing**. | ||
- In the absence of concurrency (i.e. absence of parallel regions), a statechart can be turned into a hierarchical state transducer. That is often enough! | ||
- there is no difference in terms of expressive power between statecharts and hierarchical transducers[^4], just as there is no difference in expressive power between extended state machines and regular state machines. The difference lies in naturalness and convenience: a 5-state extended state machine is easier to read and maintain than the equivalent 50-state regular state machine. | ||
- we argue that convenience here is on the side of being able to freely plug in any [concurrent or communication model](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.92.6145&rep=rep1&type=pdf) fitting the problem space. In highly concurrent systems, programmers may have it hard to elaborate a mental model of the statecharts solely from the visualization of concurrent statecharts. | ||
- some [statecharts practitioners](http://sismic.readthedocs.io/en/master/communication.html#) favor having separate state charts communicating[^5] in an ad-hoc way rather than an integrated statechart model where concurrent state charts are gathered in nested states of a single statechart. We agree. | ||
[^3]: As a matter of fact, more than 20 different semantics have been proposed to define | ||
precisely the concurrency model for statecharts, e.g Rhapsody, Statemate, VisualMate, StateFlow, | ||
UML, etc. do not share a single concurrency model. | ||
[^4]: David Harel, Statecharts.History.CACM : Speaking in the strict mathematical sense of power | ||
of expression, hierarchy and orthogonality are but helpful abbreviations and can be eliminated | ||
[^5]: David Harel, Statecharts.History.CACM : <<I definitely do not recommend having a single | ||
statechart for an entire system. (...) concurrency occurs on a higher level.)>> | ||
[^3]: As a matter of fact, more than 20 different semantics have been proposed to define precisely the concurrency model for statecharts, e.g Rhapsody, Statemate, VisualMate, StateFlow, UML, etc. do not share a single concurrency model. | ||
[^4]: David Harel, Statecharts.History.CACM: Speaking in the strict mathematical sense of power of expression, hierarchy and orthogonality are but helpful abbreviations and can be eliminated | ||
[^5]: David Harel, Statecharts.History.CACM: <<I definitely do not recommend having a single statechart for an entire system. (...) concurrency occurs on a higher level.)>> | ||
@@ -733,1 +733,5 @@ // Ramda fns | ||
} | ||
export function isError(obj){ | ||
return obj instanceof Error | ||
} |
@@ -9,3 +9,3 @@ import { | ||
isHistoryControlState, keys, mapOverTransitionsActions, noop, notifyAndRethrow, throwIfInvalidActionResult, | ||
throwIfInvalidEntryActionResult, throwIfInvalidGuardResult, tryCatchMachineFn, updateHistory, wrap | ||
throwIfInvalidEntryActionResult, throwIfInvalidGuardResult, tryCatchMachineFn, updateHistory, wrap, isError | ||
} from "./helpers"; | ||
@@ -303,3 +303,5 @@ import { fsmContractChecker } from "./contracts" | ||
const next_state = enter_next_state(to, updates, hash_states); | ||
console.info("ENTERING NEXT STATE : ", next_state); | ||
console.info("ENTERING NEXT STATE: ", next_state); | ||
if (isError(extendedStateOrError)) console.error("with error: ", extendedStateOrError); | ||
if (!isError(extendedStateOrError)) console.info("with extended state: ", extendedState); | ||
@@ -306,0 +308,0 @@ // allows for chaining and stop chaining guard |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
54077
5005138
242