@theotherwillembotha/node-red-plugincore 0.1.0 → 0.3.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.
Files changed (167) hide show
  1. package/README.md +616 -220
  2. package/build/GenerateNodes.js +9 -33
  3. package/build/Nodes.html +2 -1505
  4. package/build/Nodes.js +5 -19
  5. package/build/Plugins.html +11 -0
  6. package/build/Plugins.js +17 -12
  7. package/build/core/NodeConstructor.d.ts +12 -16
  8. package/build/core/NodeConstructor.d.ts.map +1 -1
  9. package/build/core/NodeConstructor.js +125 -185
  10. package/build/core/NodeGenerator.d.ts +3 -1
  11. package/build/core/NodeGenerator.d.ts.map +1 -1
  12. package/build/core/NodeGenerator.js +139 -43
  13. package/build/core/NodeManagerRuntime.d.ts +44 -0
  14. package/build/core/NodeManagerRuntime.d.ts.map +1 -0
  15. package/build/core/NodeManagerRuntime.js +140 -0
  16. package/build/core/Utils.d.ts.map +1 -1
  17. package/build/core/logger/service/LoggerService.d.ts +8 -6
  18. package/build/core/logger/service/LoggerService.d.ts.map +1 -1
  19. package/build/core/logger/service/LoggerService.js +101 -23
  20. package/build/core/logger/template/LoggerTemplate.d.ts +1 -2
  21. package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -1
  22. package/build/core/logger/template/LoggerTemplate.js +18 -5
  23. package/build/core/metrics/MetricsDecorator.d.ts +1 -2
  24. package/build/core/metrics/MetricsDecorator.d.ts.map +1 -1
  25. package/build/core/metrics/MetricsDecorator.js +89 -116
  26. package/build/core/metrics/node/CounterMetricConfigNode.d.ts.map +1 -1
  27. package/build/core/metrics/node/CounterMetricConfigNode.js +4 -5
  28. package/build/core/metrics/node/GaugeMetricConfigNode.d.ts.map +1 -1
  29. package/build/core/metrics/node/GaugeMetricConfigNode.js +5 -6
  30. package/build/core/metrics/node/MetricsConfigNode.d.ts +19 -9
  31. package/build/core/metrics/node/MetricsConfigNode.d.ts.map +1 -1
  32. package/build/core/metrics/node/MetricsConfigNode.js +14 -45
  33. package/build/core/metrics/node/TimerMetricConfigNode.d.ts.map +1 -1
  34. package/build/core/metrics/node/TimerMetricConfigNode.js +11 -17
  35. package/build/core/metrics/service/MetricsService.d.ts +110 -114
  36. package/build/core/metrics/service/MetricsService.d.ts.map +1 -1
  37. package/build/core/metrics/service/MetricsService.js +122 -339
  38. package/build/core/metrics/template/CounterMetricTemplate.d.ts +1 -2
  39. package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -1
  40. package/build/core/metrics/template/CounterMetricTemplate.js +16 -5
  41. package/build/core/metrics/template/GaugeMetricTemplate.d.ts +1 -2
  42. package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -1
  43. package/build/core/metrics/template/GaugeMetricTemplate.js +16 -5
  44. package/build/core/metrics/template/MetricsTemplate.d.ts +1 -2
  45. package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -1
  46. package/build/core/metrics/template/MetricsTemplate.js +18 -6
  47. package/build/core/metrics/template/TimerMetricTemplate.d.ts +1 -2
  48. package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -1
  49. package/build/core/metrics/template/TimerMetricTemplate.js +16 -5
  50. package/build/core/other/node/DelegatedConfigReferenceNode.d.ts +1 -1
  51. package/build/core/other/node/DelegatedConfigReferenceNode.d.ts.map +1 -1
  52. package/build/core/other/node/DelegatedConfigReferenceNode.js +1 -1
  53. package/build/core/other/service/InputService.d.ts.map +1 -1
  54. package/build/core/other/service/SettingsService.d.ts +1 -2
  55. package/build/core/other/service/SettingsService.d.ts.map +1 -1
  56. package/build/core/other/service/SettingsService.js +19 -5
  57. package/build/core/other/template/BasicTemplate.d.ts +1 -2
  58. package/build/core/other/template/BasicTemplate.d.ts.map +1 -1
  59. package/build/core/other/template/BasicTemplate.js +15 -5
  60. package/build/core/other/template/SettingsTemplate.d.ts +1 -2
  61. package/build/core/other/template/SettingsTemplate.d.ts.map +1 -1
  62. package/build/core/other/template/SettingsTemplate.js +17 -5
  63. package/build/core/state/node/InternalStateConfigNode.d.ts +16 -0
  64. package/build/core/state/node/InternalStateConfigNode.d.ts.map +1 -0
  65. package/build/core/state/node/InternalStateConfigNode.js +89 -0
  66. package/build/core/state/node/StateConfigNode.d.ts +24 -0
  67. package/build/core/state/node/StateConfigNode.d.ts.map +1 -0
  68. package/build/core/state/node/StateConfigNode.js +22 -0
  69. package/build/core/state/service/StateService.d.ts +34 -0
  70. package/build/core/state/service/StateService.d.ts.map +1 -0
  71. package/build/core/state/service/StateService.js +201 -0
  72. package/build/core/state/template/StateTemplate.d.ts +7 -0
  73. package/build/core/state/template/StateTemplate.d.ts.map +1 -0
  74. package/build/core/state/template/StateTemplate.js +25 -0
  75. package/build/core/tagging/ServiceDescriptionDecorator.d.ts +10 -0
  76. package/build/core/tagging/ServiceDescriptionDecorator.d.ts.map +1 -0
  77. package/build/core/tagging/ServiceDescriptionDecorator.js +10 -0
  78. package/build/core/tagging/TemplateDescriptionDecorator.d.ts +8 -0
  79. package/build/core/tagging/TemplateDescriptionDecorator.d.ts.map +1 -0
  80. package/build/core/tagging/TemplateDescriptionDecorator.js +10 -0
  81. package/build/core/tagging/service/NodeTypeService.d.ts +1 -2
  82. package/build/core/tagging/service/NodeTypeService.d.ts.map +1 -1
  83. package/build/core/tagging/service/NodeTypeService.js +19 -5
  84. package/build/core/ui/template/ScriptEditorTemplate.d.ts +1 -2
  85. package/build/core/ui/template/ScriptEditorTemplate.d.ts.map +1 -1
  86. package/build/core/ui/template/ScriptEditorTemplate.js +15 -5
  87. package/build/core/ui/template/UIHelperTemplate.d.ts +1 -2
  88. package/build/core/ui/template/UIHelperTemplate.d.ts.map +1 -1
  89. package/build/core/ui/template/UIHelperTemplate.js +15 -5
  90. package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -1
  91. package/build/core/webhook/service/ReverseProxyTypeService.d.ts.map +1 -1
  92. package/build/core/webhook/service/WebhookServerService.d.ts +3 -4
  93. package/build/core/webhook/service/WebhookServerService.d.ts.map +1 -1
  94. package/build/core/webhook/service/WebhookServerService.js +48 -18
  95. package/build/core/webhook/template/WebhookTemplate.d.ts +1 -2
  96. package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -1
  97. package/build/core/webhook/template/WebhookTemplate.js +19 -6
  98. package/build/index.d.ts +6 -2
  99. package/build/index.d.ts.map +1 -1
  100. package/build/index.js +7 -2
  101. package/build/runtime/NodeManagerRuntime.js +140 -0
  102. package/documentation/CounterMetricConfigNode.png +0 -0
  103. package/documentation/GaugeMetricConfigNode.png +0 -0
  104. package/documentation/LoggerTemplate.png +0 -0
  105. package/documentation/MetricsTemplate.png +0 -0
  106. package/documentation/StateTemplate.png +0 -0
  107. package/documentation/TimerMetricConfigNode_histogram.png +0 -0
  108. package/documentation/TimerMetricConfigNode_summary.png +0 -0
  109. package/documentation/WebhookServerConfigNode.png +0 -0
  110. package/documentation/WebhookTemplate.png +0 -0
  111. package/package.json +4 -11
  112. package/src/GenerateNodes.ts +10 -40
  113. package/src/core/NodeConstructor.ts +150 -228
  114. package/src/core/NodeGenerator.ts +156 -47
  115. package/src/core/NodeManagerRuntime.ts +179 -0
  116. package/src/core/logger/service/LoggerService.ts +60 -32
  117. package/src/core/logger/template/LoggerTemplate.html +11 -6
  118. package/src/core/logger/template/LoggerTemplate.ts +9 -11
  119. package/src/core/metrics/MetricsDecorator.ts +140 -168
  120. package/src/core/metrics/node/CounterMetricConfigNode.ts +20 -20
  121. package/src/core/metrics/node/GaugeMetricConfigNode.ts +24 -25
  122. package/src/core/metrics/node/MetricsConfigNode.ts +29 -48
  123. package/src/core/metrics/node/TimerMetricConfigNode.ts +50 -59
  124. package/src/core/metrics/service/MetricsService.ts +214 -489
  125. package/src/core/metrics/template/CounterMetricTemplate.html +0 -1
  126. package/src/core/metrics/template/CounterMetricTemplate.ts +8 -11
  127. package/src/core/metrics/template/GaugeMetricTemplate.html +1 -1
  128. package/src/core/metrics/template/GaugeMetricTemplate.ts +8 -11
  129. package/src/core/metrics/template/MetricsTemplate.html +132 -13
  130. package/src/core/metrics/template/MetricsTemplate.ts +9 -10
  131. package/src/core/metrics/template/TimerMetricTemplate.ts +8 -10
  132. package/src/core/other/node/DelegatedConfigReferenceNode.ts +1 -1
  133. package/src/core/other/service/SettingsService.ts +6 -10
  134. package/src/core/other/template/BasicTemplate.html +1 -1
  135. package/src/core/other/template/BasicTemplate.ts +6 -11
  136. package/src/core/other/template/SettingsTemplate.ts +8 -13
  137. package/src/core/state/node/InternalStateConfigNode.html +77 -0
  138. package/src/core/state/node/InternalStateConfigNode.ts +102 -0
  139. package/src/core/state/node/StateConfigNode.ts +29 -0
  140. package/src/core/state/service/StateService.ts +180 -0
  141. package/src/core/state/template/StateTemplate.html +244 -0
  142. package/src/core/state/template/StateTemplate.ts +18 -0
  143. package/src/core/tagging/ServiceDescriptionDecorator.ts +23 -0
  144. package/src/core/tagging/TemplateDescriptionDecorator.ts +19 -0
  145. package/src/core/tagging/service/NodeTypeService.ts +6 -11
  146. package/src/core/ui/template/ScriptEditorTemplate.ts +6 -10
  147. package/src/core/ui/template/UIHelperTemplate.html +41 -26
  148. package/src/core/ui/template/UIHelperTemplate.ts +6 -10
  149. package/src/core/webhook/service/WebhookServerService.ts +39 -23
  150. package/src/core/webhook/template/WebhookTemplate.html +28 -35
  151. package/src/core/webhook/template/WebhookTemplate.ts +10 -12
  152. package/src/index.ts +8 -2
  153. package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts +0 -20
  154. package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts.map +0 -1
  155. package/build/core/logger/node/ConsoleLoggerConfigNode.js +0 -62
  156. package/build/core/logger/node/RestLoggerConfigNode.d.ts +0 -40
  157. package/build/core/logger/node/RestLoggerConfigNode.d.ts.map +0 -1
  158. package/build/core/logger/node/RestLoggerConfigNode.js +0 -128
  159. package/documentation/ConsoleLoggerConfigNode.png +0 -0
  160. package/documentation/MetricsConfigNode.png +0 -0
  161. package/documentation/RestLoggerConfigNode.png +0 -0
  162. package/documentation/TimerMetricConfigNode.png +0 -0
  163. package/documentation/WebhookNode.png +0 -0
  164. package/src/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
  165. package/src/core/logger/node/ConsoleLoggerConfigNode.ts +0 -71
  166. package/src/core/logger/node/RestLoggerConfigNode.html +0 -183
  167. 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 with built-in support for structured logging, Prometheus metrics, webhook servers, and reusable UI components.
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 has two roles:
5
+ This package serves two roles:
6
6
 
7
- 1. **Config nodes** a set of shared configuration nodes (loggers, metric collectors, webhook server) that are installed into Node-RED and referenced by other nodes in a flow.
8
- 2. **Developer framework** a TypeScript base library that plugin authors extend to build their own Node-RED nodes, with decorators and templates that wire in logging, metrics, webhooks, and editor UI automatically.
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 nodes below and makes them available in your palette. Typically you would also install a plugin such as [node-red-telemetry](https://github.com/theotherwillembotha/nodered_telemetry) to get the actual flow nodes that reference them.
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
- Config nodes are shared resources configured once and referenced across your flow. They appear under their own groups in the palette sidebar.
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
- ![Console Logger Config](documentation/ConsoleLoggerConfigNode.png)
55
-
56
- **REST Logger** — ships log entries to a remote HTTP/HTTPS endpoint. Supports Basic and API Key authentication.
57
-
58
- ![REST Logger Config](documentation/RestLoggerConfigNode.png)
28
+ #### Webhook Server
59
29
 
60
- All loggers share a **Level** selector (debug, info, warn, error) and a **Template** field a Handlebars template that controls the shape of each log entry. The default `message:{{msg}}` passes the raw message through; you can customise it to include only the fields you care about.
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
- #### Metrics
32
+ ![Webhook Server Config Node](documentation/WebhookServerConfigNode.png)
63
33
 
64
- Prometheus-compatible metric collectors. A scrape endpoint (`/metrics`) is provided automatically once any metric node is deployed.
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
- | Node | Description |
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
- ![Metrics Config](documentation/MetricsConfigNode.png)
38
+ Logger config nodes are provided by separate plugins:
74
39
 
75
- **Counter** and **Gauge** share the same layout — a name, a description, and an optional reset-on-deploy toggle:
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
- ![Counter Metric Config](documentation/CounterMetricConfigNode.png)
78
- ![Gauge Metric Config](documentation/GaugeMetricConfigNode.png)
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
- The **Timer** config adds a metric type selector (Histogram or Summary) with configurable bucket or percentile strategies:
45
+ #### Metrics providers
81
46
 
82
- ![Timer Metric Config](documentation/TimerMetricConfigNode.png)
47
+ Metrics config nodes are provided by separate plugins:
83
48
 
84
- #### Webhook Server
49
+ - **Prometheus** (Counter, Gauge, Histogram, `/metrics` endpoint) - [@theotherwillembotha/node-red-prometheus](https://github.com/theotherwillembotha/nodered_prometheus)
85
50
 
86
- | Node | Description |
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
- ![Webhook Server Config](documentation/WebhookNode.png)
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 Building plugins with this framework
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+ with `experimentalDecorators` and `emitDecoratorMetadata` enabled
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
- Your `tsconfig.json` must include:
69
+ ### Required tsconfig.json
109
70
 
110
71
  ```json
111
72
  {
112
73
  "compilerOptions": {
113
- "experimentalDecorators": true,
114
- "emitDecoratorMetadata": true
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
- ### Defining a node
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
- Extend `BaseNode` (or `ConfigNode` for config nodes) and annotate the class with `@NodeDescription`. The decorator registers the node type, its editor HTML file, the palette group it appears in, and any shared templates it composes in.
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
- Log, Logger
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
- interface MyNodeConfig extends BaseNodeConfig, LoggerTemplateConfig {
133
- name: string;
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: "my-node",
138
- name: "My Node",
139
- group: "my-plugin",
140
- sourceFile: SourceUtility.getSourcePath("/build/", "/src/") + "MyNode.html",
141
- package: "@myscope/my-nodered-plugin",
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
- { template: LoggerTemplate, config: {} }
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 MyNode extends BaseNode<MyNodeConfig> {
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
- constructor(node: Node, config: MyNodeConfig) {
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
- protected onInit() {
156
- this.log.log("MyNode initialised");
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
+ ![Logger Template](documentation/LoggerTemplate.png)
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
- The `LoggerTemplate` fragment is automatically composed into the node's editor panel, giving the user a logger selector and optional message template override with no additional HTML required.
475
+ **Screenshot - MetricsTemplate section:**
162
476
 
163
- ### Available decorators
477
+ ![Metrics Template](documentation/MetricsTemplate.png)
164
478
 
165
- | Decorator | Property type | What it injects |
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
- ### Templates
481
+ **Metric config nodes** (provided by `node-red-prometheus`):
173
482
 
174
- Templates bundle reusable UI fragments that compose into any node's editor panel. Include them in the `templates` array of `@NodeDescription`.
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
- | Template | Adds to editor |
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
+ ![Counter Metric Config Node](documentation/CounterMetricConfigNode.png)
188
486
 
189
- ### UI helpers
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
- Including `UIHelperTemplate` in a node's `templates` list injects two client-side factory functions into the Node-RED editor page. Both are available globally as `PluginCore.dialog(...)` and `PluginCore.table(...)` and are styled to match Node-RED's own editor aesthetic.
489
+ ![Gauge Metric Config Node](documentation/GaugeMetricConfigNode.png)
192
490
 
193
- #### `PluginCore.dialog(options)`
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
- Opens a modal overlay with a title bar and one or more tabs. Closes on the close button, an overlay click, or Escape.
493
+ Timer Metric Config Nodes can operate in two modes selected by the **Type** dropdown:
494
+
495
+ ![Timer Metric Config Node - Summary](documentation/TimerMetricConfigNode_summary.png)
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
+ ![Timer Metric Config Node - Histogram](documentation/TimerMetricConfigNode_histogram.png)
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
+ ![State Template](documentation/StateTemplate.png)
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
+ ![Webhook Template](documentation/WebhookTemplate.png)
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 Status",
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", label: "ID" },
208
- { key: "name", label: "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 ? "Enabled" : "Disabled");
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
- **Options:**
227
-
228
- | Field | Type | Description |
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; append content into it |
668
+ | `tabs[].render` | `function($container)` | Called with a jQuery element - append content into it |
234
669
 
235
- **Returns:** `{ close() }` call `close()` to dismiss the dialog programmatically.
236
-
237
- ---
670
+ **Returns:** `{ close() }` - call `close()` to dismiss programmatically.
238
671
 
239
- #### `PluginCore.table(config)`
672
+ ### `PluginCore.table(config)`
240
673
 
241
- Returns a styled jQuery `<table>` element ready to append into any container.
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. Return a string or jQuery element for custom cell rendering |
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 available for cell content:**
695
+ **CSS classes for cell content:**
266
696
 
267
- | Class | Colour | Intended use |
268
- |-------|--------|-------------|
269
- | `plugincore-status-enabled` | Green | Enabled / active state |
270
- | `plugincore-status-disabled` | Red | Disabled / inactive state |
697
+ | Class | Colour | Use |
698
+ |-------|--------|-----|
699
+ | `plugincore-status-enabled` | Green | Active / enabled state |
700
+ | `plugincore-status-disabled` | Red | Inactive / disabled state |
271
701
 
272
- #### `PluginCore.createScriptEditor(elementId, template, initialValue)`
702
+ ### `PluginCore.createScriptEditor(elementId, template, initialValue)`
273
703
 
274
- Including `ScriptEditorTemplate` in a node's `templates` list injects a Monaco-based script editor factory into the Node-RED editor page. It wraps the async Monaco initialisation boilerplate into a single call and returns a `{ getValue(), dispose() }` handle.
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 scriptTemplate = `
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', // DOM id of the container element
287
- scriptTemplate, // TypeScript context template
288
- node.script || 'return true;' // initial value
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 IncludeEditCancel:
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
- > **Note — Handlebars escaping in node HTML files**
730
+ > **Handlebars escaping in node HTML files**
307
731
  >
308
- > Node HTML files (`.html` template files) are processed by Handlebars during the build. This means any `{{ }}` syntax in the HTML including in JavaScript comments or JSDoc will be interpreted as a Handlebars expression and produce unexpected output or an error.
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 in the file:
734
+ > Escape curly braces with a backslash wherever they appear literally:
311
735
  >
312
736
  > ```javascript
313
- > // Wrong Handlebars will try to evaluate this:
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
- ### Node HTML file — template sections
743
+ ## Registering nodes for generation
325
744
 
326
- 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 (e.g. `LoggerTemplate`, `MetricsTemplate`) are merged automatically in the order they were registered.
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, LoggerTemplate,
387
- ConsoleLoggerConfigNode, RestLoggerConfigNode
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
- new NodeGenerator("./src/")
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
- .registerNode(ConsoleLoggerConfigNode)
395
- .registerNode(RestLoggerConfigNode)
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
- Only register the services and config nodes your plugin actually depends on. You do not need to re-register nodes from this package if your plugin does not expose them directly.
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)