Skip to content

Commit 52a215d

Browse files
committed
feat: add xstate persist option
1 parent 02e3404 commit 52a215d

32 files changed

+660
-98
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [3.2.0] - 2022-07-22
9+
10+
### Updates
11+
- Add `persist` argument to `interpret`, `createXStateService` and `wrapXStateService` functions
12+
813
## [3.1.1] - 2022-07-15
914

1015
### Fixes
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* global describe cy before it */
2+
describe('App fsm getters persist', () => {
3+
before(() => {
4+
cy.visit('app-with-fsm-getters-persist')
5+
})
6+
7+
it('has an add water button', () => {
8+
cy.get('add-water-button')
9+
.should('exist')
10+
})
11+
12+
it('should display water level', () => {
13+
cy.get('display-button')
14+
.should('have.text', 'You have not filled the glass yet, keep going! (there are 10 fills to go!)')
15+
})
16+
17+
function filling (expecting) {
18+
describe('adding water', () => {
19+
before(() => {
20+
cy.get('add-water-button button').click()
21+
})
22+
23+
it('should display water level', () => {
24+
cy.get('display-button')
25+
.should('have.text', expecting)
26+
})
27+
})
28+
}
29+
30+
[
31+
'You have not filled the glass yet, keep going! (there are 9 fills to go!)',
32+
'You have not filled the glass yet, keep going! (there are 8 fills to go!)',
33+
'You have not filled the glass yet, keep going! (there are 7 fills to go!)',
34+
'You have not filled the glass yet, keep going! (there are 6 fills to go!)'
35+
].forEach(e => filling(e))
36+
37+
describe('reload for persistence', () => {
38+
before(() => {
39+
cy.reload()
40+
})
41+
42+
it('set the initial state to the last persisted state', () => {
43+
cy.get('display-button')
44+
.should('have.text', 'You have not filled the glass yet, keep going! (there are 6 fills to go!)')
45+
})
46+
})
47+
})
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* global describe cy before it */
2+
describe('App XState full getters', () => {
3+
before(() => {
4+
cy.visit('app-with-xstate-full-getters')
5+
})
6+
7+
it('has an add water button', () => {
8+
cy.get('add-water-button')
9+
.should('exist')
10+
})
11+
12+
it('should display water level', () => {
13+
cy.get('display-button')
14+
.should('have.text', 'You have not filled the glass yet, keep going! (there are 10 fills to go!)')
15+
})
16+
17+
function filling (expecting) {
18+
describe('adding water', () => {
19+
before(() => {
20+
cy.get('add-water-button button').click()
21+
})
22+
23+
it('should display water level', () => {
24+
cy.get('display-button')
25+
.should('have.text', expecting)
26+
})
27+
})
28+
}
29+
30+
[
31+
'You have not filled the glass yet, keep going! (there are 9 fills to go!)',
32+
'You have not filled the glass yet, keep going! (there are 8 fills to go!)',
33+
'You have not filled the glass yet, keep going! (there are 7 fills to go!)',
34+
'You have not filled the glass yet, keep going! (there are 6 fills to go!)',
35+
'You have not filled the glass yet, keep going! (there are 5 fills to go!)',
36+
'You have not filled the glass yet, keep going! (there are 4 fills to go!)',
37+
'You have not filled the glass yet, keep going! (there are 3 fills to go!)',
38+
'You have not filled the glass yet, keep going! (there are 2 fills to go!)',
39+
'You have not filled the glass yet, keep going! (there are 1 fills to go!)',
40+
'The glass is full!',
41+
'The glass is full!'
42+
].forEach(e => filling(e))
43+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* global describe cy before it */
2+
describe('App fsm getters persist', () => {
3+
before(() => {
4+
cy.visit('app-with-xstate-full-getters-persist')
5+
})
6+
7+
it('has an add water button', () => {
8+
cy.get('add-water-button')
9+
.should('exist')
10+
})
11+
12+
it('should display water level', () => {
13+
cy.get('display-button')
14+
.should('have.text', 'You have not filled the glass yet, keep going! (there are 10 fills to go!)')
15+
})
16+
17+
function filling (expecting) {
18+
describe('adding water', () => {
19+
before(() => {
20+
cy.get('add-water-button button').click()
21+
})
22+
23+
it('should display water level', () => {
24+
cy.get('display-button')
25+
.should('have.text', expecting)
26+
})
27+
})
28+
}
29+
30+
[
31+
'You have not filled the glass yet, keep going! (there are 9 fills to go!)',
32+
'You have not filled the glass yet, keep going! (there are 8 fills to go!)',
33+
'You have not filled the glass yet, keep going! (there are 7 fills to go!)',
34+
'You have not filled the glass yet, keep going! (there are 6 fills to go!)'
35+
].forEach(e => filling(e))
36+
37+
describe('reload for persistence', () => {
38+
before(() => {
39+
cy.reload()
40+
})
41+
42+
it('set the initial state to the last persisted state', () => {
43+
cy.get('display-button')
44+
.should('have.text', 'You have not filled the glass yet, keep going! (there are 6 fills to go!)')
45+
})
46+
})
47+
})

dist/index.mjs

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

dist/with-xstate-service.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
const s=Object.freeze({INIT:-1,NOT_STARTED:0,RUNNING:1,STOPPED:2});function withXStateService(t,e){return{...e,created(){this._subscribeCallback=()=>{this.computedCache={},this._processRender()},this.setXStateService(t),e.created&&e.created.call(this)},mounted(){this._subscribeToFsmService(!1),e.mounted&&e.mounted.call(this)},updated(){this._subscribeToFsmService(!1),e.updated&&e.updated.call(this)},removed(){this._unsubscribeFromFsmService(),e.removed&&e.removed.call(this)},setXStateService(t){this.fsm=t&&t.status!==s.INIT?t:function(t){return{_xstateService:t,get status(){return this._xstateService?this._xstateService.status:s.INIT},get state(){return this._xstateService.state},subscribe(s){return this._xstateService.subscribe(s)},send(s){this._xstateService.send(s)},start(){this._xstateService.start()}}}(t),this._fsmSubscriptionCallback=()=>{this.fsm.status===s.RUNNING&&this._fsmSubscription&&this._subscribeCallback()},this._subscribeToFsmService()},_startFsmService(){this.fsm&&this._fsmSubscription&&this.fsm.status===s.NOT_STARTED&&this.fsm.start()},_subscribeToFsmService(t=!0){this.fsm&&this.fsm.status!==s.INIT&&!this._fsmSubscription&&(this._fsmSubscription=this.fsm.subscribe(this._fsmSubscriptionCallback),t&&this._fsmSubscriptionCallback(),this._startFsmService())},_unsubscribeFromFsmService(){this.fsm&&this.fsm.status!==s.INIT&&this._fsmSubscription&&"function"==typeof this._fsmSubscription&&(this._fsmSubscription(),this._fsmSubscription=null)}}}export{withXStateService};
1+
class t{constructor(t,s,e={}){this.namespace=t,this.storage=s,this.options=e,this._init()}_init(){if("performance"in globalThis&&this.options.clearOnReload){const t=globalThis.performance.getEntriesByType("navigation").map((t=>t.type));this.lastUpdated()&&t.includes("reload")&&this.removeState()}}setState(t){t?(this.storage.setItem(`${this.namespace}:state`,this._normalizeState(t)),this.storage.setItem(`${this.namespace}:lastUpdated`,(new Date).getTime().toString())):this.removeState()}_normalizeState(t){return"object"==typeof t&&this.options.saveState&&"function"==typeof this.options.saveState?JSON.stringify(this.options.saveState(t)):"string"!=typeof t?JSON.stringify(t):t}getState(){const t=this.storage.getItem(`${this.namespace}:state`);return t?JSON.parse(t):void 0}lastUpdated(){const t=this.storage.getItem(`${this.namespace}:lastUpdated`);return t?parseInt(t,10):void 0}removeState(){this.storage.removeItem(`${this.namespace}:state`),this.storage.removeItem(`${this.namespace}:lastUpdated`)}}function createPersist(s,e="session",i){return new t(s,"local"===e?globalThis.localStorage:globalThis.sessionStorage,i)}const s=Object.freeze({INIT:-1,NOT_STARTED:0,RUNNING:1,STOPPED:2});function withXStateService(t,e){return{...e,created(){this._subscribeCallback=()=>{this.computedCache={},this._processRender()},this.setXStateService(t),e.created&&e.created.call(this)},mounted(){this._subscribeToFsmService(!1),e.mounted&&e.mounted.call(this)},updated(){this._subscribeToFsmService(!1),e.updated&&e.updated.call(this)},removed(){this._unsubscribeFromFsmService(),e.removed&&e.removed.call(this)},setXStateService(t){this.fsm=t&&t.status!==s.INIT?t:function(t,e,i){const a={_xstateService:t,_getterCache:{},_persist:"string"==typeof i?createPersist(i):i,get status(){return this._xstateService?this._xstateService.status:s.INIT},get state(){return this._xstateService.state},subscribe(t){return this._xstateService.subscribe((()=>{this._getterCache={},this._persist&&this._xstateService.status===s.RUNNING&&this._persist.setState(this._xstateService.state),t()}))},send(t){this._xstateService.send(t)},start(){this._xstateService.start(this._persist?this._persist.getState():void 0)}};return e&&(a.getters=new Proxy(e,{get(t,s){if(!a._getterCache[s]){const e=t[s](a._xstateService.state.context);a._getterCache[s]=e}return a._getterCache[s]}})),a}(t),this._fsmSubscriptionCallback=()=>{this.fsm.status===s.RUNNING&&this._fsmSubscription&&this._subscribeCallback()},this._subscribeToFsmService()},_startFsmService(){this.fsm&&this._fsmSubscription&&this.fsm.status===s.NOT_STARTED&&this.fsm.start()},_subscribeToFsmService(t=!0){this.fsm&&this.fsm.status!==s.INIT&&!this._fsmSubscription&&(this._fsmSubscription=this.fsm.subscribe(this._fsmSubscriptionCallback),t&&this._fsmSubscriptionCallback(),this._startFsmService())},_unsubscribeFromFsmService(){this.fsm&&this.fsm.status!==s.INIT&&this._fsmSubscription&&"function"==typeof this._fsmSubscription&&(this._fsmSubscription(),this._fsmSubscription=null)}}}export{withXStateService};

dist/xstate-service.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1212
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1313
PERFORMANCE OF THIS SOFTWARE.
1414
***************************************************************************** */
15-
function t(e,n){var _="function"==typeof Symbol&&e[Symbol.iterator];if(!_)return e;var g,v,h=_.call(e),S=[];try{for(;(void 0===n||n-- >0)&&!(g=h.next()).done;)S.push(g.value)}catch(e){v={error:e}}finally{try{g&&!g.done&&(_=h.return)&&_.call(h)}finally{if(v)throw v.error}}return S}var e;!function(e){e[e.NotStarted=0]="NotStarted",e[e.Running=1]="Running",e[e.Stopped=2]="Stopped"}(e||(e={}));var n={type:"xstate.init"};function r(e){return void 0===e?[]:[].concat(e)}function i(e){return{type:"xstate.assign",assignment:e}}function o(e,n){return"string"==typeof(e="string"==typeof e&&n&&n[e]?n[e]:e)?{type:e}:"function"==typeof e?{type:e.name,exec:e}:e}function a(e){return function(n){return e===n}}function u(e){return"string"==typeof e?{type:e}:e}function c(e,n){return{value:e,context:n,actions:[],changed:!1,matches:a(e)}}function f(e,n,_){var g=n,v=!1;return[e.filter((function(e){if("xstate.assign"===e.type){v=!0;var n=Object.assign({},g);return"function"==typeof e.assignment?n=e.assignment(g,_):Object.keys(e.assignment).forEach((function(v){n[v]="function"==typeof e.assignment[v]?e.assignment[v](g,_):e.assignment[v]})),g=n,!1}return!0})),g,v]}function s(e,_){void 0===_&&(_={});var g=t(f(r(e.states[e.initial].entry).map((function(e){return o(e,_.actions)})),e.context,n),2),v=g[0],h=g[1],S={config:e,_options:_,initialState:{value:e.initial,actions:v,context:h,matches:a(e.initial)},transition:function(n,_){var g,v,h="string"==typeof n?{value:n,context:e.context}:n,x=h.value,p=h.context,y=u(_),b=e.states[x];if(b.on){var d=r(b.on[y.type]);try{for(var m=function(e){var n="function"==typeof Symbol&&Symbol.iterator,_=n&&e[n],g=0;if(_)return _.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&g>=e.length&&(e=void 0),{value:e&&e[g++],done:!e}}};throw new TypeError(n?"Object is not iterable.":"Symbol.iterator is not defined.")}(d),T=m.next();!T.done;T=m.next()){var j=T.value;if(void 0===j)return c(x,p);var w="string"==typeof j?{target:j}:j,N=w.target,X=w.actions,E=void 0===X?[]:X,O=w.cond,R=void 0===O?function(){return!0}:O,C=void 0===N,I=null!=N?N:x,P=e.states[I];if(R(p,y)){var D=t(f((C?r(E):[].concat(b.exit,E,P.entry).filter((function(e){return e}))).map((function(e){return o(e,S._options.actions)})),p,y),3),k=D[0],z=D[1],A=D[2],G=null!=N?N:x;return{value:G,context:z,actions:k,changed:N!==x||k.length>0||A,matches:a(G)}}}}catch(e){g={error:e}}finally{try{T&&!T.done&&(v=m.return)&&v.call(m)}finally{if(g)throw g.error}}}return c(x,p)}};return S}var l=function(e,n){return e.actions.forEach((function(_){var g=_.exec;return g&&g(e.context,n)}))};function wrapXStateService(e){return{_xstateService:e,get status(){return this._xstateService?this._xstateService.status:_.INIT},get state(){return this._xstateService.state},subscribe(e){return this._xstateService.subscribe(e)},send(e){this._xstateService.send(e)},start(){this._xstateService.start()}}}const _=Object.freeze({INIT:-1,NOT_STARTED:0,RUNNING:1,STOPPED:2});function interpret(_,g){const v=wrapXStateService(function(_){var g=_.initialState,v=e.NotStarted,h=new Set,S={_machine:_,send:function(n){v===e.Running&&(g=_.transition(g,n),l(g,u(n)),h.forEach((function(e){return e(g)})))},subscribe:function(e){return h.add(e),e(g),{unsubscribe:function(){return h.delete(e)}}},start:function(h){if(h){var x="object"==typeof h?h:{context:_.config.context,value:h};g={value:x.value,actions:[],context:x.context,matches:a(x.value)}}else g=_.initialState;return v=e.Running,l(g,n),S},stop:function(){return v=e.Stopped,h.clear(),S},get state(){return g},get status(){return v}};return S}(_));return g?function(e,n){return e._getterCache={},e.getters=new Proxy(n,{get(n,_){if(!e._getterCache[_]){const g=n[_](e._xstateService.state.context);e._getterCache[_]=g}return e._getterCache[_]}}),e.subscribe((()=>{e._getterCache={}})),e}(v,g):v}function createXStateService(e,n,_){const g=interpret(n,_);return globalThis.__ficusjs__=globalThis.__ficusjs__||{},globalThis.__ficusjs__.xstate=globalThis.__ficusjs__.xstate||{},globalThis.__ficusjs__.xstate[e]=g,g}function addXStateService(e,n){return globalThis.__ficusjs__=globalThis.__ficusjs__||{},globalThis.__ficusjs__.xstate=globalThis.__ficusjs__.xstate||{},globalThis.__ficusjs__.xstate[e]=n,n}function getXStateService(e){if(globalThis.__ficusjs__&&globalThis.__ficusjs__.xstate&&globalThis.__ficusjs__.xstate[e])return globalThis.__ficusjs__.xstate[e]}export{_ as XStateServiceStatus,addXStateService,i as assign,s as createMachine,createXStateService,getXStateService,interpret,wrapXStateService};
15+
function t(e,n){var _="function"==typeof Symbol&&e[Symbol.iterator];if(!_)return e;var g,h,p=_.call(e),v=[];try{for(;(void 0===n||n-- >0)&&!(g=p.next()).done;)v.push(g.value)}catch(e){h={error:e}}finally{try{g&&!g.done&&(_=p.return)&&_.call(p)}finally{if(h)throw h.error}}return v}var e;!function(e){e[e.NotStarted=0]="NotStarted",e[e.Running=1]="Running",e[e.Stopped=2]="Stopped"}(e||(e={}));var n={type:"xstate.init"};function r(e){return void 0===e?[]:[].concat(e)}function i(e){return{type:"xstate.assign",assignment:e}}function o(e,n){return"string"==typeof(e="string"==typeof e&&n&&n[e]?n[e]:e)?{type:e}:"function"==typeof e?{type:e.name,exec:e}:e}function a(e){return function(n){return e===n}}function u(e){return"string"==typeof e?{type:e}:e}function c(e,n){return{value:e,context:n,actions:[],changed:!1,matches:a(e)}}function f(e,n,_){var g=n,h=!1;return[e.filter((function(e){if("xstate.assign"===e.type){h=!0;var n=Object.assign({},g);return"function"==typeof e.assignment?n=e.assignment(g,_):Object.keys(e.assignment).forEach((function(h){n[h]="function"==typeof e.assignment[h]?e.assignment[h](g,_):e.assignment[h]})),g=n,!1}return!0})),g,h]}function s(e,_){void 0===_&&(_={});var g=t(f(r(e.states[e.initial].entry).map((function(e){return o(e,_.actions)})),e.context,n),2),h=g[0],p=g[1],v={config:e,_options:_,initialState:{value:e.initial,actions:h,context:p,matches:a(e.initial)},transition:function(n,_){var g,h,p="string"==typeof n?{value:n,context:e.context}:n,S=p.value,d=p.context,y=u(_),m=e.states[S];if(m.on){var x=r(m.on[y.type]);try{for(var b=function(e){var n="function"==typeof Symbol&&Symbol.iterator,_=n&&e[n],g=0;if(_)return _.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&g>=e.length&&(e=void 0),{value:e&&e[g++],done:!e}}};throw new TypeError(n?"Object is not iterable.":"Symbol.iterator is not defined.")}(x),T=b.next();!T.done;T=b.next()){var j=T.value;if(void 0===j)return c(S,d);var N="string"==typeof j?{target:j}:j,I=N.target,w=N.actions,O=void 0===w?[]:w,X=N.cond,R=void 0===X?function(){return!0}:X,E=void 0===I,U=null!=I?I:S,$=e.states[U];if(R(d,y)){var C=t(f((E?r(O):[].concat(m.exit,O,$.entry).filter((function(e){return e}))).map((function(e){return o(e,v._options.actions)})),d,y),3),P=C[0],z=C[1],D=C[2],J=null!=I?I:S;return{value:J,context:z,actions:P,changed:I!==S||P.length>0||D,matches:a(J)}}}}catch(e){g={error:e}}finally{try{T&&!T.done&&(h=b.return)&&h.call(b)}finally{if(g)throw g.error}}}return c(S,d)}};return v}var l=function(e,n){return e.actions.forEach((function(_){var g=_.exec;return g&&g(e.context,n)}))};class _{constructor(e,n,_={}){this.namespace=e,this.storage=n,this.options=_,this._init()}_init(){if("performance"in globalThis&&this.options.clearOnReload){const e=globalThis.performance.getEntriesByType("navigation").map((e=>e.type));this.lastUpdated()&&e.includes("reload")&&this.removeState()}}setState(e){e?(this.storage.setItem(`${this.namespace}:state`,this._normalizeState(e)),this.storage.setItem(`${this.namespace}:lastUpdated`,(new Date).getTime().toString())):this.removeState()}_normalizeState(e){return"object"==typeof e&&this.options.saveState&&"function"==typeof this.options.saveState?JSON.stringify(this.options.saveState(e)):"string"!=typeof e?JSON.stringify(e):e}getState(){const e=this.storage.getItem(`${this.namespace}:state`);return e?JSON.parse(e):void 0}lastUpdated(){const e=this.storage.getItem(`${this.namespace}:lastUpdated`);return e?parseInt(e,10):void 0}removeState(){this.storage.removeItem(`${this.namespace}:state`),this.storage.removeItem(`${this.namespace}:lastUpdated`)}}function createPersist(e,n="session",g){return new _(e,"local"===n?globalThis.localStorage:globalThis.sessionStorage,g)}function wrapXStateService(e,n,_){const h={_xstateService:e,_getterCache:{},_persist:"string"==typeof _?createPersist(_):_,get status(){return this._xstateService?this._xstateService.status:g.INIT},get state(){return this._xstateService.state},subscribe(e){return this._xstateService.subscribe((()=>{this._getterCache={},this._persist&&this._xstateService.status===g.RUNNING&&this._persist.setState(this._xstateService.state),e()}))},send(e){this._xstateService.send(e)},start(){this._xstateService.start(this._persist?this._persist.getState():void 0)}};return n&&(h.getters=new Proxy(n,{get(e,n){if(!h._getterCache[n]){const _=e[n](h._xstateService.state.context);h._getterCache[n]=_}return h._getterCache[n]}})),h}const g=Object.freeze({INIT:-1,NOT_STARTED:0,RUNNING:1,STOPPED:2});function interpret(_,g,h){const p=wrapXStateService(function(_){var g=_.initialState,h=e.NotStarted,p=new Set,v={_machine:_,send:function(n){h===e.Running&&(g=_.transition(g,n),l(g,u(n)),p.forEach((function(e){return e(g)})))},subscribe:function(e){return p.add(e),e(g),{unsubscribe:function(){return p.delete(e)}}},start:function(p){if(p){var S="object"==typeof p?p:{context:_.config.context,value:p};g={value:S.value,actions:[],context:S.context,matches:a(S.value)}}else g=_.initialState;return h=e.Running,l(g,n),v},stop:function(){return h=e.Stopped,p.clear(),v},get state(){return g},get status(){return h}};return v}(_),g,h);return p}function createXStateService(e,n,_,g){const h=interpret(n,_,g);return globalThis.__ficusjs__=globalThis.__ficusjs__||{},globalThis.__ficusjs__.xstate=globalThis.__ficusjs__.xstate||{},globalThis.__ficusjs__.xstate[e]=h,h}function addXStateService(e,n){return globalThis.__ficusjs__=globalThis.__ficusjs__||{},globalThis.__ficusjs__.xstate=globalThis.__ficusjs__.xstate||{},globalThis.__ficusjs__.xstate[e]=n,n}function getXStateService(e){if(globalThis.__ficusjs__&&globalThis.__ficusjs__.xstate&&globalThis.__ficusjs__.xstate[e])return globalThis.__ficusjs__.xstate[e]}export{g as XStateServiceStatus,addXStateService,i as assign,s as createMachine,createXStateService,getXStateService,interpret,wrapXStateService};

0 commit comments

Comments
 (0)