Purescript bindings to the geteventstore projections library.
Purescript is a pure functional strongly typed programming language which compiles to javascript. With purescript-projections you can write your eventstore projections in a pure functional and typesafe manner to guarantee more correctness and safety. And to make awesomeness more awesome, purescript ships with a haskell style quickcheck implementation, which means you can leverage property-based tests to make working with projections even more awesome!
Install purescript-projections with bower:
$ bower install purescript-projections
To write an eventstore projection in purescript first of all you have to model your events and your state:
type UserJoined = {username:: String, id:: Int}
type State = {count:: Int}
then write a function which takes an event and a state and produces a new state:
handler :: State -> UserJoined -> State
handler s e = {count: s.count+1}
In main call runProjection with name of the eventstream, the inital state and the handler like this:
main = runProjection (fromStream "chatroom") {count:0} defaultOptions $ when "UserJoined" handler
Now build the project to a single javascript output file and upload the file as projection to your eventstore installation:
$ pulp build -O --to projection.js
$ curl -i -d @projection.js http://127.0.0.1:2113/projections/transient?enabled=yes -u admin:changeit -H "Content-Type: application/json"
The projections behaviour is defined by four basic properties, these are:
Property | description | default |
---|---|---|
resultstream name | The name of the outputstate stream. | $projections-{projection-name}-result |
include links | Links to events in the source stream are processed. | false |
reorder events | Process events by storing a buffer of events ordered by their prepare position. | false |
processinglag | When reorderEvents is turned on, this value is used to compare the total milliseconds between the first and last events in the buffer and if the value is equal or greater, the events in the buffer will be processed. The buffer is an ordered list of events. | 500ms |
These properties are either controlled via an options parameter passed to runProjections for general configurations (like resultstream name) or they are directly passed to the functions whichs behaviour is altered by the configuration parameter.
If you just want the default behaviour of projections pass defaultOptions to runProjections:
runProjection source initialState defaultOptions fold
This controls wether an outputstate stream should be created and which name this stream should have:
runProjection source initialState (outputState "projection-state") fold
There is a variaty of ways to select events from different streams.
To select events from a single stream call fromStream with the name of the stream to select the events from:
fromStream "chatroom"
To select events from multiple streams call fromStreams with an Array of the names of the streams:
fromStreams ["chatroom", "lobby"]
To select the events from a category call fromCategory with the name of the category. To run a seperate projection for each category call forEachInCategory.
fromCategory "category"
forEachInCategory "category"
The function fromAll simply runs the projection for all streams in the database. ForEach runs a seperate projection for each category.
Once the source of events is defined we can specifiy how to derive state from them, or - to say it in other words - how to 'fold' the events
When produces eventhandler for events of the given eventname by applying a given function which takes a state s, an event e and produces a new state s. The following example derives the number of created accounts from the 'accountCreated' event.
when "accountCreated" (\s e -> {count: s.count+1})
WhenAny can be used to define an eventhandler for any event.
whenAny (\s e -> {count: s.count+1})
FoldE is an instance of Semigroup, so you can define multiple eventhandlers and then combine them:
when "accountCreated" (\s e -> {count: s.count+1}) <> when "accountClosed" (\s e -> {count: s.count-1})
Check LICENSE file for more information.