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

select-when

v0.1.9

Published

nodejs library to create expressions that pattern match over an event stream

Downloads

26

Readme

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