@clipboard-health/rules-engine 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.1.1](https://github.com/ClipboardHealth/cbh-core/compare/rules-engine-0.1.0...rules-engine-0.1.1) (2023-12-22)
6
+
5
7
  ## 0.1.0 (2023-12-20)
6
8
 
7
9
 
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Exposes a functional rules-engine with 2 engines:
4
4
 
5
- - firstMatch: Runs the first rule that matches the criteria
6
- - all: Runs all the rules that matches the criteria
5
+ - `firstMatch`: Runs the first rule that matches the criteria
6
+ - `all`: Runs all the rules that matches the criteria
7
7
 
8
8
  ## Table of Contents
9
9
 
@@ -22,7 +22,13 @@ npm install @clipboard-health/rules-engine
22
22
  ```ts
23
23
  // ./examples/rules.ts
24
24
 
25
- import { all, firstMatch, type Rule, type RuleContext } from "@clipboard-health/rules-engine";
25
+ import {
26
+ all,
27
+ appendOutput,
28
+ firstMatch,
29
+ type Rule,
30
+ type RuleContext,
31
+ } from "@clipboard-health/rules-engine";
26
32
 
27
33
  interface Input {
28
34
  number1: number;
@@ -33,9 +39,7 @@ interface Output {
33
39
  result: number;
34
40
  }
35
41
 
36
- interface ExampleRuleContext extends RuleContext<Input, Output> {}
37
-
38
- const exampleContext: ExampleRuleContext = {
42
+ const exampleContext: RuleContext<Input, Output> = {
39
43
  input: {
40
44
  number1: 2,
41
45
  number2: 5,
@@ -43,33 +47,30 @@ const exampleContext: ExampleRuleContext = {
43
47
  output: [],
44
48
  };
45
49
 
46
- const addNumbersIfPositiveRule: Rule<ExampleRuleContext> = {
47
- runIf: (context) => context.input.number1 > 0 && context.input.number2 > 0,
50
+ const addNumbersIfPositiveRule: Rule<Input, Output> = {
51
+ runIf: (input) => input.number1 > 0 && input.number2 > 0,
48
52
  run: (context) => {
49
53
  const { number1, number2 } = context.input;
50
54
  const sum = number1 + number2;
51
- context.output.push({ result: sum });
52
- return context;
55
+ return appendOutput(context, { result: sum });
53
56
  },
54
57
  };
55
58
 
56
- const multiplyNumbersIfPositiveRule: Rule<ExampleRuleContext> = {
57
- runIf: (context) => context.input.number1 > 0 && context.input.number2 > 0,
59
+ const multiplyNumbersIfPositiveRule: Rule<Input, Output> = {
60
+ runIf: (input) => input.number1 > 0 && input.number2 > 0,
58
61
  run: (context) => {
59
62
  const { number1, number2 } = context.input;
60
63
  const sum = number1 * number2;
61
- context.output.push({ result: sum });
62
- return context;
64
+ return appendOutput(context, { result: sum });
63
65
  },
64
66
  };
65
67
 
66
- const divideNumbersIfNegative: Rule<ExampleRuleContext> = {
67
- runIf: (context) => context.input.number1 < 0 && context.input.number2 < 0,
68
+ const divideNumbersIfNegative: Rule<Input, Output> = {
69
+ runIf: (input) => input.number1 < 0 && input.number2 < 0,
68
70
  run: (context) => {
69
71
  const { number1, number2 } = context.input;
70
72
  const sum = number1 * number2;
71
- context.output.push({ result: sum });
72
- return context;
73
+ return appendOutput(context, { result: sum });
73
74
  },
74
75
  };
75
76
 
@@ -80,7 +81,6 @@ const allResult = all(
80
81
  multiplyNumbersIfPositiveRule,
81
82
  ).run(exampleContext);
82
83
 
83
- // eslint-disable-next-line no-console
84
84
  console.log(allResult.output); // AllResult.output = [{ result: 7 }, { result: 10 }]
85
85
 
86
86
  // Using firstMatch() applies the first the rules to the context
@@ -90,7 +90,6 @@ const firstMatchResult = firstMatch(
90
90
  multiplyNumbersIfPositiveRule,
91
91
  ).run(exampleContext);
92
92
 
93
- // eslint-disable-next-line no-console
94
93
  console.log(firstMatchResult.output); // [{ result: 7 }]
95
94
  ```
96
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/rules-engine",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "main": "./src/index.js",
5
5
  "scripts": {
6
6
  "build": "nx build rules-engine",
@@ -8,5 +8,8 @@
8
8
  "lint": "nx lint rules-engine",
9
9
  "test": "nx test rules-engine"
10
10
  },
11
+ "dependencies": {
12
+ "type-fest": "4.8.3"
13
+ },
11
14
  "type": "commonjs"
12
15
  }
@@ -1,3 +1,7 @@
1
+ import { type ReadonlyDeep } from "type-fest";
1
2
  import { type RuleContext } from "./rule";
2
- export declare function appendOutput<TInput, TOutput>(context: RuleContext<TInput, TOutput>, output: TOutput): RuleContext<TInput, TOutput>;
3
+ /**
4
+ * Rule output is immutable, do not modify existing items, only append.
5
+ */
6
+ export declare function appendOutput<TInput, TOutput>(context: RuleContext<TInput, TOutput>, output: ReadonlyDeep<TOutput>): RuleContext<TInput, TOutput>;
3
7
  //# sourceMappingURL=append-output.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"append-output.d.ts","sourceRoot":"","sources":["../../../../../packages/rules-engine/src/lib/append-output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAC1C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,MAAM,EAAE,OAAO,GACd,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAK9B"}
1
+ {"version":3,"file":"append-output.d.ts","sourceRoot":"","sources":["../../../../../packages/rules-engine/src/lib/append-output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAC1C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,GAC5B,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAK9B"}
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.appendOutput = void 0;
4
+ /**
5
+ * Rule output is immutable, do not modify existing items, only append.
6
+ */
4
7
  function appendOutput(context, output) {
5
8
  return {
6
9
  input: context.input,
@@ -1 +1 @@
1
- {"version":3,"file":"append-output.js","sourceRoot":"","sources":["../../../../../packages/rules-engine/src/lib/append-output.ts"],"names":[],"mappings":";;;AAEA,SAAgB,YAAY,CAC1B,OAAqC,EACrC,MAAe;IAEf,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC;KACpC,CAAC;AACJ,CAAC;AARD,oCAQC"}
1
+ {"version":3,"file":"append-output.js","sourceRoot":"","sources":["../../../../../packages/rules-engine/src/lib/append-output.ts"],"names":[],"mappings":";;;AAIA;;GAEG;AACH,SAAgB,YAAY,CAC1B,OAAqC,EACrC,MAA6B;IAE7B,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC;KACpC,CAAC;AACJ,CAAC;AARD,oCAQC"}
package/src/lib/rule.d.ts CHANGED
@@ -1,9 +1,29 @@
1
- export interface RuleContext<TInput = unknown, TOutput = unknown> {
2
- input: TInput;
3
- output: TOutput[];
1
+ import { type ReadonlyDeep } from "type-fest";
2
+ export interface RuleContext<TInput, TOutput> {
3
+ /**
4
+ * Input is immutable, rules must not modify it.
5
+ */
6
+ input: ReadonlyDeep<TInput>;
7
+ /**
8
+ * Output is immutable, do not modify existing items, only append.
9
+ *
10
+ * @see {@link appendOutput}
11
+ */
12
+ output: ReadonlyArray<ReadonlyDeep<TOutput>>;
4
13
  }
5
- export interface Rule<TContext extends RuleContext> {
6
- runIf: (context: TContext) => boolean;
14
+ export interface Rule<TInput, TOutput, TContext extends RuleContext<TInput, TOutput> = RuleContext<TInput, TOutput>> {
15
+ /**
16
+ * Returns whether the rule should run or not.
17
+ *
18
+ * Only the `input` and not the full `context` is passed to `runIf`. This prevents rules from
19
+ * relying on `output`, which may be modified by previous rules by the time `run` is called.
20
+ */
21
+ runIf: (input: ReadonlyDeep<TInput>) => boolean;
22
+ /**
23
+ * Runs the actual rule and returns a new context by appending to the output array.
24
+ *
25
+ * @see {@link appendOutput}
26
+ */
7
27
  run: (context: TContext) => TContext;
8
28
  }
9
29
  //# sourceMappingURL=rule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../../../../packages/rules-engine/src/lib/rule.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,IAAI,CAAC,QAAQ,SAAS,WAAW;IAChD,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IACtC,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,QAAQ,CAAC;CACtC"}
1
+ {"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../../../../packages/rules-engine/src/lib/rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,WAAW,WAAW,CAAC,MAAM,EAAE,OAAO;IAC1C;;OAEG;IACH,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAE5B;;;;OAIG;IACH,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,IAAI,CACnB,MAAM,EACN,OAAO,EACP,QAAQ,SAAS,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC;IAE5E;;;;;OAKG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAEhD;;;;OAIG;IACH,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,QAAQ,CAAC;CACtC"}
@@ -1,9 +1,8 @@
1
- import { type Rule, type RuleContext } from "../..";
1
+ import { type Rule, type RuleContext } from "../rule";
2
2
  /**
3
- * Runs all the rules that return true for `runIf`.
3
+ * Runs all rules that return true for `runIf`.
4
4
  *
5
5
  * @param rules The rules to run.
6
- * @returns Rule<TContext>
7
6
  */
8
- export declare function all<TContext extends RuleContext>(...rules: Array<Rule<TContext>>): Rule<TContext>;
7
+ export declare function all<TInput, TOutput, TContext extends RuleContext<TInput, TOutput>>(...rules: Array<Rule<TInput, TOutput, TContext>>): Rule<TInput, TOutput, TContext>;
9
8
  //# sourceMappingURL=all.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"all.d.ts","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/all.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpD;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,QAAQ,SAAS,WAAW,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAejG"}
1
+ {"version":3,"file":"all.d.ts","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/all.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,SAAS,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EAChF,GAAG,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,GAC/C,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAQjC"}
@@ -2,24 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.all = void 0;
4
4
  /**
5
- * Runs all the rules that return true for `runIf`.
5
+ * Runs all rules that return true for `runIf`.
6
6
  *
7
7
  * @param rules The rules to run.
8
- * @returns Rule<TContext>
9
8
  */
10
9
  function all(...rules) {
11
10
  return {
12
- runIf: function (context) {
13
- return rules.some((rule) => rule.runIf(context));
14
- },
15
- run: function (context) {
16
- return rules.reduce((previousContext, rule) => {
17
- if (rule.runIf(context)) {
18
- return rule.run(previousContext);
19
- }
20
- return previousContext;
21
- }, context);
22
- },
11
+ runIf: (input) => rules.some((rule) => rule.runIf(input)),
12
+ run: (context) => rules
13
+ .filter((rule) => rule.runIf(context.input))
14
+ .reduce((previousContext, rule) => rule.run(previousContext), context),
23
15
  };
24
16
  }
25
17
  exports.all = all;
@@ -1 +1 @@
1
- {"version":3,"file":"all.js","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/all.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACH,SAAgB,GAAG,CAA+B,GAAG,KAA4B;IAC/E,OAAO;QACL,KAAK,EAAE,UAAU,OAAO;YACtB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,GAAG,EAAE,UAAU,OAAO;YACpB,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE;gBAC5C,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;oBACvB,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;iBAClC;gBAED,OAAO,eAAe,CAAC;YACzB,CAAC,EAAE,OAAO,CAAC,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC;AAfD,kBAeC"}
1
+ {"version":3,"file":"all.js","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/all.ts"],"names":[],"mappings":";;;AAEA;;;;GAIG;AACH,SAAgB,GAAG,CACjB,GAAG,KAA6C;IAEhD,OAAO;QACL,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CACf,KAAK;aACF,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;aAC3C,MAAM,CAAC,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3E,CAAC;AACJ,CAAC;AAVD,kBAUC"}
@@ -1,9 +1,9 @@
1
- import { type Rule, type RuleContext } from "../..";
1
+ import { type Rule, type RuleContext } from "../rule";
2
2
  /**
3
- * Runs the first rule that returns true for `runIf`.
3
+ * Run the first rule that returns true for `runIf`.
4
4
  *
5
5
  * @param rules The rules to run.
6
6
  * @returns A rule that runs the first rule that matches the context.
7
7
  */
8
- export declare function firstMatch<TContext extends RuleContext>(...rules: Array<Rule<TContext>>): Rule<TContext>;
8
+ export declare function firstMatch<TInput, TOutput, TContext extends RuleContext<TInput, TOutput>>(...rules: Array<Rule<TInput, TOutput, TContext>>): Rule<TInput, TOutput, TContext>;
9
9
  //# sourceMappingURL=first-match.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"first-match.d.ts","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/first-match.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,SAAS,WAAW,EACrD,GAAG,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAC9B,IAAI,CAAC,QAAQ,CAAC,CAWhB"}
1
+ {"version":3,"file":"first-match.d.ts","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/first-match.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,SAAS,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACvF,GAAG,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,GAC/C,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAQjC"}
@@ -2,18 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.firstMatch = void 0;
4
4
  /**
5
- * Runs the first rule that returns true for `runIf`.
5
+ * Run the first rule that returns true for `runIf`.
6
6
  *
7
7
  * @param rules The rules to run.
8
8
  * @returns A rule that runs the first rule that matches the context.
9
9
  */
10
10
  function firstMatch(...rules) {
11
11
  return {
12
- runIf: function (context) {
13
- return rules.some((rule) => rule.runIf(context));
14
- },
15
- run: function (context) {
16
- const rule = rules.find((rule) => rule.runIf(context));
12
+ runIf: (input) => rules.some((rule) => rule.runIf(input)),
13
+ run: (context) => {
14
+ const rule = rules.find((rule) => rule.runIf(context.input));
17
15
  return rule ? rule.run(context) : context;
18
16
  },
19
17
  };
@@ -1 +1 @@
1
- {"version":3,"file":"first-match.js","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/first-match.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACH,SAAgB,UAAU,CACxB,GAAG,KAA4B;IAE/B,OAAO;QACL,KAAK,EAAE,UAAU,OAAO;YACtB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,GAAG,EAAE,UAAU,OAAO;YACpB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAEvD,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5C,CAAC;KACF,CAAC;AACJ,CAAC;AAbD,gCAaC"}
1
+ {"version":3,"file":"first-match.js","sourceRoot":"","sources":["../../../../../../packages/rules-engine/src/lib/runners/first-match.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACH,SAAgB,UAAU,CACxB,GAAG,KAA6C;IAEhD,OAAO;QACL,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE;YACf,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7D,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5C,CAAC;KACF,CAAC;AACJ,CAAC;AAVD,gCAUC"}