Releases: LevelbossMike/ember-statecharts
v0.7.0
From this release on ember-statecharts
will use xstate
's interpreter-functionality internally. This brings several advantages over the prior approach of providing a custom interpreter. Most importantly we now support all features that xstate provides 🎉 - This means ember-statecharts
now also supports new features like delays and activities.
!!Warning!!
This is a 💥 breaking release. To upgrade your projects you have to update your statechart configurations:
Parameter order for statechart actions has changed
To align with xstate
we don't change the order of params passed to actions anymore.
This means:
// ....
statechart: statechart({
// ....
}, {
actions: {
doSomething(data, context) {
context.anInternalComponentAction();
}
}
});
needs to be changed to:
// ....
statechart: statechart({
// ....
}, {
actions: {
doSomething(context, eventObject) {
// see the rest of the relase notes for changes regarding the `data`-param
context.anInternalComponentAction();
}
}
});
No this
in actions anymore
statechart actions
won't have their implementing object set as the this
-context anymore.
This means actions like this:
// ....
statechart: statechart({
// ....
}, {
actions: {
doSomething() {
this.anInternalComponentAction();
}
}
});
will need to be changed to this:
// ....
statechart: statechart({
// ....
}, {
actions: {
doSomething(context, /* eventObject */) {
context.anInternalComponentAction();
}
}
});
No magic data
wrapping of data passed to events
To align with xstate
's behavior we won't wrap data passed to events into a magic data
-property anymore - we will use xstate
's eventObject
s instead:
This means:
statechart: statechart({
// ...
}, {
actions: {
sayName(context, data) {
const { name } = data;
// name: 'tomster'
// ...
}
}
}),
will need to be changed to:
statechart: statechart({
// ...
}, {
actions: {
sayName(context, eventObject) {
const { type, name } = eventObject;
// name: 'tomster', type: 'sayHello'
// ...
}
}
}),
Summary
Overall this is a very exciting release that aligns ember-statecharts
with xstate
and makes it future-proof. We are very excited to finally support all of xstate
's features and we feel this release cleans up ember-statecharts
significantly.
We are sorry for the caused churn but we are certain the additional features make the effort of changing your statechart configurations worthwhile.
You can have a look at the changes we had to do to make ember-statecharts
test-suite compatible with the interpreter changes in this release in the PR that added this enhancement.
v0.6.1
This is a maintenance release that updates ember packages to version 3.5.x
Thanks @loganrosen for submitting the update PR #12 🎉
v0.6.0
This updates the project to make use of xstate 4.0.x
. Quite a bit has changed - You can look at the xstate release-notes to see the details what needs to be changed in your statechart-configuration. Overall the new way to declare statecharts cleans everything quite a bit.
!!Warning!!
This is a breaking release. To upgrade your projects you have to update your statechart configurations to the syntax that xstate 4.0.x expects.
Please have a look at the xstate 3.x-4.x migration guide to see what you need to do to migrate your statechart configurations.
Notable changes
String actions that will call functions on the statechart's context object won't work anymore. Anonymous action-functions only declared inline in the statechart config also stop working. Actions need to be declared on the options passed to the statechart config.
Example:
// this won't work anymore
O.extend({
machineIsOn: false,
statechart: statechart({
initial: 'powerOff',
states: {
powerOff: {
onEntry: ['_turnMachineOff'],
on: {
power: {
target: 'powerOn',
actions: ['_turnMachineOn']
}
}
},
powerOn: {
on: {
// "simple" transitions still work as usual
power: 'powerOff'
}
}
}
}),
_turnMachineOn(/* data, context*/) {
this.set('machineIsOn', true);
},
_turnMachineOff() {
this.set('machineIsOn', false);
},
// ...
});
Instead you can declare the referenced actions in the options passed to the statechart configuration - the same as you would do with guards referenced by strings. This is nice because you now have a clear differentiation between actions triggered by the statechart and internal methods of your objects. To make your life easier the this
-context of actions of your statechart configuration is always set to the object implementing the statechart:
O.extend({
machineIsOn: false,
statechart: statechart({
initial: 'powerOff',
states: {
powerOff: {
onEntry: ['turnMachineOff'],
on: {
power: {
target: 'powerOn',
actions: ['turnMachineOn']
}
}
},
powerOn: {
on: {
// "simple" transitions still work as usual
power: 'powerOff'
}
}
}
}, {
actions: {
turnMachineOn(/* data, context*/) {
// `this` is the object that implements the statechart
this.set('machineIsOn', true);
},
turnMachineOff() {
this.set('machineIsOn', false);
}
}
})
})
The statechart-computed will now only work with xstate 4.x compliant configurations.
Other relevant changes:
The statechart will now execute the onEntry
actions of the initialState when being created. Earlier releases of ember-statecharts
did not execute the onEntry
-function of the initial state because the initialState wasn't entered via a transition. This is now fixed but might lead to surprising behavior if you relied on this unintuitive behavior. Example:
Example:
O.extend({
statechart: statechart({
initial: 'willInit',
states: {
willInit: {
onEntry: ['initAsync'],
on: {
resolve: 'success',
reject: 'error'
}
},
// ...
}
}, {
actions: {
initAsync() {
return this.somethingAsync()
.then(() => this.statechart.send('resolve'))
.catch(() => this.statechart.send('reject'));
}
}
})
})
When statechart initialization is async send calls will be enqueued until initialization
Example:
const o = O.extend({
statechart: statechart({
initial: 'willInit',
states: {
willInit: {
onEntry: ['initAsync'],
on: {
click: 'busy',
}
},
busy: {
onEntry: ['beBusy']
}
// ...
}
}, {
actions: {
initAsync() {
return this.somethingAsync();
},
beBusy() {
console.log("I'm busy now");
}
}
})
}).create();
// this will initialize the statechart and execute `initAsync`
const sc = o.get('statechart');
// we don't wait and send an event right away
sc.send('click');
// after `initAsync` has finished
// => "I'm busy"
Although this works modeling this explicitely via a transient state is most likely the better option:
O.extend({
statechart: statechart({
initial: 'willInit'
states: {
willInit: {
on: {
init: 'initializing'
}
},
initializing: {
onEntry: ['asyncCall'],
on: {
resolve: 'success',
reject: 'error'
}
}
},
// ...
}, {
actions: {
asyncCall() {
// ...
}
}
})
})
Notes
xstate now supports activities and delays. ember-statecharts
does not support those featues because there's already a way to do this in the ember ecosystem with the existing functionality - you are encouraged to use ember-concurrency instead.
Example:
const trafficLight = O.extend({
statechart: statechart({
initial: 'red',
states: {
red: {
onEntry: ['startTimer'],
on: {
timer: 'yellow'
}
},
yellow: {
// ...
},
// ...
}
}, {
actions: {
startTimer() {
this.timerTask.perform();
}
}
}),
timerTask: task(function *() {
yield timeout(TIMEOUT);
this.statechart.send('timer');
})
});
v0.5.0
This is a breaking release. You need to update your statechart configurations going from 0.4.0
to 0.5.0
.
No more Statechart
-mixin
Instead of relying on magically adding functionality to your Ember-Objects via a mixin ember-statechart
will now rely on a statechart
-computed-property-macro to add statechart functionality to your Ember-Objects.
You have to refactor the old way:
import Component from '@ember/component';
import Statechart from 'ember-statecharts/mixins/statechart';
import { matchesState } from 'ember-statecharts/computed';
export default Component.extend(Statechart, {
statechart: computed(function() {
initial: 'powerOff',
states: {
powerOff: {
on: {
power: 'powerOn'
}
},
powerOn: {
on: {
power: 'powerOff'
},
initial: 'stopped',
states: {
stopped: {},
playing: {}
}
}
}
}),
playerIsStopped: matchesState({
powerOn: 'stopped'
}),
actions: {
power() {
return this.states.send('power');
}
}
})
To this:
import Component from '@ember/component';
import { statechart, matchesState } from 'ember-statecharts/computed';
export default Component.extend({
statechart: statechart({
initial: 'powerOff',
states: {
powerOff: {
on: {
power: 'powerOn'
}
},
powerOn: {
on: {
power: 'powerOff'
},
initial: 'stopped',
states: {
stopped: {},
playing: {}
}
}
}
}),
playerIsStopped: matchesState({
powerOn: 'stopped'
}),
actions: {
power() {
return this.statechart.send('power');
}
}
})
Overall this is a very minimal change. If you want to use statechart-options (for guards via string references introduced in v0.4.0
) this is still possible. Instead of an array of parameters you will pass two parameters directly instead:
const wat = EmberObject.extend({
power: 1,
statechart: statechart(
{
initial: 'powerOff',
states: {
powerOff: {
on: {
power: {
powerOn: {
cond: 'enoughPowerIsAvailable'
}
}
}
},
powerOn: {
initial: 'stopped',
states: {
stopped: {},
playing: {}
},
on: {
power: 'powerOff'
}
}
}
},
{
guards: {
enougPowerIsAvailable: (context, eventData) => {
return context.get('power') > 9000;
}
}
}
})
});
wat.get('statechart').send('power'); // won't transition power is not over 9000
wat.set('power', 9001);
wat.get('statechart').send('power') // will transition to `powerOn` as power is now over 9000
v0.4.0
This release brings some development ergonomics improvements.
You can now use the matchesState
-computed-macro to check if a statechart is in a specified state without relying on writing your own computed based on Xstate's matchesState
-function. Example:
matchesState
- and debugState
-computeds
import Component from '@ember/component';
import Statechart from 'ember-statecharts/mixins/statechart';
import { matchesState } from 'ember-statecharts/computed';
export default Component.extend(Statechart, {
statechart: computed(function() {
initial: 'powerOff',
states: {
powerOff: {
on: {
power: 'powerOn'
}
},
powerOn: {
on: {
power: 'powerOff'
},
initial: 'stopped',
states: {
stopped: {},
playing: {}
}
}
}
}),
playerIsStopped: matchesState({
powerOn: 'stopped'
})
})
The matcheState
-computed is a convenience-wrapper around XState's matchesState
so please have a look at the docs for that function to get an idea of how to use it correctly.
We also added the debugState
-computed that can be used to print a string representation of the statechart's currentState
-value. This can be useful when developing and saves you from declaring the same function in your own code.
Example:
import { debugState } from 'ember-statecharts/computed';
const wat = EmberObject.extend(Statechart, {
statechart: computed(function() {
return {
initial: 'powerOff',
states: {
powerOff: {
on: {
power: 'powerOn'
}
},
powerOn: {
initial: 'stopped',
states: {
stopped: {},
playing: {}
},
on: {
power: 'powerOff'
}
}
}
};
}),
_debugState: debugState()
});
wat.get('_debugState') // => "powerOff"
wat.get('states').send('power') // => "{ powerOn: 'stopped' }"
Guard references by string
This release also adds the possibility to declare guards / conditional transitions in a DRY-way by referencing guards via strings and passing options to the statechart configuration. This allows multiple transitions to reuse the same guard functions and leads to arguably more readable statechart-configurations.
const wat = EmberObject.extend(Statechart, {
power: 1,
statechart: computed(function() {
return [
{
initial: 'powerOff',
states: {
powerOff: {
on: {
power: {
powerOn: {
cond: 'enoughPowerIsAvailable'
}
}
}
},
powerOn: {
initial: 'stopped',
states: {
stopped: {},
playing: {}
},
on: {
power: 'powerOff'
}
}
}
},
{
guards: {
enougPowerIsAvailable: (context, eventData) => {
return context.get('power') > 9000;
}
}
}
];
})
});
wat.get('states').send('power'); // won't transition power is not over 9000
wat.set('power', 9001);
wat.get('states').send('power') // will transition to `powerOn` as power is now over 9000
Please have a look at the xstate-docs to see how to get the most out of this feature.
v0.3.0
From now on ember-auto-import will be used to import npm dependencies (xstate).
v0.2.0
Updated dependencies:
- xstate 3.3.3
- ember packages 3.3.0
v0.1.0
No changes compared to v0.1.0-beta.0
Statecharts are now implemented based on xState. This is a breaking change as you will need to update your statechart configuration to match what xstate expects.
v0.1.0-beta.0
This release refactors the implementation to use xstate internally to do the heavy lifting for us when implementing statechart functionality.
This is a breaking change as you will need to update your statechart configuration to match what xstate expects. Please look at xstate's docs to learn more.
This release also brings the ability to use orthogonal states in your statecharts 🎉
v0.0.2
Release v0.0.2