choo-pull

Wrap handlers to use pull-stream

Usage no npm install needed!

<script type="module">
  import chooPull from 'https://cdn.skypack.dev/choo-pull';
</script>

README

choo-pull stability

npm version build status test coverage downloads js-standard-style

Wrap handlers to use pull-stream in a choo plugin. This is intended to go beyond basic choo usage, and tread into the domain of managing asynchronous complexity using streams / FRP.

While streams code takes longer to write up front, resulting code is generally stateless, pretty damn fast and surprisingly reusable. pull-streams are a minimal version of streams that weigh 200 bytes and handle backpressure phenomenally.

Usage

const pull = require('choo-pull')
const choo = require('choo')

const app = choo()
app.use(pull())

const tree = app.start()
document.body.appendChild(tree)

Now each handler in a model expects a valid through pull-stream to be returned synchronously. Initial data will be passed as the source, errors handling and done() calls are appended in the sink:

const through = require('pull-through')
const ws = require('pull-ws')
const xhr = require('xhr')

module.exports = {
  namespace: 'my-model',
  state: {
    count: 0
  },
  reducers: {
    increment: (data, state) => ({ count: state.count + data }),
    decrement: (data, state) => ({ count: state.count - data }),
  },
  subscriptions: {
    getDataFromSocket: (Send$) => {
      const ws$ = Ws$('wss://echo.websocket.org')
      return pull(ws$, Deserialize$(), Send$('performXhr'))
    }
  },
  effects: {
    performXhr: (state, Send$) => pull(Xhr$(), Deserialize$())
  }
}

function Xhr$ () {
  return through((data, cb) => {
    xhr('/foo/bar', { data: data }, (err, res) => {
      if (err) return cb(err)
      cb(null, res)
    })
  })
}

function Deserialize$ () {
  return through((data, cb) {
    try {
      cb(null, JSON.parse(data))
    } catch (e) {
      cb(e)
    }
  })
}

function Ws$ (url) {
  return ws(new window.WebSocket(url))
}

Using send()

Like all other API methods, so too does the send() method become a pull-stream. More specifically it becomes a through stream that takes the action name as the sole arugment, and pushes any results into any a connecting through or sink stream:

const through = require('pull-through')

module.exports = {
  state: {
    count: 0
  },
  reducers: {
    bar: (state) => ({ state.count + data })
  },
  effects: {
    callBar: (state, prev, Send$) => Send$('bar'),
    callFoo: (state, prev, Send$) => Send$('foo')
  }
}

// send('callFoo', 1)
// => state.count = 1

API

hooks = pull(opts)

Create an object of hooks that can be passed to app.use(). Internally ties into the following hooks:

  • wrapSubscriptions: changes the API of subscriptions to be (Send$)
  • wrapEffects: changes the API of effects to be (state, Send$)

The following options can be passed:

  • opts.subscriptions: default: true. Determine if subscriptions should be wrapped
  • opts.effects: default: true. Determine if effects should be wrapped

Incrementally enabling options can be useful when incrementally upgrading from a CSP-style codebase to a reactive / streaming one.

pull.subscription(subscription)

Wrap a single subscription. Useful to incrementally upgrade a CSP-style codebase to a reactive / streaming one.

pull.effect(effect)

Wrap a single effect. Useful to incrementally upgrade a CSP-style codebase to a reactive / streaming one.

FAQ

Why aren't reducers wrapped in pull-streams?

In choo@3 the internal workings demand that data always be returned synchronously. Because pull-stream returns data in a callback, reducers cannot be wrapped. Perhaps at some point we'll allow for a hybrid API, but at this point it's frankly not possible.

Installation

$ npm install choo-pull

License

MIT