frēmit
An FRP-style event-stream-to-collections interface toolkit
Here's the basic idea. Your application can be expressed as
a series of reactions to inputs or events. This is the idea
behind event-driven programming. The idea behind this
library is to organize your program in such a way that
tracing the reaction to events becomes possible if not
trivial.
Example
Let's illustrate. "Hello World!" is always convenient:
<body>
<button class="hello">Hello, Computer!</button>
</body>
import emit from 'fremit';
const stream = emit.stream.share()
window.addEventListener('click', emit);
stream
.filter(e => e.target.classList.contains('hello'))
.forEach(e => alert('Hello, Person!'));
It should be pretty clear how to use the stream
. There are
more details on the API below. It's an explicit
design decision to allow one stream
per app. This is a
core difference between this library and RxJS.
The other half of this library is how to use emit
. One
should use emit
to publish system events such as user
input or messages received from the server to the stream. It
is the integration point for frēmit. The interface is
simple: one function, one argument. The argument is an event
object, which usually has a type
property to allow
differentiation in the stream handler.
Here's an (rather long-ish) example for AJAX (assuming a DOM
with the right stuff in it):
import $ from 'jquery';
import emit from 'fremit';
const stream = emit.stream.share()
$(document).ajaxStart(() => emit({type: 'jqXHRStart'}));
$(document).ajaxSend((event, jqXHR, ajaxOptions) => emit({
event, jqXHR, ajaxOptions, type: 'jqXHRSend'
}));
$(document).ajaxSuccess((event, jqXHR, ajaxOptions, data) => emit({
event, jqXHR, ajaxOptions, data, type: 'jqXHRSuccess'
}));
$(document).ajaxError((event, jqXHR, ajaxOptions, thrownError) => emit({
event, jqXHR, ajaxOptions, thrownError, type: 'jqXHRError'
}));
$(document).ajaxComplete((event, jqXHR, ajaxOptions) => emit({
event, jqXHR, ajaxOptions, type: 'jqXHRComplete'
}));
$(document).ajaxStop(() => emit({type: 'jqXHRStop'}));
stream
.filter(e => e.type === 'jqXHRStart')
.forEach(e => $('.loading').show());
stream
.filter(e => e.type === 'jqXHRStop')
.forEach(e => $('.loading').hide());
stream
.filter(e => e.type === 'jqXHRSuccess' || e.type === 'jqXHRError')
.forEach(e => {
pushDataIntoStore(e.data || e.thrownError);
queueRender();
});
Note the general emphasis on listening for global events,
rather than listening to particular requests or to
particular DOM elements.
API
API documentation is currently a work in progress... bare
with me.
.share()
Share this stream, which means that any further handlers
attached to this stream will create new child streams,
rather than returning this one. This makes the branching
builder-style approach seen above possible.
If you don't share
the stream, any handlers you attach will
return a reference to this stream, which means that if you
filter events, they will be filtered from the rest of the
handlers.
This makes an interesting use case possible that I'll
explain later.
.filter (fn(event))
Return true from your fn
if handlers later in the stream
should handle this event.
.map (fn(event))
return an object that is a function of the event, and
handlers further down the stream will make use of the
newly returned object.
.unwind (path)
Looks for an array at the specified path
, creates a new
cloned event for each element of the array and pushes them
further into the stream.
See MongoDB's definition of $unwind
, and lodash's definition of path
.forEach (fn(event))
Calls the given function for any event. This is your
imperative release valve if you need it.
Roadmap
- 0.1: Get it out there. Get some commentary
- 0.5: Make any essential interface changes, get more commentary
- 0.9: Incorporate all feedback, release-candidate level quality for docs and testing
- 1.0: Full release, production ready. No more API changes
- 1.x: Add additional handlers as needed or convenient
- 2.0: (Hopefully not necessary) Any additional API changes. Semver FTW, baby!