@theotherwillembotha/node-red-plugincore 0.0.55 → 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 -217
- package/build/GenerateNodes.js +3 -6
- package/build/Nodes.html +3 -1502
- package/build/Nodes.js +5 -15
- package/build/Plugins.html +227 -0
- package/build/Plugins.js +18 -9
- package/build/core/NodeConstructor.d.ts +10 -16
- package/build/core/NodeConstructor.d.ts.map +1 -1
- package/build/core/NodeConstructor.js +112 -156
- package/build/core/NodeGenerator.d.ts.map +1 -1
- package/build/core/NodeGenerator.js +79 -29
- 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/WebhookDecorator.d.ts.map +1 -1
- package/build/core/webhook/WebhookDecorator.js +43 -6
- package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -1
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts +1 -0
- 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 -23
- 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 +5 -12
- package/src/GenerateNodes.ts +7 -11
- package/src/core/NodeConstructor.ts +134 -197
- package/src/core/NodeGenerator.ts +88 -34
- package/src/core/NodeManagerRuntime.ts +179 -0
- package/src/core/logger/service/LoggerService.ts +39 -26
- package/src/core/logger/template/LoggerTemplate.html +13 -8
- 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.html +2 -2
- 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 +5 -2
- 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/WebhookDecorator.ts +53 -10
- package/src/core/webhook/service/ReverseProxyTypeService.ts +1 -0
- package/src/core/webhook/service/WebhookServerService.ts +39 -28
- package/src/core/webhook/template/WebhookTemplate.html +29 -36
- 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.html +0 -90
- 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.html +0 -183
- package/build/core/logger/node/RestLoggerConfigNode.js +0 -128
- package/build/core/logger/template/LoggerTemplate.html +0 -331
- package/build/core/metrics/node/CounterMetricConfigNode.html +0 -48
- package/build/core/metrics/node/GaugeMetricConfigNode.html +0 -48
- package/build/core/metrics/node/MetricsConfigNode.html +0 -21
- package/build/core/metrics/node/TimerMetricConfigNode.html +0 -190
- package/build/core/metrics/template/CounterMetricTemplate.html +0 -40
- package/build/core/metrics/template/GaugeMetricTemplate.html +0 -39
- package/build/core/metrics/template/MetricsTemplate.html +0 -54
- package/build/core/metrics/template/TimerMetricTemplate.html +0 -39
- package/build/core/other/node/DelegatedConfigReferenceNode.html +0 -1
- package/build/core/other/template/BasicTemplate.html +0 -76
- package/build/core/other/template/SettingsTemplate.html +0 -128
- package/build/core/ui/template/ScriptEditorTemplate.html +0 -50
- package/build/core/ui/template/UIHelperTemplate.html +0 -229
- package/build/core/webhook/node/WebhookServerConfigNode.html +0 -61
- package/build/core/webhook/template/WebhookTemplate.html +0 -442
- 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,28 +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 logger config node. Extends the logging infrastructure provided by this package with a Loki transport, enabling log delivery directly to a Loki instance from any node built with the `@Logger` decorator. |
|
|
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 transformation nodes powered by the TC39 Temporal API. Parse, convert, adjust, and format date/time values across timezones using named presets or Moment.js-style custom format strings. |
|
|
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
|
-
|
|
25
|
-
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.
|
|
26
11
|
|
|
27
12
|
---
|
|
28
13
|
|
|
@@ -36,65 +21,44 @@ Either use the **Manage Palette** option in the Node-RED editor, or run the foll
|
|
|
36
21
|
npm install @theotherwillembotha/node-red-plugincore
|
|
37
22
|
```
|
|
38
23
|
|
|
39
|
-
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`).
|
|
40
25
|
|
|
41
26
|
### Config nodes
|
|
42
27
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#### Logging
|
|
46
|
-
|
|
47
|
-
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.
|
|
48
|
-
|
|
49
|
-
**Console Logger** — writes structured log output to stdout via Winston. Ideal for development and containerised deployments that forward stdout to a log aggregator.
|
|
50
|
-
|
|
51
|
-

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

|
|
28
|
+
#### Webhook Server
|
|
56
29
|
|
|
57
|
-
|
|
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.
|
|
58
31
|
|
|
59
|
-
|
|
32
|
+

|
|
60
33
|
|
|
61
|
-
|
|
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.*
|
|
62
35
|
|
|
63
|
-
|
|
64
|
-
|------|-------------|
|
|
65
|
-
| **Metrics Config** | Top-level Prometheus registry. One per deployment. |
|
|
66
|
-
| **Counter Metric** | An ever-increasing counter (e.g. messages processed, errors). |
|
|
67
|
-
| **Gauge Metric** | A value that goes up and down (e.g. queue depth, active connections). |
|
|
68
|
-
| **Timer Metric** | A histogram or summary for measuring durations (e.g. processing time per message). |
|
|
36
|
+
#### Logger providers
|
|
69
37
|
|
|
70
|
-
|
|
38
|
+
Logger config nodes are provided by separate plugins:
|
|
71
39
|
|
|
72
|
-
**
|
|
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)
|
|
73
42
|
|
|
74
|
-
|
|
75
|
-

|
|
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.
|
|
76
44
|
|
|
77
|
-
|
|
45
|
+
#### Metrics providers
|
|
78
46
|
|
|
79
|
-
|
|
47
|
+
Metrics config nodes are provided by separate plugins:
|
|
80
48
|
|
|
81
|
-
|
|
49
|
+
- **Prometheus** (Counter, Gauge, Histogram, `/metrics` endpoint) - [@theotherwillembotha/node-red-prometheus](https://github.com/theotherwillembotha/nodered_prometheus)
|
|
82
50
|
|
|
83
|
-
|
|
84
|
-
|------|-------------|
|
|
85
|
-
| **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. |
|
|
86
|
-
|
|
87
|
-

|
|
51
|
+
The **Metrics** section appears automatically in any node editor that supports metrics. It is hidden when no metrics plugins are installed.
|
|
88
52
|
|
|
89
53
|
---
|
|
90
54
|
|
|
91
|
-
## Development
|
|
55
|
+
## Development - Building plugins with this framework
|
|
92
56
|
|
|
93
57
|
### Prerequisites
|
|
94
58
|
|
|
95
59
|
- Node.js 18+
|
|
96
60
|
- Node-RED 4+
|
|
97
|
-
- TypeScript 5+
|
|
61
|
+
- TypeScript 5+
|
|
98
62
|
|
|
99
63
|
### Installation
|
|
100
64
|
|
|
@@ -102,98 +66,574 @@ The **Timer** config adds a metric type selector (Histogram or Summary) with con
|
|
|
102
66
|
npm install @theotherwillembotha/node-red-plugincore
|
|
103
67
|
```
|
|
104
68
|
|
|
105
|
-
|
|
69
|
+
### Required tsconfig.json
|
|
106
70
|
|
|
107
71
|
```json
|
|
108
72
|
{
|
|
109
73
|
"compilerOptions": {
|
|
110
|
-
"
|
|
111
|
-
"
|
|
74
|
+
"target": "es2022",
|
|
75
|
+
"module": "commonjs",
|
|
76
|
+
"rootDir": "./src",
|
|
77
|
+
"outDir": "./build",
|
|
78
|
+
"declaration": true,
|
|
79
|
+
"experimentalDecorators": true,
|
|
80
|
+
"emitDecoratorMetadata": true,
|
|
81
|
+
"useDefineForClassFields": false
|
|
112
82
|
}
|
|
113
83
|
}
|
|
114
84
|
```
|
|
115
85
|
|
|
116
|
-
|
|
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`**
|
|
117
147
|
|
|
118
|
-
|
|
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`**
|
|
119
199
|
|
|
120
200
|
```typescript
|
|
121
201
|
import {
|
|
122
202
|
BaseNode, BaseNodeConfig,
|
|
123
203
|
NodeDescription, SourceUtility,
|
|
124
|
-
LoggerTemplate, LoggerTemplateConfig,
|
|
125
|
-
|
|
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
|
|
126
211
|
} from "@theotherwillembotha/node-red-plugincore";
|
|
127
212
|
import { Node } from "node-red";
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
131
222
|
}
|
|
132
223
|
|
|
133
224
|
@NodeDescription({
|
|
134
|
-
id:
|
|
135
|
-
name:
|
|
136
|
-
group:
|
|
137
|
-
sourceFile: SourceUtility.getSourcePath("/build/", "/src/") + "
|
|
138
|
-
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",
|
|
139
230
|
templates: [
|
|
140
|
-
|
|
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: {} },
|
|
141
236
|
]
|
|
142
237
|
})
|
|
143
|
-
class
|
|
238
|
+
class WebhookListenerNode extends BaseNode<WebhookListenerNodeConfig> {
|
|
144
239
|
|
|
240
|
+
// Injected by the framework at runtime. Declare with ! - TypeScript cannot
|
|
241
|
+
// see the injection mechanism at compile time.
|
|
145
242
|
@Logger()
|
|
146
243
|
private log!: Log;
|
|
147
244
|
|
|
148
|
-
|
|
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) {
|
|
149
258
|
super(node, config);
|
|
150
259
|
}
|
|
151
260
|
|
|
152
|
-
|
|
153
|
-
|
|
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);
|
|
154
274
|
}
|
|
155
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;
|
|
156
473
|
```
|
|
157
474
|
|
|
158
|
-
|
|
475
|
+
**Screenshot - MetricsTemplate section:**
|
|
159
476
|
|
|
160
|
-
|
|
477
|
+

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

|
|
185
486
|
|
|
186
|
-
|
|
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.*
|
|
187
488
|
|
|
188
|
-
|
|
489
|
+

|
|
189
490
|
|
|
190
|
-
|
|
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.*
|
|
191
492
|
|
|
192
|
-
|
|
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.
|
|
193
633
|
|
|
194
634
|
```javascript
|
|
195
635
|
PluginCore.dialog({
|
|
196
|
-
title: "My Plugin
|
|
636
|
+
title: "My Plugin - Status",
|
|
197
637
|
tabs: [
|
|
198
638
|
{
|
|
199
639
|
label: "Proxy Hosts",
|
|
@@ -201,14 +641,14 @@ PluginCore.dialog({
|
|
|
201
641
|
$container.append(
|
|
202
642
|
PluginCore.table({
|
|
203
643
|
columns: [
|
|
204
|
-
{ key: "id",
|
|
205
|
-
{ key: "name",
|
|
644
|
+
{ key: "id", label: "ID" },
|
|
645
|
+
{ key: "name", label: "Name" },
|
|
206
646
|
{ key: "enabled", label: "Enabled",
|
|
207
647
|
render: function(v) {
|
|
208
648
|
return $("<span>")
|
|
209
649
|
.addClass(v ? "plugincore-status-enabled"
|
|
210
650
|
: "plugincore-status-disabled")
|
|
211
|
-
.text(v ? "
|
|
651
|
+
.text(v ? "Enabled" : "Disabled");
|
|
212
652
|
}}
|
|
213
653
|
],
|
|
214
654
|
rows: data
|
|
@@ -220,22 +660,18 @@ PluginCore.dialog({
|
|
|
220
660
|
});
|
|
221
661
|
```
|
|
222
662
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
|
226
|
-
|-------|------|-------------|
|
|
227
|
-
| `title` | `string` | Heading shown in the dialog title bar |
|
|
663
|
+
| Option | Type | Description |
|
|
664
|
+
|--------|------|-------------|
|
|
665
|
+
| `title` | `string` | Heading in the dialog title bar |
|
|
228
666
|
| `tabs` | `array` | One or more tab definitions |
|
|
229
667
|
| `tabs[].label` | `string` | Tab heading |
|
|
230
|
-
| `tabs[].render` | `function($container)` | Called with a jQuery element
|
|
668
|
+
| `tabs[].render` | `function($container)` | Called with a jQuery element - append content into it |
|
|
231
669
|
|
|
232
|
-
**Returns:** `{ close() }`
|
|
233
|
-
|
|
234
|
-
---
|
|
670
|
+
**Returns:** `{ close() }` - call `close()` to dismiss programmatically.
|
|
235
671
|
|
|
236
|
-
|
|
672
|
+
### `PluginCore.table(config)`
|
|
237
673
|
|
|
238
|
-
Returns a styled jQuery `<table>`
|
|
674
|
+
Returns a styled jQuery `<table>` ready to append into any container.
|
|
239
675
|
|
|
240
676
|
```javascript
|
|
241
677
|
var $table = PluginCore.table({
|
|
@@ -249,154 +685,97 @@ var $table = PluginCore.table({
|
|
|
249
685
|
$container.append($table);
|
|
250
686
|
```
|
|
251
687
|
|
|
252
|
-
**Config:**
|
|
253
|
-
|
|
254
688
|
| Field | Type | Description |
|
|
255
689
|
|-------|------|-------------|
|
|
256
|
-
| `columns` | `array` | Column definitions |
|
|
257
690
|
| `columns[].key` | `string` | Property name on each row object |
|
|
258
691
|
| `columns[].label` | `string` | Column header text |
|
|
259
|
-
| `columns[].render` | `function(value, row)` | Optional
|
|
692
|
+
| `columns[].render` | `function(value, row)` | Optional - return a string or jQuery element for custom cells |
|
|
260
693
|
| `rows` | `object[]` | Data rows |
|
|
261
694
|
|
|
262
|
-
**CSS classes
|
|
695
|
+
**CSS classes for cell content:**
|
|
263
696
|
|
|
264
|
-
| Class | Colour |
|
|
265
|
-
|
|
266
|
-
| `plugincore-status-enabled` | Green |
|
|
267
|
-
| `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 |
|
|
268
701
|
|
|
269
|
-
|
|
702
|
+
### `PluginCore.createScriptEditor(elementId, template, initialValue)`
|
|
270
703
|
|
|
271
|
-
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.
|
|
272
705
|
|
|
273
706
|
```javascript
|
|
274
707
|
// In onIncludeEditPrepare:
|
|
275
|
-
let
|
|
276
|
-
interface Message { [key: string]: any; }
|
|
277
|
-
async function(msg: Message) {
|
|
278
|
-
\${script}
|
|
279
|
-
}
|
|
280
|
-
`;
|
|
281
|
-
|
|
708
|
+
let node = this;
|
|
282
709
|
node.scriptEditor = PluginCore.createScriptEditor(
|
|
283
|
-
'node-input-script-editor',
|
|
284
|
-
|
|
285
|
-
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
|
|
286
713
|
);
|
|
287
714
|
|
|
288
715
|
// In onIncludeEditSave:
|
|
289
716
|
node.script = node.scriptEditor.getValue();
|
|
290
717
|
delete node.scriptEditor;
|
|
291
718
|
|
|
292
|
-
// In
|
|
719
|
+
// In onIncludeEditCancel - always call dispose() to avoid Monaco memory leaks:
|
|
293
720
|
node.scriptEditor.dispose();
|
|
294
721
|
delete node.scriptEditor;
|
|
295
722
|
```
|
|
296
723
|
|
|
297
|
-
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.
|
|
298
|
-
|
|
299
724
|
**Returns:** `{ getValue(): string, dispose(): void }`
|
|
300
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
|
+
|
|
301
728
|
---
|
|
302
729
|
|
|
303
|
-
> **
|
|
730
|
+
> **Handlebars escaping in node HTML files**
|
|
304
731
|
>
|
|
305
|
-
> 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.
|
|
306
733
|
>
|
|
307
|
-
> Escape curly braces with a backslash wherever they appear literally
|
|
734
|
+
> Escape curly braces with a backslash wherever they appear literally:
|
|
308
735
|
>
|
|
309
736
|
> ```javascript
|
|
310
|
-
> // Wrong
|
|
311
|
-
> // @returns {{ getValue(): string }}
|
|
312
|
-
>
|
|
313
|
-
> // Correct — escaped so Handlebars passes it through:
|
|
314
|
-
> // @returns \{{ getValue(): string \}}
|
|
737
|
+
> // Wrong: @returns {{ getValue(): string }}
|
|
738
|
+
> // Correct: @returns \{{ getValue(): string \}}
|
|
315
739
|
> ```
|
|
316
|
-
>
|
|
317
|
-
> This applies anywhere in the HTML file: `<script>` blocks, inline styles, markdown documentation sections, and comments.
|
|
318
740
|
|
|
319
741
|
---
|
|
320
742
|
|
|
321
|
-
|
|
743
|
+
## Registering nodes for generation
|
|
322
744
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
```html
|
|
326
|
-
<script type="text/javascript" template-section="onCompose"> ... </script>
|
|
327
|
-
<div template-section="onIncludeOnce"> ... </div>
|
|
328
|
-
<script type="text/javascript" template-section="onIncludeEditPrepare"> ... </script>
|
|
329
|
-
<script type="text/html" template-section="onIncludeEditForm"> ... </script>
|
|
330
|
-
<script type="text/javascript" template-section="onIncludeEditSave"> ... </script>
|
|
331
|
-
<script type="text/javascript" template-section="onIncludeEditCancel"> ... </script>
|
|
332
|
-
<script type="text/javascript" template-section="onIncludeEditDelete"> ... </script>
|
|
333
|
-
<script type="text/markdown" template-section="onIncludeDocumentation"> ... </script>
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
#### Section reference
|
|
337
|
-
|
|
338
|
-
| Section | When it runs | Typical use |
|
|
339
|
-
|---------|-------------|-------------|
|
|
340
|
-
| `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. |
|
|
341
|
-
| `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>`. |
|
|
342
|
-
| `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. |
|
|
343
|
-
| `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. |
|
|
344
|
-
| `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. |
|
|
345
|
-
| `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. |
|
|
346
|
-
| `onIncludeEditDelete` | Runs when the node is **deleted** from the canvas | Release any persistent resources tied to this node instance. Rarely needed for most nodes. |
|
|
347
|
-
| `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. |
|
|
348
|
-
|
|
349
|
-
#### `onCompose` — NodeBuilder API
|
|
350
|
-
|
|
351
|
-
The `onCompose` script runs at build time with `node` bound to a `NodeBuilder` instance. The following methods are available:
|
|
352
|
-
|
|
353
|
-
| Method | Description |
|
|
354
|
-
|--------|-------------|
|
|
355
|
-
| `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. |
|
|
356
|
-
| `node.setLabel(fn)` | Set a function that returns the node's palette label at runtime. |
|
|
357
|
-
| `node.setPaletteLabel(label)` | Set the fixed palette label. |
|
|
358
|
-
| `node.setColor(color)` | Set the node's palette colour (hex string). |
|
|
359
|
-
| `node.setIcon(icon)` | Set the node's palette icon filename (relative to the plugin's `icons/` directory). |
|
|
360
|
-
| `node.setLabelStyle(style)` | Set the CSS class for the palette label (e.g. `node_label_white`). |
|
|
361
|
-
| `node.setInput(label)` | Add an input port with the given label. |
|
|
362
|
-
| `node.addOutputs(labels)` | Add one or more output ports. Pass a string array for multiple labelled outputs. |
|
|
363
|
-
|
|
364
|
-
#### Data flow through the edit lifecycle
|
|
365
|
-
|
|
366
|
-
```
|
|
367
|
-
oneditprepare → [user edits] → oneditsave (Done clicked)
|
|
368
|
-
→ oneditcancel (Cancel clicked)
|
|
369
|
-
→ oneditdelete (node deleted)
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
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`.
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
### Registering nodes for generation
|
|
377
|
-
|
|
378
|
-
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.
|
|
379
746
|
|
|
380
747
|
```typescript
|
|
381
748
|
import { NodeGenerator } from "@theotherwillembotha/node-red-plugincore";
|
|
382
749
|
import {
|
|
383
|
-
LoggerService,
|
|
384
|
-
|
|
750
|
+
LoggerService, MetricsService, NodeTypeService, SettingsService,
|
|
751
|
+
DelegatedConfigReferenceNode,
|
|
752
|
+
BasicTemplate, LoggerTemplate, MetricsTemplate,
|
|
385
753
|
} from "@theotherwillembotha/node-red-plugincore";
|
|
386
|
-
import { MyNode } from "./nodes/MyNode";
|
|
387
754
|
|
|
388
|
-
|
|
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
|
|
389
761
|
.registerService(LoggerService)
|
|
762
|
+
.registerService(MetricsService)
|
|
763
|
+
.registerService(NodeTypeService)
|
|
764
|
+
.registerService(SettingsService)
|
|
765
|
+
.registerTemplate(BasicTemplate)
|
|
390
766
|
.registerTemplate(LoggerTemplate)
|
|
391
|
-
.
|
|
392
|
-
.registerNode(
|
|
767
|
+
.registerTemplate(MetricsTemplate)
|
|
768
|
+
.registerNode(DelegatedConfigReferenceNode)
|
|
769
|
+
// ── Plugin-specific ──────────────────────────────────────────────────────
|
|
770
|
+
.registerService(MyService)
|
|
771
|
+
.registerNode(MyConfigNode)
|
|
393
772
|
.registerNode(MyNode)
|
|
394
773
|
.generate("./build/Nodes", "./build/Plugins");
|
|
395
774
|
|
|
396
775
|
process.exit(0);
|
|
397
776
|
```
|
|
398
777
|
|
|
399
|
-
|
|
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.
|
|
400
779
|
|
|
401
780
|
### Wiring up package.json
|
|
402
781
|
|
|
@@ -413,12 +792,32 @@ Only register the services and config nodes your plugin actually depends on. You
|
|
|
413
792
|
### Build
|
|
414
793
|
|
|
415
794
|
```bash
|
|
416
|
-
npm run build # clean → tsc → generate node files → copy icons
|
|
795
|
+
npm run build # clean → tsc → generate node files → bundle (esbuild) → copy icons
|
|
417
796
|
npm run clean # remove build/
|
|
418
797
|
```
|
|
419
798
|
|
|
420
799
|
---
|
|
421
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
|
+
|
|
422
821
|
## Repository
|
|
423
822
|
|
|
424
823
|
- Source: [github.com/theotherwillembotha/nodered_plugincore](https://github.com/theotherwillembotha/nodered_plugincore)
|