select-when
TypeScript icon, indicating that this package has built-in type declarations

0.1.9 • Public • Published

select-when

Build Status

This javascript library makes it easy to create rules that pattern match on event streams.

Rationale

It's based on the ruleset pattern and event expressions of the Kinetic Rule Language (KRL). Read more rational here, and look into pico-engine if you want to run KRL code.

Declarative Event Expressions

Describe event patterns you want to select on.

For example:

  • When aaa or bbb signals or(e("aaa"), e("bbb"))
  • When aaa comes after bbb after(e("aaa"), e("bbb"))
  • When any 2 of these 3 events happen within 1 second within(1000, any(2, e("a1"), e("a2"), e("a3")))

Organize code and execution into Rulesets

Create a set of rules to run serially in the order they are declared. This makes it easy for programmers to understand their program and reason about ordering while still building in the asynchronous javascript environment.

Event Anatomy

Events in this system are simple json objects that have 4 parts. The domain, name, data and time.

interface Event<DataT> {
  // The domain/namespace of the event, this is optional
  domain?: string;

  // The name of event, required
  name: string;

  // Payload data of any kind to go with the event
  data?: DataT;

  // a unix timestamp, number of milliseconds since Jan 1, 1970 UTC
  time: int; // defaults to Date.now()
}

One can use strings as a shorthand for representing events.

"aaa"     { domain:  null, name: "aaa" }
"bbb:ccc" { domain: "bbb", name: "ccc" }

Example

import { SelectWhen, e, or, then } from "select-when";

let rs = new SelectWhen();

// KRL: select when hello:world
rs.when(e("hello:world"), function(event, state) {
  console.log("rule 1 ->", event);
});

// KRL: select when hello:world or (*:a then *:b)
rs.when(or(e("hello:world"), then(e("a"), e("b"))), function(event, state) {
  console.log("rule 2 ->", event);
});

rs.send("hello:world");
// rule 1 -> { domain: 'hello', name: 'world', data: null, time: 1541... }
// rule 2 -> { domain: 'hello', name: 'world', data: null, time: 1541... }

rs.send("a");
rs.send("b");
// rule 2 -> { domain: null, name: 'b', data: null, time: 1541... }

API

rs = new SelectWhen()

Create a new ruleset.

rs.when(Rule | StateMachine, body)

Call the body function when a given rule or state machine matches an event.

The body is a function(event, state){} that runs when the rule matches. It can also be async (return a promise).

rs.send(event)

Send an event to be processed by the ruleset. This returns a promise that resolves when all the rules have finished processing. Rules process serially in the order they are declared. Events sent are json objects or the string shorthad. (See the Event Anatomy section above.)

rs.getSaliance()

Returns an array of { domain: string, name: string } that are salient for the ruleset. "*" means any.

rule = new Rule()

Create a rule.

let rule = new Rule();

// set the initial state, under the hood this will go through Object.freeze
rule.state = {};

// set which domain/name patterns that this rule will care about
// by default all events are salient
rule.saliance = [
  { domain: "*", name: "aaa" }, // all events with name = "aaa"
  { domain: "bbb", name: "*" } // all events with domain = "bbb"
];

rule.matcher = function(event, state) {
  // This function is called on all salient events.
  // Return whether or not the event matches, and the new state.
  // The state is similar to a memo in a reducer function.
  // NOTE: this function can also be async (i.e. return a promise)
  return {
    match: true,
    state: Object.assign({}, state, { some: "change" })
  };
};

// Ask the rule to determine if an event matches, this will also update rule.state
rule.select(event).then(function(didMatch) {
  if (didMatch) {
    // do something
  }
});

Event Expressions

These functions create StateMachine's or Rules that can be passed into rs.when(..

e(str, matcher?)

This creates a basic state machine to match events. This is the basic building block for all event expressions.

  • str - A salient event pattern (see examples below)
  • matcher - An optional matcher function (see rule.matcher for more info)
"bbb:ccc" { domain: "bbb", name: "ccc" }
"bbb:*"   { domain: "bbb", name: "*"   }
"aaa"     { domain: "*"  , name: "aaa" }
"*:*"     { domain: "*"  , name: "*"   }

For example:

rs.when(e("aaa:*"), function(event, state) {
  // run this on all events with domain "aaa"
});

or(a, b)

A state machine that matches when a or b matches.

and(a, b)

A state machine that matches when a and b matches.

before(a, b)

A state machine that matches when a before b matches.

then(a, b)

A state machine that matches when a then b matches, with no interleaving salient events.

after(a, b)

A state machine that matches when a after b matches.

between(a, b, c)

A state machine that matches when a comes between b and c.

notBetween(a, b, c)

A state machine that matches when a comes not between b and c.

any(n, ...a)

A state machine that matches any n of the events.

For example: any(2, e("a"), e("b"), e("c"))

a
b // match
a
z
c // match
b
a // match

count(n, a)

A state machine that matches after n of a's have matched.

For example: count(3, e("a"))

a
a
a // match
a
a
a // match
a
z
z
z
a
a // match

repeat(n, a)

The same as count except once it matches, it will always match on a

For example: repeat(3, e("a"))

a
z
a
z
z
a // match
a // match
z
a // match
a // match

within(timeLimit, a)

A rule that will reset the statemachine a when the time has expired.

timeLimit is be the number of milliseconds ms or a function that returns the time limit (event, state) => ms

For example: within(1000, count(3, e("a")),

a
a
// wait 60 minutes
a // the machine reset so we are back to the 1st event
a
a // match

NOTE: It uses the time on the event object, not the current execution time.

License

MIT

Dependents (2)

Package Sidebar

Install

npm i select-when

Weekly Downloads

18

Version

0.1.9

License

MIT

Unpacked Size

77.3 kB

Total Files

27

Last publish

Collaborators

  • b1conrad
  • farskipper
  • windley