ganglion-impulse

A JavaScript library that transmits impulses from a central ganglion, triggering chain of actions.

Usage no npm install needed!

<script type="module">
  import ganglionImpulse from 'https://cdn.skypack.dev/ganglion-impulse';
</script>

README

Ganglion Impulse

In anatomy, a ganglion (/ˈɡæŋɡliən/ gang-glee-ən; plural ganglia) is a nerve cell cluster.

A JavaScript library for registering fibers that transmit impulses from a central ganglion, triggering a chain of actions.

Ganglion is heavily inspired by Cerebral but does not include a data store or any React component integration as Cerebral does.

Ganglion is small (~1k minimised and gzipped) and it does not have any dependencies.

Why Use Ganglion

Ganglion can be used as the central nervous system of an application. It facilitates a clean and understandable definition of one way data flows through an application. It can utilise reusable action functions and manage asynchronous actions in series or parallel. If your async server operation is taking longer than expected it can let your app know it needs to give feedback to the user via a spinner or asking the user if they wish to cancel.

Ganglion is best when paired with an immutable data store, which it can pass to all actions via context provided to its constructor. For user interface rendering React works well, but other libraries could also be used.

Hooks on the ganglion allow operations to be executed before or after every impulse. These can be used to setup automatic re-rendering after mutative impulses have completed.

Installation

npm install ganglion-impulse

Include ganglion in your app with:

var Ganglion = require('ganglion-impulse');

For a client-side project you can use Browserify or webpack.

Usage

Example

// es6 example, but will also work with es5.

import Ganglion from 'ganglion-impulse';
import { sendCredentialsToServer, setUserInfo } from './custom-application-actions';

// create a new ganglion
let ganglion = new Ganglion();

// define some fibers
ganglion.fiber('loginClicked', sendCredentialsToServer, setUserInfo);

// send impulses
ganglion.impulse.loginClicked({ userName, password }).then(function () {
  console.log('impulse completed');
});

If actions return a Promise then ganglion will wait for them to resolve before calling the next action in the chain.

Parallel actions

Actions can run in parallel if passed as an array:

ganglion.fiber('buttonClicked',
  prepareAction, [parallelAction1,
                  parallelAction2], finalAction);

Sending Data

The first action receives the parameter passed when the impulse is initiated, subsequent actions receive the response from the previous action. The response from the final action is returned via a promise to the caller. When parallel actions are used, the response of both will be passed as an array to the next in the same order as the actions are defined on the fiber.

Context Data

Context information can be passed to the ganglion constructor which will then be made available to all actions called when an impulse is initiated:

// create a new ganglion with some context data
let ganglion = new Ganglion({
  context: { dataStore: {} }
});

ganglion.fiber('userChangedName', function updateUserName(userName) {
  this.dataStore.userName = userName;
});

The name of the current fiber is also available via the impulse's context:

ganglion.fiber('eventReceived', function logTheFiberName() {
  console.log(this.fiberName);
});

Context is initialised per impulse. Any mutations will be available to subsequent actions but will be discarded after the final action. The properties defined with original context passed to the ganglion constructor are retained across all impulses.

To make changes to the context after the ganglion is initialised you can use the addToContext method:

// create a new ganglion with some context data
let ganglion = new Ganglion({
  context: { dataStore: {} }
});

// add log method to the ganglion
ganglion.addToContext({
  log: logMethod
});

ganglion.fiber('userChangedName', function updateUserName(userName) {
  this.log(`The user changed their name to ${userName}`);
});

Any existing values with the same key will be overwritten.

Events

beforeImpulse and afterImpulse

beforeImpulse and afterImpulse events are fired by the ganglion before and after every impulse emitted. Handlers have the same signature as actions, but the return value is discarded.

let ganglion = new Ganglion();

// add beforeImpulse and afterImpulse event handlers
ganglion.on('beforeImpulse', function (data) {
  // data will be also be passed to the first action
  console.log(`${this.fiberName} impulse started`);
});
ganglion.on('afterImpulse', function (data) {
  // data is the value that was returned by the last action
  console.log(`${this.fiberName} impulse ended`);
});

slowAsyncActionStart and slowAsyncActionEnd

slowAsyncActionStart and slowAsyncActionEnd events are fired by the ganglion when async actions take a while to complete.

let slowActionHandler = function (isStart) {
  let status = isStart ? 'is running slow' : 'completed';
  console.log(`${this.fiberName} async action ${status}`);
};

let ganglion = new Ganglion({
  callSlowAsyncActionAfter: 500, // ms after which the
                                 // onSlowAsyncActionStart will be called
});

// add slowAsyncActionStart and slowAsyncActionEnd event handlers
ganglion.on('slowAsyncActionStart', slowActionHandler);
ganglion.on('slowAsyncActionEnd', slowActionHandler);

To ensure that the slow async action events are not triggered unnecessarily, callSlowAsyncActionAfter can be used to define after how many milliseconds should be hooks be called. By default this is set to 500ms.

error

error events are fired when async actions reject their promise.

let ganglion = new Ganglion();

// add error event handlers
ganglion.on('error', function (error) {
  console.error(`${this.fiberName} returned an error`, error);
});

Canceling an Impulse

Any action or event handler can cancel an impulse by setting cancelImpulse = true on the context:

ganglion.fiber('buttonClicked',
  firstAction,
  function secondAction(data) { this.cancelImpulse = true; },
  finalActionWontBeCalled);

Contributing

Checkout the git repository and install the build and test dependencies

npm install

Testing

The automated tests can be run via

npm test

Building

Ganglion uses es6 and should be transpiled prior to publishing

npm run build

Change Log

0.4.2

  • Added addToContext method

0.4.1

  • Emit error events
  • Removed debug code which can now be acheived with events
  • Added more tests

0.4.0

  • Allow alternative spelling of fiber/fibre
  • [Breaking Change] Changed hooks to events

0.3.1

  • Improved the documentation
  • Added the ability for any action or hook to cancel an impulse

0.3.0

  • Added onSlowAsyncActionStart and onSlowAsyncActionEnd hooks

0.2.0

  • Added onBeforeImpulse and onAfterImpulse hooks

0.1.1

  • Fixed dist build

0.1.0

  • Initial release

License

The MIT License (MIT)

Copyright (c) 2015 Garth Williams

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.