function valueEnumerable(value) { return { enumerable: true, value }; } function valueEnumerableWritable(value) { return { enumerable: true, writable: true, value }; } export let d = {}; let truthy = () => true; let empty = () => ({}); let identity = a => a; let callBoth = (par, fn, self, args) => par.apply(self, args) && fn.apply(self, args); let callForward = (par, fn, self, [a, b]) => fn.call(self, par.call(self, a, b), b); let create = (a, b) => Object.freeze(Object.create(a, b)); function stack(fns, def, caller) { return fns.reduce((par, fn) => { return function(...args) { return caller(par, fn, this, args); }; }, def); } function fnType(fn) { return create(this, { fn: valueEnumerable(fn) }); } let reduceType = {}; export let reduce = fnType.bind(reduceType); export let action = fn => reduce((ctx, ev) => !!~fn(ctx, ev) && ctx); let guardType = {}; export let guard = fnType.bind(guardType); function filter(Type, arr) { return arr.filter(value => Type.isPrototypeOf(value)); } function makeTransition(from, to, ...args) { let guards = stack(filter(guardType, args).map(t => t.fn), truthy, callBoth); let reducers = stack(filter(reduceType, args).map(t => t.fn), identity, callForward); return create(this, { from: valueEnumerable(from), to: valueEnumerable(to), guards: valueEnumerable(guards), reducers: valueEnumerable(reducers) }); } let transitionType = {}; let immediateType = {}; export let transition = makeTransition.bind(transitionType); export let immediate = makeTransition.bind(immediateType, null); function enterImmediate(machine, service, event) { return transitionTo(service, machine, event, this.immediates) || machine; } function transitionsToMap(transitions) { let m = new Map(); for(let t of transitions) { if(!m.has(t.from)) m.set(t.from, []); m.get(t.from).push(t); } return m; } let stateType = { enter: identity }; export function state(...args) { let transitions = filter(transitionType, args); let immediates = filter(immediateType, args); let desc = { final: valueEnumerable(args.length === 0), transitions: valueEnumerable(transitionsToMap(transitions)) }; if(immediates.length) { desc.immediates = valueEnumerable(immediates); desc.enter = valueEnumerable(enterImmediate); } return create(stateType, desc); } let invokeFnType = { enter(machine2, service, event) { let rn = this.fn.call(service, service.context, event) if(machine.isPrototypeOf(rn)) return create(invokeMachineType, { machine: valueEnumerable(rn), transitions: valueEnumerable(this.transitions) }).enter(machine2, service, event) rn .then(data => { if (machine2 === service.machine) return service.send({ type: 'done', data }); }) .catch(error => { if (machine2 === service.machine) return service.send({ type: 'error', error }); }); return machine2; } }; let invokeMachineType = { enter(machine, service, event) { service.child = interpret(this.machine, s => { service.onChange(s); if(service.child == s && s.machine.state.value.final) { delete service.child; service.send({ type: 'done', data: s.context }); } }, service.context, event); if(service.child.machine.state.value.final) { let data = service.child.context; delete service.child; return transitionTo(service, machine, { type: 'done', data }, this.transitions.get('done')); } return machine; } }; export function invoke(fn, ...transitions) { let t = valueEnumerable(transitionsToMap(transitions)); return machine.isPrototypeOf(fn) ? create(invokeMachineType, { machine: valueEnumerable(fn), transitions: t }) : create(invokeFnType, { fn: valueEnumerable(fn), transitions: t }); } let machine = { get state() { return { name: this.current, value: this.states[this.current] }; } }; export function createMachine(current, states, contextFn = empty) { if(typeof current !== 'string') { contextFn = states || empty; states = current; current = Object.keys(states)[0]; } if(d._create) d._create(current, states); return create(machine, { context: valueEnumerable(contextFn), current: valueEnumerable(current), states: valueEnumerable(states) }); } function transitionTo(service, machine, fromEvent, candidates) { let { context } = service; for(let { to, guards, reducers } of candidates) { if(guards(context, fromEvent)) { service.context = reducers.call(service, context, fromEvent); let original = machine.original || machine; let newMachine = create(original, { current: valueEnumerable(to), original: { value: original } }); if (d._onEnter) d._onEnter(machine, to, service.context, context, fromEvent); let state = newMachine.state.value; service.machine = newMachine; let ret = state.enter(newMachine, service, fromEvent); service.onChange(service); return ret; } } } function send(service, event) { let eventName = event.type || event; let { machine } = service; let { value: state, name: currentStateName } = machine.state; if(state.transitions.has(eventName)) { return transitionTo(service, machine, event, state.transitions.get(eventName)) || machine; } else { if(d._send) d._send(eventName, currentStateName); } return machine; } let service = { send(event) { send(this, event); } }; export function interpret(machine, onChange, initialContext, event) { let s = Object.create(service, { machine: valueEnumerableWritable(machine), context: valueEnumerableWritable(machine.context(initialContext, event)), onChange: valueEnumerable(onChange) }); s.send = s.send.bind(s); s.machine = s.machine.state.value.enter(s.machine, s, event); return s; }