stream-tree is a way to write functions as ASCII flowcharts in JavaScript.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
scripts
src
test
.babelrc
.gitignore
.markdown-doctest-setup.js
LICENSE
README.md
index.html
index.js
package.json

README.md

stream-tree

stability

stream-tree is a way to write functions as ascii flowcharts in JavaScript.

import diagram from 'stream-tree';

const doubleNumbers = diagram`
  {sources.numbers}
         |
 {.map(i => i * 2)}
         |
   doubledNumbers
`;

console.log(doubleNumbers({numbers: [1, 2, 3]}))
// => {doubledNumbers: [2, 4, 6]}

This is equivalent to this es6 code:

function doubleNumbers({numbers}) {
  return {
    doubledNumbers: numbers.map(i => i * 2)
  }
}

So diagram takes a string and returns a function that takes in a sources object and returns an object.

Why objects as arguments and return values?

stream-tree is designed to be used to write Cycle.js applications. A main function in Cycle.js takes in an object of streams and returns an object of streams.

Consider this counter example:

function main (sources) {
  const add$ = sources.DOM
    .select('.add')
    .events('click')
    .mapTo(+1);

  const subtract$ = sources.DOM
    .select('subtract')
    .events('click')
    .mapTo(-1);

  const change$ = xs.merge(add$, subtract$);

  const count$ = change$.fold((total, change) => total + change, 0);

  return {
    DOM: count$.map(renderView)
  }
}

function renderView (count) {
  return (
    div('.counter', [
      `Count: ${count}`,

      button('.add', 'Add'),
      button('.subtract', 'Subtract')
    ])
  );
}

We can rewrite this with stream-tree:

const main = diagram`
  Given: ${{xs, renderView}}

                  {sources.DOM}
                  /           \
   {.select('.add')}          {.select('.subtract')}
                 |              |
   {.events('click'}          {.events('click'}
                 |              |
        {.mapTo(+1)}          {.mapTo(-1)}
                  \            /
                 add$        subtract$
                    \        /
            {xs.merge(add$, subtract$})
                        |
    {.fold((total, change) => total + change, 0)}
                        |
                {.map(renderView)}
                        |
                       DOM
`;

We can also decompose this function into smaller functions:

const intent = diagram`
                  {sources.DOM}
                  /           \
   {.select('.add')}          {.select('.subtract')}
                 |              |
   {.events('click'}          {.events('click'}
                 |              |
                add$          subtract$
`;

const model = diagram`
  Given: ${{xs}}

        {sources.add$}        {sources.subtract$}
                 |              |
        {.mapTo(+1)}          {.mapTo(-1)}
                  \            /
                 add$        subtract$
                    \        /
            {xs.merge(add$, subtract$})
                        |
    {.fold((total, change) => total + change, 0)}
                        |
                      count$
`;

const view = diagram`
  Given: ${{renderView}}

                 {sources.count$}
                        |
                {.map(renderView)}
                        |
                      vtree$
`;

const main = diagram`
  Given: ${{xs, view}}

                    {sources}
                        |
                    {intent}
                        |
                     {model}
                        |
                     {view}
                        |
                    {.vtree$}
                        |
                       DOM
`;