Awesome
choo-pull
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 ifsubscriptions
should be wrapped - opts.effects: default:
true
. Determine ifeffects
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