@peter.naydenov/fsm 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/Changelog.md +90 -0
  2. package/LICENSE +21 -0
  3. package/Migration.guide.md +86 -0
  4. package/README.md +96 -173
  5. package/README_v.4.x.x.md +491 -0
  6. package/blueprint.md +22 -0
  7. package/coverage/lcov-report/index.html +1 -1
  8. package/coverage/lcov-report/src/index.html +1 -1
  9. package/coverage/lcov-report/src/index.js.html +1 -1
  10. package/coverage/lcov-report/src/methods/_getChain.js.html +1 -1
  11. package/coverage/lcov-report/src/methods/_onUpdateTask.js.html +1 -1
  12. package/coverage/lcov-report/src/methods/_setTransitions.js.html +1 -1
  13. package/coverage/lcov-report/src/methods/_transit.js.html +1 -1
  14. package/coverage/lcov-report/src/methods/_triggerCacheUpdate.js.html +1 -1
  15. package/coverage/lcov-report/src/methods/_updateStep.js.html +1 -1
  16. package/coverage/lcov-report/src/methods/_warn.js.html +1 -1
  17. package/coverage/lcov-report/src/methods/exportState.js.html +1 -1
  18. package/coverage/lcov-report/src/methods/getState.js.html +1 -1
  19. package/coverage/lcov-report/src/methods/ignoreCacheUpdates.js.html +1 -1
  20. package/coverage/lcov-report/src/methods/importState.js.html +1 -1
  21. package/coverage/lcov-report/src/methods/index.html +1 -1
  22. package/coverage/lcov-report/src/methods/index.js.html +1 -1
  23. package/coverage/lcov-report/src/methods/off.js.html +1 -1
  24. package/coverage/lcov-report/src/methods/on.js.html +1 -1
  25. package/coverage/lcov-report/src/methods/reset.js.html +1 -1
  26. package/coverage/lcov-report/src/methods/setDependencies.js.html +1 -1
  27. package/coverage/lcov-report/src/methods/update.js.html +1 -1
  28. package/coverage/tmp/coverage-51040-1691437648869-0.json +1 -0
  29. package/package.json +8 -7
  30. package/src/main.js +75 -0
  31. package/src/methods/_getChain.js +3 -2
  32. package/src/methods/_onUpdateTask.js +1 -1
  33. package/src/methods/_setTransitions.js +3 -3
  34. package/src/methods/_transit.js +7 -4
  35. package/src/methods/_triggerCacheUpdate.js +2 -0
  36. package/src/methods/_updateStateData.js +43 -0
  37. package/src/methods/_updateStep.js +10 -13
  38. package/src/methods/_warn.js +1 -1
  39. package/src/methods/exportState.js +5 -8
  40. package/src/methods/extractList.js +26 -0
  41. package/src/methods/getDependencies.js +10 -0
  42. package/src/methods/importState.js +3 -1
  43. package/src/methods/index.js +23 -15
  44. package/src/methods/reset.js +3 -2
  45. package/src/methods/setDependencies.js +1 -1
  46. package/src/methods/update.js +14 -4
  47. package/src/queryStateUpdate.js +38 -0
  48. package/coverage/tmp/coverage-29775-1668580484020-0.json +0 -1
  49. package/src/index.js +0 -41
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@peter.naydenov/fsm",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "Simple Finite State Machine",
5
- "main": "src/index.js",
5
+ "main": "src/main.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "test": "mocha test",
@@ -11,13 +11,14 @@
11
11
  "author": "Peter Naydenov",
12
12
  "license": "MIT",
13
13
  "dependencies": {
14
- "@peter.naydenov/walk": "3.0.1",
15
- "ask-for-promise": "1.3.1"
14
+ "@peter.naydenov/dt-queries": "^1.0.2",
15
+ "ask-for-promise": "1.3.1",
16
+ "dt-toolbox": "^7.1.3"
16
17
  },
17
18
  "devDependencies": {
18
- "c8": "^7.12.0",
19
- "chai": "4.3.7",
20
- "mocha": "10.1.0"
19
+ "c8": "^8.0.1",
20
+ "chai": "4.3.8",
21
+ "mocha": "10.2.0"
21
22
  },
22
23
  "repository": {
23
24
  "type": "git",
package/src/main.js ADDED
@@ -0,0 +1,75 @@
1
+ import askForPromise from 'ask-for-promise' // Docs: https://github.com/PeterNaydenov/ask-for-promise
2
+ import dtbox from 'dt-toolbox' // Docs: https://github.com/PeterNaydenov/dt-toolbox
3
+ import {
4
+ splitSegments
5
+ , joinSegments
6
+ , updateState
7
+ } from '@peter.naydenov/dt-queries' // Docs: https://github.com/PeterNaydenov/dt-queries
8
+ import methods from './methods/index.js'
9
+
10
+ const
11
+ MISSING_STATE = 'N/A' // State name if not defined
12
+ , walk = dtbox.getWalk () // Docs: https://github.com/PeterNaydenov/walk
13
+ ;
14
+
15
+
16
+ function Fsm ({init, behavior, stateData={}, debug, stateDataFormat={as:'std'} }, lib={} ) {
17
+ const
18
+ fsm = this
19
+ , api = {}
20
+ ;
21
+
22
+ fsm.state = init || MISSING_STATE
23
+ fsm.initialState = init || MISSING_STATE
24
+ fsm.stateDataFormat = stateDataFormat // Used in 'extractList' methods (library and transitions).
25
+ fsm.lock = false // Switch 'ON' during transition in progress. Write other updates in cache.
26
+ fsm.cache = [] // cached 'update' actions
27
+
28
+ fsm.dependencies = {
29
+ walk
30
+ , dtbox
31
+ , askForPromise
32
+ , query : { splitSegments, joinSegments, updateState }
33
+ }
34
+
35
+ fsm.callback = {
36
+ update : []
37
+ , transition : []
38
+ , positive : []
39
+ , negative : []
40
+ }
41
+
42
+ for ( let k in methods ) { // Separate public and private methods.
43
+ if ( k.startsWith('_') ) fsm[k] = methods[k](fsm) // Methods with '_' are private.
44
+ else api[k] = methods[k](fsm)
45
+ }
46
+ fsm.api = api
47
+ fsm.stateData = dtbox.init ( stateData ).query ( splitSegments )
48
+ fsm.initialStateData = dtbox.init ( stateData ).query ( splitSegments )
49
+
50
+ const {transitions, nextState, chainActions } = fsm._setTransitions ( behavior, lib );
51
+ if ( debug ) {
52
+ fsm._warn ( transitions )
53
+ global.debugFSM = fsm
54
+ }
55
+
56
+ fsm.transitions = transitions
57
+ fsm.nextState = nextState
58
+ fsm.chainActions = chainActions
59
+ return api
60
+ } // Fsm func.
61
+
62
+
63
+
64
+ Fsm.dependencies = {
65
+ walk
66
+ , dtbox
67
+ , askForPromise
68
+ , query : { splitSegments, joinSegments, updateState }
69
+ }
70
+
71
+
72
+
73
+ export default Fsm
74
+
75
+
@@ -1,5 +1,6 @@
1
- function _getChain () {
2
- return function ( chainActions, key ) {
1
+ function _getChain ( fsm ) {
2
+ return function _getChain ( key ) {
3
+ const chainActions = fsm.chainActions;
3
4
  if ( !chainActions[key] ) return false
4
5
  return chainActions [key]
5
6
  }} // getChain func.
@@ -2,7 +2,7 @@ function _onUpdateTask ( fsm ) {
2
2
  return function _onUpdateTask ( data ) {
3
3
  const
4
4
  cb = fsm.callback
5
- , updateCallbacks = fsm.askForPromise ( cb['update'] )
5
+ , updateCallbacks = fsm.dependencies.askForPromise ( cb['update'] )
6
6
  ;
7
7
 
8
8
  cb [ 'update' ].forEach ( (fn,i) => {
@@ -1,14 +1,14 @@
1
1
  function _setTransitions () {
2
- return function ( table, lib ) { // ( machineTable, transitionLib ) --> {transitions, nextState, chainActions}
2
+ return function ( behavior, lib ) { // ( machineTable, transitionLib ) --> {transitions, nextState, chainActions}
3
3
  // *** Converts initial FSM data to useful fsm objects.
4
4
  let
5
5
  transitions = {}
6
6
  , nextState = {}
7
7
  , chainActions = {}
8
8
  ;
9
- table.forEach ( el => {
9
+ behavior.forEach ( line => {
10
10
  const
11
- [ from, action, next, transitionName, alt ] = el
11
+ [ from, action, next, transitionName, alt ] = line
12
12
  , transition = lib [ transitionName ]
13
13
  , key = `${from}/${action}`
14
14
  ;
@@ -1,13 +1,16 @@
1
1
  function _transit (fsm ) {
2
- return function ( task, key, dt ) { // ( promiseObj, transitionKey, additionalData ) -> void
2
+ return function () { // -> void
3
3
  // *** Execute transition if exists. Ignore all non-predefined cases
4
4
  const
5
- dependencies = fsm.dependencies
6
- , stateData = { ...fsm.stateData }
5
+ [ task, key,...args] = arguments // ( promiseObj, transitionKey, ...additionalData )
6
+ , { state, stateData, dependencies } = fsm
7
7
  , transition = fsm.transitions [ key ]
8
+ , extractList = fsm.api.extractList
9
+ , system = { task, state, extractList, dependencies }
8
10
  ;
9
- if ( typeof transition === 'function' ) transition ( task, dependencies, stateData, dt )
11
+ if ( typeof transition === 'function' ) transition ( system, ...args )
10
12
  else task.done ({ success : false }) // ignore all non-predefined cases
13
+
11
14
  }} // _transit func.
12
15
 
13
16
 
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  function _triggerCacheUpdate ( fsm ) {
2
4
  return function () {
3
5
  if ( fsm.cache.length !== 0 ) {
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ function _updateStateData ( fsm ) {
4
+ /**
5
+ * Updates the state data.
6
+ * @param {object} updateObject - The update object.
7
+ * @returns {dt-object} - The updated state data.
8
+ */
9
+ return function _updateStateData ( updateObject ) {
10
+
11
+ const { dtbox, query } = fsm.dependencies;
12
+ // Recognize the updateObject type: dt-object, dt-model or javascript object;
13
+ let updateType = 'javascriptObject';
14
+ if ( updateObject.export ) updateType = 'dt-object'
15
+ if (
16
+ updateObject instanceof Array &&
17
+ updateObject[0][0] === updateObject[0][2] &&
18
+ updateObject.every ( line => line.length === 4)
19
+ ) {
20
+ updateType = 'dt-model'
21
+ }
22
+ if (
23
+ updateType === 'javascriptObject' &&
24
+ updateObject instanceof Array
25
+ ) { // Wrong updateObject! Should be an object, because property name of top-level is the name of the segment.
26
+ console.error ( 'State update failed. Reason: Received an array. Expectation: Object where top-level property name is the name of the data segment.' )
27
+ return fsm.stateData
28
+ }
29
+
30
+ // Setup the update segments and root object of dt-model
31
+ if ( ['javascriptObject'].includes(updateType) ) updateObject = dtbox.init ( updateObject ).export ()
32
+ if ( ['javascriptObject','dt-model'].includes(updateType) ) updateObject = dtbox.load ( updateObject )
33
+ if ( ['javascriptObject','dt-model','dt-object'].includes(updateType) ) updateObject = updateObject.query ( query.splitSegments )
34
+ // Update the state data
35
+
36
+ return fsm.stateData.query ( query.updateState, updateObject )
37
+ }} // _updateStateData func.
38
+
39
+
40
+
41
+ export default _updateStateData
42
+
43
+
@@ -1,12 +1,14 @@
1
1
  function _updateStep ( fsm ) {
2
- return function ( updateTask, action, dt ) {
2
+ return function ( updateTask, action, data ) {
3
3
  const
4
- task = fsm.askForPromise ()
4
+ { askForPromise } = fsm.dependencies
5
+ , task = askForPromise ()
5
6
  , key = `${fsm.state}/${action}`
6
- , cb = fsm.callback
7
+ , cb = fsm.callback // Event-based callbacks
7
8
  ;
9
+
8
10
  fsm.lock = true
9
- fsm._transit ( task, key, dt )
11
+ fsm._transit ( task, key, data )
10
12
  task.onComplete (
11
13
  result => {
12
14
  /**
@@ -15,18 +17,17 @@ return function ( updateTask, action, dt ) {
15
17
  * success - boolean. Is it transition successful.
16
18
  * ? stateData - object. Flat object with fsm state values.
17
19
  * ? response - object. External data as response of transition if needed.
18
- * ? command - string. Next action if function-chaining. (depricated!) Use a chainAction inside the logic table
19
20
  * }
20
21
  */
21
-
22
+
22
23
  let
23
- chainActions = fsm._getChain ( fsm.chainActions, key)
24
+ chainActions = fsm._getChain ( key )
24
25
  , data = result.response
25
26
  ;
26
-
27
27
  if ( result.success ) {
28
28
  fsm.state = fsm.nextState [ key ]
29
- if ( result.stateData ) fsm.stateData = result.stateData
29
+ if ( result.stateData != null ) fsm.stateData = fsm._updateStateData ( result.stateData )
30
+
30
31
  cb [ 'positive' ].forEach ( fn => fn ( fsm.state, data) )
31
32
  cb [ 'transition' ].forEach ( fn => fn ( fsm.state, data) )
32
33
  if ( chainActions && chainActions[0] ) {
@@ -42,10 +43,6 @@ return function ( updateTask, action, dt ) {
42
43
  return
43
44
  }
44
45
  }
45
- if ( result.command ) {
46
- fsm._updateStep ( updateTask, result.command, data )
47
- return
48
- }
49
46
  updateTask.done ( data )
50
47
  }) // task onComplete
51
48
 
@@ -1,7 +1,7 @@
1
1
  function _warn ( fsm ) {
2
2
  return function ( transitions ) {
3
3
  // *** Warn if transition function used in description table is not defined.
4
- Object.entries ( transitions ).forEach ( ([v,k]) => {
4
+ Object.entries ( transitions ).forEach ( ([k,v]) => {
5
5
  if ( v == null ) console.log ( `Warning: Transition for ${k} is not defined` )
6
6
  })
7
7
  }} // warn func.
@@ -1,13 +1,10 @@
1
- function exportState (fsm) {
2
- return function () {
1
+ function exportState ( fsm ) {
2
+ return function exportState () {
3
+ const { query } = fsm.dependencies;
3
4
  // *** Export internal flags and state as an object
4
- const
5
- state = fsm.state
6
- , stateData = { ...fsm.stateData }
7
- ;
8
5
  return {
9
- state
10
- , stateData
6
+ state: fsm.state
7
+ , stateData : fsm.stateData.query ( query.joinSegments ).export ()
11
8
  }
12
9
  }} // exportState func.
13
10
 
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ function extractList ( fsm ) {
4
+ /**
5
+ * Returns a list of requested segments from the state data.
6
+ * @param {array.<string>} requestedSegments - The list of requested segments.
7
+ * @param {string|boolean} [options=false] - The model option for the query.
8
+ * @returns {array.<[object,dt-object,string,number,null]>} - The list of requested segments.
9
+ */
10
+ return function extractList ( requestedSegments, options=false ) {
11
+ // *** Returns a list of requested segments
12
+
13
+
14
+ const query = fsm.dependencies.query;
15
+ // v--- It's a debug case. Return all data to see what is available.
16
+ if ( arguments.length == 0 ) return fsm.stateData.query ( query.joinSegments ).model (() => ({as:'std'}))
17
+
18
+ if ( !options ) return fsm.stateData.extractList ( requestedSegments, fsm.stateDataFormat )
19
+ else return fsm.stateData.extractList ( requestedSegments, options )
20
+ }} // extractList func.
21
+
22
+
23
+
24
+ export default extractList
25
+
26
+
@@ -0,0 +1,10 @@
1
+ function getDependencies (fsm) {
2
+ return function getDependencies () {
3
+ return fsm.dependencies
4
+ }} // getDependencies func.
5
+
6
+
7
+
8
+ export default getDependencies
9
+
10
+
@@ -1,10 +1,12 @@
1
1
  function importState (fsm) {
2
2
  return function ( {state, stateData } ) {
3
3
  // *** Import existing state to fsm
4
+ const { dtbox, query } = fsm.dependencies;
4
5
  if ( state ) {
5
6
  fsm.state = state
6
7
  if ( stateData ) {
7
- fsm.stateData = { ...stateData }
8
+ const update = dtbox.load ( stateData ).query ( query.splitSegments )
9
+ fsm.stateData = fsm.stateData.query ( query.updateState, update )
8
10
  }
9
11
  }
10
12
  }} // importState func.
@@ -1,25 +1,31 @@
1
- import _setTransitions from './_setTransitions.js'
2
- import _updateStep from './_updateStep.js'
3
- import _warn from './_warn.js'
4
- import _transit from './_transit.js'
5
- import _getChain from './_getChain.js'
1
+ import _setTransitions from './_setTransitions.js'
2
+
3
+ import _updateStateData from './_updateStateData.js'
4
+ import _updateStep from './_updateStep.js'
5
+ import _warn from './_warn.js'
6
+ import _transit from './_transit.js'
7
+ import _getChain from './_getChain.js'
6
8
  import _triggerCacheUpdate from './_triggerCacheUpdate.js'
7
- import _onUpdateTask from './_onUpdateTask.js'
8
- import setDependencies from './setDependencies.js'
9
- import on from './on.js'
10
- import off from './off.js'
11
- import importState from './importState.js'
12
- import exportState from './exportState.js'
13
- import update from './update.js'
14
- import reset from './reset.js'
9
+ import _onUpdateTask from './_onUpdateTask.js'
10
+
11
+ import setDependencies from './setDependencies.js'
12
+ import getDependencies from './getDependencies.js'
13
+ import on from './on.js'
14
+ import off from './off.js'
15
+ import importState from './importState.js'
16
+ import exportState from './exportState.js'
17
+ import update from './update.js'
18
+ import reset from './reset.js'
15
19
  import ignoreCachedUpdates from './ignoreCacheUpdates.js'
16
- import getState from './getState.js'
20
+ import getState from './getState.js'
21
+ import extractList from './extractList.js'
17
22
 
18
23
 
19
24
 
20
25
  const fn = {
21
26
  // *** "Private" methods
22
27
  _setTransitions // Convert machine configuration and transition library in a internal fsm structures
28
+ , _updateStateData // Update stateData dt-object
23
29
  , _updateStep // Process results of transitions
24
30
  , _warn // Warn on issues if "debug:true" in machine configuration
25
31
  , _transit // Executes a transition if it is defined
@@ -27,7 +33,8 @@ const fn = {
27
33
  , _triggerCacheUpdate // Call next transition from the cache
28
34
  , _onUpdateTask // Executes on update success. Used in "update" and "_triggerCacheUpdate"
29
35
  // *** Public methods
30
- , setDependencies // Add objects in dependencies
36
+ , setDependencies // Add objects to dependencies object
37
+ , getDependencies // Returns the dependencies object
31
38
  , on // Register callback functions on: 'update', 'transition', 'negative', 'positive'
32
39
  , off // Remove callbacks
33
40
  , importState // Get state and data from outside
@@ -36,6 +43,7 @@ const fn = {
36
43
  , reset // Change state and stateData to initial values
37
44
  , ignoreCachedUpdates // Disable update records in the cache
38
45
  , getState // Return a state value
46
+ , extractList // Return a stateData values
39
47
  }
40
48
 
41
49
 
@@ -1,8 +1,9 @@
1
+ 'use strict'
1
2
  function reset ( fsm ) {
2
3
  return function () {
4
+ const { dtbox } = fsm.dependencies;
3
5
  fsm.state = fsm.initialState
4
- fsm.stateData = {}
5
- Object.entries ( fsm.initialStateData ).forEach ( ([key,value]) => fsm.stateData[key] = value )
6
+ fsm.stateData = dtbox.load ( fsm.initialStateData.export() )
6
7
  }} // reset func.
7
8
 
8
9
 
@@ -1,5 +1,5 @@
1
1
  function setDependencies ( fsm ) {
2
- return function ( deps ) {
2
+ return function setDependencies ( deps ) {
3
3
  fsm.dependencies = { ...fsm.dependencies, ...deps }
4
4
  }} // setDependencies func.
5
5
 
@@ -1,13 +1,23 @@
1
1
  function update ( fsm ) {
2
- return function ( action, dt ) { // () -> Promise<transitionResponse>
2
+ /**
3
+ * @method update
4
+ * @description Executes transition-functions or sets transition steps into a cache queue
5
+ * @param {string} action - action name.
6
+ * @param {any} dt - any upcoming data.
7
+ * @returns {Promise} - Promise<transitionResponse>
8
+ */
9
+ return function update ( action, dt ) { // () -> Promise<transitionResponse>
3
10
  // *** Executes transition-functions and transition-chains.
4
- const updateTask = fsm.askForPromise ();
5
-
11
+ const
12
+ { askForPromise } = fsm.dependencies
13
+ , updateTask = askForPromise ()
14
+ ;
15
+
6
16
  if ( fsm.lock ) {
7
17
  fsm.cache.push ( { updateTask, action, dt })
8
18
  return updateTask.promise
9
19
  }
10
-
20
+
11
21
  fsm._updateStep ( updateTask, action, dt )
12
22
  updateTask.onComplete ( data => fsm._onUpdateTask ( data ) )
13
23
  return updateTask.promise
@@ -0,0 +1,38 @@
1
+ 'use strict'
2
+
3
+ function queryStateUpdate ( fsm ) {
4
+ return function queryStateUpdate ( stateData, updateObject ) {
5
+ const { dtbox, query } = fsm.dependencies;
6
+
7
+ // Recognize the updateObject type: dt-object, dt-model or javascript object;
8
+ let updateType = 'javascriptObject';
9
+ if ( updateObject.export ) updateType = 'dt-object'
10
+ if (
11
+ updateObject instanceof Array &&
12
+ updateObject[0] === updateObject[3] &&
13
+ updateObject.every ( line => line.length === 4)
14
+ ) {
15
+ updateType = 'dt-model'
16
+ }
17
+ if (
18
+ updateType === 'javascriptObject' &&
19
+ updateObject instanceof Array
20
+ ) { // Wrong updateObject! Should be an object, because property name of top-level is the name of the segment.
21
+ console.error ( 'State update failed. Reason: Received an array. Expectation: Object where top-level property name is the name of the data segment.' )
22
+ return
23
+ }
24
+
25
+ // Setup the update segments and root object of dt-model
26
+ if ( ['javascriptObject'].includes(updateType) ) updateObject = dtbox.init(updateObject).export ()
27
+ if ( ['javascriptObject','dt-model'].includes(updateType) ) updateObject = dtbox.load ( updateObject )
28
+ if ( ['javascriptObject','dt-model', 'dt-object' ].includes(updateType) ) updateObject = updateObject.query ( query.splitSegments )
29
+
30
+ return stateData.query ( query.updateState, updateObject )
31
+
32
+ }} // queryStateUpdate func.
33
+
34
+
35
+
36
+ export default queryStateUpdate
37
+
38
+