@bonsae/nrg 0.3.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "NRG framework — build Node-RED nodes with Vue 3, TypeScript, and JSON Schema",
5
5
  "author": "Allan Oricil <allanoricil@duck.com>",
6
6
  "license": "MIT",
@@ -98,6 +98,7 @@
98
98
  "@semantic-release/exec": "^7.1.0",
99
99
  "@semantic-release/git": "^10.0.1",
100
100
  "@types/express": "^5.0.1",
101
+ "@types/jquery": "^4.0.0",
101
102
  "@types/mime-types": "^2.1.4",
102
103
  "@types/node": "^22.15.18",
103
104
  "@typescript-eslint/eslint-plugin": "^8.32.1",
@@ -1,17 +1,320 @@
1
1
  /**
2
2
  * Global type declarations for the Node-RED editor environment.
3
3
  * These are provided by Node-RED at runtime, not imported.
4
+ *
5
+ * Based on: https://github.com/GogoVega/node-red/blob/33d83d016a0c990c/packages/node_modules/%40node-red/editor-client/types/index.d.ts
4
6
  */
5
7
 
6
- // jQuery injected by Node-RED editor
7
- declare const $: any;
8
+ /// <reference types="jquery" />
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // jQuery Node-RED widget extensions
12
+ // ---------------------------------------------------------------------------
13
+
14
+ interface JQuery<TElement = HTMLElement> {
15
+ // Node-RED TypedInput widget
16
+ typedInput(options: NodeRED.TypedInputOptions): this;
17
+ typedInput(action: "value"): string;
18
+ typedInput(action: "value", value: string): void;
19
+ typedInput(action: "type"): string;
20
+ typedInput(action: "type", value: string): void;
21
+ typedInput(action: "types", value: any[]): void;
22
+ typedInput(action: "validate"): boolean;
23
+ typedInput(
24
+ action: "validate",
25
+ options: { returnErrorMessage: boolean },
26
+ ): string | boolean;
27
+ typedInput(action: "disable", value?: boolean): void;
28
+ typedInput(action: "enable"): void;
29
+ typedInput(action: "focus"): void;
30
+ typedInput(action: "hide"): void;
31
+ typedInput(action: "show"): void;
32
+ typedInput(action: "width", value: string | number): void;
33
+
34
+ // Node-RED i18n
35
+ i18n(): this;
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Node-RED editor types
40
+ // ---------------------------------------------------------------------------
41
+
42
+ declare namespace NodeRED {
43
+ type UID = string;
44
+
45
+ // -- Nodes --
46
+
47
+ interface BaseNode {
48
+ id: UID;
49
+ type: string;
50
+ changed: boolean;
51
+ z?: string;
52
+ _def: any;
53
+ [key: string]: any;
54
+ }
55
+
56
+ interface Node extends BaseNode {
57
+ x: number;
58
+ y: number;
59
+ w: number;
60
+ h: number;
61
+ name: string;
62
+ wires: UID[][];
63
+ inputs: number;
64
+ outputs: number;
65
+ g?: string;
66
+ _: (key: string) => string;
67
+ }
68
+
69
+ interface ConfigNode extends BaseNode {
70
+ name: string;
71
+ credentials?: Record<string, any>;
72
+ users: BaseNode[];
73
+ _def: {
74
+ category: "config";
75
+ [key: string]: any;
76
+ };
77
+ }
78
+
79
+ interface Link {
80
+ source: BaseNode;
81
+ target: BaseNode;
82
+ sourcePort: number;
83
+ }
84
+
85
+ interface Workspace {
86
+ id: UID;
87
+ type: "tab";
88
+ label: string;
89
+ disabled: boolean;
90
+ info: string;
91
+ }
92
+
93
+ // -- Editor --
94
+
95
+ interface EditorOptions {
96
+ id: string;
97
+ stateId?: string;
98
+ mode?: string;
99
+ value?: string;
100
+ focus?: boolean;
101
+ globals?: Record<string, boolean>;
102
+ }
103
+
104
+ interface TrayButton {
105
+ id?: string;
106
+ class?: string;
107
+ click?: (event: JQuery.ClickEvent) => void;
108
+ text?: string;
109
+ }
110
+
111
+ interface TrayOptions {
112
+ title?: string;
113
+ buttons?: TrayButton[];
114
+ focusElement?: boolean;
115
+ maximized?: boolean;
116
+ width?: "inherit" | number | string;
117
+ overlay?: boolean;
118
+ open?: (tray: JQuery, done?: () => void) => void;
119
+ close?: () => void;
120
+ resize?: (options: { width: number; height?: number }) => void;
121
+ show?: () => void;
122
+ }
123
+
124
+ // -- Popover --
125
+
126
+ interface PopoverOptions {
127
+ target: JQuery;
128
+ direction?: string;
129
+ trigger?: "hover" | "click" | "modal";
130
+ content: string | JQuery | (() => string | JQuery);
131
+ delay?: { show: number; hide: number };
132
+ autoClose?: number;
133
+ width?: number | string;
134
+ maxWidth?: number | string;
135
+ tooltip?: boolean;
136
+ interactive?: boolean;
137
+ class?: string;
138
+ }
139
+
140
+ interface PopoverInstance {
141
+ readonly element: JQuery | null;
142
+ close(instant?: boolean): PopoverInstance;
143
+ open(instant?: boolean): PopoverInstance;
144
+ setContent(content: PopoverOptions["content"]): PopoverInstance;
145
+ move(options: Partial<PopoverOptions>): void;
146
+ }
147
+
148
+ interface TooltipInstance extends PopoverInstance {
149
+ delete(): void;
150
+ setAction(action: string): void;
151
+ }
152
+
153
+ // -- Notifications --
154
+
155
+ interface NotificationOptions {
156
+ type?: "warning" | "compact" | "success" | "error";
157
+ fixed?: boolean;
158
+ modal?: boolean;
159
+ timeout?: number;
160
+ buttons?: Array<{
161
+ text: string;
162
+ class?: string;
163
+ click?: (event: JQuery.ClickEvent) => void;
164
+ }>;
165
+ id?: string;
166
+ }
167
+
168
+ interface NotificationElement {
169
+ update(msg: string | JQuery, options?: NotificationOptions): void;
170
+ close(): void;
171
+ }
172
+
173
+ // -- TypedInput --
174
+
175
+ type DefaultTypedInputType =
176
+ (typeof import("../constants").TYPED_INPUT_TYPES)[number];
177
+
178
+ interface TypedInputTypeDefinition {
179
+ value: string;
180
+ label?: string;
181
+ icon?: string;
182
+ hasValue?: boolean;
183
+ multiple?: boolean;
184
+ options?: string[] | Array<{ value: string; label: string }>;
185
+ validate?: ((value: string) => boolean) | RegExp;
186
+ valueLabel?: (container: JQuery, value: string) => void;
187
+ autoComplete?: (
188
+ value: string,
189
+ done: (result?: Array<{ value: string; label: string | JQuery }>) => void,
190
+ ) => void;
191
+ }
192
+
193
+ interface TypedInputOptions {
194
+ default?: DefaultTypedInputType | string;
195
+ types: Array<DefaultTypedInputType | TypedInputTypeDefinition>;
196
+ typeField?: JQuery.Selector | JQuery;
197
+ }
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // RED global object
202
+ // ---------------------------------------------------------------------------
8
203
 
9
- // Node-RED editor API — injected by Node-RED editor
10
204
  declare const RED: {
205
+ /** Internationalization / translation function */
206
+ _: (key: string, substitutions?: Record<string, string>) => string;
207
+
208
+ /** Node management */
11
209
  nodes: {
12
210
  registerType(type: string, definition: any): void;
13
- node(id: string): any;
211
+ node(id: NodeRED.UID): NodeRED.BaseNode | null;
14
212
  dirty(): boolean;
213
+ dirty(dirty: boolean): void;
214
+ eachNode(callback: (node: NodeRED.Node) => void | false): void;
215
+ eachConfig(callback: (node: NodeRED.ConfigNode) => void | false): void;
216
+ filterNodes(filter: { z?: NodeRED.UID; type?: string }): NodeRED.BaseNode[];
217
+ filterLinks(filter: {
218
+ source?: NodeRED.BaseNode;
219
+ target?: NodeRED.BaseNode;
220
+ }): NodeRED.Link[];
221
+ getType(type: string): any;
222
+ id(): NodeRED.UID;
223
+ add(node: any): NodeRED.BaseNode;
224
+ remove(id: NodeRED.UID): {
225
+ links: NodeRED.Link[];
226
+ nodes: NodeRED.BaseNode[];
227
+ };
228
+ };
229
+
230
+ /** Code editor (ACE/Monaco) */
231
+ editor: {
232
+ createEditor(options: NodeRED.EditorOptions): any;
233
+ edit(node: NodeRED.Node, defaultTab?: any): void;
234
+ editConfig(
235
+ name: string,
236
+ type: string,
237
+ id: string,
238
+ prefix?: string,
239
+ editContext?: NodeRED.Node,
240
+ ): void;
241
+ prepareConfigNodeSelect(
242
+ node: NodeRED.BaseNode,
243
+ property: string,
244
+ type: string,
245
+ prefix?: string,
246
+ filter?: (configNode: NodeRED.ConfigNode) => boolean,
247
+ ): void;
248
+ validateNode(node: NodeRED.BaseNode): boolean;
249
+ };
250
+
251
+ /** Side panel tray */
252
+ tray: {
253
+ show(options: NodeRED.TrayOptions): void;
254
+ close(): void;
255
+ };
256
+
257
+ /** Popover / tooltip */
258
+ popover: {
259
+ create(options: NodeRED.PopoverOptions): NodeRED.PopoverInstance;
260
+ tooltip(
261
+ target: JQuery,
262
+ text: string,
263
+ direction?: string,
264
+ ): NodeRED.TooltipInstance;
265
+ };
266
+
267
+ /** Toast notifications */
268
+ notify(
269
+ message: string | JQuery,
270
+ options?: NodeRED.NotificationOptions,
271
+ ): NodeRED.NotificationElement;
272
+
273
+ /** Event system */
274
+ events: {
275
+ on(event: string, listener: (...args: any[]) => void): void;
276
+ off(event: string, listener: (...args: any[]) => void): void;
277
+ emit(event: string, ...args: any[]): void;
278
+ };
279
+
280
+ /** Undo/redo */
281
+ history: {
282
+ push(event: any): void;
283
+ pop(): any;
284
+ peek(): any;
285
+ list(): any[];
286
+ };
287
+
288
+ /** Keyboard shortcuts */
289
+ keyboard: {
290
+ add(scope: string, key: string, callback: () => void): void;
291
+ remove(key: string): void;
292
+ };
293
+
294
+ /** Runtime communication (WebSocket) */
295
+ comms: {
296
+ subscribe(topic: string, callback: (topic: string, msg: any) => void): void;
297
+ unsubscribe(
298
+ topic: string,
299
+ callback: (topic: string, msg: any) => void,
300
+ ): void;
301
+ };
302
+
303
+ /** Editor settings */
304
+ settings: {
305
+ get(key: string): any;
306
+ set(key: string, value: any): any;
307
+ remove(key: string): any;
308
+ [key: string]: any;
15
309
  };
310
+
311
+ /** Canvas view */
312
+ view: {
313
+ focus(): void;
314
+ selection(): { nodes?: NodeRED.Node[] };
315
+ redraw(updateActive?: boolean): void;
316
+ };
317
+
318
+ /** Catch-all for untyped properties */
16
319
  [key: string]: any;
17
320
  };
@@ -1,5 +1,5 @@
1
1
  import type { Schema } from "../schemas/types";
2
- import { type RED } from "../../server/types";
2
+ import { type RED, type NodeRedNode } from "../../server/types";
3
3
  import type {
4
4
  ConfigNodeContext,
5
5
  IONodeContext,
@@ -80,7 +80,7 @@ abstract class Node<TConfig = any, TCredentials = any, TSettings = any> {
80
80
  RED.log.info("Settings are valid");
81
81
  }
82
82
  protected readonly RED: RED;
83
- protected readonly node: any;
83
+ protected readonly node: NodeRedNode;
84
84
  protected readonly context!: ConfigNodeContext | IONodeContext;
85
85
  public readonly config!: NodeConfig<TConfig>;
86
86
 
@@ -89,7 +89,7 @@ abstract class Node<TConfig = any, TCredentials = any, TSettings = any> {
89
89
 
90
90
  constructor(
91
91
  RED: RED,
92
- node: any,
92
+ node: NodeRedNode,
93
93
  config: NodeConfig<TConfig>,
94
94
  credentials: NodeCredentials<TCredentials>,
95
95
  ) {
@@ -1,38 +1,204 @@
1
- import type { TObject } from "@sinclair/typebox";
1
+ import type { EventEmitter } from "events";
2
+ import type { NodeRedRuntimeSettings } from "../../../types";
2
3
 
3
- interface RED {
4
- _: (key: string, substitutions?: Record<string, string>) => string;
5
- log: {
6
- info(msg: any): void;
7
- warn(msg: any): void;
8
- error(message: string, error: any): void;
9
- debug(msg: any): void;
10
- trace(msg: any): void;
11
- };
12
- nodes: {
13
- registerType: (type: string, def: any, opts?: any) => void;
14
- getNode: <T = any>(id: string) => T | undefined;
15
- createNode: (node: any, config: Record<string, any>) => void;
16
- getCredentials: (id: string) => Record<string, any> | undefined;
17
- };
18
- httpAdmin: {
19
- get(path: string, handler: (req: any, res: any) => void): void;
20
- post(path: string, handler: (req: any, res: any) => void): void;
21
- put(path: string, handler: (req: any, res: any) => void): void;
22
- delete(path: string, handler: (req: any, res: any) => void): void;
23
- };
24
- util: {
25
- evaluateNodeProperty(
26
- value: any,
27
- type: string,
28
- node: any,
29
- msg: Record<string, any> | undefined,
30
- callback: (err: Error | null, result: any) => void,
31
- ): void;
4
+ // ---------------------------------------------------------------------------
5
+ // RED.log
6
+ // ---------------------------------------------------------------------------
7
+
8
+ interface NodeRedLog {
9
+ info(msg: any): void;
10
+ warn(msg: any): void;
11
+ error(msg: any, error?: any): void;
12
+ debug(msg: any): void;
13
+ trace(msg: any): void;
14
+ log(msg: { level: number; msg: string }): void;
15
+ metric(): boolean;
16
+ audit(msg: Record<string, any>, req?: any): void;
17
+ addHandler(handler: (msg: any) => void): void;
18
+ removeHandler(handler: (msg: any) => void): void;
19
+ FATAL: 10;
20
+ ERROR: 20;
21
+ WARN: 30;
22
+ INFO: 40;
23
+ DEBUG: 50;
24
+ TRACE: 60;
25
+ AUDIT: 98;
26
+ METRIC: 99;
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Node-RED runtime node (the raw node object from RED.nodes.createNode)
31
+ // ---------------------------------------------------------------------------
32
+
33
+ interface NodeRedNode {
34
+ id: string;
35
+ type: string;
36
+ name?: string;
37
+ z?: string;
38
+ x: number;
39
+ y: number;
40
+ g?: string;
41
+ wires: string[][];
42
+ credentials: any;
43
+ _node?: any;
44
+ send(msg: any): void;
45
+ receive(msg: any): void;
46
+ status(
47
+ status: string | { fill?: string; shape?: string; text?: string },
48
+ ): void;
49
+ updateWires(wires: string[][]): void;
50
+ on(event: string, callback: (...args: any[]) => void): void;
51
+ log(msg: any): void;
52
+ warn(msg: any): void;
53
+ error(msg: any, errorMsg?: any): void;
54
+ context(): NodeRedNodeContext;
55
+ [key: string]: any;
56
+ }
57
+
58
+ interface NodeRedNodeContext extends NodeRedContextStore {
59
+ flow: NodeRedContextStore;
60
+ global: NodeRedContextStore;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // RED.nodes
65
+ // ---------------------------------------------------------------------------
66
+
67
+ interface NodeRedNodes {
68
+ registerType(type: string, constructor: any, opts?: any): void;
69
+ getNode(id: string): (NodeRedNode & { _node?: any }) | undefined;
70
+ createNode(node: NodeRedNode, config: Record<string, any>): void;
71
+ getCredentials(id: string): Record<string, any> | undefined;
72
+ eachNode(callback: (node: any) => void): void;
73
+ getType(type: string): any;
74
+ getNodeInfo(type: string): any;
75
+ getNodeList(filter?: any): any[];
76
+ getModuleInfo(module: string): any;
77
+ installModule(module: string, version?: string): Promise<any>;
78
+ uninstallModule(module: string): Promise<any>;
79
+ enableNode(id: string): Promise<any>;
80
+ disableNode(id: string): Promise<any>;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // RED.util
85
+ // ---------------------------------------------------------------------------
86
+
87
+ interface NodeRedUtil {
88
+ evaluateNodeProperty(
89
+ value: any,
90
+ type: string,
91
+ node: any,
92
+ msg: Record<string, any> | undefined,
93
+ callback: (err: Error | null, result: any) => void,
94
+ ): void;
95
+ generateId(): string;
96
+ cloneMessage<T = any>(msg: T): T;
97
+ ensureString(o: any): string;
98
+ ensureBuffer(o: any): Buffer;
99
+ compareObjects(obj1: any, obj2: any): boolean;
100
+ getMessageProperty(msg: any, expr: string): any;
101
+ setMessageProperty(
102
+ msg: any,
103
+ prop: string,
104
+ value: any,
105
+ createMissing?: boolean,
106
+ ): void;
107
+ getObjectProperty(obj: any, expr: string): any;
108
+ setObjectProperty(
109
+ obj: any,
110
+ prop: string,
111
+ value: any,
112
+ createMissing?: boolean,
113
+ ): void;
114
+ normalisePropertyExpression(
115
+ str: string,
116
+ msg?: any,
117
+ toString?: boolean,
118
+ ): string[];
119
+ normaliseNodeTypeName(name: string): string;
120
+ prepareJSONataExpression(value: string, node: any): any;
121
+ evaluateJSONataExpression(
122
+ expr: any,
123
+ msg: any,
124
+ callback: (err: Error | null, result: any) => void,
125
+ ): void;
126
+ parseContextStore(key: string): {
127
+ store: string | undefined;
128
+ key: string;
32
129
  };
33
- settings: Record<string, any>;
130
+ getSetting(node: any, name: string, flow?: any): any;
131
+ encodeObject(obj: any): any;
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // RED.hooks
136
+ // ---------------------------------------------------------------------------
137
+
138
+ interface NodeRedHooks {
139
+ add(hookId: string, callback: (event: any) => void | Promise<void>): void;
140
+ remove(hookId: string): void;
141
+ trigger(
142
+ hookId: string,
143
+ event: any,
144
+ callback?: (err?: Error) => void,
145
+ ): void | Promise<void>;
146
+ has(hookId: string): boolean;
147
+ clear(): void;
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Express-like HTTP app (httpAdmin / httpNode)
152
+ // ---------------------------------------------------------------------------
153
+
154
+ type NodeRedRequestHandler = (req: any, res: any, next?: () => void) => void;
155
+
156
+ interface NodeRedExpressApp {
157
+ get(path: string, ...handlers: NodeRedRequestHandler[]): void;
158
+ post(path: string, ...handlers: NodeRedRequestHandler[]): void;
159
+ put(path: string, ...handlers: NodeRedRequestHandler[]): void;
160
+ delete(path: string, ...handlers: NodeRedRequestHandler[]): void;
161
+ patch(path: string, ...handlers: NodeRedRequestHandler[]): void;
162
+ options(path: string, ...handlers: NodeRedRequestHandler[]): void;
163
+ head(path: string, ...handlers: NodeRedRequestHandler[]): void;
164
+ use(
165
+ path: string | NodeRedRequestHandler,
166
+ ...handlers: NodeRedRequestHandler[]
167
+ ): void;
168
+ all(path: string, ...handlers: NodeRedRequestHandler[]): void;
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // RED (main interface)
173
+ // ---------------------------------------------------------------------------
174
+
175
+ interface RED {
176
+ /** Internationalization function */
177
+ _(key: string, substitutions?: Record<string, string>): string;
178
+ /** Logging API */
179
+ log: NodeRedLog;
180
+ /** Node registry and management */
181
+ nodes: NodeRedNodes;
182
+ /** Utility functions */
183
+ util: NodeRedUtil;
184
+ /** Hook system for message lifecycle and module events */
185
+ hooks: NodeRedHooks;
186
+ /** Runtime event emitter */
187
+ events: EventEmitter;
188
+ /** Express app for admin HTTP endpoints */
189
+ httpAdmin: NodeRedExpressApp;
190
+ /** Express app for node HTTP endpoints */
191
+ httpNode: NodeRedExpressApp;
192
+ /** Runtime settings (user-provided settings plus node-registered settings) */
193
+ settings: NodeRedRuntimeSettings & Record<string, any>;
194
+ /** Node-RED version string */
195
+ version(): string;
34
196
  }
35
197
 
198
+ // ---------------------------------------------------------------------------
199
+ // Context store (internal)
200
+ // ---------------------------------------------------------------------------
201
+
36
202
  interface NodeRedContextStore {
37
203
  get(
38
204
  key: string,
@@ -51,23 +217,4 @@ interface NodeRedContextStore {
51
217
  ): void;
52
218
  }
53
219
 
54
- interface NodeDefinitionApiResponse {
55
- type: string;
56
- align?: "left" | "right";
57
- category?: "config" | string;
58
- color?: `#${string}`;
59
- icon?: string;
60
- labelStyle?: "node_label" | "node_label_italic" | string;
61
- paletteLabel?: string;
62
- inputs?: number;
63
- outputs?: number;
64
- inputLabels?: string | string[];
65
- outputLabels?: string | string[];
66
- configSchema: TObject | null;
67
- credentialsSchema: TObject | null;
68
- inputSchema?: TObject | null;
69
- outputsSchema?: TObject | null;
70
- settingsSchema?: TObject | null;
71
- }
72
-
73
- export { RED, NodeDefinitionApiResponse, NodeRedContextStore };
220
+ export { RED, NodeRedNode, NodeRedNodeContext, NodeRedContextStore };
@@ -1,17 +0,0 @@
1
- import { type NodeDefinitionApiResponse } from "../../server/types";
2
-
3
- async function fetchNodeDefinition(
4
- type: string,
5
- ): Promise<NodeDefinitionApiResponse> {
6
- const response = await fetch(`/nrg/nodes/${type}`);
7
-
8
- if (!response.ok) {
9
- throw new Error(
10
- `Failed to fetch node definition for "${type}": ${response.status}`,
11
- );
12
- }
13
-
14
- return response.json() as Promise<NodeDefinitionApiResponse>;
15
- }
16
-
17
- export { fetchNodeDefinition };
@@ -1,5 +0,0 @@
1
- declare module "virtual:nrg/node-definitions" {
2
- import type { NodeDefinitionApiResponse } from "../server/types";
3
- const definitions: Record<string, NodeDefinitionApiResponse>;
4
- export default definitions;
5
- }