Skip to content

Commit 2a45737

Browse files
committed
feat: add ability to set next state as objects
1 parent a4624e5 commit 2a45737

File tree

6 files changed

+30
-6
lines changed

6 files changed

+30
-6
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+
## [1.1.0] - 2021-05-23
9+
10+
### New
11+
- Ability to pass an object as a state value that contains `{ target: 'nextState', action: 'actionNameToInvoke' }
12+
813
## [1.0.1] - 2021-05-16
914

1015
### Fixes

dist/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
function isPromise(t){return("object"==typeof t||"function"==typeof t)&&"function"==typeof t.then}class t{constructor(t,s){this.namespace=t,this.storage=s}setState(t){t?(this.storage.setItem(`${this.namespace}:state`,"string"==typeof t?t:JSON.stringify(t)),this.storage.setItem(`${this.namespace}:lastUpdated`,(new Date).getTime().toString())):this.removeState()}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"){return new t(s,"local"===e?globalThis.localStorage:globalThis.sessionStorage)}function withStore(t,s){return{...s,created(){this.subscribeCallback=()=>{this.computedCache={},this._processRender()},this.setStore(t),s.created&&s.created.call(this)},mounted(){this._subscribeToStores(!1),s.mounted&&s.mounted.call(this)},updated(){this._subscribeToStores(!1),s.updated&&s.updated.call(this)},removed(){this._unsubscribeFromStores(),s.removed&&s.removed.call(this)},setStore(t){this.store=t,this._subscribeToStores()},_subscribeToStores(t=!0){if(this.store&&this.store.subscribe&&"function"==typeof this.store.subscribe&&!this.unsubscribe)this.unsubscribe=this.store.subscribe(this.subscribeCallback),t&&this.subscribeCallback();else if(this.store&&"object"==typeof this.store&&!this.store.subscribe){this.unsubscribe={};Object.keys(this.store).forEach((t=>{this.store[t]&&this.store[t].subscribe&&"function"==typeof this.store[t].subscribe&&!this.unsubscribe[t]&&(this.unsubscribe[t]=this.store[t].subscribe(this.subscribeCallback))})),t&&this.subscribeCallback()}},_unsubscribeFromStores(){if(this.store&&this.unsubscribe&&"object"==typeof this.unsubscribe){Object.keys(this.unsubscribe).forEach((t=>{this.unsubscribe[t]()})),this.unsubscribe=null}else this.store&&this.unsubscribe&&"function"==typeof this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null)}}}class s{constructor(t){const s=this;s.state={},s.getterCache={},s.status="resting",s.transaction=!1,s.transactionCache={},s.callbacks=[],this._processActions(t);let e=t.initialState||{};if(s.copyOfInitialState=s._copyValue(e),s.ttl=-1,s.lastUpdatedState={},t.ttl&&(s.ttl=t.ttl,Object.keys(s.copyOfInitialState).forEach((t=>s.lastUpdatedState[t]=(new Date).getTime()))),t.persist){s.persist="string"==typeof t.persist?createPersist(t.persist):t.persist;const i=s.persist.getState(),a=s.persist.lastUpdated();i&&a&&(-1===s.ttl||s._lastUpdatedTimeDiff(a)<s.ttl)&&(e=i)}this._processState(e)}_processActions(t){const s=this,e=Object.keys(t);e.length&&e.forEach((e=>{s[e]||"function"!=typeof t[e]||(s[e]=t[e].bind(s))}))}_processState(t){const s=this;s.state=new Proxy(t,{set:(t,e,i)=>(s.transaction&&!s.transactionCache[e]&&(s.transactionCache[e]=s._copyValue(t[e])),t[e]=i,s.lastUpdatedState[e]=(new Date).getTime(),s.getterCache={},s.transaction||(s.persist&&s.persist.setState(s.state),s.status="resting",s._processCallbacks(s.state)),!0),get:(t,e)=>s.ttl>-1&&s._lastUpdatedTimeDiff(s.lastUpdatedState[e])>s.ttl?(s.persist&&s.persist.removeState(),s.copyOfInitialState[e]):t[e]})}_lastUpdatedTimeDiff(t){return Math.round((new Date).getTime()-t)}setState(t){const setter=t=>{if(!t||"object"!=typeof t)return;const s=this.transaction;s||(this.transactionCache={},this.transaction=!0);for(const s in t)this.state[s]&&this.state[s]===t[s]||(this.state[s]=t[s]);s||(this.transaction=!1,this.persist&&this.persist.setState(this.state),this._processCallbacks(this.state))},s=t(this.state);isPromise(s)?s.then(setter):setter(s)}getState(t){if(t){if(!this.getterCache[t]){const s=(Array.isArray(t)?t:t.match(/([^[.\]])+/g)).reduce(((t,s)=>t&&t[s]),this.state);if(null==s)return;this.getterCache[t]=s}return this.getterCache[t]}}_processCallbacks(t){return!!this.callbacks.length&&(this.callbacks.forEach((s=>s(t))),!0)}subscribe(t){if("function"!=typeof t)throw new Error("Dude, you can only subscribe to store changes with a valid function");return this.callbacks.push(t),()=>{this.callbacks=this.callbacks.filter((s=>s!==t))}}_copyValue(t){return t?JSON.parse(JSON.stringify(t)):t}clear(t=!0){this.getterCache={},this.transactionCache={},this.lastUpdatedState={},this.persist&&this.persist.removeState(),this.transaction=!0,this.status="clear";const s=this._copyValue(this.copyOfInitialState);for(const t in s)this.state[t]=s[t];this.transaction=!1,this.status="resting",t&&this._processCallbacks(this.state)}}function createAppState(t,e){let i=getAppState(t);return i||(i=new s(e),globalThis.__ficusjs__=globalThis.__ficusjs__||{},globalThis.__ficusjs__.store=globalThis.__ficusjs__.store||{},globalThis.__ficusjs__.store[t]=i,i)}function getAppState(t){if(globalThis.__ficusjs__&&globalThis.__ficusjs__.store&&globalThis.__ficusjs__.store[t])return globalThis.__ficusjs__.store[t]}class e{constructor(t){this.machine=t}get initialState(){return this.machine.initial||Object.keys(this.machine.states)[0]}transition(t,s){return this.machine.states[t].on[s]}}function createStateMachine(t){return new e(t)}function withLocalState(t){return{...t,created(){if(t.state&&"function"!=typeof t.state)throw new Error("State must be a function!");this._state=t.state||{},"function"==typeof this._state&&(this._state=this._state.bind(this)()),this.state=this._monitorState(this._state),this.setState=(t,s)=>{const setter=t=>{if(!t||"object"!=typeof t)return;const e=this.updated;s&&(this.updated=()=>{s(),this.updated=e||void 0}),this.status="transaction";for(const s in t)this.state[s]&&this.state[s]===t[s]||(this.state[s]=t[s]);this.status="render",this._processRender()},e=t(this.state);isPromise(e)?e.then(setter):setter(e)},t.created&&t.created.call(this)},_monitorState(t){const s=this;return new Proxy(t,{set:(t,e,i)=>(t[e]===i||(t[e]=i,s.computedCache={},"render"===s.status&&s._processRender()),!0)})}}}function withStateMachine(t,s){return{...s,created(){this._stateMachineDefinition=t,this._stateMachine=createStateMachine(t),this.initialState=this._stateMachine.initialState,this.state={context:{},matches(t){return t===this.value},value:this.initialState},this.setState=(t,s)=>{if(!t||"object"!=typeof t)return;const e=this.updated;s&&(this.updated=()=>{s.call(this),this.updated=e||void 0}),this.status="transaction";for(const s in t)"value"===s?this.state[s]=t[s]:this.state.context[s]&&this.state.context[s]===t[s]||(this.state.context[s]=t[s]);this.status="render",this._processRender()},s.created&&s.created.call(this)},send(t){let s,e;if("string"==typeof t)s=t;else{const{type:i,...a}=t;s=i,e=a}const{value:i}=this.state,a=this._stateMachine.transition(i,s)||i,r=this._stateMachineDefinition.actions&&this._stateMachineDefinition.actions[a]?()=>this._stateMachineDefinition.actions[a].call(this,e):()=>{};this.setState({value:a},r)}}}function withXStateService(t,s){return{...s,created(){this._setupService(t),s.created&&s.created.call(this)},send(t){this.service.send(t)},mounted(){this._startService(),s.mounted&&s.mounted.call(this)},updated(){this._startService(),s.updated&&s.updated.call(this)},removed(){this._stopService(),s.removed&&s.removed.call(this)},_setupService(t){this.service=t,this.subscription=t.subscribe((t=>{this.state=t,this.computedCache={},this._processRender()})),this._startService()},_startService(){this.service&&this.subscription&&"Running"!==this.service.status&&this.service.start()},_stopService(){this.service&&this.subscription&&"Running"===this.service.status&&this.service.stop()}}}export{createAppState,createPersist,createStateMachine,getAppState,withLocalState,withStateMachine,withStore,withXStateService};
1+
function isPromise(t){return("object"==typeof t||"function"==typeof t)&&"function"==typeof t.then}class t{constructor(t,s){this.namespace=t,this.storage=s}setState(t){t?(this.storage.setItem(`${this.namespace}:state`,"string"==typeof t?t:JSON.stringify(t)),this.storage.setItem(`${this.namespace}:lastUpdated`,(new Date).getTime().toString())):this.removeState()}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"){return new t(s,"local"===e?globalThis.localStorage:globalThis.sessionStorage)}function withStore(t,s){return{...s,created(){this.subscribeCallback=()=>{this.computedCache={},this._processRender()},this.setStore(t),s.created&&s.created.call(this)},mounted(){this._subscribeToStores(!1),s.mounted&&s.mounted.call(this)},updated(){this._subscribeToStores(!1),s.updated&&s.updated.call(this)},removed(){this._unsubscribeFromStores(),s.removed&&s.removed.call(this)},setStore(t){this.store=t,this._subscribeToStores()},_subscribeToStores(t=!0){if(this.store&&this.store.subscribe&&"function"==typeof this.store.subscribe&&!this.unsubscribe)this.unsubscribe=this.store.subscribe(this.subscribeCallback),t&&this.subscribeCallback();else if(this.store&&"object"==typeof this.store&&!this.store.subscribe){this.unsubscribe={};Object.keys(this.store).forEach((t=>{this.store[t]&&this.store[t].subscribe&&"function"==typeof this.store[t].subscribe&&!this.unsubscribe[t]&&(this.unsubscribe[t]=this.store[t].subscribe(this.subscribeCallback))})),t&&this.subscribeCallback()}},_unsubscribeFromStores(){if(this.store&&this.unsubscribe&&"object"==typeof this.unsubscribe){Object.keys(this.unsubscribe).forEach((t=>{this.unsubscribe[t]()})),this.unsubscribe=null}else this.store&&this.unsubscribe&&"function"==typeof this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null)}}}class s{constructor(t){const s=this;s.state={},s.getterCache={},s.status="resting",s.transaction=!1,s.transactionCache={},s.callbacks=[],this._processActions(t);let e=t.initialState||{};if(s.copyOfInitialState=s._copyValue(e),s.ttl=-1,s.lastUpdatedState={},t.ttl&&(s.ttl=t.ttl,Object.keys(s.copyOfInitialState).forEach((t=>s.lastUpdatedState[t]=(new Date).getTime()))),t.persist){s.persist="string"==typeof t.persist?createPersist(t.persist):t.persist;const i=s.persist.getState(),a=s.persist.lastUpdated();i&&a&&(-1===s.ttl||s._lastUpdatedTimeDiff(a)<s.ttl)&&(e=i)}this._processState(e)}_processActions(t){const s=this,e=Object.keys(t);e.length&&e.forEach((e=>{s[e]||"function"!=typeof t[e]||(s[e]=t[e].bind(s))}))}_processState(t){const s=this;s.state=new Proxy(t,{set:(t,e,i)=>(s.transaction&&!s.transactionCache[e]&&(s.transactionCache[e]=s._copyValue(t[e])),t[e]=i,s.lastUpdatedState[e]=(new Date).getTime(),s.getterCache={},s.transaction||(s.persist&&s.persist.setState(s.state),s.status="resting",s._processCallbacks(s.state)),!0),get:(t,e)=>s.ttl>-1&&s._lastUpdatedTimeDiff(s.lastUpdatedState[e])>s.ttl?(s.persist&&s.persist.removeState(),s.copyOfInitialState[e]):t[e]})}_lastUpdatedTimeDiff(t){return Math.round((new Date).getTime()-t)}setState(t){const setter=t=>{if(!t||"object"!=typeof t)return;const s=this.transaction;s||(this.transactionCache={},this.transaction=!0);for(const s in t)this.state[s]&&this.state[s]===t[s]||(this.state[s]=t[s]);s||(this.transaction=!1,this.persist&&this.persist.setState(this.state),this._processCallbacks(this.state))},s=t(this.state);isPromise(s)?s.then(setter):setter(s)}getState(t){if(t){if(!this.getterCache[t]){const s=(Array.isArray(t)?t:t.match(/([^[.\]])+/g)).reduce(((t,s)=>t&&t[s]),this.state);if(null==s)return;this.getterCache[t]=s}return this.getterCache[t]}}_processCallbacks(t){return!!this.callbacks.length&&(this.callbacks.forEach((s=>s(t))),!0)}subscribe(t){if("function"!=typeof t)throw new Error("Dude, you can only subscribe to store changes with a valid function");return this.callbacks.push(t),()=>{this.callbacks=this.callbacks.filter((s=>s!==t))}}_copyValue(t){return t?JSON.parse(JSON.stringify(t)):t}clear(t=!0){this.getterCache={},this.transactionCache={},this.lastUpdatedState={},this.persist&&this.persist.removeState(),this.transaction=!0,this.status="clear";const s=this._copyValue(this.copyOfInitialState);for(const t in s)this.state[t]=s[t];this.transaction=!1,this.status="resting",t&&this._processCallbacks(this.state)}}function createAppState(t,e){let i=getAppState(t);return i||(i=new s(e),globalThis.__ficusjs__=globalThis.__ficusjs__||{},globalThis.__ficusjs__.store=globalThis.__ficusjs__.store||{},globalThis.__ficusjs__.store[t]=i,i)}function getAppState(t){if(globalThis.__ficusjs__&&globalThis.__ficusjs__.store&&globalThis.__ficusjs__.store[t])return globalThis.__ficusjs__.store[t]}class e{constructor(t){this.machine=t}get initialState(){return this.machine.initial||Object.keys(this.machine.states)[0]}transition(t,s){return this.machine.states[t].on[s]}}function createStateMachine(t){return new e(t)}function withLocalState(t){return{...t,created(){if(t.state&&"function"!=typeof t.state)throw new Error("State must be a function!");this._state=t.state||{},"function"==typeof this._state&&(this._state=this._state.bind(this)()),this.state=this._monitorState(this._state),this.setState=(t,s)=>{const setter=t=>{if(!t||"object"!=typeof t)return;const e=this.updated;s&&(this.updated=()=>{s(),this.updated=e||void 0}),this.status="transaction";for(const s in t)this.state[s]&&this.state[s]===t[s]||(this.state[s]=t[s]);this.status="render",this._processRender()},e=t(this.state);isPromise(e)?e.then(setter):setter(e)},t.created&&t.created.call(this)},_monitorState(t){const s=this;return new Proxy(t,{set:(t,e,i)=>(t[e]===i||(t[e]=i,s.computedCache={},"render"===s.status&&s._processRender()),!0)})}}}function withStateMachine(t,s){return{...s,created(){this._stateMachineDefinition=t,this._stateMachine=createStateMachine(t),this.initialState=this._stateMachine.initialState,this.state={context:{},matches(t){return t===this.value},value:this.initialState},this.setState=(t,s)=>{if(!t||"object"!=typeof t)return;const e=this.updated;s&&(this.updated=()=>{s.call(this),this.updated=e||void 0}),this.status="transaction";for(const s in t)"value"===s?this.state[s]=t[s]:this.state.context[s]&&this.state.context[s]===t[s]||(this.state.context[s]=t[s]);this.status="render",this._processRender()},s.created&&s.created.call(this)},send(t){let s,e;if("string"==typeof t)s=t;else{const{type:i,...a}=t;s=i,e=a}const{value:i}=this.state,a=this._stateMachine.transition(i,s)||i,r="object"==typeof a&&a.target?a.target:a,c="object"==typeof a&&a.action?a.action:a,n=this._stateMachineDefinition.actions&&this._stateMachineDefinition.actions[c]?()=>this._stateMachineDefinition.actions[c].call(this,e):()=>{};this.setState({value:r},n)}}}function withXStateService(t,s){return{...s,created(){this._setupService(t),s.created&&s.created.call(this)},send(t){this.service.send(t)},mounted(){this._startService(),s.mounted&&s.mounted.call(this)},updated(){this._startService(),s.updated&&s.updated.call(this)},removed(){this._stopService(),s.removed&&s.removed.call(this)},_setupService(t){this.service=t,this.subscription=t.subscribe((t=>{this.state=t,this.computedCache={},this._processRender()})),this._startService()},_startService(){this.service&&this.subscription&&"Running"!==this.service.status&&this.service.start()},_stopService(){this.service&&this.subscription&&"Running"===this.service.status&&this.service.stop()}}}export{createAppState,createPersist,createStateMachine,getAppState,withLocalState,withStateMachine,withStore,withXStateService};

dist/with-state-machine.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
class t{constructor(t){this.machine=t}get initialState(){return this.machine.initial||Object.keys(this.machine.states)[0]}transition(t,i){return this.machine.states[t].on[i]}}function withStateMachine(i,e){return{...e,created(){this._stateMachineDefinition=i,this._stateMachine=new t(i),this.initialState=this._stateMachine.initialState,this.state={context:{},matches(t){return t===this.value},value:this.initialState},this.setState=(t,i)=>{if(!t||"object"!=typeof t)return;const e=this.updated;i&&(this.updated=()=>{i.call(this),this.updated=e||void 0}),this.status="transaction";for(const i in t)"value"===i?this.state[i]=t[i]:this.state.context[i]&&this.state.context[i]===t[i]||(this.state.context[i]=t[i]);this.status="render",this._processRender()},e.created&&e.created.call(this)},send(t){let i,e;if("string"==typeof t)i=t;else{const{type:s,...a}=t;i=s,e=a}const{value:s}=this.state,a=this._stateMachine.transition(s,i)||s,n=this._stateMachineDefinition.actions&&this._stateMachineDefinition.actions[a]?()=>this._stateMachineDefinition.actions[a].call(this,e):()=>{};this.setState({value:a},n)}}}export{withStateMachine};
1+
class t{constructor(t){this.machine=t}get initialState(){return this.machine.initial||Object.keys(this.machine.states)[0]}transition(t,e){return this.machine.states[t].on[e]}}function withStateMachine(e,i){return{...i,created(){this._stateMachineDefinition=e,this._stateMachine=new t(e),this.initialState=this._stateMachine.initialState,this.state={context:{},matches(t){return t===this.value},value:this.initialState},this.setState=(t,e)=>{if(!t||"object"!=typeof t)return;const i=this.updated;e&&(this.updated=()=>{e.call(this),this.updated=i||void 0}),this.status="transaction";for(const e in t)"value"===e?this.state[e]=t[e]:this.state.context[e]&&this.state.context[e]===t[e]||(this.state.context[e]=t[e]);this.status="render",this._processRender()},i.created&&i.created.call(this)},send(t){let e,i;if("string"==typeof t)e=t;else{const{type:s,...a}=t;e=s,i=a}const{value:s}=this.state,a=this._stateMachine.transition(s,e)||s,n="object"==typeof a&&a.target?a.target:a,h="object"==typeof a&&a.action?a.action:a,c=this._stateMachineDefinition.actions&&this._stateMachineDefinition.actions[h]?()=>this._stateMachineDefinition.actions[h].call(this,i):()=>{};this.setState({value:n},c)}}}export{withStateMachine};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ficusjs/state",
3-
"version": "1.0.1",
3+
"version": "1.1.0",
44
"description": "State functions for FicusJS",
55
"type": "module",
66
"main": "dist/index.mjs",

src/with-state-machine.mjs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ export function withStateMachine (stateMachineDefinition, options) {
4848
}
4949
const { value } = this.state
5050
const nextState = this._stateMachine.transition(value, eventType) || value
51-
const action = this._stateMachineDefinition.actions && this._stateMachineDefinition.actions[nextState]
52-
? () => this._stateMachineDefinition.actions[nextState].call(this, eventPayload)
51+
const nextStateValue = typeof nextState === 'object' && nextState.target ? nextState.target : nextState
52+
const nextStateAction = typeof nextState === 'object' && nextState.action ? nextState.action : nextState
53+
const action = this._stateMachineDefinition.actions && this._stateMachineDefinition.actions[nextStateAction]
54+
? () => this._stateMachineDefinition.actions[nextStateAction].call(this, eventPayload)
5355
: () => {}
54-
this.setState({ value: nextState }, action)
56+
this.setState({ value: nextStateValue }, action)
5557
}
5658
}
5759
}

test/unit/with-state-machine-commands.spec.mjs renamed to test/unit/with-state-machine-actions.spec.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ test('send method to person', t => {
5757
t.is(t.context.state.context.name, 'Matt')
5858
})
5959

60+
test('send method to person setting the action name', t => {
61+
t.context._processRender = function () {
62+
this.updated()
63+
}.bind(t.context)
64+
stateMachineDefinition.states.idle.on.CLICK = {
65+
target: 'loading',
66+
action: 'anyActionName'
67+
}
68+
stateMachineDefinition.actions.anyActionName = function () {
69+
this.setState({ name: 'Matt' }, () => this.send('RESOLVE'))
70+
}
71+
t.context.send('CLICK')
72+
t.truthy(t.context.state.matches('person'))
73+
t.is(t.context.state.context.name, 'Matt')
74+
stateMachineDefinition.states.idle.on.CLICK = 'loading'
75+
})
76+
6077
test('send method to error', t => {
6178
t.context._processRender = function () {
6279
this.updated()

0 commit comments

Comments
 (0)