Skip to content

Commit 77ad10e

Browse files
authored
Merge pull request #5 from SebaSOFT/growth/njs-growth-02-publish-runnable-examples
docs: add runnable neuron-js examples
2 parents caf5d8e + 31c2fae commit 77ad10e

21 files changed

Lines changed: 738 additions & 3 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Use it when hardcoded `if/else` logic is too rigid, but a heavyweight workflow o
2020
- Documentation: <https://sebasoft.github.io/neuron-js/>
2121
- npm: <https://www.npmjs.com/package/@sebasoft/neuron-js>
2222
- GitHub: <https://github.com/SebaSOFT/neuron-js>
23-
- Examples: planned under `examples/` as `NJS-GROWTH-02`
23+
- Examples: [`examples/`](examples/) with pricing, eligibility, and workflow-routing scenarios
2424
- Schemas and validation docs: planned as `NJS-GROWTH-03`
2525
- AI-readable docs: planned as `NJS-GROWTH-04`
2626

@@ -152,11 +152,14 @@ interface ExecutionContext {
152152

153153
## Roadmap-aligned docs
154154

155-
The current public surface focuses on installation, positioning, core concepts, and runtime architecture.
155+
The current public surface includes installation, positioning, core concepts, runtime architecture, and runnable examples.
156+
157+
Available adoption assets:
158+
159+
- Runnable examples: [`examples/`](examples/)
156160

157161
Planned next adoption assets:
158162

159-
- Runnable examples: `NJS-GROWTH-02`
160163
- JSON Schemas, validation, and explain output: `NJS-GROWTH-03`
161164
- `llms.txt` and AI-assistant documentation: `NJS-GROWTH-04`
162165
- Comparison and migration pages: `NJS-GROWTH-05`
@@ -177,6 +180,7 @@ We use a modern toolchain for high-signal development:
177180
```bash
178181
yarn test # Run test suite
179182
yarn lint # Check linting and formatting
183+
yarn examples # Build and verify runnable examples
180184
yarn build # Generate ESM/CJS bundles
181185
yarn docs:build # Build API docs and VitePress site
182186
```

docs/.vitepress/config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default defineConfig({
1010
nav: [
1111
{ text: 'Home', link: '/' },
1212
{ text: 'Concepts', link: '/overview' },
13+
{ text: 'Examples', link: '/use-cases/runnable-examples' },
1314
{ text: 'API', link: '/api/README' }
1415
],
1516
sidebar: [
@@ -27,6 +28,14 @@ export default defineConfig({
2728
{ text: 'Implementation Examples', link: '/concepts/implementation-examples' }
2829
]
2930
},
31+
{
32+
text: 'Use Cases',
33+
items: [
34+
{ text: 'Runnable Examples', link: '/use-cases/runnable-examples' },
35+
{ text: 'Business Rules Engine', link: '/use-cases/business-rules-engine' },
36+
{ text: 'Dynamic Routing', link: '/use-cases/dynamic-routing' }
37+
]
38+
},
3039
{
3140
text: 'API Reference',
3241
link: '/api/README'

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ hero:
1212
- theme: brand
1313
text: Get Started
1414
link: /overview
15+
- theme: alt
16+
text: Run Examples
17+
link: /use-cases/runnable-examples
1518
- theme: alt
1619
text: View on GitHub
1720
link: https://github.com/SebaSOFT/neuron-js
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Runnable examples
2+
3+
`neuron-js` includes first-party examples that can be executed from a clean checkout. Each example keeps the rule definition, input context, expected output, and runner separate so developers and coding agents can inspect the full contract before changing code.
4+
5+
## Run all examples
6+
7+
From the repository root:
8+
9+
```bash
10+
yarn examples
11+
```
12+
13+
The command builds the package, then runs:
14+
15+
- `examples/pricing-rules/run.ts`
16+
- `examples/eligibility-check/run.ts`
17+
- `examples/workflow-routing/run.ts`
18+
19+
Each runner exits with a non-zero status if the actual output differs from `expected-output.json`.
20+
21+
## Example catalog
22+
23+
### Pricing rules
24+
25+
Path: [`examples/pricing-rules/`](https://github.com/SebaSOFT/neuron-js/tree/main/examples/pricing-rules)
26+
27+
Demonstrates a cart-pricing decision stored as JSON. The script checks a cart subtotal and applies a VIP discount through a registered action.
28+
29+
Files:
30+
31+
- `rules.json` — serializable script.
32+
- `input.json` — execution context.
33+
- `expected-output.json` — verified output summary.
34+
- `run.ts` — executable TypeScript runner.
35+
36+
### Eligibility check
37+
38+
Path: [`examples/eligibility-check/`](https://github.com/SebaSOFT/neuron-js/tree/main/examples/eligibility-check)
39+
40+
Demonstrates an approval decision. The script checks an applicant score and writes an approved eligibility decision into context.
41+
42+
Files:
43+
44+
- `rules.json` — serializable script.
45+
- `input.json` — execution context.
46+
- `expected-output.json` — verified output summary.
47+
- `run.ts` — executable TypeScript runner.
48+
49+
### Workflow routing
50+
51+
Path: [`examples/workflow-routing/`](https://github.com/SebaSOFT/neuron-js/tree/main/examples/workflow-routing)
52+
53+
Demonstrates deterministic routing for automation workflows. The script checks ticket priority and assigns an escalation route with an SLA.
54+
55+
Files:
56+
57+
- `rules.json` — serializable script.
58+
- `input.json` — execution context.
59+
- `expected-output.json` — verified output summary.
60+
- `run.ts` — executable TypeScript runner.
61+
62+
## Why this structure
63+
64+
The examples are intentionally data-first:
65+
66+
- JSON rule files can be stored, versioned, reviewed, or generated.
67+
- Input files make scenario testing repeatable.
68+
- Expected outputs define a clear contract for humans and AI coding agents.
69+
- TypeScript runners show the minimal registry setup required by each scenario.

examples/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Runnable Examples
2+
3+
These examples are copy-paste friendly Neuron-JS scenarios. Each folder contains a serializable `rules.json`, an `input.json` execution context, an `expected-output.json` contract, and a `run.ts` file that executes and verifies the scenario.
4+
5+
## Available examples
6+
7+
- [Pricing rules](pricing-rules/) — apply a VIP discount when a cart meets a subtotal threshold.
8+
- [Eligibility check](eligibility-check/) — approve an applicant when a score crosses a threshold.
9+
- [Workflow routing](workflow-routing/) — route a high-priority support ticket to an escalation lane.
10+
11+
## Run all examples
12+
13+
From the repository root:
14+
15+
```bash
16+
yarn examples
17+
```
18+
19+
Or run one example directly after building:
20+
21+
```bash
22+
yarn build
23+
node examples/pricing-rules/run.ts
24+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Eligibility Check Example
2+
3+
Run an eligibility decision from JSON. The rule checks whether an applicant score passes the required threshold, then writes the approved status into the execution context.
4+
5+
## Run
6+
7+
From the repository root:
8+
9+
```bash
10+
yarn build
11+
node examples/eligibility-check/run.ts
12+
```
13+
14+
Expected summary:
15+
16+
```json
17+
{
18+
"ok": true,
19+
"rulesExecuted": 1,
20+
"eligible": true,
21+
"decision": "approved",
22+
"messages": ["Eligibility decision: approved"]
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.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ok": true,
3+
"rulesExecuted": 1,
4+
"eligible": true,
5+
"decision": "approved",
6+
"messages": ["Eligibility decision: approved"]
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"messages": [],
3+
"state": {
4+
"applicant": { "score": 735, "region": "AR" }
5+
}
6+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"id": "eligibility-check-demo",
3+
"rules": [
4+
{
5+
"id": "approve-qualified-applicant",
6+
"type": "simple_rule",
7+
"options": {},
8+
"conditions": [
9+
{
10+
"id": "score-threshold",
11+
"type": "compare_two_numbers",
12+
"options": {},
13+
"params": [
14+
{ "id": "applicant-score", "name": "op1", "type": "state_number", "value": "applicant.score", "options": {} },
15+
{ "id": "comparison", "name": "comp", "type": "comparator", "value": ">=", "options": {} },
16+
{ "id": "required-score", "name": "op2", "type": "simple_number", "value": "700", "options": {} }
17+
]
18+
}
19+
],
20+
"actions": [
21+
{
22+
"id": "mark-approved",
23+
"type": "set_decision",
24+
"options": {},
25+
"params": [
26+
{ "id": "decision-value", "name": "decision", "type": "simple_string", "value": "approved", "options": {} }
27+
]
28+
}
29+
]
30+
}
31+
]
32+
}

examples/eligibility-check/run.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)