npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

pager

v0.1.3

Published

JavaScript rule engine without the fuss

Downloads

49

Readme

logo

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.

Concept

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 named channel (to be referenced to later)
  • list all Rule this subscription should comply and/or reject to trigger its Action
  • list all Action to trigger if every Rule 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 the message call and the helpers provided in the pager 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 the message call and the helpers provided in the pager 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 the message call and the helpers provided in the pager 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.