@theotherwillembotha/node-red-circuitbreaker 0.0.53

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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Willem Botha
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # @theotherwillembotha/node-red-circuitbreaker
2
+
3
+ Circuit Breaker nodes for Node-RED with configurable fault detection, trip conditions, and state management. Built on [@theotherwillembotha/node-red-plugincore](https://github.com/theotherwillembotha/nodered_plugincore).
4
+
5
+ ---
6
+
7
+ ## What is a Circuit Breaker?
8
+
9
+ A circuit breaker is a resilience pattern that protects high-volume flows from cascading failures. When an external dependency (an HTTP endpoint, a database, a third-party service) starts failing, a circuit breaker can detect the fault, stop sending requests to the failing system, and give it time to recover, rather than hammering it with traffic and making the situation worse.
10
+
11
+ The breaker has two states:
12
+
13
+ - **Closed** - the system is healthy. Messages flow through normally.
14
+ - **Open** (tripped) - a fault threshold has been reached. Messages are diverted so they can be buffered, dropped, or handled gracefully until the system recovers.
15
+
16
+ Recovery is fully customisable. You can reset automatically after a timeout, send periodic probe messages to test recovery, or require a configurable number of consecutive successes before closing again.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ Either use the **Manage Palette** option in the Node-RED editor, or run the following in your Node-RED user directory (typically `~/.node-red`):
23
+
24
+ ```bash
25
+ npm install @theotherwillembotha/node-red-circuitbreaker
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Nodes
31
+
32
+ ### Circuit Breaker Config
33
+
34
+ Shared state node that holds the breaker's open/closed state and context store. Every other node in this plugin must reference a config node. Multiple breaker nodes can share the same config node; if one of several integrations with a system detects a fault, all of them can trip together.
35
+
36
+ | Field | Description |
37
+ |---------------|-------------|
38
+ | Name | A descriptive label for this breaker instance. |
39
+ | Default State | The state the breaker starts in on deployment. **Closed** (default) - messages flow normally. **Open** - the breaker starts tripped. |
40
+
41
+ The config node also holds a **BreakerContext** - a key/value store accessible from Fault Detector and State scripts, useful for maintaining rolling windows or counters across messages.
42
+
43
+ ![Circuit Breaker Config](documentation/circuitbreakerconfignode.png)
44
+
45
+ ---
46
+
47
+ ### Circuit Breaker
48
+
49
+ Routes incoming messages based on the current breaker state. Place this node before any integration that may fail.
50
+
51
+ | Output | When | Description |
52
+ |--------|------|-------------|
53
+ | 1 - Closed | Breaker is closed (healthy) | Message is forwarded for normal processing |
54
+ | 2 - Open | Breaker is tripped | Message is diverted for buffering or error handling |
55
+
56
+ The node displays a **green dot** when closed and a **red dot** when open, updating automatically on state changes.
57
+
58
+ ![Circuit Breaker](documentation/curcuitbreakernode.png)
59
+
60
+ ---
61
+
62
+ ### Circuit Breaker Fault Detector
63
+
64
+ Inspects messages for fault conditions and decides whether to trip the breaker. Place this node after the integration you are protecting - typically after an HTTP request node.
65
+
66
+ | Output | When | Description |
67
+ |--------|------|-------------|
68
+ | 1 - Output | No fault detected | Message forwarded normally |
69
+ | 2 - Fault | Fault detected | Message forwarded for error handling |
70
+
71
+ #### Fault function
72
+
73
+ Called on every message that passes through this node. Return `true` if the message represents a failure, or `false` if it is healthy. This is where you define what "failure" means for your integration - a bad HTTP status code, a missing property, an error flag set upstream, and so on.
74
+
75
+ When the fault function returns `true`, the message is routed to the **fault** output and the trip function is evaluated. When it returns `false`, the message is considered successful and routed to the normal **output**.
76
+
77
+ ```javascript
78
+ // Trip if the HTTP response was an error
79
+ return msg.statusCode !== undefined && msg.statusCode >= 400;
80
+ ```
81
+
82
+ #### Trip function
83
+
84
+ Called only when the fault function returns `true`. Return `true` to trip the breaker immediately, or `false` to record the fault but leave the breaker closed.
85
+
86
+ This is where you implement your threshold or windowing strategy. You rarely want to trip on a single fault - instead you accumulate faults over a rolling time window and trip only when enough failures occur within that period. The breaker's context store persists between messages, making it ideal for tracking this state.
87
+
88
+ The example below trips the breaker once 5 faults occur within a 10-second window. Each call adds the current timestamp to a buffer, discards entries outside the window, saves the buffer back, then checks whether the count has hit the threshold:
89
+
90
+ ```javascript
91
+ let buffer = breaker.context().get("buffer", []);
92
+ let interval = 10 * 1000; // sliding window of 10 seconds
93
+ let capacity = 5; // trip after 5 faults within the window
94
+ let now = new Date().getTime();
95
+
96
+ buffer.push(now);
97
+ buffer = buffer.filter(t => t + interval >= now); // discard entries outside the window
98
+ breaker.context().set("buffer", buffer);
99
+
100
+ return buffer.length >= capacity; // true = trip the breaker
101
+ ```
102
+
103
+ ![Circuit Breaker Fault Detector](documentation/curcuitbreakerfaultdetectornode.png)
104
+
105
+ ---
106
+
107
+ ### Circuit Breaker Event
108
+
109
+ Fires a message whenever the breaker changes state. No input required - use this node to trigger notifications, update dashboards, or start recovery flows.
110
+
111
+ | Output | Fires when | Message |
112
+ |--------|-----------|---------|
113
+ | 1 - Trip | Breaker is tripped | `{ state: "Trip" }` |
114
+ | 2 - Reset | Breaker is reset | `{ state: "Reset" }` |
115
+
116
+ ![Circuit Breaker Event](documentation/circuitbreakereventnode.png)
117
+
118
+ ---
119
+
120
+ ### Circuit Breaker State
121
+
122
+ Manually controls the breaker state when a message arrives. Use this node to build recovery flows, health-check driven resets, or manual override controls.
123
+
124
+ | Action | Description |
125
+ |--------|-------------|
126
+ | **Reset** | Closes the breaker - messages flow through Circuit Breaker nodes normally again. |
127
+ | **Trip** | Opens the breaker - messages are diverted to the open output of Circuit Breaker nodes. |
128
+ | **Script** | Runs a custom script with access to the full breaker API and the incoming message. |
129
+
130
+ #### Script action
131
+
132
+ The script receives `(breaker, message)`. Use it for conditional or gradual recovery logic:
133
+
134
+ ```javascript
135
+ // Reset only if the breaker has been open for at least 30 seconds
136
+ let trippedAt = breaker.context().get("trippedAt", 0);
137
+ if (new Date().getTime() - trippedAt > 30000) {
138
+ breaker.reset();
139
+ }
140
+ ```
141
+
142
+ The message is forwarded on the output after the action has been applied.
143
+
144
+ ![Circuit Breaker State](documentation/circuitbreakerstatenode.png)
145
+
146
+ ---
147
+
148
+ ## Typical flow
149
+
150
+ ```
151
+ [Inject] → [Circuit Breaker] → closed → [HTTP Request] → [Fault Detector] → output → [Process Response]
152
+ ↓ open ↓ fault
153
+ [Buffer / Drop] [Log / Notify]
154
+
155
+ [Circuit Breaker Event] → trip → [Notify / Start recovery timer]
156
+ → reset → [Resume buffered messages]
157
+
158
+ [Recovery probe / Inject] → [Circuit Breaker State: Reset]
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Integration with node-red-plugincore
164
+
165
+ All action nodes (Circuit Breaker, Fault Detector, Event, State) support the `LoggerTemplate` and `MetricsTemplate` from [@theotherwillembotha/node-red-plugincore](https://github.com/theotherwillembotha/nodered_plugincore):
166
+
167
+ - **Logging** - attach a Console, REST, or Loki logger config node to capture structured logs from each node.
168
+ - **Metrics** - enable Prometheus counters that track message throughput, trip counts, and reset counts per node instance.
169
+
170
+ ---
171
+
172
+ ## Example flow
173
+
174
+ ![Example Flow](documentation/circuitbreaker_exampleflow01.png)
175
+
176
+ On deployment, the circuit breaker initialises in the state configured on the Circuit Breaker Config node. While the breaker is closed, the Circuit Breaker node forwards messages to its closed output — in this example, an inject node drives a periodic HTTP request to an intentionally invalid URL that returns a 404. The response is fed into the Fault Detector node, which evaluates the HTTP status code to determine whether a fault occurred. When a fault is detected, the trip function checks whether the threshold has been exceeded: it maintains a rolling buffer of failure timestamps and trips the breaker if five or more faults occur within a 10-second window.
177
+
178
+ Once the breaker trips open, the Circuit Breaker node diverts subsequent messages to its open output rather than forwarding them to the HTTP request. At the same moment, the Circuit Breaker Event node fires and emits a message on its **Trip** output, which can be used to trigger a notification or start a recovery flow.
179
+
180
+ A manual **Reset** button is included in the flow. Pressing it sends a message into the Circuit Breaker State node, which is configured with a Script action that calls `breaker.reset()`. The same node can equally be set to **Reset** or **Trip** directly, without a script, for simpler override controls.
181
+
182
+ ---
183
+
184
+ ## References
185
+
186
+ - [node-red-plugincore](https://github.com/theotherwillembotha/nodered_plugincore)
187
+ - [Circuit Breaker pattern - Martin Fowler](https://martinfowler.com/bliki/CircuitBreaker.html)
188
+
189
+ ## License
190
+
191
+ [ISC](LICENSE)
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
4
+ // services.
5
+ // nodes.
6
+ const CircuitBreakerConfigNode_1 = require("./circuitbreaker/node/CircuitBreakerConfigNode");
7
+ const CircuitBreakerNode_1 = require("./circuitbreaker/node/CircuitBreakerNode");
8
+ const CircuitBreakerFaultDetectorNode_1 = require("./circuitbreaker/node/CircuitBreakerFaultDetectorNode");
9
+ const CircuitBreakerEventNode_1 = require("./circuitbreaker/node/CircuitBreakerEventNode");
10
+ const CircuitBreakerStateNode_1 = require("./circuitbreaker/node/CircuitBreakerStateNode");
11
+ new node_red_plugincore_1.NodeGenerator("./src/")
12
+ // services.
13
+ // nodes
14
+ .registerNode(CircuitBreakerConfigNode_1.CircuitBreakerConfigNode)
15
+ .registerNode(CircuitBreakerNode_1.CircuitBreakerNode)
16
+ .registerNode(CircuitBreakerFaultDetectorNode_1.CircuitBreakerFaultDetectorNode)
17
+ .registerNode(CircuitBreakerEventNode_1.CircuitBreakerEventNode)
18
+ .registerNode(CircuitBreakerStateNode_1.CircuitBreakerStateNode)
19
+ // done.
20
+ .generate("./build/Nodes", "./build/Plugins");
21
+ process.exit(0);