@sebasoft/neuron-js 0.3.0 → 0.5.0

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.
Files changed (178) hide show
  1. package/README.md +84 -45
  2. package/dist/commonjs/Synapse.d.ts +18 -0
  3. package/dist/commonjs/Synapse.d.ts.map +1 -1
  4. package/dist/commonjs/Synapse.js +18 -0
  5. package/dist/commonjs/Synapse.js.map +1 -1
  6. package/dist/commonjs/abstracts/AbstractAction.d.ts +15 -0
  7. package/dist/commonjs/abstracts/AbstractAction.d.ts.map +1 -0
  8. package/dist/commonjs/abstracts/AbstractAction.js +25 -0
  9. package/dist/commonjs/abstracts/AbstractAction.js.map +1 -0
  10. package/dist/commonjs/abstracts/AbstractCondition.d.ts +15 -0
  11. package/dist/commonjs/abstracts/AbstractCondition.d.ts.map +1 -0
  12. package/dist/commonjs/abstracts/AbstractCondition.js +25 -0
  13. package/dist/commonjs/abstracts/AbstractCondition.js.map +1 -0
  14. package/dist/commonjs/contracts/explain.d.ts +9 -0
  15. package/dist/commonjs/contracts/explain.d.ts.map +1 -0
  16. package/dist/commonjs/contracts/explain.js +51 -0
  17. package/dist/commonjs/contracts/explain.js.map +1 -0
  18. package/dist/commonjs/contracts/validation.d.ts +40 -0
  19. package/dist/commonjs/contracts/validation.d.ts.map +1 -0
  20. package/dist/commonjs/contracts/validation.js +263 -0
  21. package/dist/commonjs/contracts/validation.js.map +1 -0
  22. package/dist/commonjs/index.d.ts +64 -0
  23. package/dist/commonjs/index.d.ts.map +1 -1
  24. package/dist/commonjs/index.js +74 -1
  25. package/dist/commonjs/index.js.map +1 -1
  26. package/dist/commonjs/interfaces/Action.d.ts +14 -0
  27. package/dist/commonjs/interfaces/Action.d.ts.map +1 -1
  28. package/dist/commonjs/interfaces/Condition.d.ts +21 -0
  29. package/dist/commonjs/interfaces/Condition.d.ts.map +1 -1
  30. package/dist/commonjs/interfaces/Element.d.ts +16 -0
  31. package/dist/commonjs/interfaces/Element.d.ts.map +1 -1
  32. package/dist/commonjs/interfaces/HookEvents.d.ts +16 -0
  33. package/dist/commonjs/interfaces/HookEvents.d.ts.map +1 -1
  34. package/dist/commonjs/interfaces/HookEvents.js +16 -0
  35. package/dist/commonjs/interfaces/HookEvents.js.map +1 -1
  36. package/dist/commonjs/interfaces/Parameter.d.ts +16 -0
  37. package/dist/commonjs/interfaces/Parameter.d.ts.map +1 -1
  38. package/dist/commonjs/interfaces/Rule.d.ts +23 -0
  39. package/dist/commonjs/interfaces/Rule.d.ts.map +1 -1
  40. package/dist/commonjs/interfaces/Script.d.ts +10 -0
  41. package/dist/commonjs/interfaces/Script.d.ts.map +1 -1
  42. package/dist/commonjs/runtime/ActionRuntime.js +1 -1
  43. package/dist/commonjs/runtime/ActionRuntime.js.map +1 -1
  44. package/dist/commonjs/runtime/ConditionRuntime.js +3 -3
  45. package/dist/commonjs/runtime/ConditionRuntime.js.map +1 -1
  46. package/dist/commonjs/runtime/RuleRuntime.js +1 -1
  47. package/dist/commonjs/runtime/RuleRuntime.js.map +1 -1
  48. package/dist/commonjs/types/ExecutionContext.d.ts +13 -0
  49. package/dist/commonjs/types/ExecutionContext.d.ts.map +1 -1
  50. package/dist/commonjs/types/ExecutionContext.js +3 -0
  51. package/dist/commonjs/types/ExecutionContext.js.map +1 -1
  52. package/dist/commonjs/types/ExecutionResult.d.ts +14 -0
  53. package/dist/commonjs/types/ExecutionResult.d.ts.map +1 -1
  54. package/dist/commonjs/types/ExecutionResult.js +14 -0
  55. package/dist/commonjs/types/ExecutionResult.js.map +1 -1
  56. package/dist/commonjs/types/HookEmitter.d.ts +8 -1
  57. package/dist/commonjs/types/HookEmitter.d.ts.map +1 -1
  58. package/dist/esm/Synapse.d.ts +18 -0
  59. package/dist/esm/Synapse.d.ts.map +1 -1
  60. package/dist/esm/Synapse.js +18 -0
  61. package/dist/esm/Synapse.js.map +1 -1
  62. package/dist/esm/abstracts/AbstractAction.d.ts +15 -0
  63. package/dist/esm/abstracts/AbstractAction.d.ts.map +1 -0
  64. package/dist/esm/abstracts/AbstractAction.js +21 -0
  65. package/dist/esm/abstracts/AbstractAction.js.map +1 -0
  66. package/dist/esm/abstracts/AbstractCondition.d.ts +15 -0
  67. package/dist/esm/abstracts/AbstractCondition.d.ts.map +1 -0
  68. package/dist/esm/abstracts/AbstractCondition.js +21 -0
  69. package/dist/esm/abstracts/AbstractCondition.js.map +1 -0
  70. package/dist/esm/contracts/explain.d.ts +9 -0
  71. package/dist/esm/contracts/explain.d.ts.map +1 -0
  72. package/dist/esm/contracts/explain.js +48 -0
  73. package/dist/esm/contracts/explain.js.map +1 -0
  74. package/dist/esm/contracts/validation.d.ts +40 -0
  75. package/dist/esm/contracts/validation.d.ts.map +1 -0
  76. package/dist/esm/contracts/validation.js +255 -0
  77. package/dist/esm/contracts/validation.js.map +1 -0
  78. package/dist/esm/index.d.ts +64 -0
  79. package/dist/esm/index.d.ts.map +1 -1
  80. package/dist/esm/index.js +50 -0
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/interfaces/Action.d.ts +14 -0
  83. package/dist/esm/interfaces/Action.d.ts.map +1 -1
  84. package/dist/esm/interfaces/Condition.d.ts +21 -0
  85. package/dist/esm/interfaces/Condition.d.ts.map +1 -1
  86. package/dist/esm/interfaces/Element.d.ts +16 -0
  87. package/dist/esm/interfaces/Element.d.ts.map +1 -1
  88. package/dist/esm/interfaces/HookEvents.d.ts +16 -0
  89. package/dist/esm/interfaces/HookEvents.d.ts.map +1 -1
  90. package/dist/esm/interfaces/HookEvents.js +16 -0
  91. package/dist/esm/interfaces/HookEvents.js.map +1 -1
  92. package/dist/esm/interfaces/Parameter.d.ts +16 -0
  93. package/dist/esm/interfaces/Parameter.d.ts.map +1 -1
  94. package/dist/esm/interfaces/Rule.d.ts +23 -0
  95. package/dist/esm/interfaces/Rule.d.ts.map +1 -1
  96. package/dist/esm/interfaces/Script.d.ts +10 -0
  97. package/dist/esm/interfaces/Script.d.ts.map +1 -1
  98. package/dist/esm/runtime/ActionRuntime.js +1 -1
  99. package/dist/esm/runtime/ActionRuntime.js.map +1 -1
  100. package/dist/esm/runtime/ConditionRuntime.js +3 -3
  101. package/dist/esm/runtime/ConditionRuntime.js.map +1 -1
  102. package/dist/esm/runtime/RuleRuntime.js +1 -1
  103. package/dist/esm/runtime/RuleRuntime.js.map +1 -1
  104. package/dist/esm/types/ExecutionContext.d.ts +13 -0
  105. package/dist/esm/types/ExecutionContext.d.ts.map +1 -1
  106. package/dist/esm/types/ExecutionContext.js +3 -0
  107. package/dist/esm/types/ExecutionContext.js.map +1 -1
  108. package/dist/esm/types/ExecutionResult.d.ts +14 -0
  109. package/dist/esm/types/ExecutionResult.d.ts.map +1 -1
  110. package/dist/esm/types/ExecutionResult.js +14 -0
  111. package/dist/esm/types/ExecutionResult.js.map +1 -1
  112. package/dist/esm/types/HookEmitter.d.ts +8 -1
  113. package/dist/esm/types/HookEmitter.d.ts.map +1 -1
  114. package/examples/README.md +24 -0
  115. package/examples/eligibility-check/README.md +31 -0
  116. package/examples/eligibility-check/expected-output.json +7 -0
  117. package/examples/eligibility-check/input.json +6 -0
  118. package/examples/eligibility-check/rules.json +32 -0
  119. package/examples/eligibility-check/run.ts +128 -0
  120. package/examples/pricing-rules/README.md +31 -0
  121. package/examples/pricing-rules/expected-output.json +7 -0
  122. package/examples/pricing-rules/input.json +7 -0
  123. package/examples/pricing-rules/rules.json +32 -0
  124. package/examples/pricing-rules/run.ts +136 -0
  125. package/examples/workflow-routing/README.md +31 -0
  126. package/examples/workflow-routing/expected-output.json +7 -0
  127. package/examples/workflow-routing/input.json +6 -0
  128. package/examples/workflow-routing/rules.json +33 -0
  129. package/examples/workflow-routing/run.ts +130 -0
  130. package/package.json +31 -4
  131. package/schemas/execution-context.schema.json +23 -0
  132. package/schemas/execution-output.schema.json +16 -0
  133. package/schemas/explanation-trace.schema.json +32 -0
  134. package/schemas/script.schema.json +90 -0
  135. package/schemas/validation-error.schema.json +13 -0
  136. package/src/Synapse.ts +18 -0
  137. package/src/abstracts/AbstractAction.ts +34 -0
  138. package/src/abstracts/AbstractCondition.ts +34 -0
  139. package/src/contracts/explain.ts +66 -0
  140. package/src/contracts/validation.ts +348 -0
  141. package/src/index.ts +116 -0
  142. package/src/interfaces/Action.ts +14 -0
  143. package/src/interfaces/Condition.ts +23 -0
  144. package/src/interfaces/Element.ts +18 -0
  145. package/src/interfaces/HookEvents.ts +16 -0
  146. package/src/interfaces/Parameter.ts +18 -0
  147. package/src/interfaces/Rule.ts +24 -0
  148. package/src/interfaces/Script.ts +11 -0
  149. package/src/runtime/ActionRuntime.ts +1 -1
  150. package/src/runtime/ConditionRuntime.ts +3 -3
  151. package/src/runtime/RuleRuntime.ts +1 -1
  152. package/src/types/ExecutionContext.ts +13 -0
  153. package/src/types/ExecutionResult.ts +14 -0
  154. package/src/types/HookEmitter.ts +5 -0
  155. package/dist/commonjs/Synapse.test.d.ts +0 -2
  156. package/dist/commonjs/Synapse.test.d.ts.map +0 -1
  157. package/dist/commonjs/Synapse.test.js +0 -15
  158. package/dist/commonjs/Synapse.test.js.map +0 -1
  159. package/dist/commonjs/index.test.d.ts +0 -2
  160. package/dist/commonjs/index.test.d.ts.map +0 -1
  161. package/dist/commonjs/index.test.js +0 -132
  162. package/dist/commonjs/index.test.js.map +0 -1
  163. package/dist/commonjs/runtime/ConditionRuntime.test.d.ts +0 -2
  164. package/dist/commonjs/runtime/ConditionRuntime.test.d.ts.map +0 -1
  165. package/dist/commonjs/runtime/ConditionRuntime.test.js +0 -70
  166. package/dist/commonjs/runtime/ConditionRuntime.test.js.map +0 -1
  167. package/dist/esm/Synapse.test.d.ts +0 -2
  168. package/dist/esm/Synapse.test.d.ts.map +0 -1
  169. package/dist/esm/Synapse.test.js +0 -13
  170. package/dist/esm/Synapse.test.js.map +0 -1
  171. package/dist/esm/index.test.d.ts +0 -2
  172. package/dist/esm/index.test.d.ts.map +0 -1
  173. package/dist/esm/index.test.js +0 -130
  174. package/dist/esm/index.test.js.map +0 -1
  175. package/dist/esm/runtime/ConditionRuntime.test.d.ts +0 -2
  176. package/dist/esm/runtime/ConditionRuntime.test.d.ts.map +0 -1
  177. package/dist/esm/runtime/ConditionRuntime.test.js +0 -68
  178. package/dist/esm/runtime/ConditionRuntime.test.js.map +0 -1
@@ -0,0 +1,128 @@
1
+ import {
2
+ ExecutionResult,
3
+ MessageType,
4
+ Neuron,
5
+ Synapse,
6
+ type ActionOptions,
7
+ type ExecutionContext,
8
+ type ParameterInterface,
9
+ } from "../../dist/esm/index.js";
10
+ import expectedOutput from "./expected-output.json" with { type: "json" };
11
+ import input from "./input.json" with { type: "json" };
12
+ import script from "./rules.json" with { type: "json" };
13
+
14
+ function readStatePath(context: ExecutionContext, path: string): unknown {
15
+ return path.split(".").reduce<unknown>((current, segment) => {
16
+ if (current && typeof current === "object" && segment in current) {
17
+ return (current as Record<string, unknown>)[segment];
18
+ }
19
+ return undefined;
20
+ }, context.state);
21
+ }
22
+
23
+ class StateNumberParameter {
24
+ static readonly TYPE = "state_number";
25
+
26
+ readonly id: string;
27
+ readonly type: string;
28
+ readonly name: string;
29
+ readonly value: string;
30
+ readonly options: Record<string, unknown>;
31
+
32
+ constructor(
33
+ id: string,
34
+ type: string,
35
+ name: string,
36
+ value: string,
37
+ options: Record<string, unknown>,
38
+ ) {
39
+ this.id = id;
40
+ this.type = type;
41
+ this.name = name;
42
+ this.value = value;
43
+ this.options = options;
44
+ }
45
+
46
+ getValue(context: ExecutionContext): number | null {
47
+ const value = readStatePath(context, this.value);
48
+ return typeof value === "number" ? value : null;
49
+ }
50
+ }
51
+
52
+ class SetDecisionAction {
53
+ static readonly TYPE = "set_decision";
54
+
55
+ readonly id: string;
56
+ readonly type: string;
57
+ private readonly params: ParameterInterface[];
58
+ readonly options: ActionOptions;
59
+ private readonly neuron: Neuron;
60
+
61
+ constructor(
62
+ id: string,
63
+ type: string,
64
+ params: ParameterInterface[],
65
+ options: ActionOptions,
66
+ neuron: Neuron,
67
+ ) {
68
+ this.id = id;
69
+ this.type = type;
70
+ this.params = params;
71
+ this.options = options;
72
+ this.neuron = neuron;
73
+ }
74
+
75
+ execute(context: ExecutionContext): ExecutionResult<string | null> {
76
+ const decisionParam = this.params.find((param) => param.name === "decision");
77
+ if (!decisionParam) {
78
+ return new ExecutionResult(false, context, null, ["Missing decision parameter"]);
79
+ }
80
+
81
+ const ParamCtor = this.neuron.getParameter(decisionParam.type);
82
+ const decision = ParamCtor
83
+ ? new ParamCtor(decisionParam.id, decisionParam.type, decisionParam.name, decisionParam.value, decisionParam.options, decisionParam.defaultValue).getValue(context)
84
+ : null;
85
+
86
+ if (typeof decision !== "string") {
87
+ return new ExecutionResult(false, context, null, ["Invalid decision value"]);
88
+ }
89
+
90
+ const nextContext: ExecutionContext = {
91
+ ...context,
92
+ messages: [
93
+ ...context.messages,
94
+ { type: MessageType.INFO, text: `Eligibility decision: ${decision}` },
95
+ ],
96
+ state: {
97
+ ...context.state,
98
+ eligibility: { eligible: decision === "approved", decision },
99
+ },
100
+ };
101
+
102
+ return new ExecutionResult(true, nextContext, decision);
103
+ }
104
+ }
105
+
106
+ const neuron = new Neuron();
107
+ neuron.registerParameter(StateNumberParameter.TYPE, StateNumberParameter);
108
+ neuron.registerAction(SetDecisionAction.TYPE, SetDecisionAction);
109
+
110
+ const result = new Synapse(neuron).execute(script, input as ExecutionContext);
111
+ const eligibility = result.context.state.eligibility as
112
+ | { eligible?: boolean; decision?: string }
113
+ | undefined;
114
+ const actual = {
115
+ ok: result.isSuccessful(),
116
+ rulesExecuted: result.value,
117
+ eligible: eligibility?.eligible,
118
+ decision: eligibility?.decision,
119
+ messages: result.context.messages.map((message) => message.text),
120
+ };
121
+
122
+ if (JSON.stringify(actual) !== JSON.stringify(expectedOutput)) {
123
+ console.error(JSON.stringify({ expected: expectedOutput, actual }, null, 2));
124
+ process.exit(1);
125
+ }
126
+
127
+ console.log(JSON.stringify(actual, null, 2));
128
+
@@ -0,0 +1,31 @@
1
+ # Pricing Rules Example
2
+
3
+ Run a pricing decision from serializable JSON. The rule checks the cart subtotal from `input.json`, applies a VIP discount from `rules.json`, and verifies the final context against `expected-output.json`.
4
+
5
+ ## Run
6
+
7
+ From the repository root:
8
+
9
+ ```bash
10
+ yarn build
11
+ node examples/pricing-rules/run.ts
12
+ ```
13
+
14
+ Expected summary:
15
+
16
+ ```json
17
+ {
18
+ "ok": true,
19
+ "rulesExecuted": 1,
20
+ "finalTotal": 105,
21
+ "discountAmount": 20,
22
+ "messages": ["Applied 16% discount: -20"]
23
+ }
24
+ ```
25
+
26
+ ## Files
27
+
28
+ - `rules.json` — the serializable Neuron-JS script.
29
+ - `input.json` — the execution context used by the script.
30
+ - `expected-output.json` — the checked output summary.
31
+ - `run.ts` — registers the example vocabulary, executes the script, and fails if output differs.
@@ -0,0 +1,7 @@
1
+ {
2
+ "ok": true,
3
+ "rulesExecuted": 1,
4
+ "finalTotal": 105,
5
+ "discountAmount": 20,
6
+ "messages": ["Applied 16% discount: -20"]
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "messages": [],
3
+ "state": {
4
+ "cart": { "subtotal": 125, "currency": "USD" },
5
+ "customer": { "segment": "vip" }
6
+ }
7
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "pricing-rules-demo",
3
+ "rules": [
4
+ {
5
+ "id": "vip-order-discount",
6
+ "type": "simple_rule",
7
+ "options": {},
8
+ "conditions": [
9
+ {
10
+ "id": "minimum-cart-subtotal",
11
+ "type": "compare_two_numbers",
12
+ "options": {},
13
+ "params": [
14
+ { "id": "cart-subtotal", "name": "op1", "type": "state_number", "value": "cart.subtotal", "options": {} },
15
+ { "id": "comparison", "name": "comp", "type": "comparator", "value": ">=", "options": {} },
16
+ { "id": "discount-threshold", "name": "op2", "type": "simple_number", "value": "100", "options": {} }
17
+ ]
18
+ }
19
+ ],
20
+ "actions": [
21
+ {
22
+ "id": "apply-vip-discount",
23
+ "type": "apply_discount",
24
+ "options": {},
25
+ "params": [
26
+ { "id": "discount-percent", "name": "percent", "type": "simple_number", "value": "16", "options": {} }
27
+ ]
28
+ }
29
+ ]
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ ExecutionResult,
3
+ MessageType,
4
+ Neuron,
5
+ Synapse,
6
+ type ActionOptions,
7
+ type ExecutionContext,
8
+ type ParameterInterface,
9
+ } from "../../dist/esm/index.js";
10
+ import expectedOutput from "./expected-output.json" with { type: "json" };
11
+ import input from "./input.json" with { type: "json" };
12
+ import script from "./rules.json" with { type: "json" };
13
+
14
+ function readStatePath(context: ExecutionContext, path: string): unknown {
15
+ return path.split(".").reduce<unknown>((current, segment) => {
16
+ if (current && typeof current === "object" && segment in current) {
17
+ return (current as Record<string, unknown>)[segment];
18
+ }
19
+ return undefined;
20
+ }, context.state);
21
+ }
22
+
23
+ class StateNumberParameter {
24
+ static readonly TYPE = "state_number";
25
+
26
+ readonly id: string;
27
+ readonly type: string;
28
+ readonly name: string;
29
+ readonly value: string;
30
+ readonly options: Record<string, unknown>;
31
+
32
+ constructor(
33
+ id: string,
34
+ type: string,
35
+ name: string,
36
+ value: string,
37
+ options: Record<string, unknown>,
38
+ ) {
39
+ this.id = id;
40
+ this.type = type;
41
+ this.name = name;
42
+ this.value = value;
43
+ this.options = options;
44
+ }
45
+
46
+ getValue(context: ExecutionContext): number | null {
47
+ const value = readStatePath(context, this.value);
48
+ return typeof value === "number" ? value : null;
49
+ }
50
+ }
51
+
52
+ class ApplyDiscountAction {
53
+ static readonly TYPE = "apply_discount";
54
+
55
+ readonly id: string;
56
+ readonly type: string;
57
+ private readonly params: ParameterInterface[];
58
+ readonly options: ActionOptions;
59
+ private readonly neuron: Neuron;
60
+
61
+ constructor(
62
+ id: string,
63
+ type: string,
64
+ params: ParameterInterface[],
65
+ options: ActionOptions,
66
+ neuron: Neuron,
67
+ ) {
68
+ this.id = id;
69
+ this.type = type;
70
+ this.params = params;
71
+ this.options = options;
72
+ this.neuron = neuron;
73
+ }
74
+
75
+ execute(context: ExecutionContext): ExecutionResult<number | null> {
76
+ const percentParam = this.params.find((param) => param.name === "percent");
77
+ if (!percentParam) {
78
+ return new ExecutionResult(false, context, null, ["Missing percent parameter"]);
79
+ }
80
+
81
+ const ParamCtor = this.neuron.getParameter(percentParam.type);
82
+ const percent = ParamCtor
83
+ ? new ParamCtor(percentParam.id, percentParam.type, percentParam.name, percentParam.value, percentParam.options, percentParam.defaultValue).getValue(context)
84
+ : null;
85
+ const subtotal = readStatePath(context, "cart.subtotal");
86
+
87
+ if (typeof subtotal !== "number" || typeof percent !== "number") {
88
+ return new ExecutionResult(false, context, null, ["Invalid discount input"]);
89
+ }
90
+
91
+ const discountAmount = Math.round(subtotal * (percent / 100));
92
+ const finalTotal = subtotal - discountAmount;
93
+ const nextContext: ExecutionContext = {
94
+ ...context,
95
+ messages: [
96
+ ...context.messages,
97
+ { type: MessageType.INFO, text: `Applied ${percent}% discount: -${discountAmount}` },
98
+ ],
99
+ state: {
100
+ ...context.state,
101
+ cart: {
102
+ ...(context.state.cart as Record<string, unknown>),
103
+ discountPercent: percent,
104
+ discountAmount,
105
+ finalTotal,
106
+ },
107
+ },
108
+ };
109
+
110
+ return new ExecutionResult(true, nextContext, finalTotal);
111
+ }
112
+ }
113
+
114
+ const neuron = new Neuron();
115
+ neuron.registerParameter(StateNumberParameter.TYPE, StateNumberParameter);
116
+ neuron.registerAction(ApplyDiscountAction.TYPE, ApplyDiscountAction);
117
+
118
+ const result = new Synapse(neuron).execute(script, input as ExecutionContext);
119
+ const cart = result.context.state.cart as
120
+ | { finalTotal?: number; discountAmount?: number }
121
+ | undefined;
122
+ const actual = {
123
+ ok: result.isSuccessful(),
124
+ rulesExecuted: result.value,
125
+ finalTotal: cart?.finalTotal,
126
+ discountAmount: cart?.discountAmount,
127
+ messages: result.context.messages.map((message) => message.text),
128
+ };
129
+
130
+ if (JSON.stringify(actual) !== JSON.stringify(expectedOutput)) {
131
+ console.error(JSON.stringify({ expected: expectedOutput, actual }, null, 2));
132
+ process.exit(1);
133
+ }
134
+
135
+ console.log(JSON.stringify(actual, null, 2));
136
+
@@ -0,0 +1,31 @@
1
+ # Workflow Routing Example
2
+
3
+ Run a deterministic workflow-routing decision from JSON. The rule checks ticket priority from `input.json` and routes high-priority work to the escalation lane.
4
+
5
+ ## Run
6
+
7
+ From the repository root:
8
+
9
+ ```bash
10
+ yarn build
11
+ node examples/workflow-routing/run.ts
12
+ ```
13
+
14
+ Expected summary:
15
+
16
+ ```json
17
+ {
18
+ "ok": true,
19
+ "rulesExecuted": 1,
20
+ "route": "escalation",
21
+ "slaHours": 4,
22
+ "messages": ["Workflow route: escalation within 4h"]
23
+ }
24
+ ```
25
+
26
+ ## Files
27
+
28
+ - `rules.json` — the serializable Neuron-JS script.
29
+ - `input.json` — the execution context used by the script.
30
+ - `expected-output.json` — the checked output summary.
31
+ - `run.ts` — registers the example vocabulary, executes the script, and fails if output differs.
@@ -0,0 +1,7 @@
1
+ {
2
+ "ok": true,
3
+ "rulesExecuted": 1,
4
+ "route": "escalation",
5
+ "slaHours": 4,
6
+ "messages": ["Workflow route: escalation within 4h"]
7
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "messages": [],
3
+ "state": {
4
+ "ticket": { "id": "SUP-1001", "priority": 9, "channel": "enterprise" }
5
+ }
6
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "workflow-routing-demo",
3
+ "rules": [
4
+ {
5
+ "id": "route-critical-ticket",
6
+ "type": "simple_rule",
7
+ "options": {},
8
+ "conditions": [
9
+ {
10
+ "id": "priority-threshold",
11
+ "type": "compare_two_numbers",
12
+ "options": {},
13
+ "params": [
14
+ { "id": "ticket-priority", "name": "op1", "type": "state_number", "value": "ticket.priority", "options": {} },
15
+ { "id": "comparison", "name": "comp", "type": "comparator", "value": ">=", "options": {} },
16
+ { "id": "critical-threshold", "name": "op2", "type": "simple_number", "value": "8", "options": {} }
17
+ ]
18
+ }
19
+ ],
20
+ "actions": [
21
+ {
22
+ "id": "assign-escalation-route",
23
+ "type": "set_route",
24
+ "options": {},
25
+ "params": [
26
+ { "id": "route-name", "name": "route", "type": "simple_string", "value": "escalation", "options": {} },
27
+ { "id": "sla-hours", "name": "slaHours", "type": "simple_number", "value": "4", "options": {} }
28
+ ]
29
+ }
30
+ ]
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,130 @@
1
+ import {
2
+ ExecutionResult,
3
+ MessageType,
4
+ Neuron,
5
+ Synapse,
6
+ type ActionOptions,
7
+ type ExecutionContext,
8
+ type ParameterInterface,
9
+ } from "../../dist/esm/index.js";
10
+ import expectedOutput from "./expected-output.json" with { type: "json" };
11
+ import input from "./input.json" with { type: "json" };
12
+ import script from "./rules.json" with { type: "json" };
13
+
14
+ function readStatePath(context: ExecutionContext, path: string): unknown {
15
+ return path.split(".").reduce<unknown>((current, segment) => {
16
+ if (current && typeof current === "object" && segment in current) {
17
+ return (current as Record<string, unknown>)[segment];
18
+ }
19
+ return undefined;
20
+ }, context.state);
21
+ }
22
+
23
+ class StateNumberParameter {
24
+ static readonly TYPE = "state_number";
25
+
26
+ readonly id: string;
27
+ readonly type: string;
28
+ readonly name: string;
29
+ readonly value: string;
30
+ readonly options: Record<string, unknown>;
31
+
32
+ constructor(
33
+ id: string,
34
+ type: string,
35
+ name: string,
36
+ value: string,
37
+ options: Record<string, unknown>,
38
+ ) {
39
+ this.id = id;
40
+ this.type = type;
41
+ this.name = name;
42
+ this.value = value;
43
+ this.options = options;
44
+ }
45
+
46
+ getValue(context: ExecutionContext): number | null {
47
+ const value = readStatePath(context, this.value);
48
+ return typeof value === "number" ? value : null;
49
+ }
50
+ }
51
+
52
+ class SetRouteAction {
53
+ static readonly TYPE = "set_route";
54
+
55
+ readonly id: string;
56
+ readonly type: string;
57
+ private readonly params: ParameterInterface[];
58
+ readonly options: ActionOptions;
59
+ private readonly neuron: Neuron;
60
+
61
+ constructor(
62
+ id: string,
63
+ type: string,
64
+ params: ParameterInterface[],
65
+ options: ActionOptions,
66
+ neuron: Neuron,
67
+ ) {
68
+ this.id = id;
69
+ this.type = type;
70
+ this.params = params;
71
+ this.options = options;
72
+ this.neuron = neuron;
73
+ }
74
+
75
+ private resolveParam(context: ExecutionContext, name: string): unknown {
76
+ const param = this.params.find((item) => item.name === name);
77
+ if (!param) return null;
78
+ const ParamCtor = this.neuron.getParameter(param.type);
79
+ return ParamCtor
80
+ ? new ParamCtor(param.id, param.type, param.name, param.value, param.options, param.defaultValue).getValue(context)
81
+ : null;
82
+ }
83
+
84
+ execute(context: ExecutionContext): ExecutionResult<string | null> {
85
+ const route = this.resolveParam(context, "route");
86
+ const slaHours = this.resolveParam(context, "slaHours");
87
+
88
+ if (typeof route !== "string" || typeof slaHours !== "number") {
89
+ return new ExecutionResult(false, context, null, ["Invalid route input"]);
90
+ }
91
+
92
+ const nextContext: ExecutionContext = {
93
+ ...context,
94
+ messages: [
95
+ ...context.messages,
96
+ { type: MessageType.INFO, text: `Workflow route: ${route} within ${slaHours}h` },
97
+ ],
98
+ state: {
99
+ ...context.state,
100
+ workflow: { route, slaHours },
101
+ },
102
+ };
103
+
104
+ return new ExecutionResult(true, nextContext, route);
105
+ }
106
+ }
107
+
108
+ const neuron = new Neuron();
109
+ neuron.registerParameter(StateNumberParameter.TYPE, StateNumberParameter);
110
+ neuron.registerAction(SetRouteAction.TYPE, SetRouteAction);
111
+
112
+ const result = new Synapse(neuron).execute(script, input as ExecutionContext);
113
+ const workflow = result.context.state.workflow as
114
+ | { route?: string; slaHours?: number }
115
+ | undefined;
116
+ const actual = {
117
+ ok: result.isSuccessful(),
118
+ rulesExecuted: result.value,
119
+ route: workflow?.route,
120
+ slaHours: workflow?.slaHours,
121
+ messages: result.context.messages.map((message) => message.text),
122
+ };
123
+
124
+ if (JSON.stringify(actual) !== JSON.stringify(expectedOutput)) {
125
+ console.error(JSON.stringify({ expected: expectedOutput, actual }, null, 2));
126
+ process.exit(1);
127
+ }
128
+
129
+ console.log(JSON.stringify(actual, null, 2));
130
+
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@sebasoft/neuron-js",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
- "description": "Pluggable and extensible execution runtime for functional logic.",
5
+ "description": "AI-friendly TypeScript rules engine for serializable JSON business rules and deterministic workflow decisions.",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/SebaSOFT/neuron-js.git"
@@ -13,9 +13,10 @@
13
13
  "node": ">=24.0.0"
14
14
  },
15
15
  "scripts": {
16
- "build": "tshy",
16
+ "build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tshy",
17
17
  "dev": "vitest",
18
18
  "test": "vitest run",
19
+ "examples": "yarn build && node examples/pricing-rules/run.ts && node examples/eligibility-check/run.ts && node examples/workflow-routing/run.ts",
19
20
  "lint": "biome check .",
20
21
  "format": "biome format --write .",
21
22
  "prepare": "yarn build",
@@ -48,6 +49,8 @@
48
49
  "files": [
49
50
  "dist",
50
51
  "src",
52
+ "schemas",
53
+ "examples",
51
54
  "!src/**/*.test.ts",
52
55
  "README.md",
53
56
  "LICENSE"
@@ -66,5 +69,29 @@
66
69
  "default": "./dist/commonjs/index.js"
67
70
  }
68
71
  }
69
- }
72
+ },
73
+ "keywords": [
74
+ "rules-engine",
75
+ "rule-engine",
76
+ "json-rules",
77
+ "business-rules",
78
+ "decision-engine",
79
+ "json-logic",
80
+ "workflow-automation",
81
+ "typescript",
82
+ "javascript",
83
+ "nodejs",
84
+ "browser",
85
+ "ai-agents",
86
+ "llm",
87
+ "mcp",
88
+ "deterministic",
89
+ "functional-programming",
90
+ "validation",
91
+ "feature-flags",
92
+ "policy-engine",
93
+ "authorization",
94
+ "n8n",
95
+ "langgraph"
96
+ ]
70
97
  }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sebasoft.github.io/neuron-js/schemas/execution-context.schema.json",
4
+ "title": "Neuron-JS Execution Context",
5
+ "type": "object",
6
+ "additionalProperties": true,
7
+ "required": ["messages", "state"],
8
+ "properties": {
9
+ "messages": {
10
+ "type": "array",
11
+ "items": {
12
+ "type": "object",
13
+ "required": ["type", "text"],
14
+ "properties": {
15
+ "type": { "enum": ["debug", "info", "warn", "error"] },
16
+ "text": { "type": "string" }
17
+ },
18
+ "additionalProperties": true
19
+ }
20
+ },
21
+ "state": { "type": "object", "additionalProperties": true }
22
+ }
23
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sebasoft.github.io/neuron-js/schemas/execution-output.schema.json",
4
+ "title": "Neuron-JS Execution Output",
5
+ "type": "object",
6
+ "additionalProperties": true,
7
+ "required": ["ok", "rulesExecuted", "messages"],
8
+ "properties": {
9
+ "ok": { "type": "boolean" },
10
+ "rulesExecuted": { "type": ["number", "null"] },
11
+ "messages": {
12
+ "type": "array",
13
+ "items": { "type": "string" }
14
+ }
15
+ }
16
+ }