@bonsae/nrg 0.16.0 → 0.18.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.
@@ -0,0 +1,200 @@
1
+ // src/test/client/mocks.ts
2
+ function createRED() {
3
+ return {
4
+ _: (key) => key,
5
+ editor: {
6
+ createEditor(options) {
7
+ let currentValue = options.value || "";
8
+ const session = {
9
+ on(_event, _cb) {
10
+ }
11
+ };
12
+ return {
13
+ getValue: () => currentValue,
14
+ setValue: (val) => {
15
+ currentValue = val;
16
+ },
17
+ getSession: () => session,
18
+ focus: () => {
19
+ },
20
+ destroy: () => {
21
+ },
22
+ saveView: () => {
23
+ },
24
+ restoreView: () => {
25
+ }
26
+ };
27
+ },
28
+ prepareConfigNodeSelect: () => {
29
+ },
30
+ validateNode: () => true
31
+ },
32
+ tray: {
33
+ show: () => {
34
+ },
35
+ close: () => {
36
+ }
37
+ },
38
+ popover: {
39
+ tooltip: () => ({ delete: () => {
40
+ }, setAction: () => {
41
+ } })
42
+ },
43
+ nodes: {
44
+ registerType: () => {
45
+ },
46
+ node: () => null,
47
+ dirty: () => false
48
+ },
49
+ events: {
50
+ on: () => {
51
+ },
52
+ off: () => {
53
+ },
54
+ emit: () => {
55
+ }
56
+ },
57
+ settings: {},
58
+ notify: () => {
59
+ }
60
+ };
61
+ }
62
+ function ensureState(el) {
63
+ if (el && !el.__jqState) {
64
+ el.__jqState = {
65
+ typedInput: { value: "", type: "" },
66
+ listeners: {}
67
+ };
68
+ }
69
+ return el ? el.__jqState : { typedInput: { value: "", type: "" }, listeners: {} };
70
+ }
71
+ function createJQ(el) {
72
+ const state = ensureState(el);
73
+ const jq = {
74
+ 0: el,
75
+ length: el ? 1 : 0,
76
+ typedInput(action, value) {
77
+ if (typeof action === "object") {
78
+ state.typedInput = { value: "", type: action.default || "" };
79
+ return jq;
80
+ }
81
+ if (action === "value") {
82
+ if (value !== void 0) {
83
+ state.typedInput.value = String(value);
84
+ if (el) el.setAttribute("value", String(value));
85
+ return void 0;
86
+ }
87
+ return state.typedInput.value;
88
+ }
89
+ if (action === "type") {
90
+ if (value !== void 0) {
91
+ state.typedInput.type = String(value);
92
+ return void 0;
93
+ }
94
+ return state.typedInput.type;
95
+ }
96
+ return jq;
97
+ },
98
+ on(event, cb) {
99
+ if (!state.listeners[event]) state.listeners[event] = [];
100
+ state.listeners[event].push(cb);
101
+ return jq;
102
+ },
103
+ off(event) {
104
+ if (event) {
105
+ delete state.listeners[event];
106
+ } else {
107
+ for (const key of Object.keys(state.listeners)) {
108
+ delete state.listeners[key];
109
+ }
110
+ }
111
+ return jq;
112
+ },
113
+ val(value) {
114
+ if (value !== void 0) {
115
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
116
+ el.value = String(value);
117
+ }
118
+ return jq;
119
+ }
120
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
121
+ return el.value;
122
+ }
123
+ return "";
124
+ },
125
+ find(selector) {
126
+ return createJQ(el?.querySelector(selector) ?? null);
127
+ },
128
+ append(child) {
129
+ const childEl = child?.[0] || child;
130
+ if (el && childEl instanceof Element) el.appendChild(childEl);
131
+ return jq;
132
+ },
133
+ appendTo(target) {
134
+ const t = target?.[0] || target;
135
+ if (t instanceof Element && el) t.appendChild(el);
136
+ return jq;
137
+ },
138
+ html(content) {
139
+ if (el) el.innerHTML = content;
140
+ return jq;
141
+ },
142
+ empty() {
143
+ if (el) el.innerHTML = "";
144
+ return jq;
145
+ },
146
+ i18n() {
147
+ return jq;
148
+ },
149
+ addClass(cls) {
150
+ el?.classList.add(cls);
151
+ return jq;
152
+ },
153
+ removeClass(cls) {
154
+ el?.classList.remove(cls);
155
+ return jq;
156
+ },
157
+ __trigger(event) {
158
+ (state.listeners[event] || []).forEach((cb) => cb());
159
+ }
160
+ };
161
+ return jq;
162
+ }
163
+ function createJQuery() {
164
+ return function $(selector, attrs) {
165
+ if (typeof selector === "string") {
166
+ if (selector.trim().startsWith("<")) {
167
+ const tpl = document.createElement("template");
168
+ tpl.innerHTML = selector.trim();
169
+ const el = tpl.content.firstElementChild;
170
+ if (el && attrs) {
171
+ for (const [key, value] of Object.entries(attrs)) {
172
+ if (key === "html") {
173
+ el.innerHTML = String(value);
174
+ } else {
175
+ el.setAttribute(key, String(value));
176
+ }
177
+ }
178
+ }
179
+ return createJQ(el);
180
+ }
181
+ return createJQ(document.querySelector(selector));
182
+ }
183
+ if (selector instanceof Element) return createJQ(selector);
184
+ if (selector && typeof selector === "object" && selector.nodeType)
185
+ return createJQ(selector);
186
+ return createJQ(null);
187
+ };
188
+ }
189
+
190
+ // src/test/client/unit/index.ts
191
+ var defaultConfig = {
192
+ testTimeout: 3e4,
193
+ environment: "happy-dom",
194
+ setupFiles: ["@bonsae/nrg/test/client/unit/setup"]
195
+ };
196
+ export {
197
+ createJQuery,
198
+ createRED,
199
+ defaultConfig
200
+ };
@@ -0,0 +1,199 @@
1
+ // src/test/client/unit/setup.ts
2
+ import { beforeEach } from "vitest";
3
+
4
+ // src/test/client/mocks.ts
5
+ function createRED() {
6
+ return {
7
+ _: (key) => key,
8
+ editor: {
9
+ createEditor(options) {
10
+ let currentValue = options.value || "";
11
+ const session = {
12
+ on(_event, _cb) {
13
+ }
14
+ };
15
+ return {
16
+ getValue: () => currentValue,
17
+ setValue: (val) => {
18
+ currentValue = val;
19
+ },
20
+ getSession: () => session,
21
+ focus: () => {
22
+ },
23
+ destroy: () => {
24
+ },
25
+ saveView: () => {
26
+ },
27
+ restoreView: () => {
28
+ }
29
+ };
30
+ },
31
+ prepareConfigNodeSelect: () => {
32
+ },
33
+ validateNode: () => true
34
+ },
35
+ tray: {
36
+ show: () => {
37
+ },
38
+ close: () => {
39
+ }
40
+ },
41
+ popover: {
42
+ tooltip: () => ({ delete: () => {
43
+ }, setAction: () => {
44
+ } })
45
+ },
46
+ nodes: {
47
+ registerType: () => {
48
+ },
49
+ node: () => null,
50
+ dirty: () => false
51
+ },
52
+ events: {
53
+ on: () => {
54
+ },
55
+ off: () => {
56
+ },
57
+ emit: () => {
58
+ }
59
+ },
60
+ settings: {},
61
+ notify: () => {
62
+ }
63
+ };
64
+ }
65
+ function ensureState(el) {
66
+ if (el && !el.__jqState) {
67
+ el.__jqState = {
68
+ typedInput: { value: "", type: "" },
69
+ listeners: {}
70
+ };
71
+ }
72
+ return el ? el.__jqState : { typedInput: { value: "", type: "" }, listeners: {} };
73
+ }
74
+ function createJQ(el) {
75
+ const state = ensureState(el);
76
+ const jq = {
77
+ 0: el,
78
+ length: el ? 1 : 0,
79
+ typedInput(action, value) {
80
+ if (typeof action === "object") {
81
+ state.typedInput = { value: "", type: action.default || "" };
82
+ return jq;
83
+ }
84
+ if (action === "value") {
85
+ if (value !== void 0) {
86
+ state.typedInput.value = String(value);
87
+ if (el) el.setAttribute("value", String(value));
88
+ return void 0;
89
+ }
90
+ return state.typedInput.value;
91
+ }
92
+ if (action === "type") {
93
+ if (value !== void 0) {
94
+ state.typedInput.type = String(value);
95
+ return void 0;
96
+ }
97
+ return state.typedInput.type;
98
+ }
99
+ return jq;
100
+ },
101
+ on(event, cb) {
102
+ if (!state.listeners[event]) state.listeners[event] = [];
103
+ state.listeners[event].push(cb);
104
+ return jq;
105
+ },
106
+ off(event) {
107
+ if (event) {
108
+ delete state.listeners[event];
109
+ } else {
110
+ for (const key of Object.keys(state.listeners)) {
111
+ delete state.listeners[key];
112
+ }
113
+ }
114
+ return jq;
115
+ },
116
+ val(value) {
117
+ if (value !== void 0) {
118
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
119
+ el.value = String(value);
120
+ }
121
+ return jq;
122
+ }
123
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
124
+ return el.value;
125
+ }
126
+ return "";
127
+ },
128
+ find(selector) {
129
+ return createJQ(el?.querySelector(selector) ?? null);
130
+ },
131
+ append(child) {
132
+ const childEl = child?.[0] || child;
133
+ if (el && childEl instanceof Element) el.appendChild(childEl);
134
+ return jq;
135
+ },
136
+ appendTo(target) {
137
+ const t = target?.[0] || target;
138
+ if (t instanceof Element && el) t.appendChild(el);
139
+ return jq;
140
+ },
141
+ html(content) {
142
+ if (el) el.innerHTML = content;
143
+ return jq;
144
+ },
145
+ empty() {
146
+ if (el) el.innerHTML = "";
147
+ return jq;
148
+ },
149
+ i18n() {
150
+ return jq;
151
+ },
152
+ addClass(cls) {
153
+ el?.classList.add(cls);
154
+ return jq;
155
+ },
156
+ removeClass(cls) {
157
+ el?.classList.remove(cls);
158
+ return jq;
159
+ },
160
+ __trigger(event) {
161
+ (state.listeners[event] || []).forEach((cb) => cb());
162
+ }
163
+ };
164
+ return jq;
165
+ }
166
+ function createJQuery() {
167
+ return function $(selector, attrs) {
168
+ if (typeof selector === "string") {
169
+ if (selector.trim().startsWith("<")) {
170
+ const tpl = document.createElement("template");
171
+ tpl.innerHTML = selector.trim();
172
+ const el = tpl.content.firstElementChild;
173
+ if (el && attrs) {
174
+ for (const [key, value] of Object.entries(attrs)) {
175
+ if (key === "html") {
176
+ el.innerHTML = String(value);
177
+ } else {
178
+ el.setAttribute(key, String(value));
179
+ }
180
+ }
181
+ }
182
+ return createJQ(el);
183
+ }
184
+ return createJQ(document.querySelector(selector));
185
+ }
186
+ if (selector instanceof Element) return createJQ(selector);
187
+ if (selector && typeof selector === "object" && selector.nodeType)
188
+ return createJQ(selector);
189
+ return createJQ(null);
190
+ };
191
+ }
192
+
193
+ // src/test/client/unit/setup.ts
194
+ var RED = createRED();
195
+ window.$ = createJQuery();
196
+ window.RED = RED;
197
+ beforeEach(() => {
198
+ RED.settings = {};
199
+ });
@@ -1,9 +1,9 @@
1
- // src/test/index.ts
1
+ // src/test/server/unit/index.ts
2
2
  import { vi as vi2 } from "vitest";
3
3
 
4
- // src/test/mocks.ts
4
+ // src/test/server/unit/mocks.ts
5
5
  import { vi } from "vitest";
6
- function createNodeRedRuntime(options = {}) {
6
+ function createRED(options = {}) {
7
7
  const { settings = {} } = options;
8
8
  const nodes = {};
9
9
  const red = {
@@ -398,7 +398,8 @@ function initValidator(RED) {
398
398
  // src/core/server/nodes/symbols.ts
399
399
  var WIRE_HANDLERS = Symbol.for("nrg.wireHandlers");
400
400
 
401
- // src/test/index.ts
401
+ // src/test/server/unit/index.ts
402
+ import { Kind } from "@sinclair/typebox";
402
403
  function buildConfig(NodeClass, userConfig = {}) {
403
404
  const defaults = {};
404
405
  if (NodeClass.configSchema?.properties) {
@@ -413,7 +414,7 @@ function buildConfig(NodeClass, userConfig = {}) {
413
414
  }
414
415
  return { ...defaults, ...userConfig };
415
416
  }
416
- function attachHelpers(node, nodeRedNode) {
417
+ function attachHelpers(node, nodeRedNode, NodeClass) {
417
418
  const sentMessages = [];
418
419
  const statusCalls = [];
419
420
  nodeRedNode.send.mockImplementation((msg) => {
@@ -449,6 +450,15 @@ function attachHelpers(node, nodeRedNode) {
449
450
  },
450
451
  sent(port) {
451
452
  if (port === void 0) return [...sentMessages];
453
+ if (typeof port === "string") {
454
+ const schema = NodeClass.outputsSchema;
455
+ if (!schema || Array.isArray(schema) || typeof schema === "object" && Kind in schema) {
456
+ return [];
457
+ }
458
+ const idx = Object.keys(schema).indexOf(port);
459
+ if (idx === -1) return [];
460
+ return sentMessages.map((msg) => Array.isArray(msg) ? msg[idx] : void 0).filter((msg) => msg != null);
461
+ }
452
462
  return sentMessages.map(
453
463
  (msg) => Array.isArray(msg) ? msg[port] : port === 0 ? msg : void 0
454
464
  ).filter((msg) => msg != null);
@@ -497,7 +507,7 @@ async function createNode(NodeClass, options = {}) {
497
507
  resolvedConfig[key] = value;
498
508
  }
499
509
  }
500
- const RED = createNodeRedRuntime({ settings });
510
+ const RED = createRED({ settings });
501
511
  initValidator(RED);
502
512
  for (const [id, value] of Object.entries(configNodes)) {
503
513
  RED.registerNrgNode(id, value);
@@ -523,12 +533,17 @@ async function createNode(NodeClass, options = {}) {
523
533
  NodeClass.validateSettings(RED);
524
534
  await Promise.resolve(NodeClass.registered?.(RED));
525
535
  const node = new NodeClass(RED, nodeRedNode, config, credentials);
526
- const augmented = attachHelpers(node, nodeRedNode);
536
+ const augmented = attachHelpers(
537
+ node,
538
+ nodeRedNode,
539
+ NodeClass
540
+ );
527
541
  const createdPromise = Promise.resolve(node.created?.());
528
542
  node[WIRE_HANDLERS](nodeRedNode, createdPromise);
529
543
  await createdPromise;
530
544
  return { node: augmented, RED };
531
545
  }
532
546
  export {
533
- createNode
547
+ createNode,
548
+ createRED
534
549
  };
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"]
5
+ },
6
+ "files": [
7
+ "../../types/shims/shims-vue.d.ts",
8
+ "../../types/shims/components.d.ts",
9
+ "../../types/shims/globals.d.ts"
10
+ ]
11
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "extends": "./base.json",
2
+ "extends": "../base.json",
3
3
  "compilerOptions": {
4
4
  "lib": ["ES2022"]
5
5
  }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"]
5
+ },
6
+ "files": [
7
+ "../../../types/shims/shims-vue.d.ts",
8
+ "../../../types/shims/components.d.ts",
9
+ "../../../types/shims/globals.d.ts"
10
+ ]
11
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext", "DOM"]
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"]
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext"]
5
+ }
6
+ }
package/types/client.d.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ import { SchemaOptions, Static, TSchema } from '@sinclair/typebox';
3
4
  import { App, Component } from 'vue';
4
5
 
6
+ interface NodeRefResolved<T = any> {
7
+ readonly __nrg_node_ref: true;
8
+ readonly __instance: T;
9
+ }
5
10
  export interface NodeStateCredentials {
6
11
  [key: string]: any;
7
12
  }
@@ -87,13 +92,74 @@ export interface NodeDefinition {
87
92
  onPaletteRemove?: (this: NodeRedNode) => void;
88
93
  form?: NodeFormDefinition;
89
94
  }
95
+ export interface NodeDefaults {
96
+ [key: string]: {
97
+ value: any;
98
+ type?: string;
99
+ label?: string;
100
+ required?: boolean;
101
+ validate?: (this: NodeRedNode, value: any, opt: any) => any;
102
+ };
103
+ }
104
+ export interface NodeCredentials {
105
+ [key: string]: {
106
+ value?: string;
107
+ type?: "password" | "text";
108
+ label?: string;
109
+ required?: boolean;
110
+ };
111
+ }
112
+ export interface MergedNodeDefinition extends NodeDefinition {
113
+ defaults?: NodeDefaults;
114
+ credentials?: NodeCredentials;
115
+ configSchema?: Record<string, any>;
116
+ credentialsSchema?: {
117
+ properties?: Record<string, any>;
118
+ };
119
+ outputsSchema?: Record<string, any>;
120
+ inputSchema?: Record<string, any>;
121
+ }
90
122
  export interface NodeFeatures {
91
123
  hasInputSchema: boolean;
92
124
  hasOutputSchema: boolean;
93
125
  }
126
+ /** Client-side representation of a TypedInput field: the raw value string and its type selector. */
127
+ export interface TypedInputValue {
128
+ value: string;
129
+ type: string;
130
+ }
131
+ type _ToClient<T> = T extends NodeRefResolved<any> ? string : T extends {
132
+ resolve(...args: any[]): any;
133
+ value: unknown;
134
+ type: string;
135
+ } ? TypedInputValue : T extends (...args: any[]) => any ? T : T extends Array<infer I> ? _ToClient<I>[] : T extends object ? {
136
+ [K in keyof T]: _ToClient<T[K]>;
137
+ } : T;
138
+ /**
139
+ * Infers the client-side TypeScript type from a TypeBox schema.
140
+ *
141
+ * Resolves schema types to their client form representations:
142
+ * - `NodeRef<T>` → `string` (node ID in the editor)
143
+ * - `TypedInput<T>` → `{ value: string; type: string }`
144
+ * - All other types resolve via TypeBox's `Static<T>`
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * import type { Infer } from "@bonsae/nrg/client";
149
+ * import type { ConfigSchema } from "../schemas/my-node";
150
+ *
151
+ * type Config = Infer<typeof ConfigSchema>;
152
+ * ```
153
+ */
154
+ export type Infer<T extends TSchema> = _ToClient<Static<T>>;
94
155
 
95
156
  export {};
96
157
 
97
158
  export declare function defineNode<T extends NodeDefinition>(options: T): T;
98
159
  export declare function registerType(definition: NodeDefinition): Promise<void>;
99
160
  export declare function registerTypes(nodes: NodeDefinition[]): Promise<void>;
161
+ export declare function useFormNode<TConfig extends TSchema = TSchema, TCredentials extends TSchema = TSchema>(): {
162
+ node: NodeRedNode & Infer<TConfig> & { credentials: Infer<TCredentials> & Record<string, any> };
163
+ schema: Record<string, any>;
164
+ errors: Record<string, string>;
165
+ };