@bonsae/nrg 0.13.1 → 0.14.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 +3 -40
- package/package.json +3 -2
- package/server/index.cjs +248 -237
- package/server/resources/nrg-client.js +514 -500
- package/test/index.js +267 -36
- package/types/server.d.ts +290 -1847
- package/types/shims/components.d.ts +13 -10
- package/types/shims/form/components/node-red-config-input.vue.d.ts +112 -0
- package/types/shims/form/components/node-red-editor-input.vue.d.ts +105 -0
- package/types/shims/form/components/node-red-input-label.vue.d.ts +34 -0
- package/types/shims/form/components/node-red-input.vue.d.ts +111 -0
- package/types/shims/form/components/node-red-json-schema-form.vue.d.ts +651 -0
- package/types/shims/form/components/node-red-select-input.vue.d.ts +117 -0
- package/types/shims/form/components/node-red-toggle.vue.d.ts +36 -0
- package/types/shims/form/components/node-red-typed-input.vue.d.ts +106 -0
- package/types/shims/globals.d.ts +1 -1
- package/types/shims/schema-options.d.ts +11 -0
- package/types/shims/typebox.d.ts +10 -0
- package/types/test.d.ts +413 -32
- package/types/vite.d.ts +164 -7
- package/vite/index.js +223 -31
- package/server/resources/vue.esm-browser.js +0 -18708
- package/server/resources/vue.esm-browser.prod.js +0 -13
package/test/index.js
CHANGED
|
@@ -3,19 +3,44 @@ import { vi as vi2 } from "vitest";
|
|
|
3
3
|
|
|
4
4
|
// src/test/mocks.ts
|
|
5
5
|
import { vi } from "vitest";
|
|
6
|
-
function
|
|
7
|
-
const {
|
|
8
|
-
|
|
6
|
+
function createNodeRedRuntime(options = {}) {
|
|
7
|
+
const { settings = {} } = options;
|
|
8
|
+
const nodes = {};
|
|
9
|
+
const red = {
|
|
9
10
|
log: {
|
|
10
11
|
info: vi.fn(),
|
|
11
12
|
warn: vi.fn(),
|
|
12
13
|
error: vi.fn(),
|
|
13
|
-
debug: vi.fn()
|
|
14
|
+
debug: vi.fn(),
|
|
15
|
+
trace: vi.fn(),
|
|
16
|
+
log: vi.fn(),
|
|
17
|
+
metric: vi.fn(() => false),
|
|
18
|
+
audit: vi.fn(),
|
|
19
|
+
addHandler: vi.fn(),
|
|
20
|
+
removeHandler: vi.fn(),
|
|
21
|
+
FATAL: 10,
|
|
22
|
+
ERROR: 20,
|
|
23
|
+
WARN: 30,
|
|
24
|
+
INFO: 40,
|
|
25
|
+
DEBUG: 50,
|
|
26
|
+
TRACE: 60,
|
|
27
|
+
AUDIT: 98,
|
|
28
|
+
METRIC: 99
|
|
14
29
|
},
|
|
15
30
|
nodes: {
|
|
16
31
|
getNode: vi.fn((id) => nodes[id]),
|
|
17
32
|
registerType: vi.fn(),
|
|
18
|
-
createNode: vi.fn()
|
|
33
|
+
createNode: vi.fn(),
|
|
34
|
+
getCredentials: vi.fn(),
|
|
35
|
+
eachNode: vi.fn(),
|
|
36
|
+
getType: vi.fn(),
|
|
37
|
+
getNodeInfo: vi.fn(),
|
|
38
|
+
getNodeList: vi.fn(() => []),
|
|
39
|
+
getModuleInfo: vi.fn(),
|
|
40
|
+
installModule: vi.fn(),
|
|
41
|
+
uninstallModule: vi.fn(),
|
|
42
|
+
enableNode: vi.fn(),
|
|
43
|
+
disableNode: vi.fn()
|
|
19
44
|
},
|
|
20
45
|
httpAdmin: {
|
|
21
46
|
get: vi.fn(),
|
|
@@ -24,6 +49,25 @@ function createMockRED(options = {}) {
|
|
|
24
49
|
delete: vi.fn(),
|
|
25
50
|
use: vi.fn()
|
|
26
51
|
},
|
|
52
|
+
httpNode: {
|
|
53
|
+
get: vi.fn(),
|
|
54
|
+
post: vi.fn(),
|
|
55
|
+
put: vi.fn(),
|
|
56
|
+
delete: vi.fn(),
|
|
57
|
+
use: vi.fn()
|
|
58
|
+
},
|
|
59
|
+
hooks: {
|
|
60
|
+
add: vi.fn(),
|
|
61
|
+
remove: vi.fn(),
|
|
62
|
+
trigger: vi.fn(),
|
|
63
|
+
has: vi.fn(() => false),
|
|
64
|
+
clear: vi.fn()
|
|
65
|
+
},
|
|
66
|
+
events: {
|
|
67
|
+
on: vi.fn(),
|
|
68
|
+
emit: vi.fn(),
|
|
69
|
+
removeListener: vi.fn()
|
|
70
|
+
},
|
|
27
71
|
settings: { ...settings },
|
|
28
72
|
_: vi.fn((key, subs) => {
|
|
29
73
|
if (!subs) return key;
|
|
@@ -80,18 +124,34 @@ function createMockRED(options = {}) {
|
|
|
80
124
|
callback(err, void 0);
|
|
81
125
|
}
|
|
82
126
|
}
|
|
83
|
-
)
|
|
127
|
+
),
|
|
128
|
+
generateId: vi.fn(() => "mock-id"),
|
|
129
|
+
cloneMessage: vi.fn((msg) => ({ ...msg })),
|
|
130
|
+
ensureString: vi.fn((o) => String(o)),
|
|
131
|
+
ensureBuffer: vi.fn(),
|
|
132
|
+
compareObjects: vi.fn(),
|
|
133
|
+
getMessageProperty: vi.fn(),
|
|
134
|
+
setMessageProperty: vi.fn(),
|
|
135
|
+
getObjectProperty: vi.fn(),
|
|
136
|
+
setObjectProperty: vi.fn(),
|
|
137
|
+
normalisePropertyExpression: vi.fn(),
|
|
138
|
+
normaliseNodeTypeName: vi.fn(),
|
|
139
|
+
prepareJSONataExpression: vi.fn(),
|
|
140
|
+
evaluateJSONataExpression: vi.fn(),
|
|
141
|
+
parseContextStore: vi.fn(),
|
|
142
|
+
getSetting: vi.fn(),
|
|
143
|
+
encodeObject: vi.fn()
|
|
84
144
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
145
|
+
version: vi.fn(() => "0.0.0-test"),
|
|
146
|
+
validator: void 0,
|
|
147
|
+
registerNode(id, nodeRedNode) {
|
|
148
|
+
nodes[id] = nodeRedNode;
|
|
88
149
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
version: vi.fn(() => "0.0.0-test")
|
|
150
|
+
registerNrgNode(id, nrgInstance) {
|
|
151
|
+
nodes[id] = createNodeRedNode({ id, _node: nrgInstance });
|
|
152
|
+
}
|
|
94
153
|
};
|
|
154
|
+
return red;
|
|
95
155
|
}
|
|
96
156
|
function getProperty(obj, path) {
|
|
97
157
|
return path.split(".").reduce((acc, key) => acc?.[key], obj);
|
|
@@ -113,7 +173,7 @@ function createContextStore() {
|
|
|
113
173
|
)
|
|
114
174
|
};
|
|
115
175
|
}
|
|
116
|
-
function
|
|
176
|
+
function createNodeRedNode(options = {}) {
|
|
117
177
|
const nodeCtx = createContextStore();
|
|
118
178
|
const flowCtx = createContextStore();
|
|
119
179
|
const globalCtx = createContextStore();
|
|
@@ -123,14 +183,14 @@ function createMockNodeRedNode(options = {}) {
|
|
|
123
183
|
global: globalCtx
|
|
124
184
|
};
|
|
125
185
|
return {
|
|
126
|
-
id: options.id ?? `
|
|
186
|
+
id: options.id ?? `node-${Math.random().toString(36).slice(2, 10)}`,
|
|
127
187
|
type: options.type ?? "test-node",
|
|
128
|
-
name: options.name ?? "",
|
|
188
|
+
name: options.name ?? "test-node",
|
|
129
189
|
z: options.z ?? "flow-1",
|
|
130
190
|
x: 100,
|
|
131
191
|
y: 200,
|
|
132
|
-
g:
|
|
133
|
-
wires: options.wires ?? [[]],
|
|
192
|
+
g: "group-1",
|
|
193
|
+
wires: options.wires ?? [["node-2"]],
|
|
134
194
|
credentials: options.credentials ?? {},
|
|
135
195
|
log: vi.fn(),
|
|
136
196
|
warn: vi.fn(),
|
|
@@ -145,32 +205,201 @@ function createMockNodeRedNode(options = {}) {
|
|
|
145
205
|
};
|
|
146
206
|
}
|
|
147
207
|
|
|
208
|
+
// src/core/validator.ts
|
|
209
|
+
import Ajv from "ajv";
|
|
210
|
+
import addFormats from "ajv-formats";
|
|
211
|
+
import addErrors from "ajv-errors";
|
|
212
|
+
var Validator = class {
|
|
213
|
+
ajv;
|
|
214
|
+
constructor(options) {
|
|
215
|
+
const { customKeywords, customFormats, ...ajvOptions } = options || {};
|
|
216
|
+
this.ajv = new Ajv({
|
|
217
|
+
allErrors: true,
|
|
218
|
+
code: {
|
|
219
|
+
source: false
|
|
220
|
+
},
|
|
221
|
+
coerceTypes: true,
|
|
222
|
+
removeAdditional: false,
|
|
223
|
+
strict: false,
|
|
224
|
+
strictSchema: false,
|
|
225
|
+
useDefaults: true,
|
|
226
|
+
validateFormats: true,
|
|
227
|
+
// NOTE: typebox handles validation via typescript
|
|
228
|
+
// NOTE: if true, types that are not serializable JSON, like Function, would not work
|
|
229
|
+
validateSchema: false,
|
|
230
|
+
verbose: true,
|
|
231
|
+
...ajvOptions
|
|
232
|
+
});
|
|
233
|
+
addFormats(this.ajv);
|
|
234
|
+
addErrors(this.ajv);
|
|
235
|
+
this.addCustomKeywords(customKeywords || []);
|
|
236
|
+
this.addCustomFormats(customFormats || {});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Add custom keywords to the validator
|
|
240
|
+
*/
|
|
241
|
+
addCustomKeywords(keywords) {
|
|
242
|
+
if (!keywords) return;
|
|
243
|
+
keywords.forEach((keyword) => {
|
|
244
|
+
this.ajv.addKeyword(keyword);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Add custom formats to the validator
|
|
249
|
+
*/
|
|
250
|
+
addCustomFormats(formats) {
|
|
251
|
+
if (!formats) return;
|
|
252
|
+
Object.entries(formats).forEach(([name, validator]) => {
|
|
253
|
+
if (validator instanceof RegExp) {
|
|
254
|
+
this.ajv.addFormat(name, validator);
|
|
255
|
+
} else {
|
|
256
|
+
this.ajv.addFormat(name, { validate: validator });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Create a validator function with caching
|
|
262
|
+
* @param schema - JSON Schema to validate against
|
|
263
|
+
* @param cacheKey - Optional cache key for reusing validators
|
|
264
|
+
*/
|
|
265
|
+
createValidator(schema, cacheKey) {
|
|
266
|
+
if (cacheKey && !schema.$id) {
|
|
267
|
+
schema.$id = cacheKey;
|
|
268
|
+
}
|
|
269
|
+
if (schema.$id) {
|
|
270
|
+
const cached = this.ajv.getSchema(schema.$id);
|
|
271
|
+
if (cached) return cached;
|
|
272
|
+
}
|
|
273
|
+
const validator = this.ajv.compile(schema);
|
|
274
|
+
return validator;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Validate data against a schema and return a structured result
|
|
278
|
+
*/
|
|
279
|
+
validate(data, schema, options) {
|
|
280
|
+
const validator = this.createValidator(schema, options?.cacheKey);
|
|
281
|
+
const valid = validator(data);
|
|
282
|
+
if (!valid) {
|
|
283
|
+
const errorMessage = this.formatErrors(validator.errors);
|
|
284
|
+
if (options?.throwOnError) {
|
|
285
|
+
throw new ValidationError(errorMessage, validator.errors || []);
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
valid: false,
|
|
289
|
+
errors: validator.errors || void 0,
|
|
290
|
+
errorMessage
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
valid: true,
|
|
295
|
+
data
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Format errors into a human-readable string
|
|
300
|
+
*/
|
|
301
|
+
formatErrors(errors, options) {
|
|
302
|
+
if (!errors || errors.length === 0) {
|
|
303
|
+
return "No errors";
|
|
304
|
+
}
|
|
305
|
+
return this.ajv.errorsText(errors, {
|
|
306
|
+
separator: "; ",
|
|
307
|
+
dataVar: "data",
|
|
308
|
+
...options
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get detailed error information
|
|
313
|
+
*/
|
|
314
|
+
getDetailedErrors(errors) {
|
|
315
|
+
if (!errors || errors.length === 0) return [];
|
|
316
|
+
return errors.map((error) => ({
|
|
317
|
+
field: error.instancePath || "/",
|
|
318
|
+
message: error.message || "Validation failed",
|
|
319
|
+
keyword: error.keyword,
|
|
320
|
+
params: error.params,
|
|
321
|
+
schemaPath: error.schemaPath
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Add a schema to the validator for reference
|
|
326
|
+
*/
|
|
327
|
+
addSchema(schema, key) {
|
|
328
|
+
this.ajv.addSchema(schema, key);
|
|
329
|
+
return this;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Remove a schema from the validator
|
|
333
|
+
*/
|
|
334
|
+
removeSchema(key) {
|
|
335
|
+
this.ajv.removeSchema(key);
|
|
336
|
+
return this;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
var ValidationError = class _ValidationError extends Error {
|
|
340
|
+
constructor(message, errors) {
|
|
341
|
+
super(message);
|
|
342
|
+
this.errors = errors;
|
|
343
|
+
this.name = "ValidationError";
|
|
344
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/core/server/validation.ts
|
|
349
|
+
function initValidator(RED) {
|
|
350
|
+
const nrg = {
|
|
351
|
+
validator: new Validator({
|
|
352
|
+
customKeywords: [
|
|
353
|
+
{
|
|
354
|
+
keyword: "x-nrg-skip-validation",
|
|
355
|
+
schemaType: "boolean",
|
|
356
|
+
valid: true
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
keyword: "x-nrg-node-type",
|
|
360
|
+
type: "string",
|
|
361
|
+
validate: (schemaValue, dataValue) => {
|
|
362
|
+
if (!dataValue) return true;
|
|
363
|
+
const node = RED.nodes.getNode(dataValue);
|
|
364
|
+
return node?.type === schemaValue;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
customFormats: {
|
|
369
|
+
"node-id": /^[a-zA-Z0-9-_]+$/,
|
|
370
|
+
"flow-id": /^[a-f0-9]{16}$/,
|
|
371
|
+
"topic-path": (data) => /^[a-zA-Z0-9/_-]+$/.test(data)
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
};
|
|
375
|
+
Object.defineProperty(RED, "_nrg", {
|
|
376
|
+
value: nrg,
|
|
377
|
+
writable: false,
|
|
378
|
+
enumerable: false,
|
|
379
|
+
configurable: false
|
|
380
|
+
});
|
|
381
|
+
Object.defineProperty(RED, "validator", {
|
|
382
|
+
get: () => nrg.validator,
|
|
383
|
+
enumerable: false,
|
|
384
|
+
configurable: false
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
148
388
|
// src/test/index.ts
|
|
149
|
-
import { initValidator } from "@bonsae/nrg/server";
|
|
150
389
|
function buildConfig(NodeClass, userConfig = {}) {
|
|
151
390
|
const defaults = {};
|
|
152
391
|
if (NodeClass.configSchema?.properties) {
|
|
153
392
|
for (const [key, prop] of Object.entries(
|
|
154
393
|
NodeClass.configSchema.properties
|
|
155
394
|
)) {
|
|
156
|
-
|
|
157
|
-
|
|
395
|
+
const schemaProp = prop;
|
|
396
|
+
if (schemaProp.default !== void 0) {
|
|
397
|
+
defaults[key] = schemaProp.default;
|
|
158
398
|
}
|
|
159
399
|
}
|
|
160
400
|
}
|
|
161
401
|
return { ...defaults, ...userConfig };
|
|
162
402
|
}
|
|
163
|
-
function buildNodeRedNodes(configNodes) {
|
|
164
|
-
const nodes = {};
|
|
165
|
-
for (const [id, value] of Object.entries(configNodes)) {
|
|
166
|
-
if (value && typeof value === "object" && "id" in value) {
|
|
167
|
-
nodes[id] = { _node: value };
|
|
168
|
-
} else {
|
|
169
|
-
nodes[id] = value;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return nodes;
|
|
173
|
-
}
|
|
174
403
|
function attachHelpers(node, nodeRedNode) {
|
|
175
404
|
const sentMessages = [];
|
|
176
405
|
const statusCalls = [];
|
|
@@ -248,9 +477,11 @@ async function createNode(NodeClass, options = {}) {
|
|
|
248
477
|
resolvedConfig[key] = value;
|
|
249
478
|
}
|
|
250
479
|
}
|
|
251
|
-
const
|
|
252
|
-
const RED = createMockRED({ nodes: redNodes, settings });
|
|
480
|
+
const RED = createNodeRedRuntime({ settings });
|
|
253
481
|
initValidator(RED);
|
|
482
|
+
for (const [id, value] of Object.entries(configNodes)) {
|
|
483
|
+
RED.registerNrgNode(id, value);
|
|
484
|
+
}
|
|
254
485
|
const configDefaults = {
|
|
255
486
|
id: overrideOpts.id ?? `test-${Math.random().toString(36).slice(2, 10)}`,
|
|
256
487
|
type: NodeClass.type
|
|
@@ -262,7 +493,7 @@ async function createNode(NodeClass, options = {}) {
|
|
|
262
493
|
...configDefaults,
|
|
263
494
|
...resolvedConfig
|
|
264
495
|
});
|
|
265
|
-
const nodeRedNode =
|
|
496
|
+
const nodeRedNode = createNodeRedNode({
|
|
266
497
|
id: config.id,
|
|
267
498
|
type: NodeClass.type,
|
|
268
499
|
name: config.name ?? "",
|