From 0aca2ddc5982529ce35d0dcc55197dc0af9693f9 Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Fri, 17 May 2019 17:31:21 +0100 Subject: [PATCH 1/8] Transition cancel on state on exception thrown in observers If the state was already mutated, the state goes back. If it is a promise, then it catches the promise exception, reverts the state, and throws the error again. --- src/jsm.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/jsm.js b/src/jsm.js index f83c8de..060ea5c 100644 --- a/src/jsm.js +++ b/src/jsm.js @@ -108,7 +108,13 @@ mixin(JSM.prototype, { beginTransit: function() { this.pending = true; }, endTransit: function(result) { this.pending = false; return result; }, - failTransit: function(result) { this.pending = false; throw result; }, + failTransit: function(result, from, to) { + if (this.state === to) { + this.state = from; + } + this.pending = false; + throw result; + }, doTransit: function(lifecycle) { this.state = lifecycle.to; }, observe: function(args) { @@ -132,6 +138,14 @@ mixin(JSM.prototype, { return [ event, result, true ] }, + callObserver: function(event, observer, args, from, to) { + try { + return observer[event].apply(observer, args); + } catch (error) { + this.failTransit.call(this, error, from, to); + } + }, + observeEvents: function(events, args, previousEvent, previousResult) { if (events.length === 0) { return this.endTransit(previousResult === undefined ? true : previousResult); @@ -150,11 +164,16 @@ mixin(JSM.prototype, { return this.observeEvents(events, args, event, previousResult); } else { - var observer = observers.shift(), - result = observer[event].apply(observer, args); + var from = args[0].from; + var to = args[0].to; + var observer = observers.shift(); + var result = this.callObserver.call(this, event, observer, args, from, to); if (result && typeof result.then === 'function') { + var jsm = this return result.then(this.observeEvents.bind(this, events, args, event)) - .catch(this.failTransit.bind(this)) + .catch(function (error) { + jsm.failTransit.call(jsm, error, from, to); + }); } else if (result === false) { return this.endTransit(false); From 13fdfcdced4cb9f5604c85bcbedcf1a617b2e578 Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Fri, 17 May 2019 17:36:24 +0100 Subject: [PATCH 2/8] Testing that the state transition is cancelled when an error is thrown inside an observer --- test/errors.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/errors.js b/test/errors.js index fb852ca..a639dc8 100644 --- a/test/errors.js +++ b/test/errors.js @@ -158,3 +158,58 @@ test('pending transition handler can be customized', t => { }) //------------------------------------------------------------------------------------------------- + +test('exception thrown in handler cancels state transition', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step1', from: 'none', to: 'A' }, + { name: 'step2', from: 'A', to: 'B' } + ], + methods: { + onA:function () { + throw new Error('Error thrown in observer') + } + } + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step1'), true) + t.is(fsm.can('step2'), false) + + const error = t.throws(() => { + fsm.step1(); + }) + + t.is(error.message, 'Error thrown in observer') + t.is(fsm.state, 'none') + +}) + +test('exception with promise thrown in handler cancels state transition', async t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step1', from: 'none', to: 'A' }, + { name: 'step2', from: 'A', to: 'B' } + ], + methods: { + onA: async function () { + return Promise.reject(new Error('Error thrown in observer')) + } + } + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step1'), true) + t.is(fsm.can('step2'), false) + + try{ + await fsm.step1() + t.is('Should fail', 'fail') + }catch(error){ + t.is(error.message, 'Error thrown in observer') + } + + t.is(fsm.state, 'none') +}) \ No newline at end of file From e564c3a27d711e955cdb5c6c00b1293567f04970 Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Fri, 17 May 2019 17:37:09 +0100 Subject: [PATCH 3/8] Build output files --- dist/state-machine.js | 27 +++++++++++++++++++++++---- dist/state-machine.min.js | 2 +- lib/state-machine.js | 27 +++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/dist/state-machine.js b/dist/state-machine.js index b8b9e37..4d09915 100644 --- a/dist/state-machine.js +++ b/dist/state-machine.js @@ -465,7 +465,13 @@ mixin(JSM.prototype, { beginTransit: function() { this.pending = true; }, endTransit: function(result) { this.pending = false; return result; }, - failTransit: function(result) { this.pending = false; throw result; }, + failTransit: function(result, from, to) { + if (this.state === to) { + this.state = from; + } + this.pending = false; + throw result; + }, doTransit: function(lifecycle) { this.state = lifecycle.to; }, observe: function(args) { @@ -489,6 +495,14 @@ mixin(JSM.prototype, { return [ event, result, true ] }, + callObserver: function(event, observer, args, from, to) { + try { + return observer[event].apply(observer, args); + } catch (error) { + this.failTransit.call(this, error, from, to); + } + }, + observeEvents: function(events, args, previousEvent, previousResult) { if (events.length === 0) { return this.endTransit(previousResult === undefined ? true : previousResult); @@ -507,11 +521,16 @@ mixin(JSM.prototype, { return this.observeEvents(events, args, event, previousResult); } else { - var observer = observers.shift(), - result = observer[event].apply(observer, args); + var from = args[0].from; + var to = args[0].to; + var observer = observers.shift(); + var result = this.callObserver.call(this, event, observer, args, from, to); if (result && typeof result.then === 'function') { + var jsm = this return result.then(this.observeEvents.bind(this, events, args, event)) - .catch(this.failTransit.bind(this)) + .catch(function (error) { + jsm.failTransit.call(jsm, error, from, to); + }); } else if (result === false) { return this.endTransit(false); diff --git a/dist/state-machine.min.js b/dist/state-machine.min.js index b9439bc..39ca513 100644 --- a/dist/state-machine.min.js +++ b/dist/state-machine.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachine",[],n):"object"==typeof exports?exports.StateMachine=n():t.StateMachine=n()}(this,function(){return function(t){function n(e){if(i[e])return i[e].exports;var s=i[e]={i:e,l:!1,exports:{}};return t[e].call(s.exports,s,s.exports,n),s.l=!0,s.exports}var i={};return n.m=t,n.c=i,n.i=function(t){return t},n.d=function(t,i,e){n.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=5)}([function(t,n,i){"use strict";t.exports=function(t,n){var i,e,s;for(i=1;i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},failTransit:function(t){throw this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},failTransit:function(t,n,i){throw this.state===i&&(this.state=n),this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i Date: Fri, 17 May 2019 17:40:29 +0100 Subject: [PATCH 4/8] Doc update transition cancel --- docs/lifecycle-events.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/lifecycle-events.md b/docs/lifecycle-events.md index bb3381b..8add4a3 100644 --- a/docs/lifecycle-events.md +++ b/docs/lifecycle-events.md @@ -146,3 +146,6 @@ lifecycle events: All subsequent lifecycle events will be cancelled and the state will remain unchanged. +To cancel a transition, you can also throw an exception `throw new Error('')` or await for a promise that is rejected inside any lifecycle handler. +The transition state will be cancelled and the following lifecycle events will not be triggered + From 640479942ececa7bbd605430cfa9102537b4962a Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Sat, 18 May 2019 11:40:14 +0100 Subject: [PATCH 5/8] Notify plugins of transition cancelation --- src/jsm.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/jsm.js b/src/jsm.js index 060ea5c..0985aa1 100644 --- a/src/jsm.js +++ b/src/jsm.js @@ -108,9 +108,13 @@ mixin(JSM.prototype, { beginTransit: function() { this.pending = true; }, endTransit: function(result) { this.pending = false; return result; }, - failTransit: function(result, from, to) { + failTransit: function(result, args) { + var from = args[0].from; + var to = args[0].to; + if (this.state === to) { - this.state = from; + this.state = from; + plugin.hook(this, 'cancel', args); } this.pending = false; throw result; @@ -138,11 +142,11 @@ mixin(JSM.prototype, { return [ event, result, true ] }, - callObserver: function(event, observer, args, from, to) { + callObserver: function(event, observer, args) { try { return observer[event].apply(observer, args); } catch (error) { - this.failTransit.call(this, error, from, to); + this.failTransit.call(this, error, args); } }, @@ -164,15 +168,13 @@ mixin(JSM.prototype, { return this.observeEvents(events, args, event, previousResult); } else { - var from = args[0].from; - var to = args[0].to; var observer = observers.shift(); - var result = this.callObserver.call(this, event, observer, args, from, to); + var result = this.callObserver.call(this, event, observer, args); if (result && typeof result.then === 'function') { var jsm = this return result.then(this.observeEvents.bind(this, events, args, event)) .catch(function (error) { - jsm.failTransit.call(jsm, error, from, to); + jsm.failTransit.call(jsm, error, args); }); } else if (result === false) { From 0d181d17ac42d9a662eef369e32db570cff029ba Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Sat, 18 May 2019 11:42:45 +0100 Subject: [PATCH 6/8] History plugin - cancel pop one state --- src/plugin/history.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugin/history.js b/src/plugin/history.js index 46799c0..8b7e67d 100644 --- a/src/plugin/history.js +++ b/src/plugin/history.js @@ -39,6 +39,10 @@ module.exports = function(options) { options = options || {}; } }, + cancel: function(instance, lifecycle) { + instance[past].pop() + }, + methods: {}, properties: {} From f992077de97c79387efd23cbb4475a379b7b04f5 Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Sat, 18 May 2019 11:43:09 +0100 Subject: [PATCH 7/8] History plugin test transition cancel --- test/plugin/history.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/plugin/history.js b/test/plugin/history.js index 00f73ef..bfbdd5d 100644 --- a/test/plugin/history.js +++ b/test/plugin/history.js @@ -491,3 +491,35 @@ test('history can be used with a state machine factory applied to existing class }) //------------------------------------------------------------------------------------------------- + +test('exception thrown in handler cancels state transition', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step1', from: 'none', to: 'A' }, + { name: 'step2', from: 'A', to: 'B' } + ], + plugins:[ + StateMachineHistory + ], + methods: { + onB:function () { + throw new Error('Error thrown in observer') + } + } + }); + + t.is(fsm.state, 'none'); t.deepEqual(fsm.history, [ ]); + t.is(fsm.can('step1'), true) + t.is(fsm.can('step2'), false) + + fsm.step1(); t.is(fsm.state, 'A'); t.deepEqual(fsm.history, [ 'A' ]); + + const error = t.throws(() => { + fsm.step2(); + }) + + t.is(fsm.state, 'A'); t.deepEqual(fsm.history, [ 'A' ]); + + t.is(error.message, 'Error thrown in observer') +}) \ No newline at end of file From a72fa564b268c9092584d287c0695148e8c6495f Mon Sep 17 00:00:00 2001 From: Nelson Francisco Date: Sat, 18 May 2019 12:07:47 +0100 Subject: [PATCH 8/8] Build output files --- dist/state-machine-history.js | 4 ++++ dist/state-machine-history.min.js | 2 +- dist/state-machine.js | 18 ++++++++++-------- dist/state-machine.min.js | 2 +- lib/history.js | 4 ++++ lib/state-machine.js | 18 ++++++++++-------- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/dist/state-machine-history.js b/dist/state-machine-history.js index 19f7c4f..cd50b65 100644 --- a/dist/state-machine-history.js +++ b/dist/state-machine-history.js @@ -162,6 +162,10 @@ module.exports = function(options) { options = options || {}; } }, + cancel: function(instance, lifecycle) { + instance[past].pop() + }, + methods: {}, properties: {} diff --git a/dist/state-machine-history.min.js b/dist/state-machine-history.min.js index 9c7fb3f..b5e601a 100644 --- a/dist/state-machine-history.min.js +++ b/dist/state-machine-history.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("StateMachineHistory",[],e):"object"==typeof exports?exports.StateMachineHistory=e():t.StateMachineHistory=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e,n){"use strict";function r(t){if(0===t.length)return t;var e,n,r=t.split(/[_-]/);if(1===r.length&&r[0][0].toLowerCase()===r[0][0])return t;for(n=r[0].toLowerCase(),e=1;ec&&t[e].shift(),r.transition!==i&&r.transition!==s&&(t[n].length=0))},methods:{},properties:{}};return f.methods[o]=function(){this[e].length=0,this[n].length=0},f.properties[u]={get:function(){return this[e].length>1}},f.properties[p]={get:function(){return this[n].length>0}},f.methods[i]=function(){if(!this[u])throw Error("no history");var t=this[e].pop(),r=this[e].pop();this[n].push(t),this._fsm.transit(i,t,r,[])},f.methods[s]=function(){if(!this[p])throw Error("no history");var t=this.state,e=this[n].pop();this._fsm.transit(s,t,e,[])},f}}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("StateMachineHistory",[],e):"object"==typeof exports?exports.StateMachineHistory=e():t.StateMachineHistory=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e,n){"use strict";function r(t){if(0===t.length)return t;var e,n,r=t.split(/[_-]/);if(1===r.length&&r[0][0].toLowerCase()===r[0][0])return t;for(n=r[0].toLowerCase(),e=1;ec&&t[e].shift(),r.transition!==i&&r.transition!==s&&(t[n].length=0))},cancel:function(t,n){t[e].pop()},methods:{},properties:{}};return f.methods[o]=function(){this[e].length=0,this[n].length=0},f.properties[u]={get:function(){return this[e].length>1}},f.properties[p]={get:function(){return this[n].length>0}},f.methods[i]=function(){if(!this[u])throw Error("no history");var t=this[e].pop(),r=this[e].pop();this[n].push(t),this._fsm.transit(i,t,r,[])},f.methods[s]=function(){if(!this[p])throw Error("no history");var t=this.state,e=this[n].pop();this._fsm.transit(s,t,e,[])},f}}])}); \ No newline at end of file diff --git a/dist/state-machine.js b/dist/state-machine.js index 4d09915..3177183 100644 --- a/dist/state-machine.js +++ b/dist/state-machine.js @@ -465,9 +465,13 @@ mixin(JSM.prototype, { beginTransit: function() { this.pending = true; }, endTransit: function(result) { this.pending = false; return result; }, - failTransit: function(result, from, to) { + failTransit: function(result, args) { + var from = args[0].from; + var to = args[0].to; + if (this.state === to) { - this.state = from; + this.state = from; + plugin.hook(this, 'cancel', args); } this.pending = false; throw result; @@ -495,11 +499,11 @@ mixin(JSM.prototype, { return [ event, result, true ] }, - callObserver: function(event, observer, args, from, to) { + callObserver: function(event, observer, args) { try { return observer[event].apply(observer, args); } catch (error) { - this.failTransit.call(this, error, from, to); + this.failTransit.call(this, error, args); } }, @@ -521,15 +525,13 @@ mixin(JSM.prototype, { return this.observeEvents(events, args, event, previousResult); } else { - var from = args[0].from; - var to = args[0].to; var observer = observers.shift(); - var result = this.callObserver.call(this, event, observer, args, from, to); + var result = this.callObserver.call(this, event, observer, args); if (result && typeof result.then === 'function') { var jsm = this return result.then(this.observeEvents.bind(this, events, args, event)) .catch(function (error) { - jsm.failTransit.call(jsm, error, from, to); + jsm.failTransit.call(jsm, error, args); }); } else if (result === false) { diff --git a/dist/state-machine.min.js b/dist/state-machine.min.js index 39ca513..fc361d5 100644 --- a/dist/state-machine.min.js +++ b/dist/state-machine.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachine",[],n):"object"==typeof exports?exports.StateMachine=n():t.StateMachine=n()}(this,function(){return function(t){function n(e){if(i[e])return i[e].exports;var s=i[e]={i:e,l:!1,exports:{}};return t[e].call(s.exports,s,s.exports,n),s.l=!0,s.exports}var i={};return n.m=t,n.c=i,n.i=function(t){return t},n.d=function(t,i,e){n.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=5)}([function(t,n,i){"use strict";t.exports=function(t,n){var i,e,s;for(i=1;i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},failTransit:function(t,n,i){throw this.state===i&&(this.state=n),this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},failTransit:function(t,n){var i=n[0].from,e=n[0].to;throw this.state===e&&(this.state=i,o.hook(this,"cancel",n)),this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i