Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Node-State: A finite state machine (FSM) implementation for Node.js and CoffeeScript

Node-State is intended as a rough port of the Akka FSM module available for Scala. While other FSM implementation exist for Node, none seemed to offer the flexibility and the clear DSL offered by Akka. This project is an attempt at bringing that to the Node/CoffeeScript world.

installation

npm install node-state

concepts

Node-State has three main concepts: States, Events, and Transitions. At any one time, a state-machine will be in one of several pre-defined states. Each state has one or more events that it will listen for and respond to until transitioning to the next state. Transitions are special events that occur in between moving from one state to the next. Transitions can be used to set-up initial data for a new state, intercept a transition and redirect to another state, or handle loic that may not necessarily belong in a specific state.

defining a new state-machine

State machines use CoffeeScript's class and inheritance system, and should inherit from NodeState. While it is certainly possible to implement this inheritance using plain javascript, that is beyond the scope of this documentation, and is not recommended.

adding states and events

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@raise'MyCustomEvent', data
MyCustomEvent: (data) ->#do something

In the example above, we've created a new state machine with 2 states: A and B. The keys under each state are the names of events to which the state will respond. All states by default will listen for an Enter event, which is called automatically upon entering the new state. In the Enter event of state A, we see the @goto method. @goto will unregister event listeners for state A, and enter state B. The second argument to @goto is data to be passed to the next state. Upon entering state B, we see the @raise method. @raise raises an event which will be responded to by the current state, if an appropriate event has been registered. Again, the second argument may be used to pass new data to the next event handler. In both @raise and @goto, the second argument is optional. Omitting it will pass along the data received by the current event handler.

adding transitions

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'C:Enter: (data) ->@goto'D'D:Enter: (data) ->transitions:A:B: (data, callback) ->#do setup for new state'*':A: (data, callback) ->D: (data, callback) ->'*': (data, callback) ->C:'*': (data, callback) ->

Above, we have defined transitions from A -> B, * -> A, * -> D, * -> *, and C -> *. The * is a wildcard, it means "any state". There are a few important things to note about transitions. First, they are called in between states, that is after all event handlers have been unregistered from the previous state, but before registering new handlers and entering the next state. Second, only the single most applicable transition will be called, not all matching transitions. Order of precedence, from most to least important, is as follows:

  1. Explicitly named 'from' and 'to' states, i.e. A -> B
  2. Wildcard 'from' state and explicitly named 'to' state, i.e. '*' -> B
  3. Explicitly named 'from' state and wildcard 'to' state, i.e. A -> '*'
  4. Wildcard 'from' and 'to' states, i.e. '*' -> '*'

configuring and running your state-machine

The NodeState constructor supports an optional configuration object, which supports 3 properties.

  • autostart - Defaults to false. This parameter determines whether the state machine should automatically activate and enter the initial state, or if it should wait for the start() method to be called.
  • initial_data - Defaults to an empty object ( {} ). Use this to specify any data that might be needed by the initial state.
  • initial_state - The name of the first state that the machine should enter. By default, this will be the name of the first state defined in the states list.
  • sync_goto - when you issue goto the new state's Enter function is called syncrhonously, in the current run-loop iteration
fsm=newMyStateMachineautostart:trueinitial_data:'I can be data of any type, but default to {}'initial_state:'B'

available methods

Note that any of the following methods can be called from outside of the state machine by replacing @ (the CoffeeScript shortcut for this) with a reference to the state machine. example:

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@goto'B', { key:'new data'}
B:Enter: (data) ->@goto'C'fsm=newMyStateMachinefsm.start()
fsm.wait300fsm.stop()
  • @goto(state_name, [data]) - Described previously, this signals the state machine to begin transitioning from the current state to state_name. @goto takes an optional data argument to send new data to the next state.

  • @raise(event_name, [data]) - Described previously, this raises an event with the name specified by event_name, and optionally passes new data to that event handler. Note that @raise does not cause a change in state, and only the active state's event handlers can respond to the event that has been raised.

  • @wait(timeout_milliseconds, [data]) - Sleeps for the specified timeout_milliseconds before raising the WaitTimeout event. WaitTimeout's event handler is defined slightly differently than most, as it has an additional parameter for the timeout value.

classMyStateMachineextendsNodeStatestates:A:Enter: (data) ->@wait300, { key:'new data'}
WaitTimeout: (timeout, data) ->console.log timeout
B:Enter: (data) ->#do something
  • @unwait - Cancels the current wait operation. Usually, the combination of @wait/@unwait is used if you are waiting a specified time period for other events to come in. @unwait would be used once you've received an event of interest and no longer want to respond to the timer.

  • @start - Kicks off the transition to the initial state.

  • @stop - Unregisters all event handlers for the state machine, effectively turning it off. In the future, pre- and post-stop event hooks may be added to allow for additional cleanup during shutdown.

History

  • v1.4.3
    • fix bug in stop
  • v1.4.1
    • properly build js file, upgrade!
  • v1.4.0
    • added enable/disable methods
    • rewrote tests to use mocha/chai
  • v1.3.0
    • bugfixes and sync_goto option to constructor

Credits

  • originally published by Nick Fisher

About

finite state machine (fsm) implementation for node.js

Resources

Stars

48 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages