@theotherwillembotha/node-red-plugincore 0.1.0 → 0.2.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.
- package/README.md +616 -220
- package/build/GenerateNodes.js +3 -6
- package/build/Nodes.html +3 -1504
- package/build/Nodes.js +5 -15
- package/build/Plugins.html +227 -0
- package/build/Plugins.js +18 -8
- package/build/core/NodeConstructor.d.ts +9 -16
- package/build/core/NodeConstructor.d.ts.map +1 -1
- package/build/core/NodeConstructor.js +109 -146
- package/build/core/NodeGenerator.d.ts.map +1 -1
- package/build/core/NodeGenerator.js +79 -28
- package/build/core/NodeManagerRuntime.d.ts +44 -0
- package/build/core/NodeManagerRuntime.d.ts.map +1 -0
- package/build/core/NodeManagerRuntime.js +140 -0
- package/build/core/Utils.d.ts.map +1 -1
- package/build/core/logger/service/LoggerService.d.ts +6 -5
- package/build/core/logger/service/LoggerService.d.ts.map +1 -1
- package/build/core/logger/service/LoggerService.js +51 -20
- package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -1
- package/build/core/metrics/MetricsDecorator.d.ts +1 -2
- package/build/core/metrics/MetricsDecorator.d.ts.map +1 -1
- package/build/core/metrics/MetricsDecorator.js +89 -116
- package/build/core/metrics/node/CounterMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/CounterMetricConfigNode.js +4 -5
- package/build/core/metrics/node/GaugeMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/GaugeMetricConfigNode.js +5 -6
- package/build/core/metrics/node/MetricsConfigNode.d.ts +19 -9
- package/build/core/metrics/node/MetricsConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/MetricsConfigNode.js +14 -45
- package/build/core/metrics/node/TimerMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/TimerMetricConfigNode.js +11 -17
- package/build/core/metrics/service/MetricsService.d.ts +110 -114
- package/build/core/metrics/service/MetricsService.d.ts.map +1 -1
- package/build/core/metrics/service/MetricsService.js +122 -339
- package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/MetricsTemplate.js +1 -2
- package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.d.ts +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.d.ts.map +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.js +1 -1
- package/build/core/other/service/InputService.d.ts.map +1 -1
- package/build/core/other/service/SettingsService.d.ts +1 -2
- package/build/core/other/service/SettingsService.d.ts.map +1 -1
- package/build/core/other/service/SettingsService.js +19 -5
- package/build/core/other/template/BasicTemplate.d.ts.map +1 -1
- package/build/core/other/template/SettingsTemplate.d.ts.map +1 -1
- package/build/core/state/node/InternalStateConfigNode.d.ts +16 -0
- package/build/core/state/node/InternalStateConfigNode.d.ts.map +1 -0
- package/build/core/state/node/InternalStateConfigNode.js +89 -0
- package/build/core/state/node/StateConfigNode.d.ts +24 -0
- package/build/core/state/node/StateConfigNode.d.ts.map +1 -0
- package/build/core/state/node/StateConfigNode.js +22 -0
- package/build/core/state/service/StateService.d.ts +34 -0
- package/build/core/state/service/StateService.d.ts.map +1 -0
- package/build/core/state/service/StateService.js +201 -0
- package/build/core/state/template/StateTemplate.d.ts +8 -0
- package/build/core/state/template/StateTemplate.d.ts.map +1 -0
- package/build/core/state/template/StateTemplate.js +11 -0
- package/build/core/tagging/ServiceDescriptionDecorator.d.ts +10 -0
- package/build/core/tagging/ServiceDescriptionDecorator.d.ts.map +1 -0
- package/build/core/tagging/ServiceDescriptionDecorator.js +10 -0
- package/build/core/tagging/service/NodeTypeService.d.ts +1 -2
- package/build/core/tagging/service/NodeTypeService.d.ts.map +1 -1
- package/build/core/tagging/service/NodeTypeService.js +19 -5
- package/build/core/ui/template/ScriptEditorTemplate.d.ts.map +1 -1
- package/build/core/ui/template/UIHelperTemplate.d.ts.map +1 -1
- package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -1
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts.map +1 -1
- package/build/core/webhook/service/WebhookServerService.d.ts +3 -4
- package/build/core/webhook/service/WebhookServerService.d.ts.map +1 -1
- package/build/core/webhook/service/WebhookServerService.js +48 -18
- package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -1
- package/build/index.d.ts +5 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -2
- package/build/runtime/NodeManagerRuntime.js +140 -0
- package/documentation/CounterMetricConfigNode.png +0 -0
- package/documentation/GaugeMetricConfigNode.png +0 -0
- package/documentation/LoggerTemplate.png +0 -0
- package/documentation/MetricsTemplate.png +0 -0
- package/documentation/StateTemplate.png +0 -0
- package/documentation/TimerMetricConfigNode_histogram.png +0 -0
- package/documentation/TimerMetricConfigNode_summary.png +0 -0
- package/documentation/WebhookServerConfigNode.png +0 -0
- package/documentation/WebhookTemplate.png +0 -0
- package/package.json +4 -10
- package/src/GenerateNodes.ts +7 -11
- package/src/core/NodeConstructor.ts +130 -186
- package/src/core/NodeGenerator.ts +88 -33
- package/src/core/NodeManagerRuntime.ts +179 -0
- package/src/core/logger/service/LoggerService.ts +39 -26
- package/src/core/logger/template/LoggerTemplate.html +11 -6
- package/src/core/metrics/MetricsDecorator.ts +140 -168
- package/src/core/metrics/node/CounterMetricConfigNode.ts +20 -20
- package/src/core/metrics/node/GaugeMetricConfigNode.ts +24 -25
- package/src/core/metrics/node/MetricsConfigNode.ts +29 -48
- package/src/core/metrics/node/TimerMetricConfigNode.ts +50 -59
- package/src/core/metrics/service/MetricsService.ts +214 -489
- package/src/core/metrics/template/CounterMetricTemplate.html +0 -1
- package/src/core/metrics/template/GaugeMetricTemplate.html +1 -1
- package/src/core/metrics/template/MetricsTemplate.html +132 -13
- package/src/core/metrics/template/MetricsTemplate.ts +3 -4
- package/src/core/other/node/DelegatedConfigReferenceNode.ts +1 -1
- package/src/core/other/service/SettingsService.ts +6 -10
- package/src/core/other/template/BasicTemplate.html +1 -1
- package/src/core/state/node/InternalStateConfigNode.html +77 -0
- package/src/core/state/node/InternalStateConfigNode.ts +102 -0
- package/src/core/state/node/StateConfigNode.ts +29 -0
- package/src/core/state/service/StateService.ts +180 -0
- package/src/core/state/template/StateTemplate.html +244 -0
- package/src/core/state/template/StateTemplate.ts +17 -0
- package/src/core/tagging/ServiceDescriptionDecorator.ts +23 -0
- package/src/core/tagging/service/NodeTypeService.ts +6 -11
- package/src/core/ui/template/UIHelperTemplate.html +41 -26
- package/src/core/webhook/service/WebhookServerService.ts +39 -23
- package/src/core/webhook/template/WebhookTemplate.html +28 -35
- package/src/index.ts +7 -2
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts +0 -20
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts.map +0 -1
- package/build/core/logger/node/ConsoleLoggerConfigNode.js +0 -62
- package/build/core/logger/node/RestLoggerConfigNode.d.ts +0 -40
- package/build/core/logger/node/RestLoggerConfigNode.d.ts.map +0 -1
- package/build/core/logger/node/RestLoggerConfigNode.js +0 -128
- package/documentation/ConsoleLoggerConfigNode.png +0 -0
- package/documentation/MetricsConfigNode.png +0 -0
- package/documentation/RestLoggerConfigNode.png +0 -0
- package/documentation/TimerMetricConfigNode.png +0 -0
- package/documentation/WebhookNode.png +0 -0
- package/src/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
- package/src/core/logger/node/ConsoleLoggerConfigNode.ts +0 -71
- package/src/core/logger/node/RestLoggerConfigNode.html +0 -183
- package/src/core/logger/node/RestLoggerConfigNode.ts +0 -170
package/README.md
CHANGED
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
# @theotherwillembotha/node-red-plugincore
|
|
2
2
|
|
|
3
|
-
A TypeScript framework for building production-grade Node-RED plugins
|
|
3
|
+
A TypeScript framework for building production-grade Node-RED plugins. Rather than scaffolding blank nodes from scratch, plugincore provides a collection of reusable helper components - base classes, decorator-driven injection, composable UI templates, and an assembly framework - that eliminate boilerplate and allow cross-plugin functionality like logging, metrics, and state management to be shared and reused without duplication.
|
|
4
4
|
|
|
5
|
-
This package
|
|
5
|
+
This package serves two roles:
|
|
6
6
|
|
|
7
|
-
1. **
|
|
8
|
-
2. **
|
|
7
|
+
1. **Developer framework** - TypeScript base classes, decorators, and a build-time node generator that plugin authors extend to create their own Node-RED nodes. Cross-cutting concerns (logging, metrics, state, webhooks) are injected by annotation rather than manually wired.
|
|
8
|
+
2. **Shared runtime infrastructure** - a set of services and a Webhook Server config node that are installed into Node-RED and shared automatically across all plugins built on this framework.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## Plugin ecosystem
|
|
13
|
-
|
|
14
|
-
This framework is the foundation for a growing set of Node-RED plugins. The following plugins are currently available:
|
|
15
|
-
|
|
16
|
-
| Plugin | Description |
|
|
17
|
-
|--------|-------------|
|
|
18
|
-
| [@theotherwillembotha/node-red-telemetry](https://github.com/theotherwillembotha/nodered_telemetry) | Ready-to-use flow nodes for structured logging and Prometheus metrics — Logger, Counter, Gauge, and Timer nodes that attach to the config nodes provided by this package. |
|
|
19
|
-
| [@theotherwillembotha/node-red-loki](https://github.com/theotherwillembotha/nodered_loki) | Grafana Loki integration for Node-RED. Provides a Loki service config node, a Loki logger config node that plugs into the logging infrastructure provided by this package, and a LogQL query node for reading log streams from Loki. |
|
|
20
|
-
| [@theotherwillembotha/node-red-nginxproxymanager](https://github.com/theotherwillembotha/nodered_nginxproxymanager) | Node-RED nodes for managing Nginx Proxy Manager hosts directly from your flows. Includes a config node that registers as a reverse proxy provider, an Update Host node for creating and updating proxy entries, and a Get Hosts node for retrieving the current host list. |
|
|
21
|
-
| [@theotherwillembotha/node-red-circuitbreaker](https://github.com/theotherwillembotha/nodered_circuitbreaker) | Circuit Breaker nodes for building resilient flows. Detects faults in external integrations using configurable fault and trip functions, routes messages based on breaker state, and supports event-driven recovery flows. |
|
|
22
|
-
| [@theotherwillembotha/node-red-temporal](https://github.com/theotherwillembotha/nodered_temporal) | Date/time nodes powered by the TC39 Temporal API. Transform, adjust, and format date/time values across timezones, or compute the duration between two points in time — with multiple output modes, offset support, and a Moment.js-style custom format system. |
|
|
23
|
-
| [@theotherwillembotha/node-red-zookeeper](https://github.com/theotherwillembotha/nodered_zookeeper) | Apache ZooKeeper integration nodes. Subscribe to ZooKeeper node change events, read node values on demand, and write data to ZooKeeper nodes from your flows. |
|
|
24
|
-
| [@theotherwillembotha/node-red-whatsapp](https://github.com/theotherwillembotha/nodered_whatsapp) | WhatsApp messaging nodes for Node-RED. Send and receive WhatsApp messages from your flows using the Baileys library — no cloud API or paid subscription required. |
|
|
25
|
-
| [@theotherwillembotha/node-red-cluster](https://github.com/theotherwillembotha/nodered_cluster) | Clustering plugin for Node-RED. Connect multiple Node-RED instances into a coordinated cluster using NATS JetStream for reliable message transport and NATS KV for shared state, with ZooKeeper-based instance discovery and endpoint registration. |
|
|
26
|
-
| [@theotherwillembotha/node-red-kafka](https://github.com/theotherwillembotha/nodered_kafka) | Apache Kafka producer and consumer nodes for Node-RED. Publish messages to Kafka topics and subscribe to incoming records directly from your flows. |
|
|
27
|
-
|
|
28
|
-
Additional plugins will be listed here as they are published.
|
|
10
|
+
Logger providers (Console, REST, Loki) and metrics providers (Prometheus) ship as separate optional plugins that plug into this infrastructure. See the [plugin ecosystem](#plugin-ecosystem) table at the end of this document.
|
|
29
11
|
|
|
30
12
|
---
|
|
31
13
|
|
|
@@ -39,65 +21,44 @@ Either use the **Manage Palette** option in the Node-RED editor, or run the foll
|
|
|
39
21
|
npm install @theotherwillembotha/node-red-plugincore
|
|
40
22
|
```
|
|
41
23
|
|
|
42
|
-
This installs the config
|
|
24
|
+
This installs the shared infrastructure and the Webhook Server config node. To use logging or metrics in your flows, also install the relevant provider plugin (e.g. `node-red-logging`, `node-red-prometheus`).
|
|
43
25
|
|
|
44
26
|
### Config nodes
|
|
45
27
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
#### Logging
|
|
49
|
-
|
|
50
|
-
Two logger backends are built in. Additional backends are available as separate plugins (see the [plugin ecosystem](#plugin-ecosystem) table above). All expose the same interface and are interchangeable — any node built with the `@Logger` decorator can use any of them.
|
|
51
|
-
|
|
52
|
-
**Console Logger** — writes structured log output to stdout via Winston. Ideal for development and containerised deployments that forward stdout to a log aggregator.
|
|
53
|
-
|
|
54
|
-

|
|
55
|
-
|
|
56
|
-
**REST Logger** — ships log entries to a remote HTTP/HTTPS endpoint. Supports Basic and API Key authentication.
|
|
57
|
-
|
|
58
|
-

|
|
28
|
+
#### Webhook Server
|
|
59
29
|
|
|
60
|
-
|
|
30
|
+
The Webhook Server config node runs an Express v5 HTTP server on a configurable local port. It serves as the shared HTTP listener for any node that uses the `@Webhook` decorator or `WebhookTemplate`. Supports optional reverse proxy configuration so that registered webhook paths know their publicly-visible address.
|
|
61
31
|
|
|
62
|
-
|
|
32
|
+

|
|
63
33
|
|
|
64
|
-
|
|
34
|
+
*The Webhook Server config node. **Name** is a human-readable label shown in the Server dropdown of any node using `WebhookTemplate`. **Port** is the local TCP port the Express server listens on — each server instance must use a unique port. The **External Host** and **External Port** fields (not shown) are used when the server sits behind a reverse proxy, allowing registered webhook nodes to know their publicly-visible address.*
|
|
65
35
|
|
|
66
|
-
|
|
67
|
-
|------|-------------|
|
|
68
|
-
| **Metrics Config** | Top-level Prometheus registry. One per deployment. |
|
|
69
|
-
| **Counter Metric** | An ever-increasing counter (e.g. messages processed, errors). |
|
|
70
|
-
| **Gauge Metric** | A value that goes up and down (e.g. queue depth, active connections). |
|
|
71
|
-
| **Timer Metric** | A histogram or summary for measuring durations (e.g. processing time per message). |
|
|
36
|
+
#### Logger providers
|
|
72
37
|
|
|
73
|
-
|
|
38
|
+
Logger config nodes are provided by separate plugins:
|
|
74
39
|
|
|
75
|
-
**
|
|
40
|
+
- **Console Logger**, **REST Logger** - [@theotherwillembotha/node-red-logging](https://github.com/theotherwillembotha/nodered_logging)
|
|
41
|
+
- **Loki Logger** - [@theotherwillembotha/node-red-loki](https://github.com/theotherwillembotha/nodered_loki)
|
|
76
42
|
|
|
77
|
-
|
|
78
|
-

|
|
43
|
+
Once a logger plugin is installed, a **Logging** section appears automatically in the editor of any node built with this framework. It is hidden when no logger plugins are present.
|
|
79
44
|
|
|
80
|
-
|
|
45
|
+
#### Metrics providers
|
|
81
46
|
|
|
82
|
-
|
|
47
|
+
Metrics config nodes are provided by separate plugins:
|
|
83
48
|
|
|
84
|
-
|
|
49
|
+
- **Prometheus** (Counter, Gauge, Histogram, `/metrics` endpoint) - [@theotherwillembotha/node-red-prometheus](https://github.com/theotherwillembotha/nodered_prometheus)
|
|
85
50
|
|
|
86
|
-
|
|
87
|
-
|------|-------------|
|
|
88
|
-
| **Webhook Server** | Runs an Express v5 HTTP server on a configurable local port. Supports optional reverse proxy configuration so that registered webhook paths know their publicly-visible address. |
|
|
89
|
-
|
|
90
|
-

|
|
51
|
+
The **Metrics** section appears automatically in any node editor that supports metrics. It is hidden when no metrics plugins are installed.
|
|
91
52
|
|
|
92
53
|
---
|
|
93
54
|
|
|
94
|
-
## Development
|
|
55
|
+
## Development - Building plugins with this framework
|
|
95
56
|
|
|
96
57
|
### Prerequisites
|
|
97
58
|
|
|
98
59
|
- Node.js 18+
|
|
99
60
|
- Node-RED 4+
|
|
100
|
-
- TypeScript 5+
|
|
61
|
+
- TypeScript 5+
|
|
101
62
|
|
|
102
63
|
### Installation
|
|
103
64
|
|
|
@@ -105,98 +66,574 @@ The **Timer** config adds a metric type selector (Histogram or Summary) with con
|
|
|
105
66
|
npm install @theotherwillembotha/node-red-plugincore
|
|
106
67
|
```
|
|
107
68
|
|
|
108
|
-
|
|
69
|
+
### Required tsconfig.json
|
|
109
70
|
|
|
110
71
|
```json
|
|
111
72
|
{
|
|
112
73
|
"compilerOptions": {
|
|
113
|
-
"
|
|
114
|
-
"
|
|
74
|
+
"target": "es2022",
|
|
75
|
+
"module": "commonjs",
|
|
76
|
+
"rootDir": "./src",
|
|
77
|
+
"outDir": "./build",
|
|
78
|
+
"declaration": true,
|
|
79
|
+
"experimentalDecorators": true,
|
|
80
|
+
"emitDecoratorMetadata": true,
|
|
81
|
+
"useDefineForClassFields": false
|
|
115
82
|
}
|
|
116
83
|
}
|
|
117
84
|
```
|
|
118
85
|
|
|
119
|
-
|
|
86
|
+
> **`useDefineForClassFields: false` is non-negotiable.** With `target: "es2022"`, TypeScript defaults this to `true`, which emits native class field initializers that run *after* `__decorate()`. This silently overwrites every `@Logger`, `@Metrics`, and other decorator-injected property with `undefined` at runtime - with no compile error and no startup warning.
|
|
87
|
+
|
|
88
|
+
> **`declaration: true`** is required if any downstream TypeScript package imports types from your plugin. Without it, consumers get `Could not find a declaration file for module '...'`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Building a flow node
|
|
93
|
+
|
|
94
|
+
A flow node is a node that appears on the Node-RED palette and processes messages passing through a flow. It extends `BaseNode`.
|
|
95
|
+
|
|
96
|
+
### Minimal example
|
|
97
|
+
|
|
98
|
+
The simplest possible flow node - no logging, no metrics, just message handling.
|
|
99
|
+
|
|
100
|
+
**`src/myplugin/node/EchoNode.ts`**
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import {
|
|
104
|
+
BaseNode, // base class for all flow nodes
|
|
105
|
+
BaseNodeConfig, // base interface for node config - all config interfaces extend this
|
|
106
|
+
NodeDescription, // decorator that registers the node type with the build system
|
|
107
|
+
SourceUtility, // resolves HTML file path correctly in both build and dev contexts
|
|
108
|
+
onInput, // decorator that wires a method to node.on("input")
|
|
109
|
+
Message // Node-RED message type
|
|
110
|
+
} from "@theotherwillembotha/node-red-plugincore";
|
|
111
|
+
import { Node } from "node-red";
|
|
112
|
+
|
|
113
|
+
// Extend BaseNodeConfig to declare this node's own configuration fields.
|
|
114
|
+
// Each field maps to a form input with id="node-input-<fieldName>".
|
|
115
|
+
interface EchoNodeConfig extends BaseNodeConfig {
|
|
116
|
+
name: string;
|
|
117
|
+
prefix: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@NodeDescription({
|
|
121
|
+
id: "EchoNode", // unique node type identifier across all plugins
|
|
122
|
+
name: "Echo", // display name in the Node-RED editor
|
|
123
|
+
group: "my-plugin", // palette group heading
|
|
124
|
+
sourceFile: SourceUtility.getSourcePath("/build/", "/src/") + "EchoNode.html",
|
|
125
|
+
package: "@myscope/node-red-myplugin", // must match the npm package name exactly
|
|
126
|
+
})
|
|
127
|
+
class EchoNode extends BaseNode<EchoNodeConfig> {
|
|
128
|
+
|
|
129
|
+
constructor(node: Node, config: EchoNodeConfig) {
|
|
130
|
+
super(node, config);
|
|
131
|
+
// this.config is available here and throughout the class
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// @onInput wires this method to node.on("input") automatically.
|
|
135
|
+
// No need to call node.on("input", ...) in the constructor.
|
|
136
|
+
@onInput()
|
|
137
|
+
protected onMessageReceived(message: Message): void {
|
|
138
|
+
(message as any).payload = `${this.config.prefix}: ${(message as any).payload}`;
|
|
139
|
+
this.node().send(message as any);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { EchoNode };
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**`src/myplugin/node/EchoNode.html`**
|
|
120
147
|
|
|
121
|
-
|
|
148
|
+
```html
|
|
149
|
+
<!-- onCompose runs at build time. "node" is a NodeBuilder instance.
|
|
150
|
+
Register defaults and configure the palette appearance here.
|
|
151
|
+
This section is NOT shipped to the browser. -->
|
|
152
|
+
<script type="text/javascript" template-section="onCompose">
|
|
153
|
+
node.addDefault("name", { value: "" });
|
|
154
|
+
node.addDefault("prefix", { value: "Echo", required: true });
|
|
155
|
+
node.setLabel(function() { return this.name || "Echo"; });
|
|
156
|
+
node.setColor("#87CEEB");
|
|
157
|
+
node.setIcon("font-awesome/fa-reply");
|
|
158
|
+
node.setInput("in");
|
|
159
|
+
node.addOutputs(["out"]);
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<!-- onIncludeEditForm is the HTML rendered inside the editor dialog.
|
|
163
|
+
Use id="node-input-<fieldName>" - Node-RED auto-saves and restores these. -->
|
|
164
|
+
<script type="text/html" template-section="onIncludeEditForm">
|
|
165
|
+
<div class="form-row">
|
|
166
|
+
<label for="node-input-name">Name</label>
|
|
167
|
+
<input type="text" id="node-input-name" placeholder="Echo">
|
|
168
|
+
</div>
|
|
169
|
+
<div class="form-row">
|
|
170
|
+
<label for="node-input-prefix">Prefix</label>
|
|
171
|
+
<input type="text" id="node-input-prefix" placeholder="Echo">
|
|
172
|
+
</div>
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<!-- onIncludeDocumentation is rendered in the Node-RED sidebar Info panel. -->
|
|
176
|
+
<script type="text/markdown" template-section="onIncludeDocumentation">
|
|
177
|
+
## Echo
|
|
178
|
+
|
|
179
|
+
Prepends a configurable prefix to `msg.payload`.
|
|
180
|
+
|
|
181
|
+
### Properties
|
|
182
|
+
|
|
183
|
+
- **Name** - Display name in the editor.
|
|
184
|
+
- **Prefix** - Text prepended to the incoming payload.
|
|
185
|
+
|
|
186
|
+
### Output
|
|
187
|
+
|
|
188
|
+
The modified message is passed to the single output.
|
|
189
|
+
</script>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### Full-featured example
|
|
195
|
+
|
|
196
|
+
This version adds structured logging, a metrics counter, and a webhook route. Compare each addition to the minimal example above.
|
|
197
|
+
|
|
198
|
+
**`src/myplugin/node/WebhookListenerNode.ts`**
|
|
122
199
|
|
|
123
200
|
```typescript
|
|
124
201
|
import {
|
|
125
202
|
BaseNode, BaseNodeConfig,
|
|
126
203
|
NodeDescription, SourceUtility,
|
|
127
|
-
LoggerTemplate, LoggerTemplateConfig,
|
|
128
|
-
|
|
204
|
+
LoggerTemplate, LoggerTemplateConfig, // + adds the logging UI section and its config fields
|
|
205
|
+
MetricsTemplate, MetricsTemplateConfig, // + adds the metrics UI section and its config fields
|
|
206
|
+
WebhookTemplate, // + adds the webhook server selector and path field
|
|
207
|
+
Log, Logger, // @Logger injects a Log instance at runtime
|
|
208
|
+
CounterMetric, Metrics, MetricType, // @Metrics injects a metric collector at runtime
|
|
209
|
+
Webhook, // @Webhook registers HTTP routes with the webhook server
|
|
210
|
+
onInput, Message
|
|
129
211
|
} from "@theotherwillembotha/node-red-plugincore";
|
|
130
212
|
import { Node } from "node-red";
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
213
|
+
import { Request, Response } from "express";
|
|
214
|
+
|
|
215
|
+
// Extend all three mixin config interfaces to include the fields
|
|
216
|
+
// that LoggerTemplate, MetricsTemplate, and WebhookTemplate manage.
|
|
217
|
+
interface WebhookListenerNodeConfig
|
|
218
|
+
extends BaseNodeConfig, LoggerTemplateConfig, MetricsTemplateConfig {
|
|
219
|
+
name: string;
|
|
220
|
+
webhookServer: string; // id of the WebhookServerConfigNode
|
|
221
|
+
path: string; // HTTP path registered with the webhook server
|
|
134
222
|
}
|
|
135
223
|
|
|
136
224
|
@NodeDescription({
|
|
137
|
-
id:
|
|
138
|
-
name:
|
|
139
|
-
group:
|
|
140
|
-
sourceFile: SourceUtility.getSourcePath("/build/", "/src/") + "
|
|
141
|
-
package:
|
|
225
|
+
id: "WebhookListenerNode",
|
|
226
|
+
name: "Webhook Listener",
|
|
227
|
+
group: "my-plugin",
|
|
228
|
+
sourceFile: SourceUtility.getSourcePath("/build/", "/src/") + "WebhookListenerNode.html",
|
|
229
|
+
package: "@myscope/node-red-myplugin",
|
|
142
230
|
templates: [
|
|
143
|
-
|
|
231
|
+
// Each template injects its own form rows, edit lifecycle hooks, and
|
|
232
|
+
// onCompose defaults. Order here controls the order they appear in the editor.
|
|
233
|
+
{ template: LoggerTemplate, config: {} },
|
|
234
|
+
{ template: MetricsTemplate, config: {} },
|
|
235
|
+
{ template: WebhookTemplate, config: {} },
|
|
144
236
|
]
|
|
145
237
|
})
|
|
146
|
-
class
|
|
238
|
+
class WebhookListenerNode extends BaseNode<WebhookListenerNodeConfig> {
|
|
147
239
|
|
|
240
|
+
// Injected by the framework at runtime. Declare with ! - TypeScript cannot
|
|
241
|
+
// see the injection mechanism at compile time.
|
|
148
242
|
@Logger()
|
|
149
243
|
private log!: Log;
|
|
150
244
|
|
|
151
|
-
|
|
245
|
+
// Injected by the framework. The concrete implementation depends on which
|
|
246
|
+
// metrics provider plugin is installed (e.g. Prometheus). Falls back to a
|
|
247
|
+
// silent no-op if no provider is configured.
|
|
248
|
+
@Metrics({ name: "webhook_requests_total", help: "Total inbound webhook requests", type: MetricType.Counter })
|
|
249
|
+
private requestCount!: CounterMetric;
|
|
250
|
+
|
|
251
|
+
// @Webhook registers the methods below with the WebhookServerConfigNode
|
|
252
|
+
// referenced by this.config.webhookServer. Routes are registered on deploy
|
|
253
|
+
// and removed on close.
|
|
254
|
+
@Webhook()
|
|
255
|
+
private webhook: any;
|
|
256
|
+
|
|
257
|
+
constructor(node: Node, config: WebhookListenerNodeConfig) {
|
|
152
258
|
super(node, config);
|
|
153
259
|
}
|
|
154
260
|
|
|
155
|
-
|
|
156
|
-
|
|
261
|
+
// HTTP route registered via @Webhook. Method name determines the HTTP verb.
|
|
262
|
+
// The path is read from this.config.path at runtime.
|
|
263
|
+
public post(req: Request, res: Response): void {
|
|
264
|
+
this.requestCount.inc();
|
|
265
|
+
this.log.log({ event: "webhook.received", body: req.body });
|
|
266
|
+
this.node().send({ payload: req.body } as any);
|
|
267
|
+
res.status(200).json({ ok: true });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@onInput()
|
|
271
|
+
protected onMessageReceived(message: Message): void {
|
|
272
|
+
// Example: forward messages onward unchanged
|
|
273
|
+
this.node().send(message as any);
|
|
157
274
|
}
|
|
158
275
|
}
|
|
276
|
+
|
|
277
|
+
export { WebhookListenerNode };
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**`src/myplugin/node/WebhookListenerNode.html`**
|
|
281
|
+
|
|
282
|
+
The node's HTML file only contains its *own* fields. `LoggerTemplate`, `MetricsTemplate`, and `WebhookTemplate` inject their own form rows and edit lifecycle sections automatically - those sections are merged with this file's sections at build time.
|
|
283
|
+
|
|
284
|
+
```html
|
|
285
|
+
<script type="text/javascript" template-section="onCompose">
|
|
286
|
+
// Only register this node's own fields. Logger, Metrics, and Webhook
|
|
287
|
+
// fields are added automatically by their respective templates.
|
|
288
|
+
node.addDefault("name", { value: "" });
|
|
289
|
+
node.addDefault("webhookServer", { value: "", required: true });
|
|
290
|
+
node.addDefault("path", { value: "/webhook" });
|
|
291
|
+
node.setLabel(function() { return this.name || "Webhook Listener"; });
|
|
292
|
+
node.setColor("#F0A500");
|
|
293
|
+
node.setIcon("font-awesome/fa-sign-in");
|
|
294
|
+
node.setInput("in");
|
|
295
|
+
node.addOutputs(["out"]);
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<!-- This section runs when the editor dialog opens (oneditprepare).
|
|
299
|
+
Initialize any custom widgets here. Logger/Metrics/Webhook templates
|
|
300
|
+
have their own onIncludeEditPrepare that runs alongside this one. -->
|
|
301
|
+
<script type="text/javascript" template-section="onIncludeEditPrepare">
|
|
302
|
+
// Custom widget initialization goes here
|
|
303
|
+
</script>
|
|
304
|
+
|
|
305
|
+
<!-- The editor form. Logger, Metrics, and Webhook sections are appended
|
|
306
|
+
automatically below these rows by their respective templates. -->
|
|
307
|
+
<script type="text/html" template-section="onIncludeEditForm">
|
|
308
|
+
<div class="form-row">
|
|
309
|
+
<label for="node-input-name">Name</label>
|
|
310
|
+
<input type="text" id="node-input-name" placeholder="Webhook Listener">
|
|
311
|
+
</div>
|
|
312
|
+
<!-- Webhook server + path fields are injected by WebhookTemplate -->
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<script type="text/markdown" template-section="onIncludeDocumentation">
|
|
316
|
+
## Webhook Listener
|
|
317
|
+
|
|
318
|
+
Registers an HTTP endpoint with a Webhook Server config node and emits a
|
|
319
|
+
message for each incoming request.
|
|
320
|
+
|
|
321
|
+
### Properties
|
|
322
|
+
|
|
323
|
+
- **Name** - Display name in the editor.
|
|
324
|
+
- **Logging** - Select a logger provider and optionally override the log template.
|
|
325
|
+
- **Metrics** - Select a metrics provider to track request counts.
|
|
326
|
+
- **Webhook Server** - The Webhook Server config node providing the HTTP listener.
|
|
327
|
+
- **Path** - The HTTP path to register (e.g. `/webhook`).
|
|
328
|
+
|
|
329
|
+
### Output
|
|
330
|
+
|
|
331
|
+
`msg.payload` contains the parsed request body.
|
|
332
|
+
</script>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
> **Key differences from the minimal example:**
|
|
336
|
+
> - `templates` array in `@NodeDescription` - each entry auto-composes a UI section and its lifecycle hooks.
|
|
337
|
+
> - Config interface mixins (`LoggerTemplateConfig`, `MetricsTemplateConfig`) - provide TypeScript types for the fields those templates manage.
|
|
338
|
+
> - `@Logger()`, `@Metrics()`, `@Webhook()` on class properties - injected at runtime; always declare with `!`.
|
|
339
|
+
> - The node's HTML only handles its own fields; template-owned fields are never duplicated.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Building a config node
|
|
344
|
+
|
|
345
|
+
A config node is a shared configuration resource referenced by multiple flow nodes. It extends `ConfigNode`. Config nodes do not appear on the palette; they are created from the editor's config node panel.
|
|
346
|
+
|
|
347
|
+
Config nodes follow the same pattern as flow nodes with two differences:
|
|
348
|
+
- Extend `ConfigNode<TConfig>` instead of `BaseNode<TConfig>`
|
|
349
|
+
- Form field IDs use `node-config-input-<fieldName>` instead of `node-input-<fieldName>`
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
@NodeDescription({ id: "MyConfigNode", name: "My Config", group: "my-plugin", ... })
|
|
353
|
+
class MyConfigNode extends ConfigNode<MyConfigNodeConfig> {
|
|
354
|
+
constructor(node: Node, config: MyConfigNodeConfig) {
|
|
355
|
+
super(node, config);
|
|
356
|
+
// register with a service, open a connection, etc.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```html
|
|
362
|
+
<script type="text/html" template-section="onIncludeEditForm">
|
|
363
|
+
<div class="form-row">
|
|
364
|
+
<!-- config nodes use node-config-input-*, not node-input-* -->
|
|
365
|
+
<label for="node-config-input-name">Name</label>
|
|
366
|
+
<input type="text" id="node-config-input-name">
|
|
367
|
+
</div>
|
|
368
|
+
</script>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Cross-cutting infrastructure
|
|
374
|
+
|
|
375
|
+
These subsystems are the building blocks behind every shared UI section. Understanding how the pieces fit together makes it easier to build plugins that integrate cleanly with the rest of the ecosystem.
|
|
376
|
+
|
|
377
|
+
### Logger infrastructure
|
|
378
|
+
|
|
379
|
+
The logging subsystem has four parts:
|
|
380
|
+
|
|
381
|
+
| Part | Role |
|
|
382
|
+
|------|------|
|
|
383
|
+
| `LoggerService` | Background service that maintains logger instances. Must be registered in `GenerateNodes.ts`. |
|
|
384
|
+
| `@Logger()` | Property decorator. Injects a `Log` instance wired to whichever logger provider the user selected. |
|
|
385
|
+
| `LoggerTemplate` | UI template. Adds a **Logging** section to any node editor - an enable toggle, a logger provider selector, and an optional message template override. Hidden when no logger provider plugins are installed. |
|
|
386
|
+
| `LoggerConfigNode` | Abstract base class that logger provider plugins extend. Not registered in plugincore itself. |
|
|
387
|
+
|
|
388
|
+
**Using logging in a node:**
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// 1. Extend LoggerTemplateConfig in the node's config interface
|
|
392
|
+
interface MyNodeConfig extends BaseNodeConfig, LoggerTemplateConfig { ... }
|
|
393
|
+
|
|
394
|
+
// 2. Include LoggerTemplate in @NodeDescription templates
|
|
395
|
+
@NodeDescription({ ..., templates: [{ template: LoggerTemplate, config: {} }] })
|
|
396
|
+
class MyNode extends BaseNode<MyNodeConfig> {
|
|
397
|
+
|
|
398
|
+
// 3. Declare the injected property
|
|
399
|
+
@Logger()
|
|
400
|
+
private log!: Log;
|
|
401
|
+
|
|
402
|
+
@onInput()
|
|
403
|
+
protected handle(msg: Message): void {
|
|
404
|
+
this.log.log({ event: "received", payload: (msg as any).payload });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Creating a logger provider:**
|
|
410
|
+
|
|
411
|
+
Extend `LoggerConfigNode` and tag it so the `NodeTypeService` can discover it:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { LoggerConfigNode, ConfigNodeConfig, NodeDescription, SourceUtility }
|
|
415
|
+
from "@theotherwillembotha/node-red-plugincore";
|
|
416
|
+
|
|
417
|
+
interface ConsoleLoggerConfig extends ConfigNodeConfig {
|
|
418
|
+
name: string;
|
|
419
|
+
level: string;
|
|
420
|
+
template: string;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
@NodeDescription({
|
|
424
|
+
id: "ConsoleLoggerConfigNode",
|
|
425
|
+
name: "Console Logger",
|
|
426
|
+
group: "logging",
|
|
427
|
+
sourceFile: SourceUtility.getSourcePath("/build/", "/src/") + "ConsoleLoggerConfigNode.html",
|
|
428
|
+
package: "@myscope/node-red-logging",
|
|
429
|
+
tags: ["LoggerType"], // <-- required: makes this discoverable as a logger provider
|
|
430
|
+
})
|
|
431
|
+
class ConsoleLoggerConfigNode extends LoggerConfigNode<ConsoleLoggerConfig> { ... }
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The `"LoggerType"` tag is what causes this node to appear in the **Logger** dropdown of any node using `LoggerTemplate`.
|
|
435
|
+
|
|
436
|
+
**Screenshot - LoggerTemplate section** (as it appears inside a consumer node's editor):
|
|
437
|
+
|
|
438
|
+

|
|
439
|
+
|
|
440
|
+
*The Logging section injected by `LoggerTemplate`. The enable toggle controls whether log calls are forwarded to the provider. The Logger dropdown lists all installed logger providers, with edit (pencil) and create (plus) buttons. Override Template allows per-node customisation of the log format.*
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### Metrics infrastructure
|
|
445
|
+
|
|
446
|
+
The metrics subsystem mirrors the logging subsystem exactly:
|
|
447
|
+
|
|
448
|
+
| Part | Role |
|
|
449
|
+
|------|------|
|
|
450
|
+
| `MetricsService` | Background service that maintains metric collector registrations. Must be registered in `GenerateNodes.ts`. |
|
|
451
|
+
| `@Metrics({...})` | Property decorator. Injects the appropriate metric type (Counter, Gauge, Histogram) from the configured provider. Falls back to a silent no-op when no provider is configured. |
|
|
452
|
+
| `MetricsTemplate` | UI template. Adds a **Metrics** section with a provider selector. Hidden when no metrics plugins are installed. |
|
|
453
|
+
| `MetricsConfigNode` | Abstract base class that metrics provider plugins extend. |
|
|
454
|
+
|
|
455
|
+
**Metric types:**
|
|
456
|
+
|
|
457
|
+
| `MetricType` | Property type | Description |
|
|
458
|
+
|--------------|--------------|-------------|
|
|
459
|
+
| `Counter` | `CounterMetric` | Monotonically increasing count. `.inc(amount?)` |
|
|
460
|
+
| `Gauge` | `GaugeMetric` | Value that can go up or down. `.set(value)`, `.inc()`, `.dec()` |
|
|
461
|
+
| `Histogram` | `HistogramMetric` | Distribution of observed values. `.observe(value)` |
|
|
462
|
+
| `Summary` | `SummaryMetric` | Quantile distribution. `.observe(value)` |
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
@Metrics({ name: "messages_total", help: "Messages processed", type: MetricType.Counter })
|
|
466
|
+
private messageCount!: CounterMetric;
|
|
467
|
+
|
|
468
|
+
@Metrics({ name: "queue_depth", help: "Current queue depth", type: MetricType.Gauge })
|
|
469
|
+
private queueDepth!: GaugeMetric;
|
|
470
|
+
|
|
471
|
+
@Metrics({ name: "process_seconds", help: "Processing latency", type: MetricType.Histogram })
|
|
472
|
+
private latency!: HistogramMetric;
|
|
159
473
|
```
|
|
160
474
|
|
|
161
|
-
|
|
475
|
+
**Screenshot - MetricsTemplate section:**
|
|
162
476
|
|
|
163
|
-
|
|
477
|
+

|
|
164
478
|
|
|
165
|
-
|
|
166
|
-
|-----------|--------------|-----------------|
|
|
167
|
-
| `@Logger()` | `Log` | Winston logger wired to a user-selected logger config node |
|
|
168
|
-
| `@Metrics({...})` | `CounterMetric` / `GaugeMetric` / `HistogramMetric` | Prometheus metric collector |
|
|
169
|
-
| `@Webhook()` | — | Registers the node's routes with the webhook server |
|
|
170
|
-
| `@onInput()` | method | Wires the method as the Node-RED `input` message handler |
|
|
479
|
+
*The Metrics section injected by `MetricsTemplate`. Enable Metrics controls whether metric calls are forwarded. The Metric Provider dropdown lists all installed metrics providers.*
|
|
171
480
|
|
|
172
|
-
|
|
481
|
+
**Metric config nodes** (provided by `node-red-prometheus`):
|
|
173
482
|
|
|
174
|
-
|
|
483
|
+
Plugin authors can create specialised metric config nodes to let users define named, reusable metric collectors that are referenced by flow nodes.
|
|
175
484
|
|
|
176
|
-
|
|
177
|
-
|----------|---------------|
|
|
178
|
-
| `LoggerTemplate` | Logger backend selector and optional message template override |
|
|
179
|
-
| `MetricsTemplate` | Metrics enable toggle and collector reference |
|
|
180
|
-
| `CounterMetricTemplate` | Counter config node reference |
|
|
181
|
-
| `GaugeMetricTemplate` | Gauge config node reference |
|
|
182
|
-
| `TimerMetricTemplate` | Timer config node reference |
|
|
183
|
-
| `WebhookTemplate` | Webhook server reference, path, auth, and reverse proxy config |
|
|
184
|
-
| `UIHelperTemplate` | Global `PluginCore.dialog()` and `PluginCore.table()` UI factories (see below) |
|
|
185
|
-
| `ScriptEditorTemplate` | Global `PluginCore.createScriptEditor()` factory for Monaco-based script editors (see below) |
|
|
186
|
-
| `SettingsTemplate` | General settings section |
|
|
187
|
-
| `BasicTemplate` | Base styles shared by all nodes |
|
|
485
|
+

|
|
188
486
|
|
|
189
|
-
|
|
487
|
+
*Counter Metric Config Node - tracks a monotonically increasing count. **Name** and **Description** label the counter in the Prometheus `/metrics` output. **Reset on Deploy** zeroes the counter each time flows are re-deployed. **Metric Provider** selects the Prometheus instance to register with.*
|
|
190
488
|
|
|
191
|
-
|
|
489
|
+

|
|
192
490
|
|
|
193
|
-
|
|
491
|
+
*Gauge Metric Config Node - tracks a value that can increase or decrease freely. Configuration is identical to Counter, except the underlying metric type allows `.inc()`, `.dec()`, and `.set(value)` calls.*
|
|
194
492
|
|
|
195
|
-
|
|
493
|
+
Timer Metric Config Nodes can operate in two modes selected by the **Type** dropdown:
|
|
494
|
+
|
|
495
|
+

|
|
496
|
+
|
|
497
|
+
*Summary mode. **Percentile Type** controls whether percentile boundaries are computed automatically or specified manually. The **Percentiles** field accepts a comma-separated list of quantiles (e.g. `0.01, 0.1, 0.9, 0.99`).*
|
|
498
|
+
|
|
499
|
+

|
|
500
|
+
|
|
501
|
+
*Histogram mode. Observations are sorted into fixed buckets defined by **Bucket Type**, **Start**, **Interval**, and **Count**. Linear bucketing (shown) produces evenly spaced boundaries starting at Start and stepping by Interval.*
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
### State infrastructure
|
|
506
|
+
|
|
507
|
+
The state subsystem provides a common interface for externalising node state - allowing state to be stored and coordinated outside of Node-RED (e.g. in ZooKeeper). Nodes that need persistent or distributed state can use this subsystem without being coupled to a specific storage backend.
|
|
508
|
+
|
|
509
|
+
| Part | Role |
|
|
510
|
+
|------|------|
|
|
511
|
+
| `StateConfigNode` | Abstract base class. State provider plugins extend this. |
|
|
512
|
+
| `StateTemplate` | UI template. Adds a **State** section with a provider selector to any node editor. Auto-creates an `InternalStateConfigNode` on first open if no provider is configured. |
|
|
513
|
+
|
|
514
|
+
**Creating a state provider:**
|
|
515
|
+
|
|
516
|
+
Extend `StateConfigNode` and tag it `"StateProvider"`:
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
@NodeDescription({ ..., tags: ["StateProvider"] })
|
|
520
|
+
class MyStateConfigNode extends StateConfigNode<MyStateConfig> { ... }
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Screenshot - StateTemplate section:**
|
|
524
|
+
|
|
525
|
+

|
|
526
|
+
|
|
527
|
+
*The State section injected by `StateTemplate`. The Provider dropdown lists all installed state provider nodes. The pencil button opens the selected provider's editor. If no external state provider is installed, an Internal State Config Node is created automatically and selected.*
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
### Webhook infrastructure
|
|
532
|
+
|
|
533
|
+
The webhook subsystem provides a shared Express v5 HTTP server that multiple nodes can register routes on.
|
|
534
|
+
|
|
535
|
+
| Part | Role |
|
|
536
|
+
|------|------|
|
|
537
|
+
| `WebhookServerConfigNode` | Config node (provided by plugincore itself). Runs the HTTP server on a configured port. |
|
|
538
|
+
| `@Webhook()` | Property decorator. Registers public methods (`get`, `post`, `put`, `delete`) as HTTP routes with the referenced `WebhookServerConfigNode`. |
|
|
539
|
+
| `WebhookTemplate` | UI template. Adds a webhook server selector and path field to the node editor. |
|
|
540
|
+
|
|
541
|
+

|
|
542
|
+
|
|
543
|
+
*The Webhook section injected by `WebhookTemplate`. **Server** is a dropdown that lists all `WebhookServerConfigNode` instances, with edit and add buttons. **Path** is the HTTP route registered on that server (e.g. `/example`). **Authentication** selects the credential strategy — None, Basic Auth, or API Key — and reveals the relevant credential fields when a strategy other than None is selected. **Reverse Proxy** is hidden unless a reverse proxy provider plugin (e.g. `node-red-nginxproxymanager`) is installed; when shown, it lets you map the webhook path to a public-facing domain.*
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## NodeTypeService and DelegatedConfigReferenceNode
|
|
548
|
+
|
|
549
|
+
These two components are the plumbing behind the provider selector dropdowns. They are required in every plugin's `GenerateNodes.ts`.
|
|
550
|
+
|
|
551
|
+
### NodeTypeService
|
|
552
|
+
|
|
553
|
+
`NodeTypeService` exposes a `GET /nodetypeservice/find?tag=<tag>` endpoint that returns all registered node types carrying a given tag. This is how `LoggerTemplate`, `MetricsTemplate`, and `StateTemplate` discover available providers at runtime without any hardcoded knowledge of which plugins are installed.
|
|
554
|
+
|
|
555
|
+
When you annotate a node with `tags: ["LoggerType"]`, the `NodeTypeService` indexes it under that tag. Multiple tags are supported:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
@NodeDescription({
|
|
559
|
+
id: "PrometheusMetricsConfigNode",
|
|
560
|
+
tags: ["MetricsProvider"], // discoverable by MetricsTemplate
|
|
561
|
+
...
|
|
562
|
+
})
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
The selector templates query this endpoint in `onIncludeEditPrepare` and hide the entire section if the response is empty - so nodes that use `LoggerTemplate` or `MetricsTemplate` remain clean in setups where those provider plugins are not installed.
|
|
566
|
+
|
|
567
|
+
### DelegatedConfigReferenceNode
|
|
568
|
+
|
|
569
|
+
`DelegatedConfigReferenceNode` is a thin shim config node that acts as the property type for provider selector fields. It provides reference counting - Node-RED tracks how many nodes reference a given config node and prevents deletion while it is in use - without the normal behaviour where Node-RED resets a config selector to blank when the editor saves.
|
|
570
|
+
|
|
571
|
+
This is necessary because provider selectors (Logger, Metrics, State) are managed manually by the template's JavaScript rather than by Node-RED's auto-bind mechanism. Using `DelegatedConfigReferenceNode` as the declared type preserves reference counting while letting the template control the actual value.
|
|
572
|
+
|
|
573
|
+
`DelegatedConfigReferenceNode` must be registered in every plugin's `GenerateNodes.ts`:
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
new NodeGenerator(...)
|
|
577
|
+
.registerNode(DelegatedConfigReferenceNode)
|
|
578
|
+
...
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Node HTML file - template sections
|
|
584
|
+
|
|
585
|
+
Each node's `.html` file is divided into named sections using the `template-section` attribute. The build pipeline reads these sections and assembles them into the correct slots in the generated Node-RED registration call. Sections from multiple templates (`LoggerTemplate`, `MetricsTemplate`, etc.) are merged in the order they were registered.
|
|
586
|
+
|
|
587
|
+
```html
|
|
588
|
+
<script type="text/javascript" template-section="onCompose"> ... </script>
|
|
589
|
+
<div template-section="onIncludeOnce"> ... </div>
|
|
590
|
+
<script type="text/javascript" template-section="onIncludeEditPrepare"> ... </script>
|
|
591
|
+
<script type="text/html" template-section="onIncludeEditForm"> ... </script>
|
|
592
|
+
<script type="text/javascript" template-section="onIncludeEditSave"> ... </script>
|
|
593
|
+
<script type="text/javascript" template-section="onIncludeEditCancel"> ... </script>
|
|
594
|
+
<script type="text/javascript" template-section="onIncludeEditDelete"> ... </script>
|
|
595
|
+
<script type="text/markdown" template-section="onIncludeDocumentation"> ... </script>
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Section reference
|
|
599
|
+
|
|
600
|
+
| Section | When it runs | Typical use |
|
|
601
|
+
|---------|-------------|-------------|
|
|
602
|
+
| `onCompose` | At **build time** inside a `NodeBuilder` context | Call `node.addDefault(...)`, `node.setLabel(...)`, `node.setColor(...)`, `node.setIcon(...)`, `node.setInput(...)`, `node.addOutputs(...)`. Not shipped to the browser. |
|
|
603
|
+
| `onIncludeOnce` | Injected into the browser **once per page load** | Global styles (`<style>`), shared helper functions, and cached resource fetches. Wrap scripts in `<script>` inside a `<div>`. Shared across all instances. |
|
|
604
|
+
| `onIncludeEditPrepare` | Runs when the **node editor opens** (`oneditprepare`) | Initialise `typedInput` widgets, bind event handlers, fetch async data, restore saved state. Assign `this` to a variable before async code. |
|
|
605
|
+
| `onIncludeEditForm` | The **HTML form** inside the editor dialog | `<div class="form-row">` blocks. Use `id="node-input-<field>"` for flow nodes; `id="node-config-input-<field>"` for config nodes. |
|
|
606
|
+
| `onIncludeEditSave` | Runs when the user clicks **Done** (`oneditsave`) | Read widget values back into the node object. Standard `node-input-*` fields save automatically; use this only for non-standard fields. |
|
|
607
|
+
| `onIncludeEditCancel` | Runs when the user clicks **Cancel** (`oneditcancel`) | Clean up resources - always call `.dispose()` on `ScriptEditorTemplate` editors here to avoid Monaco memory leaks. |
|
|
608
|
+
| `onIncludeEditDelete` | Runs when the node is **deleted** | Release any persistent resources tied to this node instance. Rarely needed. |
|
|
609
|
+
| `onIncludeDocumentation` | Rendered in the **help panel** sidebar | Markdown describing the node's behaviour, properties, inputs, and outputs. |
|
|
610
|
+
|
|
611
|
+
### `onCompose` - NodeBuilder API
|
|
612
|
+
|
|
613
|
+
| Method | Description |
|
|
614
|
+
|--------|-------------|
|
|
615
|
+
| `node.addDefault(name, options)` | Register a config field. `options`: `{ value, required?, validate? }` |
|
|
616
|
+
| `node.setLabel(fn)` | Function returning the node's label at runtime (`this` = node instance) |
|
|
617
|
+
| `node.setPaletteLabel(label)` | Fixed palette label string |
|
|
618
|
+
| `node.setColor(color)` | Palette colour (hex string) |
|
|
619
|
+
| `node.setIcon(icon)` | Palette icon filename (relative to `icons/`, or `font-awesome/fa-*`) |
|
|
620
|
+
| `node.setLabelStyle(style)` | CSS class for the label (e.g. `node_label_white`) |
|
|
621
|
+
| `node.setInput(label)` | Add an input port |
|
|
622
|
+
| `node.addOutputs(labels)` | Add one or more output ports; pass a string array for labelled outputs |
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## UI helpers
|
|
627
|
+
|
|
628
|
+
Including `UIHelperTemplate` in a node's `templates` array injects two client-side factory functions into the Node-RED editor page. Both are globally available as `PluginCore.dialog(...)` and `PluginCore.table(...)` and are styled to match Node-RED's editor aesthetic.
|
|
629
|
+
|
|
630
|
+
### `PluginCore.dialog(options)`
|
|
631
|
+
|
|
632
|
+
Opens a modal overlay with a title bar and one or more tabs.
|
|
196
633
|
|
|
197
634
|
```javascript
|
|
198
635
|
PluginCore.dialog({
|
|
199
|
-
title: "My Plugin
|
|
636
|
+
title: "My Plugin - Status",
|
|
200
637
|
tabs: [
|
|
201
638
|
{
|
|
202
639
|
label: "Proxy Hosts",
|
|
@@ -204,14 +641,14 @@ PluginCore.dialog({
|
|
|
204
641
|
$container.append(
|
|
205
642
|
PluginCore.table({
|
|
206
643
|
columns: [
|
|
207
|
-
{ key: "id",
|
|
208
|
-
{ key: "name",
|
|
644
|
+
{ key: "id", label: "ID" },
|
|
645
|
+
{ key: "name", label: "Name" },
|
|
209
646
|
{ key: "enabled", label: "Enabled",
|
|
210
647
|
render: function(v) {
|
|
211
648
|
return $("<span>")
|
|
212
649
|
.addClass(v ? "plugincore-status-enabled"
|
|
213
650
|
: "plugincore-status-disabled")
|
|
214
|
-
.text(v ? "
|
|
651
|
+
.text(v ? "Enabled" : "Disabled");
|
|
215
652
|
}}
|
|
216
653
|
],
|
|
217
654
|
rows: data
|
|
@@ -223,22 +660,18 @@ PluginCore.dialog({
|
|
|
223
660
|
});
|
|
224
661
|
```
|
|
225
662
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
|
229
|
-
|-------|------|-------------|
|
|
230
|
-
| `title` | `string` | Heading shown in the dialog title bar |
|
|
663
|
+
| Option | Type | Description |
|
|
664
|
+
|--------|------|-------------|
|
|
665
|
+
| `title` | `string` | Heading in the dialog title bar |
|
|
231
666
|
| `tabs` | `array` | One or more tab definitions |
|
|
232
667
|
| `tabs[].label` | `string` | Tab heading |
|
|
233
|
-
| `tabs[].render` | `function($container)` | Called with a jQuery element
|
|
668
|
+
| `tabs[].render` | `function($container)` | Called with a jQuery element - append content into it |
|
|
234
669
|
|
|
235
|
-
**Returns:** `{ close() }`
|
|
236
|
-
|
|
237
|
-
---
|
|
670
|
+
**Returns:** `{ close() }` - call `close()` to dismiss programmatically.
|
|
238
671
|
|
|
239
|
-
|
|
672
|
+
### `PluginCore.table(config)`
|
|
240
673
|
|
|
241
|
-
Returns a styled jQuery `<table>`
|
|
674
|
+
Returns a styled jQuery `<table>` ready to append into any container.
|
|
242
675
|
|
|
243
676
|
```javascript
|
|
244
677
|
var $table = PluginCore.table({
|
|
@@ -252,154 +685,97 @@ var $table = PluginCore.table({
|
|
|
252
685
|
$container.append($table);
|
|
253
686
|
```
|
|
254
687
|
|
|
255
|
-
**Config:**
|
|
256
|
-
|
|
257
688
|
| Field | Type | Description |
|
|
258
689
|
|-------|------|-------------|
|
|
259
|
-
| `columns` | `array` | Column definitions |
|
|
260
690
|
| `columns[].key` | `string` | Property name on each row object |
|
|
261
691
|
| `columns[].label` | `string` | Column header text |
|
|
262
|
-
| `columns[].render` | `function(value, row)` | Optional
|
|
692
|
+
| `columns[].render` | `function(value, row)` | Optional - return a string or jQuery element for custom cells |
|
|
263
693
|
| `rows` | `object[]` | Data rows |
|
|
264
694
|
|
|
265
|
-
**CSS classes
|
|
695
|
+
**CSS classes for cell content:**
|
|
266
696
|
|
|
267
|
-
| Class | Colour |
|
|
268
|
-
|
|
269
|
-
| `plugincore-status-enabled` | Green |
|
|
270
|
-
| `plugincore-status-disabled` | Red |
|
|
697
|
+
| Class | Colour | Use |
|
|
698
|
+
|-------|--------|-----|
|
|
699
|
+
| `plugincore-status-enabled` | Green | Active / enabled state |
|
|
700
|
+
| `plugincore-status-disabled` | Red | Inactive / disabled state |
|
|
271
701
|
|
|
272
|
-
|
|
702
|
+
### `PluginCore.createScriptEditor(elementId, template, initialValue)`
|
|
273
703
|
|
|
274
|
-
Including `ScriptEditorTemplate` in a node's `templates`
|
|
704
|
+
Including `ScriptEditorTemplate` in a node's `templates` array injects a Monaco-based script editor factory. Wraps the async Monaco initialisation into a single call.
|
|
275
705
|
|
|
276
706
|
```javascript
|
|
277
707
|
// In onIncludeEditPrepare:
|
|
278
|
-
let
|
|
279
|
-
interface Message { [key: string]: any; }
|
|
280
|
-
async function(msg: Message) {
|
|
281
|
-
\${script}
|
|
282
|
-
}
|
|
283
|
-
`;
|
|
284
|
-
|
|
708
|
+
let node = this;
|
|
285
709
|
node.scriptEditor = PluginCore.createScriptEditor(
|
|
286
|
-
'node-input-script-editor',
|
|
287
|
-
|
|
288
|
-
node.script || 'return true;'
|
|
710
|
+
'node-input-script-editor', // DOM id of the container element
|
|
711
|
+
`async function(msg) { \${script} }`, // TypeScript context template
|
|
712
|
+
node.script || 'return true;' // initial value
|
|
289
713
|
);
|
|
290
714
|
|
|
291
715
|
// In onIncludeEditSave:
|
|
292
716
|
node.script = node.scriptEditor.getValue();
|
|
293
717
|
delete node.scriptEditor;
|
|
294
718
|
|
|
295
|
-
// In
|
|
719
|
+
// In onIncludeEditCancel - always call dispose() to avoid Monaco memory leaks:
|
|
296
720
|
node.scriptEditor.dispose();
|
|
297
721
|
delete node.scriptEditor;
|
|
298
722
|
```
|
|
299
723
|
|
|
300
|
-
The `template` string provides the TypeScript context that the Monaco language service uses for diagnostics, completions, and hover info. Use `\${script}` as the placeholder for the user's code. The user only sees their code — the surrounding context is invisible to them but informs type checking.
|
|
301
|
-
|
|
302
724
|
**Returns:** `{ getValue(): string, dispose(): void }`
|
|
303
725
|
|
|
726
|
+
The `template` string provides TypeScript context that the Monaco language service uses for type checking and completions. Use `${script}` as the placeholder for the user's code - they see only their code, not the surrounding context.
|
|
727
|
+
|
|
304
728
|
---
|
|
305
729
|
|
|
306
|
-
> **
|
|
730
|
+
> **Handlebars escaping in node HTML files**
|
|
307
731
|
>
|
|
308
|
-
> Node HTML files
|
|
732
|
+
> Node HTML files are processed by Handlebars during the build step. Any `{{ }}` anywhere in the file - including `<script>` blocks, comments, and markdown sections - will be interpreted as a Handlebars expression.
|
|
309
733
|
>
|
|
310
|
-
> Escape curly braces with a backslash wherever they appear literally
|
|
734
|
+
> Escape curly braces with a backslash wherever they appear literally:
|
|
311
735
|
>
|
|
312
736
|
> ```javascript
|
|
313
|
-
> // Wrong
|
|
314
|
-
> // @returns {{ getValue(): string }}
|
|
315
|
-
>
|
|
316
|
-
> // Correct — escaped so Handlebars passes it through:
|
|
317
|
-
> // @returns \{{ getValue(): string \}}
|
|
737
|
+
> // Wrong: @returns {{ getValue(): string }}
|
|
738
|
+
> // Correct: @returns \{{ getValue(): string \}}
|
|
318
739
|
> ```
|
|
319
|
-
>
|
|
320
|
-
> This applies anywhere in the HTML file: `<script>` blocks, inline styles, markdown documentation sections, and comments.
|
|
321
740
|
|
|
322
741
|
---
|
|
323
742
|
|
|
324
|
-
|
|
743
|
+
## Registering nodes for generation
|
|
325
744
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
```html
|
|
329
|
-
<script type="text/javascript" template-section="onCompose"> ... </script>
|
|
330
|
-
<div template-section="onIncludeOnce"> ... </div>
|
|
331
|
-
<script type="text/javascript" template-section="onIncludeEditPrepare"> ... </script>
|
|
332
|
-
<script type="text/html" template-section="onIncludeEditForm"> ... </script>
|
|
333
|
-
<script type="text/javascript" template-section="onIncludeEditSave"> ... </script>
|
|
334
|
-
<script type="text/javascript" template-section="onIncludeEditCancel"> ... </script>
|
|
335
|
-
<script type="text/javascript" template-section="onIncludeEditDelete"> ... </script>
|
|
336
|
-
<script type="text/markdown" template-section="onIncludeDocumentation"> ... </script>
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
#### Section reference
|
|
340
|
-
|
|
341
|
-
| Section | When it runs | Typical use |
|
|
342
|
-
|---------|-------------|-------------|
|
|
343
|
-
| `onCompose` | At **build time**, inside a `NodeBuilder` context | Call `node.addDefault(...)`, `node.setLabel(...)`, `node.setColor(...)`, `node.setIcon(...)`, `node.addOutputs(...)`, etc. to configure the node definition that will be written into the generated `Nodes.js`. This section is **not** shipped to the browser. |
|
|
344
|
-
| `onIncludeOnce` | Injected into the browser **once** per page load | Global styles (`<style>`), shared helper functions, and cached resource fetches (e.g. timezone or format lists). Everything here is shared across all node instances of this type. Wrap scripts in `<script>` tags inside a `<div>`. |
|
|
345
|
-
| `onIncludeEditPrepare` | Runs when the **node editor dialog opens** (Node-RED `oneditprepare`) | Initialise `typedInput` widgets, bind event listeners, fetch async data, restore saved state. `this` refers to the node being edited — assign it to a local variable (e.g. `let node = this`) before any async code. |
|
|
346
|
-
| `onIncludeEditForm` | The **HTML form** rendered inside the editor dialog | `<div class="form-row">` blocks containing `<label>` and `<input>` elements. Use `id="node-input-<fieldName>"` for regular nodes or `id="node-config-input-<fieldName>"` for config nodes. Include hidden `<input type="hidden">` fields for `typedInput` type tracking. |
|
|
347
|
-
| `onIncludeEditSave` | Runs when the user clicks **Done** (Node-RED `oneditsave`) | Read widget values back into the node object before it is serialised. Most `typedInput` widgets save automatically via the `node-input-*` naming convention; use this section for anything that does not. |
|
|
348
|
-
| `onIncludeEditCancel` | Runs when the user clicks **Cancel** (Node-RED `oneditcancel`) | Clean up resources that were created in `onIncludeEditPrepare` — e.g. call `.dispose()` on Monaco editor instances to avoid memory leaks. |
|
|
349
|
-
| `onIncludeEditDelete` | Runs when the node is **deleted** from the canvas | Release any persistent resources tied to this node instance. Rarely needed for most nodes. |
|
|
350
|
-
| `onIncludeDocumentation` | Rendered in the Node-RED **help panel** (sidebar Info tab) | Markdown content describing the node's behaviour, fields, and examples. Supports standard GitHub-flavoured markdown including tables, code blocks, and blockquotes. |
|
|
351
|
-
|
|
352
|
-
#### `onCompose` — NodeBuilder API
|
|
353
|
-
|
|
354
|
-
The `onCompose` script runs at build time with `node` bound to a `NodeBuilder` instance. The following methods are available:
|
|
355
|
-
|
|
356
|
-
| Method | Description |
|
|
357
|
-
|--------|-------------|
|
|
358
|
-
| `node.addDefault(name, options)` | Register a config field. `options`: `{ value, required, validate? }`. The optional `validate` function is serialised as-is into the generated `defaults` block and runs in the browser editor. |
|
|
359
|
-
| `node.setLabel(fn)` | Set a function that returns the node's palette label at runtime. |
|
|
360
|
-
| `node.setPaletteLabel(label)` | Set the fixed palette label. |
|
|
361
|
-
| `node.setColor(color)` | Set the node's palette colour (hex string). |
|
|
362
|
-
| `node.setIcon(icon)` | Set the node's palette icon filename (relative to the plugin's `icons/` directory). |
|
|
363
|
-
| `node.setLabelStyle(style)` | Set the CSS class for the palette label (e.g. `node_label_white`). |
|
|
364
|
-
| `node.setInput(label)` | Add an input port with the given label. |
|
|
365
|
-
| `node.addOutputs(labels)` | Add one or more output ports. Pass a string array for multiple labelled outputs. |
|
|
366
|
-
|
|
367
|
-
#### Data flow through the edit lifecycle
|
|
368
|
-
|
|
369
|
-
```
|
|
370
|
-
oneditprepare → [user edits] → oneditsave (Done clicked)
|
|
371
|
-
→ oneditcancel (Cancel clicked)
|
|
372
|
-
→ oneditdelete (node deleted)
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
Values flow through `node-input-<field>` (or `node-config-input-<field>`) named inputs. Node-RED automatically saves and restores these between sessions. Fields not following this convention must be manually read in `onIncludeEditSave` and written in `onIncludeEditPrepare`.
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
### Registering nodes for generation
|
|
380
|
-
|
|
381
|
-
Create a `GenerateNodes.ts` at the root of your `src/` directory. This is the composition root — register every service, template, and node, then call `.generate()` to emit the two Node-RED entry files (`Nodes.js` and `Plugins.js`).
|
|
745
|
+
Create a `GenerateNodes.ts` at the root of your `src/` directory. This is the composition root - register every service, template, and node, then call `.generate()` to emit the two Node-RED entry files.
|
|
382
746
|
|
|
383
747
|
```typescript
|
|
384
748
|
import { NodeGenerator } from "@theotherwillembotha/node-red-plugincore";
|
|
385
749
|
import {
|
|
386
|
-
LoggerService,
|
|
387
|
-
|
|
750
|
+
LoggerService, MetricsService, NodeTypeService, SettingsService,
|
|
751
|
+
DelegatedConfigReferenceNode,
|
|
752
|
+
BasicTemplate, LoggerTemplate, MetricsTemplate,
|
|
388
753
|
} from "@theotherwillembotha/node-red-plugincore";
|
|
389
|
-
import { MyNode } from "./nodes/MyNode";
|
|
390
754
|
|
|
391
|
-
|
|
755
|
+
import { MyService } from "./myplugin/service/MyService";
|
|
756
|
+
import { MyConfigNode } from "./myplugin/node/MyConfigNode";
|
|
757
|
+
import { MyNode } from "./myplugin/node/MyNode";
|
|
758
|
+
|
|
759
|
+
new NodeGenerator("./src/myplugin/")
|
|
760
|
+
// ── Infrastructure ── required by every plugin; deduplication guards make this safe to register from multiple plugins
|
|
392
761
|
.registerService(LoggerService)
|
|
762
|
+
.registerService(MetricsService)
|
|
763
|
+
.registerService(NodeTypeService)
|
|
764
|
+
.registerService(SettingsService)
|
|
765
|
+
.registerTemplate(BasicTemplate)
|
|
393
766
|
.registerTemplate(LoggerTemplate)
|
|
394
|
-
.
|
|
395
|
-
.registerNode(
|
|
767
|
+
.registerTemplate(MetricsTemplate)
|
|
768
|
+
.registerNode(DelegatedConfigReferenceNode)
|
|
769
|
+
// ── Plugin-specific ──────────────────────────────────────────────────────
|
|
770
|
+
.registerService(MyService)
|
|
771
|
+
.registerNode(MyConfigNode)
|
|
396
772
|
.registerNode(MyNode)
|
|
397
773
|
.generate("./build/Nodes", "./build/Plugins");
|
|
398
774
|
|
|
399
775
|
process.exit(0);
|
|
400
776
|
```
|
|
401
777
|
|
|
402
|
-
|
|
778
|
+
Logger and metrics provider nodes (`ConsoleLoggerConfigNode`, `PrometheusMetricsConfigNode`, etc.) are **not** registered here. They are registered by their own dedicated plugins and discovered at runtime through the `NodeTypeService` tag system.
|
|
403
779
|
|
|
404
780
|
### Wiring up package.json
|
|
405
781
|
|
|
@@ -416,12 +792,32 @@ Only register the services and config nodes your plugin actually depends on. You
|
|
|
416
792
|
### Build
|
|
417
793
|
|
|
418
794
|
```bash
|
|
419
|
-
npm run build # clean → tsc → generate node files → copy icons
|
|
795
|
+
npm run build # clean → tsc → generate node files → bundle (esbuild) → copy icons
|
|
420
796
|
npm run clean # remove build/
|
|
421
797
|
```
|
|
422
798
|
|
|
423
799
|
---
|
|
424
800
|
|
|
801
|
+
## Plugin ecosystem
|
|
802
|
+
|
|
803
|
+
These plugins are built on this framework and available from npm. Each plugin is independently installable - only install what your flows need.
|
|
804
|
+
|
|
805
|
+
| Plugin | Uses from plugincore | Provides |
|
|
806
|
+
|--------|---------------------|----------|
|
|
807
|
+
| [@theotherwillembotha/node-red-logging](https://github.com/theotherwillembotha/nodered_logging) | `LoggerService` `LoggerConfigNode` | **Console Logger Config Node** (config) - writes log entries to stdout with configurable level and Handlebars message template<br>**REST Logger Config Node** (config) - POSTs structured log entries to an HTTP endpoint |
|
|
808
|
+
| [@theotherwillembotha/node-red-loki](https://github.com/theotherwillembotha/nodered_loki) | `LoggerService` `LoggerConfigNode` `LoggerTemplate` | **Loki Config Node** (config) - shared connection reference for a Grafana Loki backend<br>**Loki Logger Config Node** (config) - streams structured log entries to Loki<br>**Loki Query Node** (flow) - executes LogQL queries against a Loki backend and emits results |
|
|
809
|
+
| [@theotherwillembotha/node-red-telemetry](https://github.com/theotherwillembotha/nodered_telemetry) | `LoggerService` `LoggerTemplate` | **Logger Node** (flow) - attaches to any installed logger provider; routes `msg` through the selected logger on each message |
|
|
810
|
+
| [@theotherwillembotha/node-red-prometheus](https://github.com/theotherwillembotha/nodered_prometheus) | `MetricsService` `MetricsConfigNode` `WebhookTemplate` | **Prometheus Metrics Config Node** (config) - hosts a `/metrics` scrape endpoint via a Webhook Server<br>**Counter Metric Config Node** (config) - named monotonic counter<br>**Gauge Metric Config Node** (config) - named up/down value gauge<br>**Timer Metric Config Node** (config) - named latency timer (Histogram or Summary mode) |
|
|
811
|
+
| [@theotherwillembotha/node-red-zookeeper](https://github.com/theotherwillembotha/nodered_zookeeper) | `BaseNode` `ConfigNode` `LoggerTemplate` `StateConfigNode` | **ZooKeeper Server Config Node** (config) - shared ZooKeeper client connection<br>**ZooKeeper State Config Node** (config) - maps flow state names to ZooKeeper node values<br>**ZooKeeper Read Node** (flow) - reads a ZooKeeper node value on demand<br>**ZooKeeper Write Node** (flow) - writes a value to a ZooKeeper node<br>**ZooKeeper Subscribe Node** (flow) - emits a message each time a ZooKeeper node changes |
|
|
812
|
+
| [@theotherwillembotha/node-red-circuitbreaker](https://github.com/theotherwillembotha/nodered_circuitbreaker) | `BaseNode` `ConfigNode` `LoggerTemplate` `MetricsTemplate` `StateTemplate` `ScriptEditorTemplate` | **Circuit Breaker Config Node** (config) - manages open/closed/half-open state with configurable fault detection and trip logic<br>**Fault Detector Config Node** (config) - user-defined TypeScript function that evaluates messages for faults<br>**Circuit Breaker Node** (flow) - routes messages based on current breaker state and emits state-change events |
|
|
813
|
+
| [@theotherwillembotha/node-red-temporal](https://github.com/theotherwillembotha/nodered_temporal) | `BaseNode` `LoggerTemplate` | **Temporal Transform Node** (flow) - parses, adjusts, and formats date/time values across timezones using the TC39 Temporal API<br>**Temporal Duration Node** (flow) - computes the signed duration between two date/time values |
|
|
814
|
+
| [@theotherwillembotha/node-red-whatsapp](https://github.com/theotherwillembotha/nodered_whatsapp) | `BaseNode` `ConfigNode` `LoggerTemplate` `WebhookTemplate` | **WhatsApp Config Node** (config) - manages a Baileys WhatsApp session (no cloud API or subscription required)<br>**WhatsApp Send Node** (flow) - sends a WhatsApp message from a flow<br>**WhatsApp Receive Node** (flow) - emits a message for each incoming WhatsApp message |
|
|
815
|
+
| [@theotherwillembotha/node-red-nginxproxymanager](https://github.com/theotherwillembotha/nodered_nginxproxymanager) | `BaseNode` `ConfigNode` `LoggerTemplate` `UIHelperTemplate` | **Nginx Proxy Manager Config Node** (config) - shared connection to an Nginx Proxy Manager instance; registers as a reverse proxy provider for Webhook Server nodes<br>**Update Host Node** (flow) - creates or updates a proxy host entry<br>**Get Hosts Node** (flow) - retrieves the current list of proxy hosts, displayable via `PluginCore.dialog()` |
|
|
816
|
+
|
|
817
|
+
Additional plugins will be listed here as they are published.
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
425
821
|
## Repository
|
|
426
822
|
|
|
427
823
|
- Source: [github.com/theotherwillembotha/nodered_plugincore](https://github.com/theotherwillembotha/nodered_plugincore)
|