11# Implementation Examples
22
3- This page provides practical examples of how to implement the core interfaces of ` neuron-js ` to build a custom rules engine tailored to your domain.
3+ This page shows how to implement the core extension points of ` neuron-js ` with the current public API.
4+
5+ The script boundary stays JSON-friendly: rules, actions, conditions, and parameters are arrays with explicit ` options ` objects. For plugin ergonomics, the abstract action and condition base classes transform the parameter array into a name-keyed ` Map ` , so plugin code can use ` this.params.get('name') ` .
46
57## 1. Condition Implementation
6- Conditions are logical predicates. They should be stateless and only depend on their parameters and the current ` ExecutionContext ` .
8+
9+ Conditions are logical predicates. They should be stateless and depend only on their parameters and the current ` ExecutionContext ` .
710
811``` typescript
9- import { AbstractCondition , ExecutionResult , ExecutionContext } from ' @sebasoft/neuron-js' ;
12+ import { AbstractCondition , ExecutionResult , type ExecutionContext } from ' @sebasoft/neuron-js' ;
1013
1114/**
12- * Checks if the current hour in the context state matches a target.
15+ * Checks if the current hour in the context state matches a target period .
1316 */
1417export class TimeOfDayCondition extends AbstractCondition {
15- static TYPE = ' is_time_of_day' ;
18+ static readonly TYPE = ' is_time_of_day' ;
1619
17- async execute(context : ExecutionContext ): Promise <ExecutionResult <boolean >> {
18- // 1. Resolve parameters (e.g., "morning", "afternoon")
19- const targetPeriod = this .params .get (' period' ).getValue (context );
20-
21- // 2. Access state from context
20+ execute(context : ExecutionContext ): ExecutionResult <boolean > {
21+ const targetPeriod = this .params .get (' period' )?.getValue (context );
2222 const currentHour = context .state .systemTime .getHours ();
23-
23+
2424 let isMatch = false ;
2525 if (targetPeriod === ' morning' ) isMatch = currentHour >= 6 && currentHour < 12 ;
2626 if (targetPeriod === ' afternoon' ) isMatch = currentHour >= 12 && currentHour < 18 ;
2727
28- // 3. Return result (Condition value is boolean)
29- return new ExecutionResult (isMatch , context , isMatch );
28+ return new ExecutionResult (true , context , isMatch );
3029 }
3130}
3231```
3332
3433## 2. Action Implementation
35- Actions perform side effects or mutate the context state. They are only triggered if the Rule's conditions pass.
34+
35+ Actions perform side effects or return an updated context. They are only triggered if the rule's conditions pass.
3636
3737``` typescript
38- import { AbstractAction , ExecutionResult , ExecutionContext } from ' @sebasoft/neuron-js' ;
38+ import { AbstractAction , ExecutionResult , MessageType , type ExecutionContext } from ' @sebasoft/neuron-js' ;
3939
4040/**
4141 * Adds a customized audit message to the context.
4242 */
4343export class AuditLogAction extends AbstractAction {
44- static TYPE = ' audit_log' ;
44+ static readonly TYPE = ' audit_log' ;
4545
46- async execute(context : ExecutionContext ): Promise < ExecutionResult <void > > {
47- const messageTemplate = this .params .get (' template' ).getValue (context );
46+ execute(context : ExecutionContext ): ExecutionResult <void > {
47+ const messageTemplate = this .params .get (' template' )? .getValue (context );
4848 const user = context .state .user .name ;
4949
50- // Mutate the context by adding a new message
5150 const nextContext = {
5251 ... context ,
5352 messages: [
5453 ... context .messages ,
55- { type: ' info ' , text: ` ${user }: ${messageTemplate } ` }
56- ]
54+ { type: MessageType . INFO , text: ` ${user }: ${messageTemplate } ` },
55+ ],
5756 };
5857
5958 return new ExecutionResult (true , nextContext );
@@ -62,126 +61,165 @@ export class AuditLogAction extends AbstractAction {
6261```
6362
6463## 3. Parameter Implementation
65- Parameters resolve values. They can be static (hardcoded in JSON) or dynamic (resolving paths in state or environment variables).
64+
65+ Parameters resolve values. They can be static, dynamic from context, or backed by another source.
6666
6767``` typescript
68- import { AbstractParameter , ExecutionContext } from ' @sebasoft/neuron-js' ;
68+ import { AbstractParameter , type ExecutionContext } from ' @sebasoft/neuron-js' ;
6969
7070/**
7171 * Resolves a value from an environment variable.
7272 */
7373export class EnvVarParameter extends AbstractParameter <string > {
74- static TYPE = ' env_var' ;
74+ static readonly TYPE = ' env_var' ;
7575
76- getValue(context : ExecutionContext ): string | null {
77- // Parameters can use their 'value' property as the key
78- const envKey = this .value ;
79- return process .env [envKey ] || this .defaultValue || null ;
76+ getValue(_context : ExecutionContext ): string | null {
77+ const envKey = this .value ;
78+ return envKey ? process .env [envKey ] ?? this .defaultValue ?? null : this .defaultValue ?? null ;
8079 }
8180}
8281```
8382
8483## 4. Context Implementation
85- While ` ExecutionContext ` is a flexible interface, you should define a strict schema for your application's state to ensure type safety in your plugins.
84+
85+ ` ExecutionContext ` is intentionally flexible. Define a stricter application-level context for your own plugins.
8686
8787``` typescript
88+ import type { ExecutionContext } from ' @sebasoft/neuron-js' ;
89+
8890export interface MyAppContext extends ExecutionContext {
8991 state: {
9092 user: { id: string ; name: string ; loyalty: number };
91- cart: { items: any []; total: number };
93+ cart: { items: unknown []; total: number };
9294 systemTime: Date ;
9395 };
9496}
9597```
9698
9799## 5. Putting It All Together
98- The following example shows how to register these custom plugins and execute a script that uses them.
100+
101+ The following example registers custom plugins and executes a JSON-serializable script.
99102
100103``` typescript
101104import { Neuron , Synapse } from ' @sebasoft/neuron-js' ;
102105
103- // 1. Initialize Registry and register plugins
104106const registry = new Neuron ();
105107registry .registerCondition (TimeOfDayCondition .TYPE , TimeOfDayCondition );
106108registry .registerAction (AuditLogAction .TYPE , AuditLogAction );
107109registry .registerParameter (EnvVarParameter .TYPE , EnvVarParameter );
108110
109- // 2. Create the Engine
110111const engine = new Synapse (registry );
111112
112- // 3. Define Logic (JSON)
113113const script = {
114114 id: ' morning-greeting' ,
115- rules: [{
116- id: ' greet-rule' ,
117- type: ' simple_rule' ,
118- conditions: [
119- {
120- type: ' is_time_of_day' ,
121- params: [{ name: ' period' , type: ' simple_string' , value: ' morning' }]
122- }
123- ],
124- actions: [
125- {
126- type: ' audit_log' ,
127- params: [{ name: ' template' , type: ' simple_string' , value: ' Good morning!' }]
128- }
129- ]
130- }]
115+ rules: [
116+ {
117+ id: ' greet-rule' ,
118+ type: ' simple_rule' ,
119+ options: {},
120+ conditions: [
121+ {
122+ id: ' morning-condition' ,
123+ type: ' is_time_of_day' ,
124+ options: {},
125+ params: [
126+ {
127+ id: ' period-param' ,
128+ name: ' period' ,
129+ type: ' simple_string' ,
130+ value: ' morning' ,
131+ options: {},
132+ },
133+ ],
134+ },
135+ ],
136+ actions: [
137+ {
138+ id: ' audit-action' ,
139+ type: ' audit_log' ,
140+ options: {},
141+ params: [
142+ {
143+ id: ' template-param' ,
144+ name: ' template' ,
145+ type: ' simple_string' ,
146+ value: ' Good morning!' ,
147+ options: {},
148+ },
149+ ],
150+ },
151+ ],
152+ },
153+ ],
131154};
132155
133- // 4. Run it
134156const context: MyAppContext = {
135157 state: {
136158 user: { id: ' 1' , name: ' Alice' , loyalty: 10 },
137159 cart: { items: [], total: 0 },
138- systemTime: new Date () // Suppose it's 9:00 AM
160+ systemTime: new Date (),
139161 },
140- messages: []
162+ messages: [],
141163};
142164
143165const result = engine .execute (script , context );
144- console .log (result .context .messages ); // [{ type: 'info', text: 'Alice: Good morning!' }]
166+ console .log (result .context .messages );
145167```
146168
147169## 6. Logical Grouping (AND/OR)
170+
148171` neuron-js ` uses a "Sum of Products" approach to logic. Conditions are grouped into blocks.
149- - ** AND** : All conditions within a block must be true.
150- - ** OR** : If any block is true, the entire rule evaluates to true.
172+
173+ - ** AND** : all conditions within a block must be true.
174+ - ** OR** : if any block is true, the entire rule evaluates to true.
151175
152176### How to trigger OR
153- Setting ` orCondition: true ` on a condition starts a ** new block** . This new block is joined to the previous block with an ` OR ` operator.
177+
178+ Setting ` orCondition: true ` on a condition's ` options ` starts a new block. This new block is joined to the previous block with an ` OR ` operator.
154179
155180### Example: (A AND B) OR (C AND D)
181+
156182``` json
157183{
158- "id" : " complex-logic" ,
184+ "id" : " complex-rule" ,
185+ "type" : " simple_rule" ,
186+ "options" : {},
187+ "actions" : [],
159188 "conditions" : [
160- { "id" : " A" , "type" : " is_gold_member" },
161- { "id" : " B" , "type" : " has_high_balance" },
162- {
163- "id" : " C" ,
164- "type" : " is_new_user" ,
165- "options" : { "orCondition" : true }
189+ { "id" : " A" , "type" : " is_gold_member" , "options" : {}, "params" : [] },
190+ { "id" : " B" , "type" : " has_high_balance" , "options" : {}, "params" : [] },
191+ {
192+ "id" : " C" ,
193+ "type" : " is_new_user" ,
194+ "options" : { "orCondition" : true },
195+ "params" : []
166196 },
167- { "id" : " D" , "type" : " has_promo_code" }
197+ { "id" : " D" , "type" : " has_promo_code" , "options" : {}, "params" : [] }
168198 ]
169199}
170200```
171- ** Evaluation Logic** : ` (A && B) || (C && D) `
201+
202+ Evaluation logic: ` (A && B) || (C && D) `
172203
173204## 7. Condition Inversion (NOT)
174- Any condition can be negated by setting ` inverted: true ` in its options. This is handled by the runtime, so your plugin implementation doesn't need to worry about it.
205+
206+ Any condition can be negated by setting ` inverted: true ` in its ` options ` . This is handled by the runtime, so plugin implementations do not need special inversion logic.
175207
176208``` json
177209{
178- "id" : " not-blocked" ,
210+ "id" : " not-blocked-rule" ,
211+ "type" : " simple_rule" ,
212+ "options" : {},
213+ "actions" : [],
179214 "conditions" : [
180- {
181- "type" : " is_user_blocked" ,
182- "options" : { "inverted" : true }
215+ {
216+ "id" : " not-blocked" ,
217+ "type" : " is_user_blocked" ,
218+ "options" : { "inverted" : true },
219+ "params" : []
183220 }
184221 ]
185222}
186223```
187- ** Evaluation Logic** : ` !is_user_blocked `
224+
225+ Evaluation logic: ` !is_user_blocked `
0 commit comments