/salt

State And Logic Traversal, for today's infinite-application

Primary LanguageJavaScriptOtherNOASSERTION

Salt

State And Logic Traversal, for today's infinite-application.

by Bemi Faison

Build Status Gitter

Description

Salt is a state-based JavaScript library that offers unparalleled code organization and unprecedented flow control. No matter how you write code, the event loop sees it as a sequence of functions, invoked over time. Salt provides a simple pattern to describe those sequences, along with an API to control timing. The result is an async-agnostic flow of your functionality, in code, bridging the gap between specification and implementation.

Salt is designed to produce long-lived documents that can grow with your requirements. Traditional, bottom-up designs fail this test of architecture, because change inevitably destabilizes dependency models. Instead, Salt uses an autonomy model that groups isolated logic, and accommodates change with increasingly granular states. This top-down approach provides a holistic view of your application's functional relationships, along with the freedom and confidence to change them.

Usage

Use Salt to define and control your "program". Programs use structure to abstract functionality into state and logic; each lexical scope forms a state, and logic is declared via "tags" - i.e., keys prefixed by an underscore ("_"). Tags declare everything, from state behavior, access control rules, and - of course - bind callbacks to transitions.

Defining your Program

Begin by passing your program to a new Salt instance. The "hello world!" example below features a contrived number of states, in order to demonstrate additional features.

var helloSalt = new Salt({
  _data: 'phrase',
  piece: {
    _in: function () {
      this.data.phrase = 'hello';
    },
    together: function () {
      this.data.phrase += ' world!';
    },
    _tail: '@next'
  },
  speak: function () {
    console.log(this.data.phrase);
  },
  _out: function () {
    console.log('good-bye!');
  }
});

Internally, Salt compiles this into a private state-chart, where the following endpoints may now be traversed.

  1. "..//" at index 0
  2. "//" at index 1
  3. "//piece/" at index 2
  4. "//piece/together/" at index 3
  5. "//speak/" at index 4

The first two states exist in every Salt instance: the null and program-root states, respectively. As well, all instances start "on" the null state, which parents the program-root - the first state from your program. The state index reflects it's compilation order, and can be used to reference navigation targets.

Using state order in logic

While tag order is irrelevant, state order can matter in a program. Understandably, some developers are uncomfortable treating an object like an array. Thus, despite exhibiting the same FIFO object-member behavior, wary developers can rest assured that you Salt may be used with it's order-dependent features. Read more about key order preservation in the wiki.

For instance, with the example above, you could replace the spatial query "@next" with the relative query "../speak". The relative query requires a specific sibling; a state named "speak. The spatial query requires a specific relationship; the older/right-adjacent sibling state. Both retain a level of portability, but only one relies on sibling order.

Controlling your Salt instance

In order to execute your logic, direct your Salt instance toward a program state - i.e., one of the pre-compiled endpoints. Salt then observes the logic of states along it's navigation path, which can invoke functions based on how a state is traversed (or, the transition type). Navigation ends when the destination state has completed an "on" transition.

The example below uses the .go() method to direct our Salt instance towards the "//piece/together/" state.

helloSalt.go('//piece/together');
// hello world!

You can also inspect the .state property, in order to determine where your Salt instance is in your program.

console.log(helloSalt.state.path);  // "//speak/"
console.log(helloSalt.state.name);  // "speak"
console.log(helloSalt.state.index); // 4

To "exit" a program, direct Salt toward it's null state (at index 0). The null state parents the program root, allowing you to trigger any program entry/exit logic.

helloSalt.go(0);
// good-bye!

This program also uses the _data tag to define "scoped" data properties. While the .data.phrase property is used, it's no longer available when on the null state. This is because the Salt instance exited the state which declared the scoped property.

console.log(helloSalt.state.index); // 0
console.log(helloSalt.data.phrase); // undefined

Salt features a hybrid, declarative and procedural architecture, which offers many programmatic advantages and opportunities... it's extensible to boot! Please visit the online wiki to review the instance API and program tag glossary, for information on how to best use Salt in your code.

Installation

Salt works within modern JavaScript environments, including CommonJS (Node.js) and AMD (require.js). If it isn't compatible with your favorite runtime, please file a bug, or (better still) make a pull-request.

Dependencies

Salt depends on the Panzer library. Visit the Panzer project page, to learn about it's dependencies and requirements.

Salt also uses the following ECMAScript 5 features:

You will need to implement shims for these methods in unsupported environments - specifically , Internet Explorer versions 6, 7 & 8. (Augment.js shims these and other missing methods.)

Web Browsers

Use a <SCRIPT> tag to load the salt.min.js file in your web page. The file includes Salt dependencies for your convenience. Loading this file adds the Salt namespace to the global scope.

  <script type="text/javascript" src="path/to/salt.min.js"></script>
  <script type="text/javascript">
    // ... Salt dependent code ...
  </script>

Node.js

  • npm install salt if you're using npm
  • component install bemson/salt if you're using component
  • bower install salt if you're using Bower

AMD

Assuming you have a require.js compatible loader, configure an alias for the Salt module (the alias "salt" is recommended, for consistency). The salt module exports the Salt constructor, not a module namespace.

require.config({
  paths: {
    salt: 'libs/salt'
  }
});

Note: You will need to define additional aliases, in order to load Salt dependencies.

Then require and use the module in your application code:

require(['salt'], function (Salt) {
  // ... Salt dependent code ...
});

Warning: Do not load the minified file via AMD, since it includes Salt dependencies which themselves export modules. Use AMD optimizers like r.js in order to roll-up your dependency tree.

Testing

Salt has over 350 unit tests to inspire and guide your usage. They were written with Mocha, using Chai and Sinon (via the Sinon-chai plugin).

  • To browse test results, visit Salt on Travis-CI.
  • To run tests in Node, invoke npm test
  • To run tests in a browser: (1) install Salt, then (2) load test/index.html locally. (Unfortunately, the tests do not run in IE6, 7, or 8.)

Shout-outs

  • Peter Jones - Best. Sounding. Board. Ever.
  • Tom Longson - My first guinea-pig, some three years ago...
  • Malaney J. Hill - We demystified the m.e.s.s. in code: modes, exceptions, states, and steps.
  • WF2 crew - Your horrifically frustrating legacy code started all of this.

License

Salt is available under the terms of the MIT-License.

Copyright 2014, Bemi Faison

Bitdeli Badge