@bonsae/nrg 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -11
- package/package.json +2 -2
- package/server/index.cjs +10 -14
- package/server/resources/nrg-client.js +14 -15
- package/test/index.js +179 -3
- package/types/client.d.ts +87 -25
- package/types/server.d.ts +27 -21
- package/types/test.d.ts +40 -30
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/@bonsae/nrg"><img src="https://img.shields.io/npm/v/@bonsae/nrg.svg" alt="npm package"></a>
|
|
7
7
|
<a href="https://github.com/bonsaedev/nrg/actions/workflows/ci.yaml"><img src="https://github.com/bonsaedev/nrg/actions/workflows/ci.yaml/badge.svg?branch=main" alt="build status"></a>
|
|
8
|
+
<a href="https://codecov.io/gh/bonsaedev/nrg"><img src="https://codecov.io/gh/bonsaedev/nrg/graph/badge.svg" alt="codecov"/></a>
|
|
8
9
|
<a href="https://socket.dev/npm/package/@bonsae/nrg"><img src="https://badge.socket.dev/npm/package/@bonsae/nrg?v=1" alt="Socket Badge"></a>
|
|
9
10
|
</p>
|
|
10
11
|
|
|
@@ -73,15 +74,15 @@ NRG supports two ways to define nodes:
|
|
|
73
74
|
<tr><td>
|
|
74
75
|
|
|
75
76
|
```typescript
|
|
76
|
-
import { defineIONode } from "@bonsae/nrg/server";
|
|
77
|
-
import { ConfigsSchema } from "../schemas/my-node";
|
|
77
|
+
import { defineIONode, SchemaType } from "@bonsae/nrg/server";
|
|
78
|
+
import { ConfigsSchema, InputSchema, OutputSchema } from "../schemas/my-node";
|
|
78
79
|
|
|
79
80
|
export default defineIONode({
|
|
80
81
|
type: "my-node",
|
|
81
82
|
color: "#ffffff",
|
|
82
|
-
inputs: 1,
|
|
83
|
-
outputs: 1,
|
|
84
83
|
configSchema: ConfigsSchema,
|
|
84
|
+
inputSchema: InputSchema,
|
|
85
|
+
outputsSchema: OutputSchema,
|
|
85
86
|
|
|
86
87
|
async input(msg) {
|
|
87
88
|
msg.payload = `${this.config.prefix}: ${msg.payload}`;
|
|
@@ -94,21 +95,22 @@ export default defineIONode({
|
|
|
94
95
|
|
|
95
96
|
```typescript
|
|
96
97
|
import { IONode, type Schema, type Infer } from "@bonsae/nrg/server";
|
|
97
|
-
import { ConfigsSchema } from "../schemas/my-node";
|
|
98
|
+
import { ConfigsSchema, InputSchema, OutputSchema } from "../schemas/my-node";
|
|
98
99
|
|
|
99
100
|
type Config = Infer<typeof ConfigsSchema>;
|
|
101
|
+
type Input = Infer<typeof InputSchema>;
|
|
102
|
+
type Output = Infer<typeof OutputSchema>;
|
|
100
103
|
|
|
101
|
-
export default class MyNode extends IONode<Config> {
|
|
104
|
+
export default class MyNode extends IONode<Config, any, Input, Output> {
|
|
102
105
|
static readonly type = "my-node";
|
|
103
106
|
static readonly category = "function";
|
|
104
107
|
static readonly color: `#${string}` = "#ffffff";
|
|
105
|
-
static readonly inputs = 1;
|
|
106
|
-
static readonly outputs = 1;
|
|
107
108
|
static readonly configSchema: Schema = ConfigsSchema;
|
|
109
|
+
static readonly inputSchema: Schema = InputSchema;
|
|
110
|
+
static readonly outputsSchema: Schema = OutputSchema;
|
|
108
111
|
|
|
109
|
-
async input(msg:
|
|
110
|
-
|
|
111
|
-
this.send(msg);
|
|
112
|
+
async input(msg: Input) {
|
|
113
|
+
this.send({ payload: `${this.config.prefix}: ${msg.payload}` });
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bonsae/nrg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=20.19",
|
|
17
17
|
"pnpm": ">=10.11.0"
|
|
18
18
|
},
|
|
19
19
|
"exports": {
|
package/server/index.cjs
CHANGED
|
@@ -531,12 +531,18 @@ var Node = class {
|
|
|
531
531
|
var IONode = class extends Node {
|
|
532
532
|
static align;
|
|
533
533
|
static color;
|
|
534
|
-
static inputs = 0;
|
|
535
|
-
static outputs = 0;
|
|
536
534
|
static inputSchema;
|
|
537
535
|
static outputsSchema;
|
|
538
536
|
static validateInput = false;
|
|
539
537
|
static validateOutput = false;
|
|
538
|
+
static get inputs() {
|
|
539
|
+
return this.inputSchema ? 1 : 0;
|
|
540
|
+
}
|
|
541
|
+
static get outputs() {
|
|
542
|
+
const s = this.outputsSchema;
|
|
543
|
+
if (!s) return 0;
|
|
544
|
+
return Array.isArray(s) ? s.length : 1;
|
|
545
|
+
}
|
|
540
546
|
_send;
|
|
541
547
|
context;
|
|
542
548
|
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
@@ -557,6 +563,8 @@ var IONode = class extends Node {
|
|
|
557
563
|
fn.global = setupContext(context.global);
|
|
558
564
|
this.context = fn;
|
|
559
565
|
}
|
|
566
|
+
input(msg) {
|
|
567
|
+
}
|
|
560
568
|
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
561
569
|
/** @internal */
|
|
562
570
|
async _input(msg, send) {
|
|
@@ -749,8 +757,6 @@ function defineIONode(def) {
|
|
|
749
757
|
static type = def.type;
|
|
750
758
|
static category = def.category ?? "function";
|
|
751
759
|
static color = def.color ?? "#a6bbcf";
|
|
752
|
-
static inputs = def.inputs ?? 1;
|
|
753
|
-
static outputs = def.outputs ?? 1;
|
|
754
760
|
static align = def.align;
|
|
755
761
|
static configSchema = def.configSchema;
|
|
756
762
|
static credentialsSchema = def.credentialsSchema;
|
|
@@ -990,16 +996,6 @@ async function registerType(RED, NodeClass) {
|
|
|
990
996
|
`Invalid color for ${NodeClass.type}: ${NC.color} color must be in hex format`
|
|
991
997
|
);
|
|
992
998
|
}
|
|
993
|
-
if (NC.inputs !== void 0 && (!Number.isInteger(NC.inputs) || NC.inputs !== 0 && NC.inputs !== 1)) {
|
|
994
|
-
throw new NrgError(
|
|
995
|
-
`Invalid number of inputs for ${NodeClass.type}: inputs must be 0 or 1`
|
|
996
|
-
);
|
|
997
|
-
}
|
|
998
|
-
if (NC.outputs !== void 0 && (!Number.isInteger(NC.outputs) || NC.outputs < 0)) {
|
|
999
|
-
throw new NrgError(
|
|
1000
|
-
`Invalid number of outputs for ${NodeClass.type}: outputs must be a positive integer`
|
|
1001
|
-
);
|
|
1002
|
-
}
|
|
1003
999
|
RED.nodes.registerType(
|
|
1004
1000
|
NC.type,
|
|
1005
1001
|
function(config) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(){var s=document.createElement("style");s.textContent=".nrg-toggles-grid[data-v-
|
|
1
|
+
(function(){var s=document.createElement("style");s.textContent=".nrg-toggles-grid[data-v-6a594f7a]{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px 16px}[data-v-6a594f7a] .node-red-vue-input-error-message{color:var(--red-ui-text-color-error)}[data-v-6a594f7a] .form-row input[type=text],[data-v-6a594f7a] .form-row input[type=number],[data-v-6a594f7a] .form-row input[type=password]{height:34px;padding:0 8px;box-sizing:border-box}.nrg-label[data-v-8b9bd83b]{display:inline-block;width:100%;cursor:default}.nrg-label i[data-v-8b9bd83b]{margin-right:5px}.nrg-required[data-v-8b9bd83b]{color:var(--red-ui-text-color-error);margin-left:2px}.editor-wrapper[data-v-2205b8dc]{position:relative}.expand-button[data-v-2205b8dc]{position:absolute;top:-23px;right:0;z-index:10;transition:color .3s ease;cursor:pointer}.nrg-toggle-wrapper[data-v-724dcae4]{display:inline-flex;align-items:center}.nrg-toggle[data-v-724dcae4]{position:relative;display:inline-flex!important;flex-direction:column;align-items:flex-start;cursor:pointer;gap:4px;-webkit-user-select:none;user-select:none}.nrg-toggle__input[data-v-724dcae4]{position:absolute;opacity:0;width:0;height:0}.nrg-toggle__slider[data-v-724dcae4]{position:relative;display:inline-block;width:36px;min-width:36px;height:20px;background-color:var(--red-ui-secondary-border-color, #ccc);border-radius:10px;transition:background-color .2s ease}.nrg-toggle__slider[data-v-724dcae4]:after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;background-color:#fff;border-radius:50%;transition:transform .2s ease}.nrg-toggle--checked .nrg-toggle__slider[data-v-724dcae4]{background-color:var(--red-ui-text-color-link, #0070d2)}.nrg-toggle--checked .nrg-toggle__slider[data-v-724dcae4]:after{transform:translate(16px)}.nrg-toggle__label[data-v-724dcae4]{cursor:default;white-space:nowrap}.nrg-toggle__label i[data-v-724dcae4]{margin-right:2px}\n";document.head.appendChild(s);})();
|
|
2
2
|
import { defineComponent as Pe, resolveComponent as he, openBlock as W, createElementBlock as ee, Fragment as nr, createVNode as $e, createCommentVNode as ne, createElementVNode as ce, normalizeClass as sr, renderSlot as ze, createTextVNode as Ln, toDisplayString as je, createBlock as be, withDirectives as Rs, vShow as Is, renderList as yr, createApp as js } from "/nrg/assets/vue.esm-browser.prod.js";
|
|
3
3
|
function ks(e, t, { signal: r, edges: s } = {}) {
|
|
4
4
|
let l, o = null;
|
|
@@ -5597,7 +5597,7 @@ function $o(e, t, r, s, l, o) {
|
|
|
5597
5597
|
/* STABLE_FRAGMENT */
|
|
5598
5598
|
);
|
|
5599
5599
|
}
|
|
5600
|
-
const bo = /* @__PURE__ */ Oe(lo, [["render", $o], ["__scopeId", "data-v-
|
|
5600
|
+
const bo = /* @__PURE__ */ Oe(lo, [["render", $o], ["__scopeId", "data-v-6a594f7a"]]), wo = Pe({
|
|
5601
5601
|
name: "NodeRedInputLabel",
|
|
5602
5602
|
props: {
|
|
5603
5603
|
label: {
|
|
@@ -5644,7 +5644,7 @@ function Po(e, t, r, s, l, o) {
|
|
|
5644
5644
|
e.required ? (W(), ee("span", So, "*")) : ne("v-if", !0)
|
|
5645
5645
|
]);
|
|
5646
5646
|
}
|
|
5647
|
-
const Ae = /* @__PURE__ */ Oe(wo, [["render", Po], ["__scopeId", "data-v-
|
|
5647
|
+
const Ae = /* @__PURE__ */ Oe(wo, [["render", Po], ["__scopeId", "data-v-8b9bd83b"]]), Vn = "*************", No = Pe({
|
|
5648
5648
|
components: { NodeRedInputLabel: Ae },
|
|
5649
5649
|
props: {
|
|
5650
5650
|
value: {
|
|
@@ -6337,7 +6337,7 @@ function Qo(e, t, r, s, l, o) {
|
|
|
6337
6337
|
/* NEED_PATCH */
|
|
6338
6338
|
);
|
|
6339
6339
|
}
|
|
6340
|
-
const bs = /* @__PURE__ */ Oe(Jo, [["render", Qo], ["__scopeId", "data-v-
|
|
6340
|
+
const bs = /* @__PURE__ */ Oe(Jo, [["render", Qo], ["__scopeId", "data-v-2205b8dc"]]), Xo = Pe({
|
|
6341
6341
|
name: "NodeRedToggle",
|
|
6342
6342
|
props: {
|
|
6343
6343
|
modelValue: {
|
|
@@ -6410,7 +6410,7 @@ function ri(e, t, r, s, l, o) {
|
|
|
6410
6410
|
)
|
|
6411
6411
|
]);
|
|
6412
6412
|
}
|
|
6413
|
-
const ws = /* @__PURE__ */ Oe(Xo, [["render", ri], ["__scopeId", "data-v-
|
|
6413
|
+
const ws = /* @__PURE__ */ Oe(Xo, [["render", ri], ["__scopeId", "data-v-724dcae4"]]), ni = /* @__PURE__ */ new Set([
|
|
6414
6414
|
"id",
|
|
6415
6415
|
"type",
|
|
6416
6416
|
"x",
|
|
@@ -6772,13 +6772,7 @@ function pi(e, t, r, s, l, o) {
|
|
|
6772
6772
|
))
|
|
6773
6773
|
]);
|
|
6774
6774
|
}
|
|
6775
|
-
const zn = /* @__PURE__ */ Oe(oi, [["render", pi]])
|
|
6776
|
-
function vi(e) {
|
|
6777
|
-
Object.assign(Ss, e);
|
|
6778
|
-
}
|
|
6779
|
-
function _i(e) {
|
|
6780
|
-
Object.assign(ur, e);
|
|
6781
|
-
}
|
|
6775
|
+
const zn = /* @__PURE__ */ Oe(oi, [["render", pi]]);
|
|
6782
6776
|
function hi(e, t, r, s) {
|
|
6783
6777
|
const l = js(bo, {
|
|
6784
6778
|
node: e,
|
|
@@ -6793,6 +6787,13 @@ function mi(e, t, r, s, l) {
|
|
|
6793
6787
|
function rr(e) {
|
|
6794
6788
|
e._app && (e._app.unmount(), e._app = null);
|
|
6795
6789
|
}
|
|
6790
|
+
const Ss = {}, ur = {};
|
|
6791
|
+
function vi(e) {
|
|
6792
|
+
Object.assign(Ss, e);
|
|
6793
|
+
}
|
|
6794
|
+
function _i(e) {
|
|
6795
|
+
Object.assign(ur, e);
|
|
6796
|
+
}
|
|
6796
6797
|
function Fn(e) {
|
|
6797
6798
|
const t = {
|
|
6798
6799
|
credentials: {}
|
|
@@ -6914,8 +6915,6 @@ async function yi(e) {
|
|
|
6914
6915
|
const u = Object.keys(P)[0];
|
|
6915
6916
|
u && (P[u] = {
|
|
6916
6917
|
...P[u],
|
|
6917
|
-
// 2-arg signature (value, opt) tells Node-RED 3.x to accept
|
|
6918
|
-
// string/array returns as error messages for the tooltip.
|
|
6919
6918
|
validate: function(n, f) {
|
|
6920
6919
|
return uo(this, v);
|
|
6921
6920
|
}
|
|
@@ -6972,7 +6971,7 @@ async function yi(e) {
|
|
|
6972
6971
|
if (f && f !== `${t}.outputLabels`) return f;
|
|
6973
6972
|
},
|
|
6974
6973
|
align: m.align || "left",
|
|
6975
|
-
button: m.button,
|
|
6974
|
+
button: m.button ? { ...m.button, onclick: m.button.onClick } : void 0,
|
|
6976
6975
|
oneditprepare: a,
|
|
6977
6976
|
oneditsave: i,
|
|
6978
6977
|
oneditcancel: y,
|
package/test/index.js
CHANGED
|
@@ -145,8 +145,175 @@ function createMockNodeRedNode(options = {}) {
|
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
// src/core/validator.ts
|
|
149
|
+
import Ajv from "ajv";
|
|
150
|
+
import addFormats from "ajv-formats";
|
|
151
|
+
import addErrors from "ajv-errors";
|
|
152
|
+
var Validator = class {
|
|
153
|
+
ajv;
|
|
154
|
+
constructor(options) {
|
|
155
|
+
const { customKeywords, customFormats, ...ajvOptions } = options || {};
|
|
156
|
+
this.ajv = new Ajv({
|
|
157
|
+
allErrors: true,
|
|
158
|
+
code: {
|
|
159
|
+
source: false
|
|
160
|
+
},
|
|
161
|
+
coerceTypes: true,
|
|
162
|
+
removeAdditional: false,
|
|
163
|
+
strict: false,
|
|
164
|
+
strictSchema: false,
|
|
165
|
+
useDefaults: true,
|
|
166
|
+
validateFormats: true,
|
|
167
|
+
// NOTE: typebox handles validation via typescript
|
|
168
|
+
// NOTE: if true, types that are not serializable JSON, like Function, would not work
|
|
169
|
+
validateSchema: false,
|
|
170
|
+
verbose: true,
|
|
171
|
+
...ajvOptions
|
|
172
|
+
});
|
|
173
|
+
addFormats(this.ajv);
|
|
174
|
+
addErrors(this.ajv);
|
|
175
|
+
this.addCustomKeywords(customKeywords || []);
|
|
176
|
+
this.addCustomFormats(customFormats || {});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Add custom keywords to the validator
|
|
180
|
+
*/
|
|
181
|
+
addCustomKeywords(keywords) {
|
|
182
|
+
if (!keywords) return;
|
|
183
|
+
keywords.forEach((keyword) => {
|
|
184
|
+
this.ajv.addKeyword(keyword);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Add custom formats to the validator
|
|
189
|
+
*/
|
|
190
|
+
addCustomFormats(formats) {
|
|
191
|
+
if (!formats) return;
|
|
192
|
+
Object.entries(formats).forEach(([name, validator2]) => {
|
|
193
|
+
if (validator2 instanceof RegExp) {
|
|
194
|
+
this.ajv.addFormat(name, validator2);
|
|
195
|
+
} else {
|
|
196
|
+
this.ajv.addFormat(name, { validate: validator2 });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Create a validator function with caching
|
|
202
|
+
* @param schema - JSON Schema to validate against
|
|
203
|
+
* @param cacheKey - Optional cache key for reusing validators
|
|
204
|
+
*/
|
|
205
|
+
createValidator(schema, cacheKey) {
|
|
206
|
+
if (cacheKey && !schema.$id) {
|
|
207
|
+
schema.$id = cacheKey;
|
|
208
|
+
}
|
|
209
|
+
if (schema.$id) {
|
|
210
|
+
const cached = this.ajv.getSchema(schema.$id);
|
|
211
|
+
if (cached) return cached;
|
|
212
|
+
}
|
|
213
|
+
const validator2 = this.ajv.compile(schema);
|
|
214
|
+
return validator2;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Validate data against a schema and return a structured result
|
|
218
|
+
*/
|
|
219
|
+
validate(data, schema, options) {
|
|
220
|
+
const validator2 = this.createValidator(schema, options?.cacheKey);
|
|
221
|
+
const valid = validator2(data);
|
|
222
|
+
if (!valid) {
|
|
223
|
+
const errorMessage = this.formatErrors(validator2.errors);
|
|
224
|
+
if (options?.throwOnError) {
|
|
225
|
+
throw new ValidationError(errorMessage, validator2.errors || []);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
valid: false,
|
|
229
|
+
errors: validator2.errors || void 0,
|
|
230
|
+
errorMessage
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
valid: true,
|
|
235
|
+
data
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Format errors into a human-readable string
|
|
240
|
+
*/
|
|
241
|
+
formatErrors(errors, options) {
|
|
242
|
+
if (!errors || errors.length === 0) {
|
|
243
|
+
return "No errors";
|
|
244
|
+
}
|
|
245
|
+
return this.ajv.errorsText(errors, {
|
|
246
|
+
separator: "; ",
|
|
247
|
+
dataVar: "data",
|
|
248
|
+
...options
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get detailed error information
|
|
253
|
+
*/
|
|
254
|
+
getDetailedErrors(errors) {
|
|
255
|
+
if (!errors || errors.length === 0) return [];
|
|
256
|
+
return errors.map((error) => ({
|
|
257
|
+
field: error.instancePath || "/",
|
|
258
|
+
message: error.message || "Validation failed",
|
|
259
|
+
keyword: error.keyword,
|
|
260
|
+
params: error.params,
|
|
261
|
+
schemaPath: error.schemaPath
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Add a schema to the validator for reference
|
|
266
|
+
*/
|
|
267
|
+
addSchema(schema, key) {
|
|
268
|
+
this.ajv.addSchema(schema, key);
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Remove a schema from the validator
|
|
273
|
+
*/
|
|
274
|
+
removeSchema(key) {
|
|
275
|
+
this.ajv.removeSchema(key);
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var ValidationError = class _ValidationError extends Error {
|
|
280
|
+
constructor(message, errors) {
|
|
281
|
+
super(message);
|
|
282
|
+
this.errors = errors;
|
|
283
|
+
this.name = "ValidationError";
|
|
284
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/core/server/validation.ts
|
|
289
|
+
var validator = void 0;
|
|
290
|
+
function initValidator(RED) {
|
|
291
|
+
validator = new Validator({
|
|
292
|
+
customKeywords: [
|
|
293
|
+
{
|
|
294
|
+
keyword: "x-nrg-skip-validation",
|
|
295
|
+
schemaType: "boolean",
|
|
296
|
+
valid: true
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
keyword: "x-nrg-node-type",
|
|
300
|
+
type: "string",
|
|
301
|
+
validate: (schemaValue, dataValue) => {
|
|
302
|
+
if (!dataValue) return true;
|
|
303
|
+
const node = RED.nodes.getNode(dataValue);
|
|
304
|
+
return node?.type === schemaValue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
],
|
|
308
|
+
customFormats: {
|
|
309
|
+
"node-id": /^[a-zA-Z0-9-_]+$/,
|
|
310
|
+
"flow-id": /^[a-f0-9]{16}$/,
|
|
311
|
+
"topic-path": (data) => /^[a-zA-Z0-9/_-]+$/.test(data)
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
148
316
|
// src/test/index.ts
|
|
149
|
-
import { initValidator } from "@bonsae/nrg/server";
|
|
150
317
|
function buildConfig(NodeClass, userConfig = {}) {
|
|
151
318
|
const defaults = {};
|
|
152
319
|
if (NodeClass.configSchema?.properties) {
|
|
@@ -235,10 +402,19 @@ async function createNode(NodeClass, options = {}) {
|
|
|
235
402
|
const {
|
|
236
403
|
config: userConfig = {},
|
|
237
404
|
credentials = {},
|
|
238
|
-
configNodes = {},
|
|
239
405
|
settings = {},
|
|
240
406
|
overrides: overrideOpts = {}
|
|
241
407
|
} = options;
|
|
408
|
+
const resolvedConfig = {};
|
|
409
|
+
const configNodes = {};
|
|
410
|
+
for (const [key, value] of Object.entries(userConfig)) {
|
|
411
|
+
if (value && typeof value === "object" && "id" in value && "config" in value) {
|
|
412
|
+
configNodes[value.id] = value;
|
|
413
|
+
resolvedConfig[key] = value.id;
|
|
414
|
+
} else {
|
|
415
|
+
resolvedConfig[key] = value;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
242
418
|
const redNodes = buildNodeRedNodes(configNodes);
|
|
243
419
|
const RED = createMockRED({ nodes: redNodes, settings });
|
|
244
420
|
initValidator(RED);
|
|
@@ -251,7 +427,7 @@ async function createNode(NodeClass, options = {}) {
|
|
|
251
427
|
}
|
|
252
428
|
const config = buildConfig(NodeClass, {
|
|
253
429
|
...configDefaults,
|
|
254
|
-
...
|
|
430
|
+
...resolvedConfig
|
|
255
431
|
});
|
|
256
432
|
const nodeRedNode = createMockNodeRedNode({
|
|
257
433
|
id: config.id,
|
package/types/client.d.ts
CHANGED
|
@@ -1,36 +1,98 @@
|
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
1
2
|
|
|
2
|
-
import
|
|
3
|
+
import { App, Component } from 'vue';
|
|
3
4
|
|
|
5
|
+
export interface NodeStateCredentials {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
export interface NodeState {
|
|
9
|
+
credentials: NodeStateCredentials;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
4
12
|
export interface NodeButtonDefinition {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
toggle: string;
|
|
14
|
+
onClick: () => void;
|
|
15
|
+
enabled?: () => boolean;
|
|
16
|
+
visible?: () => boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface NodeRedNodeButtonDefinition {
|
|
19
|
+
toggle: string;
|
|
20
|
+
onclick: () => void;
|
|
21
|
+
enabled?: () => boolean;
|
|
22
|
+
visible?: () => boolean;
|
|
9
23
|
}
|
|
10
|
-
|
|
11
24
|
export interface NodeFormDefinition {
|
|
12
|
-
|
|
25
|
+
component?: Component;
|
|
26
|
+
}
|
|
27
|
+
export interface NodeRedNode {
|
|
28
|
+
id: string;
|
|
29
|
+
type: string;
|
|
30
|
+
name: string;
|
|
31
|
+
category: string;
|
|
32
|
+
x: string;
|
|
33
|
+
y: string;
|
|
34
|
+
g: string;
|
|
35
|
+
z: string;
|
|
36
|
+
credentials: Record<string, any>;
|
|
37
|
+
_def: {
|
|
38
|
+
defaults: Record<string, {
|
|
39
|
+
value: string;
|
|
40
|
+
type?: string;
|
|
41
|
+
label?: string;
|
|
42
|
+
required?: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
credentials: Record<string, {
|
|
45
|
+
value: string;
|
|
46
|
+
type?: "password" | "text";
|
|
47
|
+
label?: string;
|
|
48
|
+
required?: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
category: string;
|
|
51
|
+
color?: string;
|
|
52
|
+
icon?: string;
|
|
53
|
+
label?: ((this: NodeRedNode) => string) | string;
|
|
54
|
+
inputs?: number;
|
|
55
|
+
outputs?: number;
|
|
56
|
+
paletteLabel?: ((this: NodeRedNode) => string) | string;
|
|
57
|
+
labelStyle?: ((this: NodeRedNode) => string) | string;
|
|
58
|
+
inputLabels?: ((this: NodeRedNode, index: number) => string) | string;
|
|
59
|
+
outputLabels?: ((this: NodeRedNode, index: number) => string) | string;
|
|
60
|
+
align?: "left" | "right";
|
|
61
|
+
button?: NodeRedNodeButtonDefinition;
|
|
62
|
+
};
|
|
63
|
+
_newState?: NodeRedNode;
|
|
64
|
+
_app?: App | null;
|
|
65
|
+
_: (str: string) => string;
|
|
66
|
+
[key: string]: any;
|
|
13
67
|
}
|
|
14
|
-
|
|
15
68
|
export interface NodeDefinition {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
69
|
+
type: string;
|
|
70
|
+
category?: string;
|
|
71
|
+
color?: string;
|
|
72
|
+
icon?: ((this: NodeRedNode) => string) | string;
|
|
73
|
+
label?: ((this: NodeRedNode) => string) | string;
|
|
74
|
+
inputs?: number;
|
|
75
|
+
outputs?: number;
|
|
76
|
+
paletteLabel?: ((this: NodeRedNode) => string) | string;
|
|
77
|
+
labelStyle?: ((this: NodeRedNode) => string) | string;
|
|
78
|
+
inputLabels?: ((this: NodeRedNode, index: number) => string) | string;
|
|
79
|
+
outputLabels?: ((this: NodeRedNode, index: number) => string) | string;
|
|
80
|
+
align?: "left" | "right";
|
|
81
|
+
button?: NodeButtonDefinition;
|
|
82
|
+
onEditResize?: (this: NodeRedNode, size: {
|
|
83
|
+
width: number;
|
|
84
|
+
height: number;
|
|
85
|
+
}) => void;
|
|
86
|
+
onPaletteAdd?: (this: NodeRedNode) => void;
|
|
87
|
+
onPaletteRemove?: (this: NodeRedNode) => void;
|
|
88
|
+
form?: NodeFormDefinition;
|
|
33
89
|
}
|
|
90
|
+
export interface NodeFeatures {
|
|
91
|
+
hasInputSchema: boolean;
|
|
92
|
+
hasOutputSchema: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {};
|
|
34
96
|
|
|
35
97
|
export declare function defineNode<T extends NodeDefinition>(options: T): T;
|
|
36
98
|
export declare function registerType(definition: NodeDefinition): Promise<void>;
|
package/types/server.d.ts
CHANGED
|
@@ -1262,6 +1262,15 @@ export interface TVoid extends TSchema {
|
|
|
1262
1262
|
type: "void";
|
|
1263
1263
|
}
|
|
1264
1264
|
export interface SchemaOptions {
|
|
1265
|
+
exportable?: boolean;
|
|
1266
|
+
"x-nrg-node-type"?: string;
|
|
1267
|
+
"x-nrg-form"?: {
|
|
1268
|
+
icon?: string;
|
|
1269
|
+
typedInputTypes?: string[];
|
|
1270
|
+
editorLanguage?: string;
|
|
1271
|
+
toggle?: boolean;
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1265
1274
|
$schema?: string;
|
|
1266
1275
|
/** Id for this schema */
|
|
1267
1276
|
$id?: string;
|
|
@@ -2048,6 +2057,16 @@ declare class TypedInput<T = unknown> {
|
|
|
2048
2057
|
get value(): unknown;
|
|
2049
2058
|
resolve(msg?: Record<string, any>): Promise<T>;
|
|
2050
2059
|
}
|
|
2060
|
+
export interface NrgSchemaExtensions {
|
|
2061
|
+
exportable?: boolean;
|
|
2062
|
+
"x-nrg-node-type"?: string;
|
|
2063
|
+
"x-nrg-form"?: {
|
|
2064
|
+
icon?: string;
|
|
2065
|
+
typedInputTypes?: string[];
|
|
2066
|
+
editorLanguage?: string;
|
|
2067
|
+
toggle?: boolean;
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2051
2070
|
export interface TNodeRef<T = any> extends TSchema {
|
|
2052
2071
|
[Kind]: "NodeRef";
|
|
2053
2072
|
static: T;
|
|
@@ -2063,20 +2082,9 @@ export interface TTypedInput<T = unknown> extends TSchema {
|
|
|
2063
2082
|
[Kind]: "TypedInput";
|
|
2064
2083
|
static: TypedInput<T>;
|
|
2065
2084
|
}
|
|
2066
|
-
export interface
|
|
2067
|
-
icon?: string;
|
|
2068
|
-
typedInputTypes?: string[];
|
|
2069
|
-
editorLanguage?: string;
|
|
2070
|
-
toggle?: boolean;
|
|
2071
|
-
}
|
|
2072
|
-
export interface NrgSchemaOptions extends SchemaOptions {
|
|
2073
|
-
exportable?: boolean;
|
|
2074
|
-
"x-nrg-node-type"?: string;
|
|
2075
|
-
"x-nrg-form"?: NrgFormOptions;
|
|
2076
|
-
}
|
|
2077
|
-
export interface Schema<T extends TProperties = TProperties> extends TObject<T> {
|
|
2078
|
-
$id: string;
|
|
2085
|
+
export interface NrgSchemaOptions extends SchemaOptions, NrgSchemaExtensions {
|
|
2079
2086
|
}
|
|
2087
|
+
export type Schema<T extends TProperties = TProperties> = TObject<T>;
|
|
2080
2088
|
declare const NodeConfigSchema: TObject<{
|
|
2081
2089
|
id: TString;
|
|
2082
2090
|
type: TString;
|
|
@@ -2106,8 +2114,8 @@ export declare const SchemaType: JavaScriptTypeBuilder & {
|
|
|
2106
2114
|
NodeRef: typeof NodeRef;
|
|
2107
2115
|
TypedInput: typeof TypedInput$1;
|
|
2108
2116
|
};
|
|
2109
|
-
export declare function defineSchema<T extends TProperties>(properties: T, options
|
|
2110
|
-
$id
|
|
2117
|
+
export declare function defineSchema<T extends TProperties>(properties: T, options?: ObjectOptions & {
|
|
2118
|
+
$id?: string;
|
|
2111
2119
|
}): Schema<T>;
|
|
2112
2120
|
export type NodeContextScope = "node" | "flow" | "global";
|
|
2113
2121
|
export interface NodeContextStore {
|
|
@@ -2143,20 +2151,20 @@ export type ConfigNodeContext = {
|
|
|
2143
2151
|
node: NodeContextStore;
|
|
2144
2152
|
global: NodeContextStore;
|
|
2145
2153
|
};
|
|
2146
|
-
export declare
|
|
2154
|
+
export declare class IONode<TConfig = any, TCredentials = any, TInput = any, TOutput = any, TSettings = any> extends Node$1<TConfig, TCredentials, TSettings> {
|
|
2147
2155
|
static readonly align?: "left" | "right";
|
|
2148
2156
|
static readonly color: HexColor;
|
|
2149
|
-
static readonly inputs?: number;
|
|
2150
|
-
static readonly outputs?: number;
|
|
2151
2157
|
static readonly inputSchema?: Schema;
|
|
2152
2158
|
static readonly outputsSchema?: Schema | Schema[];
|
|
2153
2159
|
static readonly validateInput: boolean;
|
|
2154
2160
|
static readonly validateOutput: boolean;
|
|
2161
|
+
static get inputs(): 0 | 1;
|
|
2162
|
+
static get outputs(): number;
|
|
2155
2163
|
private _send;
|
|
2156
2164
|
readonly config: IONodeConfig<TConfig>;
|
|
2157
2165
|
protected readonly context: IONodeContext;
|
|
2158
2166
|
constructor(RED: RED, node: any, config: IONodeConfig<TConfig>, credentials: IONodeCredentials<TCredentials>);
|
|
2159
|
-
|
|
2167
|
+
input(msg: TInput): void | Promise<void>;
|
|
2160
2168
|
send(msg: TOutput): void;
|
|
2161
2169
|
private _nodeSource;
|
|
2162
2170
|
status(status: IONodeStatus): void;
|
|
@@ -2203,8 +2211,6 @@ export interface IONodeDefinition<TConfigSchema extends TSchema | undefined = un
|
|
|
2203
2211
|
type: string;
|
|
2204
2212
|
category?: string;
|
|
2205
2213
|
color?: HexColor;
|
|
2206
|
-
inputs?: 0 | 1;
|
|
2207
|
-
outputs?: number;
|
|
2208
2214
|
align?: "left" | "right";
|
|
2209
2215
|
configSchema?: TConfigSchema;
|
|
2210
2216
|
credentialsSchema?: TCredsSchema;
|
package/types/test.d.ts
CHANGED
|
@@ -1,39 +1,49 @@
|
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
1
2
|
|
|
3
|
+
export interface MockNodeRedNodeOptions {
|
|
4
|
+
id?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
z?: string;
|
|
8
|
+
wires?: string[][];
|
|
9
|
+
credentials?: Record<string, any>;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
2
12
|
export interface CreateNodeOptions {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
overrides?: Record<string, any>;
|
|
13
|
+
config?: Record<string, any>;
|
|
14
|
+
credentials?: Record<string, any>;
|
|
15
|
+
settings?: Record<string, any>;
|
|
16
|
+
overrides?: MockNodeRedNodeOptions;
|
|
8
17
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
export type ExtractInput<T> = T extends {
|
|
19
|
+
input(msg: infer I): any;
|
|
20
|
+
} ? I : any;
|
|
21
|
+
export type ExtractOutput<T> = T extends {
|
|
22
|
+
send(msg: infer O): any;
|
|
23
|
+
} ? O : any;
|
|
13
24
|
export interface TestNodeHelpers<TInput = any, TOutput = any> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
receive(msg: TInput): Promise<void>;
|
|
26
|
+
close(removed?: boolean): Promise<void>;
|
|
27
|
+
reset(): void;
|
|
28
|
+
sent(): TOutput[];
|
|
29
|
+
sent(port: number): any[];
|
|
30
|
+
statuses(): any[];
|
|
31
|
+
logged(level?: "info" | "warn" | "error" | "debug"): string[];
|
|
32
|
+
warned(): string[];
|
|
33
|
+
errored(): string[];
|
|
23
34
|
}
|
|
24
|
-
|
|
25
35
|
export interface CreateNodeResult<T> {
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
node: T & TestNodeHelpers<ExtractInput<T>, ExtractOutput<T>>;
|
|
37
|
+
RED: any;
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
new (...args: any[]): any;
|
|
39
|
+
export interface NodeClass {
|
|
40
|
+
readonly type: string;
|
|
41
|
+
readonly category?: string;
|
|
42
|
+
readonly configSchema?: any;
|
|
43
|
+
registered?(RED: any): void | Promise<void>;
|
|
44
|
+
_registered?(RED: any): void | Promise<void>;
|
|
45
|
+
new (...args: any[]): any;
|
|
37
46
|
}
|
|
38
|
-
|
|
39
47
|
export declare function createNode<T extends NodeClass>(NodeClass: T, options?: CreateNodeOptions): Promise<CreateNodeResult<InstanceType<T>>>;
|
|
48
|
+
|
|
49
|
+
export {};
|