pager
v0.1.3
Published
JavaScript rule engine without the fuss
Downloads
49
Readme
Pager
Say you want to email a user for his birthday, or when his visa card expires or maybe if his last post has been shared a 100 times. Those events are not connected to this user's actions, they rely on the passing of time or someone else behavior. This may result in chunky code out of context. Know what? You've just got a message from Pager.
Pager is a lightweight rule engine which associate a set of immediate or postponed rules to actions.
Installation
npm install pager
Usage
When something happened (ex. a post is liked) and you want to check some rules and trigger some actions accordingly, send a message
to a Pager
instance with the event data (ex. a card expiry date). Immediate rules (if any) will be evaluated and, if they all match, pager's acknowledge
method will be invoked. This do not trigger any actions yet.
To trigger the actions, call pager's process
method with the task
returned by acknowledge
. Postponed rules (if any) will be evaluated then, and, if they all match, the actions will be triggered.
As you may have guessed, each task
has a date attribute and the process
method will only evaluate postponed rules if this date is in the past.
Passing the acknowledge
tasks to the process
method is up to you, because you may choose to do so every minute, hour, or immediately, depending on your needs. You may also store those tasks in a cache, a db, or not at all.
Initializing
Before using a Pager
instance, it's flow has to be configured:
subscribe
to every message it will handle (ex. a card expiry)- gather each set of multiple
Rule
+Action
under a group namedchannel
(to be referenced to later) - list all
Rule
this subscription shouldcomply
and/orreject
totrigger
itsAction
- list all
Action
totrigger
if everyRule
match
var Pager = require('pager'),
pager = new Pager();
pager.subscribe('Card expiry')
.channel('Invite user to renew visa')
.comply(new Pager.Rule({
match: function(context) {
return new Date(context.expiry) < new Date();
}
}))
.trigger(new Pager.Action({
fire: function(context) {
console.log('Please, add a new visa');
}
}));
Action triggering
Once configured, a pager
can capture a message
and its context (ex. a card expiry date), then, it will acknowledge
if the context matches the associated rules. Actions are not triggered yet.
pager.message('Card expiry', {expiry: '2015-12'});
pager.acknowledge(function(task) {
// trigger the actions, can be stored in a cache to be processed later
pager.process(task);
});
Initializing immediate and postponed rules
Rules can be verified immediately on a message
(and acknowledge
if match) or postponed to it (and trigger
if match). On initializing, calling after
or every
with a period defers rules chained after this call.
pager.subscribe('Card expiry')
.channel('Invite user to deposit')
.comply(immediateRule)
.after(5, 'minutes')
.comply(postponedRule)
.trigger(action);
A message
to this pager
will be acknowledge
if the immediateRule immediately matches, and, if so, will return a task
with a date 5 minutes in the future. Then, when you process this task
once its date is in the past, the postponedRule will be matched, and, if so, the actions will be triggered.
The every
method follows the same pattern, but additionally updates the task
date with the configured period, ready to be processed again later. It also offers a mechanism to avoid repetitive triggers of the same action (ex. “please, change your visa”). A flag
is set on the task
— null in other cases — here sets as 'available'
until its actions has been triggered. Then, the flag is toggle to 'triggered'
. A flag
set to 'triggered'
prevent any action. This flag
is turned back to 'available'
only when one (or more) postponed rules do not match, ready to be triggered on the next process
if they match again.
Documentation
Rule
var rule = new Pager.Rule({
match: function(context, helpers) {
return new Date(context.expiry) < new Date();
}
});
[Initializing] A rule can be immediate or postponed (depending on how it's initialized in a channel
). Immediate rule are evaluated on a message
, and create a task
if match. Postponed rules will be evaluated on a process
, and trigger actions if match.
- Should return a boolean or a promise of a boolean
- Gets the
context
provided in themessage
call and thehelpers
provided in thepager
creator
Action
var action = new Pager.Action({
fire: function(context, helpers) {
console.log('Account need a deposit');
}
});
[Initializing] An action is a piece of code to execute, when rules match.
- May return a value or the promise of a value (if
process
return is used) - Gets the
context
provided in themessage
call and thehelpers
provided in thepager
creator.
Task
var task = {
channel: 'Invite user to deposit',
event: 'Card expiry',
context: {expiry: '2015-12'}, // data provided to the message method
date: ..., // date object since when this task can be processed
flag: ... // flag [null, 'available', 'triggered'], used for forever task
}
A task is returned by the acknowledge
method and awaited by the process
method. Its date represents the moment since when this task can be processed (maybe in the future). Its flag is used for forever task (using every
method set it to 'available'
), default to null
. It may be changed manually (with care) if this logic is not desired.
Middleware
var middleware = function(context, helpers) {
context.vat = helpers.vat(context.country);
}
});
[Initializing] A middleware if a function that can update the context.
- May return a promise
- Gets the
context
provided in themessage
call and thehelpers
provided in thepager
creator.
Pager
Constructor
var pager = new Pager(helpers);
[Initializing] Create a pager
with a helpers
object that will be given to every rule, action and middleware.
subscribe
pager.subscribe(event)
[Initializing] Create a subscription to a given event name. Will be invoked though message
method with the same event name. Can be chained (returns itself).
channel
var channel = pager.channel(name)
[Initializing] Create a channel — a group of rules & actions — and returns it. A subscription may contains multiple channels. The channel name will be used in the acknowledge
callback task
value.
message
pager.message(event, context)
Apply the context to the event subscribed immediate rules. Will eventually trigger the acknowledge
callback if all immediate rules match. Return a promise (mostly for test purpose).
acknowledge
pager.acknowledge(callback)
Declare a callback to invoke when a message
is invoked and all rules match. The callback argument is a task
with attributes {channel, event, context, date, flag}
. The date equals the moment since when this task can be processed. The flag helps the every
to not trigger action every time.
ack
See acknowledge
process
pager.process(task)
Evaluate postponed rules and trigger the actions if they match. The task
attributes should includes {channel, event, context, date, flag}
, the object can be a db wrapper or anything, only flag
and date
attributes may be updated here. Return a promise (mostly for test purpose).
Channel
comply
channel.comply(rule, ...)
[Initializing] Declare a rule (or a list of rules) to match on this channel for the given context. Immediate rule if after
or every
wasn't called, postponed rule otherwise. Can be chained.
reject
channel.reject(rule, ...)
[Initializing] Declare a rule (or a list of rules) to not match on this channel for the given context. Immediate rule if after
or every
wasn't called, postponed rule otherwise. Can be chained.
trigger
channel.trigger(action, ...)
[Initializing] Declare an action (or a list of rules) to trigger on this channel for the given context. Can be chained.
after
channel.after(number, unit)
[Initializing] Change following comply
and reject
from immediate rules declaration (default) to postponed rules declaration. The date of the acknowledge
callback value will be incremented by this. Unit default to 'seconds'
, can also be 'minutes'
, 'hours'
, 'days'
. Can only be called once per channel (and without every). Can be chained.
every
channel.every(number, unit)
[Initializing] Same as after
but will acknowledge
the task after process
. Basically, it creates a loop. Can only be called once per channel (and without after). Can be chained.
then
channel.then(middleware, ...)
[Initializing] Declare a function that will be invoked for the given context and can update it (ex. add a value for further usage). Can be chained.
dedup
channel.dedup(function(context) { return ...})
[Initializing] Declare a function that will be invoked for the given context and will add a dedup
attribute to each returned task. This help to spot different tasks that should be considered similar — and avoid doing twice an action that don't need to. Can be chained.
channel
channel.channel(name)
[Initializing] Returns a new channel. See pager.channel for more.