{"id":19219653,"url":"https://github.com/picolab/select-when","last_synced_at":"2025-07-02T06:38:45.997Z","repository":{"id":65493803,"uuid":"154573922","full_name":"Picolab/select-when","owner":"Picolab","description":"nodejs library to create expressions that pattern match over an event stream","archived":false,"fork":false,"pushed_at":"2023-11-12T00:15:54.000Z","size":69,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-02-05T00:24:12.343Z","etag":null,"topics":["event-driven","pattern-matching","rule-based"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Picolab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-24T21:52:26.000Z","updated_at":"2024-01-14T12:44:43.000Z","dependencies_parsed_at":"2024-11-09T14:34:58.928Z","dependency_job_id":"53ad6626-62aa-4e7f-8a52-74f63afcad35","html_url":"https://github.com/Picolab/select-when","commit_stats":{"total_commits":52,"total_committers":1,"mean_commits":52.0,"dds":0.0,"last_synced_commit":"6f99310fa23b45a7c87e3a547d0762f4534c2042"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Picolab%2Fselect-when","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Picolab%2Fselect-when/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Picolab%2Fselect-when/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Picolab%2Fselect-when/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Picolab","download_url":"https://codeload.github.com/Picolab/select-when/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240292392,"owners_count":19778311,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event-driven","pattern-matching","rule-based"],"created_at":"2024-11-09T14:32:23.505Z","updated_at":"2025-02-23T09:16:59.160Z","avatar_url":"https://github.com/Picolab.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# select-when\n\n[![Build Status](https://github.com/Picolab/select-when/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/Picolab/select-when/actions/workflows/test.yml)\n\nThis javascript library makes it _easy_ to create rules that **pattern match** on event streams.\n\n- [Rationale](#rationale)\n- [Event Anatomy](#event-anatomy)\n- [Example](#example)\n- [API](#api)\n  - [rs = new SelectWhen()](#rs--new-selectwhen)\n    - [rs.when(Rule | StateMachine, body)](#rswhenrule--statemachine-body)\n    - [rs.send(event)](#rssendevent)\n    - [rs.getSaliance()](#rsgetsaliance)\n  - [rule = new Rule()](#rule--new-rule)\n  - [Event Expressions](#event-expressions)\n    - [e(str, matcher?)](#estr-matcher)\n    - [or(a, b)](#ora-b)\n    - [and(a, b)](#anda-b)\n    - [before(a, b)](#beforea-b)\n    - [then(a, b)](#thena-b)\n    - [after(a, b)](#aftera-b)\n    - [between(a, b, c)](#betweena-b-c)\n    - [notBetween(a, b, c)](#notbetweena-b-c)\n    - [any(n, ...a)](#anyn-a)\n    - [count(n, a)](#countn-a)\n    - [repeat(n, a)](#repeatn-a)\n    - [within(timeLimit, a)](#withintimelimit-a)\n- [License](#license)\n\n## Rationale\n\nIt's based on the ruleset pattern and event expressions of the Kinetic Rule Language ([KRL](https://en.wikipedia.org/wiki/Kinetic_Rule_Language)). Read more rational [here](https://picolabs.atlassian.net/wiki/spaces/docs/pages/1189912/Event+Expressions), and look into [pico-engine](https://github.com/picolab/pico-engine) if you want to run KRL code.\n\n#### Declarative Event Expressions\n\nDescribe event patterns you want to select on.\n\nFor example:\n\n- When aaa **or** bbb signals `or(e(\"aaa\"), e(\"bbb\"))`\n- When aaa comes **after** bbb `after(e(\"aaa\"), e(\"bbb\"))`\n- When **any 2** of these 3 events happen **within 1 second** `within(1000, any(2, e(\"a1\"), e(\"a2\"), e(\"a3\")))`\n\n#### Organize code and execution into Rulesets\n\nCreate 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.\n\n## Event Anatomy\n\nEvents in this system are simple json objects that have 4 parts. The `domain`, `name`, `data` and `time`.\n\n```typescript\ninterface Event\u003cDataT\u003e {\n  // The domain/namespace of the event, this is optional\n  domain?: string;\n\n  // The name of event, required\n  name: string;\n\n  // Payload data of any kind to go with the event\n  data?: DataT;\n\n  // a unix timestamp, number of milliseconds since Jan 1, 1970 UTC\n  time: int; // defaults to Date.now()\n}\n```\n\nOne can use strings as a shorthand for representing events.\n\n```js\n\"aaa\"     { domain:  null, name: \"aaa\" }\n\"bbb:ccc\" { domain: \"bbb\", name: \"ccc\" }\n```\n\n## Example\n\n```js\nimport { SelectWhen, e, or, then } from \"select-when\";\n\nlet rs = new SelectWhen();\n\n// KRL: select when hello:world\nrs.when(e(\"hello:world\"), function(event, state) {\n  console.log(\"rule 1 -\u003e\", event);\n});\n\n// KRL: select when hello:world or (*:a then *:b)\nrs.when(or(e(\"hello:world\"), then(e(\"a\"), e(\"b\"))), function(event, state) {\n  console.log(\"rule 2 -\u003e\", event);\n});\n\nrs.send(\"hello:world\");\n// rule 1 -\u003e { domain: 'hello', name: 'world', data: null, time: 1541... }\n// rule 2 -\u003e { domain: 'hello', name: 'world', data: null, time: 1541... }\n\nrs.send(\"a\");\nrs.send(\"b\");\n// rule 2 -\u003e { domain: null, name: 'b', data: null, time: 1541... }\n```\n\n## API\n\n### rs = new SelectWhen()\n\nCreate a new ruleset.\n\n#### rs.when(Rule | StateMachine, body)\n\nCall the body function when a given rule or state machine matches an event.\n\nThe `body` is a `function(event, state){}` that runs when the rule matches. It can also be async (return a promise).\n\n#### rs.send(event)\n\nSend 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](#event-anatomy) section above.)\n\n#### rs.getSaliance()\n\nReturns an array of `{ domain: string, name: string }` that are salient for the ruleset. `\"*\"` means any.\n\n### rule = new Rule()\n\nCreate a rule.\n\n```js\nlet rule = new Rule();\n\n// set the initial state, under the hood this will go through Object.freeze\nrule.state = {};\n\n// set which domain/name patterns that this rule will care about\n// by default all events are salient\nrule.saliance = [\n  { domain: \"*\", name: \"aaa\" }, // all events with name = \"aaa\"\n  { domain: \"bbb\", name: \"*\" } // all events with domain = \"bbb\"\n];\n\nrule.matcher = function(event, state) {\n  // This function is called on all salient events.\n  // Return whether or not the event matches, and the new state.\n  // The state is similar to a memo in a reducer function.\n  // NOTE: this function can also be async (i.e. return a promise)\n  return {\n    match: true,\n    state: Object.assign({}, state, { some: \"change\" })\n  };\n};\n\n// Ask the rule to determine if an event matches, this will also update rule.state\nrule.select(event).then(function(didMatch) {\n  if (didMatch) {\n    // do something\n  }\n});\n```\n\n### Event Expressions\n\nThese functions create StateMachine's or Rules that can be passed into `rs.when(..`\n\n#### e(str, matcher?)\n\nThis creates a basic state machine to match events. This is the basic building block for all event expressions.\n\n- `str` - A salient event pattern (see examples below)\n- `matcher` - An optional matcher function (see `rule.matcher` for more info)\n\n```js\n\"bbb:ccc\" { domain: \"bbb\", name: \"ccc\" }\n\"bbb:*\"   { domain: \"bbb\", name: \"*\"   }\n\"aaa\"     { domain: \"*\"  , name: \"aaa\" }\n\"*:*\"     { domain: \"*\"  , name: \"*\"   }\n```\n\nFor example:\n\n```js\nrs.when(e(\"aaa:*\"), function(event, state) {\n  // run this on all events with domain \"aaa\"\n});\n```\n\n#### or(a, b)\n\nA state machine that matches when `a or b` matches.\n\n#### and(a, b)\n\nA state machine that matches when `a and b` matches.\n\n#### before(a, b)\n\nA state machine that matches when `a before b` matches.\n\n#### then(a, b)\n\nA state machine that matches when `a then b` matches, with no interleaving _salient_ events.\n\n#### after(a, b)\n\nA state machine that matches when `a after b` matches.\n\n#### between(a, b, c)\n\nA state machine that matches when `a` comes `between` `b` and `c`.\n\n#### notBetween(a, b, c)\n\nA state machine that matches when `a` comes `not between` `b` and `c`.\n\n#### any(n, ...a)\n\nA state machine that matches any `n` of the events.\n\nFor example: `any(2, e(\"a\"), e(\"b\"), e(\"c\"))`\n\n```\na\nb // match\na\nz\nc // match\nb\na // match\n```\n\n#### count(n, a)\n\nA state machine that matches after `n` of `a`'s have matched.\n\nFor example: `count(3, e(\"a\"))`\n\n```\na\na\na // match\na\na\na // match\na\nz\nz\nz\na\na // match\n```\n\n#### repeat(n, a)\n\nThe same as `count` except once it matches, it will always match on `a`\n\nFor example: `repeat(3, e(\"a\"))`\n\n```\na\nz\na\nz\nz\na // match\na // match\nz\na // match\na // match\n```\n\n#### within(timeLimit, a)\n\nA rule that will reset the statemachine `a` when the `time` has expired.\n\n`timeLimit` is be the number of milliseconds `ms` or a function that returns the time limit `(event, state) =\u003e ms`\n\nFor example: `within(1000, count(3, e(\"a\")),`\n\n```\na\na\n// wait 60 minutes\na // the machine reset so we are back to the 1st event\na\na // match\n```\n\nNOTE: It uses the `time` on the event object, not the current execution time.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpicolab%2Fselect-when","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpicolab%2Fselect-when","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpicolab%2Fselect-when/lists"}