@bonsae/nrg 0.13.1 → 0.15.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 +549 -453
- package/server/resources/nrg-client.js +2114 -2097
- package/test/index.js +295 -44
- package/types/server.d.ts +360 -1854
- 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 +414 -32
- package/types/vite.d.ts +164 -7
- package/vite/index.js +289 -40
- package/server/resources/vue.esm-browser.js +0 -18708
- package/server/resources/vue.esm-browser.prod.js +0 -13
package/server/index.cjs
CHANGED
|
@@ -30,203 +30,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/core/server/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
CompletePortSchema: () => CompletePortSchema,
|
|
33
34
|
ConfigNode: () => ConfigNode,
|
|
35
|
+
ErrorPortSchema: () => ErrorPortSchema,
|
|
34
36
|
IONode: () => IONode,
|
|
35
37
|
Node: () => Node,
|
|
36
38
|
NrgError: () => NrgError,
|
|
37
39
|
SchemaType: () => SchemaType,
|
|
40
|
+
StatusPortSchema: () => StatusPortSchema,
|
|
38
41
|
defineConfigNode: () => defineConfigNode,
|
|
39
42
|
defineIONode: () => defineIONode,
|
|
40
43
|
defineModule: () => defineModule,
|
|
41
44
|
defineSchema: () => defineSchema,
|
|
42
|
-
initValidator: () => initValidator,
|
|
43
45
|
registerType: () => registerType,
|
|
44
46
|
registerTypes: () => registerTypes
|
|
45
47
|
});
|
|
46
48
|
module.exports = __toCommonJS(index_exports);
|
|
47
49
|
|
|
48
|
-
// src/core/server/utils.ts
|
|
49
|
-
|
|
50
|
-
const result = {};
|
|
51
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
52
|
-
const property = value;
|
|
53
|
-
result[key] = {
|
|
54
|
-
// NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
|
|
55
|
-
required: false,
|
|
56
|
-
type: property.format === "password" ? "password" : "text",
|
|
57
|
-
value: property.default ?? void 0
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/core/validator.ts
|
|
64
|
-
var import_ajv = __toESM(require("ajv"), 1);
|
|
65
|
-
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
|
66
|
-
var import_ajv_errors = __toESM(require("ajv-errors"), 1);
|
|
67
|
-
var Validator = class {
|
|
68
|
-
ajv;
|
|
69
|
-
constructor(options) {
|
|
70
|
-
const { customKeywords, customFormats, ...ajvOptions } = options || {};
|
|
71
|
-
this.ajv = new import_ajv.default({
|
|
72
|
-
allErrors: true,
|
|
73
|
-
code: {
|
|
74
|
-
source: false
|
|
75
|
-
},
|
|
76
|
-
coerceTypes: true,
|
|
77
|
-
removeAdditional: false,
|
|
78
|
-
strict: false,
|
|
79
|
-
strictSchema: false,
|
|
80
|
-
useDefaults: true,
|
|
81
|
-
validateFormats: true,
|
|
82
|
-
// NOTE: typebox handles validation via typescript
|
|
83
|
-
// NOTE: if true, types that are not serializable JSON, like Function, would not work
|
|
84
|
-
validateSchema: false,
|
|
85
|
-
verbose: true,
|
|
86
|
-
...ajvOptions
|
|
87
|
-
});
|
|
88
|
-
(0, import_ajv_formats.default)(this.ajv);
|
|
89
|
-
(0, import_ajv_errors.default)(this.ajv);
|
|
90
|
-
this.addCustomKeywords(customKeywords || []);
|
|
91
|
-
this.addCustomFormats(customFormats || {});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Add custom keywords to the validator
|
|
95
|
-
*/
|
|
96
|
-
addCustomKeywords(keywords) {
|
|
97
|
-
if (!keywords) return;
|
|
98
|
-
keywords.forEach((keyword) => {
|
|
99
|
-
this.ajv.addKeyword(keyword);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Add custom formats to the validator
|
|
104
|
-
*/
|
|
105
|
-
addCustomFormats(formats) {
|
|
106
|
-
if (!formats) return;
|
|
107
|
-
Object.entries(formats).forEach(([name, validator2]) => {
|
|
108
|
-
if (validator2 instanceof RegExp) {
|
|
109
|
-
this.ajv.addFormat(name, validator2);
|
|
110
|
-
} else {
|
|
111
|
-
this.ajv.addFormat(name, { validate: validator2 });
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Create a validator function with caching
|
|
117
|
-
* @param schema - JSON Schema to validate against
|
|
118
|
-
* @param cacheKey - Optional cache key for reusing validators
|
|
119
|
-
*/
|
|
120
|
-
createValidator(schema, cacheKey) {
|
|
121
|
-
if (cacheKey && !schema.$id) {
|
|
122
|
-
schema.$id = cacheKey;
|
|
123
|
-
}
|
|
124
|
-
if (schema.$id) {
|
|
125
|
-
const cached = this.ajv.getSchema(schema.$id);
|
|
126
|
-
if (cached) return cached;
|
|
127
|
-
}
|
|
128
|
-
const validator2 = this.ajv.compile(schema);
|
|
129
|
-
return validator2;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Validate data against a schema and return a structured result
|
|
133
|
-
*/
|
|
134
|
-
validate(data, schema, options) {
|
|
135
|
-
const validator2 = this.createValidator(schema, options?.cacheKey);
|
|
136
|
-
const valid = validator2(data);
|
|
137
|
-
if (!valid) {
|
|
138
|
-
const errorMessage = this.formatErrors(validator2.errors);
|
|
139
|
-
if (options?.throwOnError) {
|
|
140
|
-
throw new ValidationError(errorMessage, validator2.errors || []);
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
valid: false,
|
|
144
|
-
errors: validator2.errors || void 0,
|
|
145
|
-
errorMessage
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
valid: true,
|
|
150
|
-
data
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Format errors into a human-readable string
|
|
155
|
-
*/
|
|
156
|
-
formatErrors(errors, options) {
|
|
157
|
-
if (!errors || errors.length === 0) {
|
|
158
|
-
return "No errors";
|
|
159
|
-
}
|
|
160
|
-
return this.ajv.errorsText(errors, {
|
|
161
|
-
separator: "; ",
|
|
162
|
-
dataVar: "data",
|
|
163
|
-
...options
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Get detailed error information
|
|
168
|
-
*/
|
|
169
|
-
getDetailedErrors(errors) {
|
|
170
|
-
if (!errors || errors.length === 0) return [];
|
|
171
|
-
return errors.map((error) => ({
|
|
172
|
-
field: error.instancePath || "/",
|
|
173
|
-
message: error.message || "Validation failed",
|
|
174
|
-
keyword: error.keyword,
|
|
175
|
-
params: error.params,
|
|
176
|
-
schemaPath: error.schemaPath
|
|
177
|
-
}));
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Add a schema to the validator for reference
|
|
181
|
-
*/
|
|
182
|
-
addSchema(schema, key) {
|
|
183
|
-
this.ajv.addSchema(schema, key);
|
|
184
|
-
return this;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Remove a schema from the validator
|
|
188
|
-
*/
|
|
189
|
-
removeSchema(key) {
|
|
190
|
-
this.ajv.removeSchema(key);
|
|
191
|
-
return this;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
var ValidationError = class _ValidationError extends Error {
|
|
195
|
-
constructor(message, errors) {
|
|
196
|
-
super(message);
|
|
197
|
-
this.errors = errors;
|
|
198
|
-
this.name = "ValidationError";
|
|
199
|
-
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
// src/core/server/validation.ts
|
|
204
|
-
var validator = void 0;
|
|
205
|
-
function initValidator(RED) {
|
|
206
|
-
validator = new Validator({
|
|
207
|
-
customKeywords: [
|
|
208
|
-
{
|
|
209
|
-
keyword: "x-nrg-skip-validation",
|
|
210
|
-
schemaType: "boolean",
|
|
211
|
-
valid: true
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
keyword: "x-nrg-node-type",
|
|
215
|
-
type: "string",
|
|
216
|
-
validate: (schemaValue, dataValue) => {
|
|
217
|
-
if (!dataValue) return true;
|
|
218
|
-
const node = RED.nodes.getNode(dataValue);
|
|
219
|
-
return node?.type === schemaValue;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
],
|
|
223
|
-
customFormats: {
|
|
224
|
-
"node-id": /^[a-zA-Z0-9-_]+$/,
|
|
225
|
-
"flow-id": /^[a-f0-9]{16}$/,
|
|
226
|
-
"topic-path": (data) => /^[a-zA-Z0-9/_-]+$/.test(data)
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
}
|
|
50
|
+
// src/core/server/nodes/utils.ts
|
|
51
|
+
var import_typebox = require("@sinclair/typebox");
|
|
230
52
|
|
|
231
53
|
// src/core/errors.ts
|
|
232
54
|
var NrgError = class _NrgError extends Error {
|
|
@@ -280,6 +102,9 @@ var TypedInput = class {
|
|
|
280
102
|
};
|
|
281
103
|
|
|
282
104
|
// src/core/server/nodes/utils.ts
|
|
105
|
+
function isSchemaLike(obj) {
|
|
106
|
+
return obj != null && typeof obj === "object" && import_typebox.Kind in obj;
|
|
107
|
+
}
|
|
283
108
|
function setupContext(context, store) {
|
|
284
109
|
return {
|
|
285
110
|
get: (key) => new Promise(
|
|
@@ -358,29 +183,32 @@ function setupConfigProxy(opts) {
|
|
|
358
183
|
return createProxy(config);
|
|
359
184
|
}
|
|
360
185
|
|
|
186
|
+
// src/core/server/utils.ts
|
|
187
|
+
function getCredentialsFromSchema(schema) {
|
|
188
|
+
const result = {};
|
|
189
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
190
|
+
const property = value;
|
|
191
|
+
result[key] = {
|
|
192
|
+
// NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
|
|
193
|
+
required: false,
|
|
194
|
+
type: property.format === "password" ? "password" : "text",
|
|
195
|
+
value: property.default ?? void 0
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/core/server/nodes/symbols.ts
|
|
202
|
+
var WIRE_HANDLERS = Symbol("wireHandlers");
|
|
203
|
+
|
|
361
204
|
// src/core/server/nodes/node.ts
|
|
362
|
-
var
|
|
205
|
+
var cachedSettingsMap = /* @__PURE__ */ new WeakMap();
|
|
206
|
+
var Node = class _Node {
|
|
363
207
|
static type;
|
|
364
208
|
static category;
|
|
365
209
|
static configSchema;
|
|
366
210
|
static credentialsSchema;
|
|
367
211
|
static settingsSchema;
|
|
368
|
-
static _cachedSettings = null;
|
|
369
|
-
/** @internal */
|
|
370
|
-
static _settings() {
|
|
371
|
-
if (!this.settingsSchema) return;
|
|
372
|
-
const settings = {};
|
|
373
|
-
const prefix = this.type.replace(/-./g, (x) => x[1].toUpperCase());
|
|
374
|
-
for (const [key, prop] of Object.entries(this.settingsSchema.properties)) {
|
|
375
|
-
const settingKey = prefix + key.charAt(0).toUpperCase() + key.slice(1);
|
|
376
|
-
settings[settingKey] = {
|
|
377
|
-
value: prop.default,
|
|
378
|
-
exportable: prop.exportable ?? false
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
return settings;
|
|
382
|
-
}
|
|
383
|
-
// NOTE:
|
|
384
212
|
static validateSettings(RED) {
|
|
385
213
|
if (!this.settingsSchema) return;
|
|
386
214
|
RED.log.info("Validating settings");
|
|
@@ -402,13 +230,65 @@ var Node = class {
|
|
|
402
230
|
}
|
|
403
231
|
}
|
|
404
232
|
}
|
|
405
|
-
validator.validate(settings, this.settingsSchema, {
|
|
233
|
+
RED.validator.validate(settings, this.settingsSchema, {
|
|
406
234
|
cacheKey: this.settingsSchema.$id || `${this.type}:settings`,
|
|
407
235
|
throwOnError: true
|
|
408
236
|
});
|
|
409
|
-
this
|
|
237
|
+
cachedSettingsMap.set(this, settings);
|
|
410
238
|
RED.log.info("Settings are valid");
|
|
411
239
|
}
|
|
240
|
+
static #buildSettings(NC) {
|
|
241
|
+
if (!NC.settingsSchema) return;
|
|
242
|
+
const settings = {};
|
|
243
|
+
const prefix = NC.type.replace(/-./g, (x) => x[1].toUpperCase());
|
|
244
|
+
for (const [key, prop] of Object.entries(NC.settingsSchema.properties)) {
|
|
245
|
+
const settingKey = prefix + key.charAt(0).toUpperCase() + key.slice(1);
|
|
246
|
+
settings[settingKey] = {
|
|
247
|
+
value: prop.default,
|
|
248
|
+
exportable: prop.exportable ?? false
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return settings;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Registers this node class with Node-RED. Handles instance creation,
|
|
255
|
+
* event handler wiring, settings validation, and the user's registered() hook.
|
|
256
|
+
*/
|
|
257
|
+
static async register(RED) {
|
|
258
|
+
const NodeClass = this;
|
|
259
|
+
if (NodeClass.color && !/^#[0-9A-Fa-f]{6}$/.test(NodeClass.color)) {
|
|
260
|
+
throw new NrgError(
|
|
261
|
+
`Invalid color for ${NodeClass.type}: ${NodeClass.color} color must be in hex format`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
RED.nodes.registerType(
|
|
265
|
+
NodeClass.type,
|
|
266
|
+
function(config) {
|
|
267
|
+
RED.nodes.createNode(this, config);
|
|
268
|
+
const node = new NodeClass(RED, this, config, this.credentials);
|
|
269
|
+
Object.defineProperty(this, "_node", {
|
|
270
|
+
value: node,
|
|
271
|
+
writable: false,
|
|
272
|
+
configurable: false,
|
|
273
|
+
enumerable: false
|
|
274
|
+
});
|
|
275
|
+
const createdPromise = Promise.resolve(node.created?.()).catch(
|
|
276
|
+
(error) => {
|
|
277
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
278
|
+
this.error("Error during created hook: " + message);
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
node[WIRE_HANDLERS](this, createdPromise);
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
credentials: NodeClass.credentialsSchema ? getCredentialsFromSchema(NodeClass.credentialsSchema) : {},
|
|
286
|
+
settings: _Node.#buildSettings(this)
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
NodeClass.validateSettings(RED);
|
|
290
|
+
await Promise.resolve(NodeClass.registered?.(RED));
|
|
291
|
+
}
|
|
412
292
|
RED;
|
|
413
293
|
node;
|
|
414
294
|
context;
|
|
@@ -421,7 +301,7 @@ var Node = class {
|
|
|
421
301
|
const constructor = this.constructor;
|
|
422
302
|
if (constructor.configSchema) {
|
|
423
303
|
this.log("Validating configs");
|
|
424
|
-
const configResult = validator.validate(
|
|
304
|
+
const configResult = this.RED.validator.validate(
|
|
425
305
|
config,
|
|
426
306
|
constructor.configSchema,
|
|
427
307
|
{
|
|
@@ -443,7 +323,7 @@ var Node = class {
|
|
|
443
323
|
});
|
|
444
324
|
if (constructor.credentialsSchema && credentials) {
|
|
445
325
|
this.log("Validating credentials");
|
|
446
|
-
const credResult = validator.validate(
|
|
326
|
+
const credResult = this.RED.validator.validate(
|
|
447
327
|
credentials,
|
|
448
328
|
constructor.credentialsSchema,
|
|
449
329
|
{
|
|
@@ -458,6 +338,39 @@ var Node = class {
|
|
|
458
338
|
}
|
|
459
339
|
}
|
|
460
340
|
}
|
|
341
|
+
[WIRE_HANDLERS](nodeRedNode, createdPromise) {
|
|
342
|
+
nodeRedNode.on(
|
|
343
|
+
"close",
|
|
344
|
+
async (removed, done) => {
|
|
345
|
+
try {
|
|
346
|
+
this.log("Calling closed");
|
|
347
|
+
await this.#closed(removed);
|
|
348
|
+
this.log("Node was closed");
|
|
349
|
+
done();
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (error instanceof Error) {
|
|
352
|
+
this.error("Error while closing node: " + error.message);
|
|
353
|
+
done(error);
|
|
354
|
+
} else {
|
|
355
|
+
this.error("Unknown error occurred while closing node");
|
|
356
|
+
done(new Error("Unknown error occurred while closing node"));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
async #closed(removed) {
|
|
363
|
+
try {
|
|
364
|
+
await Promise.resolve(this.closed?.(removed));
|
|
365
|
+
} finally {
|
|
366
|
+
this.log("clearing timers and intervals");
|
|
367
|
+
this.timers.forEach((t) => clearTimeout(t));
|
|
368
|
+
this.intervals.forEach((i) => clearInterval(i));
|
|
369
|
+
this.timers.clear();
|
|
370
|
+
this.intervals.clear();
|
|
371
|
+
this.log("timers and intervals cleared");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
461
374
|
i18n(key, substitutions) {
|
|
462
375
|
const nodeType = this.constructor.type;
|
|
463
376
|
return this.RED._(`${nodeType}.${key}`, substitutions);
|
|
@@ -483,20 +396,6 @@ var Node = class {
|
|
|
483
396
|
clearInterval(interval);
|
|
484
397
|
this.intervals.delete(interval);
|
|
485
398
|
}
|
|
486
|
-
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's closed again
|
|
487
|
-
/** @internal */
|
|
488
|
-
async _closed(removed) {
|
|
489
|
-
try {
|
|
490
|
-
await Promise.resolve(this.closed?.(removed));
|
|
491
|
-
} finally {
|
|
492
|
-
this.log("clearing timers and intervals");
|
|
493
|
-
this.timers.forEach((t) => clearTimeout(t));
|
|
494
|
-
this.intervals.forEach((i) => clearInterval(i));
|
|
495
|
-
this.timers.clear();
|
|
496
|
-
this.intervals.clear();
|
|
497
|
-
this.log("timers and intervals cleared");
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
399
|
on(event, callback) {
|
|
501
400
|
this.node.on(event, callback);
|
|
502
401
|
}
|
|
@@ -523,7 +422,7 @@ var Node = class {
|
|
|
523
422
|
}
|
|
524
423
|
get settings() {
|
|
525
424
|
const constructor = this.constructor;
|
|
526
|
-
return constructor
|
|
425
|
+
return cachedSettingsMap.get(constructor) ?? {};
|
|
527
426
|
}
|
|
528
427
|
};
|
|
529
428
|
|
|
@@ -541,16 +440,20 @@ var IONode = class extends Node {
|
|
|
541
440
|
static get outputs() {
|
|
542
441
|
const s = this.outputsSchema;
|
|
543
442
|
if (!s) return 0;
|
|
544
|
-
|
|
443
|
+
if (Array.isArray(s)) return s.length;
|
|
444
|
+
if (isSchemaLike(s)) return 1;
|
|
445
|
+
const keys = Object.keys(s);
|
|
446
|
+
for (const key of keys) {
|
|
447
|
+
if (/^\d+$/.test(key)) {
|
|
448
|
+
throw new NrgError(
|
|
449
|
+
`outputsSchema record key "${key}" in ${this.type} looks numeric. Use descriptive string names (e.g. "success", "failure") to avoid JavaScript object key ordering issues.`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return keys.length;
|
|
545
454
|
}
|
|
546
|
-
|
|
455
|
+
#send;
|
|
547
456
|
context;
|
|
548
|
-
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
549
|
-
/** @internal */
|
|
550
|
-
static _registered(RED) {
|
|
551
|
-
this.validateSettings(RED);
|
|
552
|
-
return this.registered?.(RED);
|
|
553
|
-
}
|
|
554
457
|
constructor(RED, node, config, credentials) {
|
|
555
458
|
super(RED, node, config, credentials);
|
|
556
459
|
const context = node.context();
|
|
@@ -563,26 +466,73 @@ var IONode = class extends Node {
|
|
|
563
466
|
fn.global = setupContext(context.global);
|
|
564
467
|
this.context = fn;
|
|
565
468
|
}
|
|
469
|
+
[WIRE_HANDLERS](nodeRedNode, createdPromise) {
|
|
470
|
+
super[WIRE_HANDLERS](nodeRedNode, createdPromise);
|
|
471
|
+
const NC = this.constructor;
|
|
472
|
+
nodeRedNode.on(
|
|
473
|
+
"input",
|
|
474
|
+
async (msg, send, done) => {
|
|
475
|
+
try {
|
|
476
|
+
await createdPromise;
|
|
477
|
+
} catch {
|
|
478
|
+
done(new Error("Node failed to initialize"));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
nodeRedNode.log("Calling input");
|
|
483
|
+
await Promise.resolve(this.#input(msg, send));
|
|
484
|
+
this.#sendToPort("complete", {
|
|
485
|
+
...msg,
|
|
486
|
+
complete: {
|
|
487
|
+
source: this.#nodeSource()
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
done();
|
|
491
|
+
nodeRedNode.log("Input processed");
|
|
492
|
+
} catch (error) {
|
|
493
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error during input handling";
|
|
494
|
+
this.#sendToPort("error", {
|
|
495
|
+
...msg,
|
|
496
|
+
error: {
|
|
497
|
+
message: errorMsg,
|
|
498
|
+
source: this.#nodeSource()
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
if (error instanceof Error) {
|
|
502
|
+
nodeRedNode.error(
|
|
503
|
+
"Error while processing input: " + error.message,
|
|
504
|
+
msg
|
|
505
|
+
);
|
|
506
|
+
done(error);
|
|
507
|
+
} else {
|
|
508
|
+
nodeRedNode.error(
|
|
509
|
+
"Unknown error occurred during input handling",
|
|
510
|
+
msg
|
|
511
|
+
);
|
|
512
|
+
done(new Error(errorMsg));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
}
|
|
566
518
|
input(msg) {
|
|
567
519
|
}
|
|
568
|
-
|
|
569
|
-
/** @internal */
|
|
570
|
-
async _input(msg, send) {
|
|
520
|
+
async #input(msg, send) {
|
|
571
521
|
const NodeClass = this.constructor;
|
|
572
522
|
const shouldValidateInput = this.config.validateInput ?? NodeClass.validateInput;
|
|
573
523
|
if (shouldValidateInput && NodeClass.inputSchema) {
|
|
574
524
|
this.log("Validating input");
|
|
575
|
-
validator.validate(msg, NodeClass.inputSchema, {
|
|
525
|
+
this.RED.validator.validate(msg, NodeClass.inputSchema, {
|
|
576
526
|
cacheKey: NodeClass.inputSchema.$id || `${NodeClass.type}:input-schema`,
|
|
577
527
|
throwOnError: true
|
|
578
528
|
});
|
|
579
529
|
this.log("Input is valid");
|
|
580
530
|
}
|
|
581
|
-
this
|
|
531
|
+
this.#send = send;
|
|
582
532
|
try {
|
|
583
533
|
await Promise.resolve(this.input(msg));
|
|
584
534
|
} finally {
|
|
585
|
-
this
|
|
535
|
+
this.#send = void 0;
|
|
586
536
|
}
|
|
587
537
|
}
|
|
588
538
|
send(msg) {
|
|
@@ -590,78 +540,108 @@ var IONode = class extends Node {
|
|
|
590
540
|
const shouldValidateOutput = this.config.validateOutput ?? NodeClass.validateOutput;
|
|
591
541
|
if (shouldValidateOutput && NodeClass.outputsSchema) {
|
|
592
542
|
this.log("Validating output");
|
|
593
|
-
const
|
|
594
|
-
if (Array.isArray(
|
|
543
|
+
const rawSchema = NodeClass.outputsSchema;
|
|
544
|
+
if (Array.isArray(rawSchema)) {
|
|
595
545
|
const msgs = msg;
|
|
596
|
-
for (let i = 0; i <
|
|
546
|
+
for (let i = 0; i < rawSchema.length; i++) {
|
|
597
547
|
if (msgs[i] == null) continue;
|
|
598
|
-
validator.validate(msgs[i],
|
|
599
|
-
cacheKey:
|
|
548
|
+
this.RED.validator.validate(msgs[i], rawSchema[i], {
|
|
549
|
+
cacheKey: rawSchema[i].$id || `${NodeClass.type}:output-schema:${i}`,
|
|
600
550
|
throwOnError: true
|
|
601
551
|
});
|
|
602
552
|
}
|
|
603
|
-
} else if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
553
|
+
} else if (isSchemaLike(rawSchema)) {
|
|
554
|
+
if (Array.isArray(msg)) {
|
|
555
|
+
const msgs = msg;
|
|
556
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
557
|
+
if (msgs[i] == null) continue;
|
|
558
|
+
this.RED.validator.validate(msgs[i], rawSchema, {
|
|
559
|
+
cacheKey: rawSchema.$id || `${NodeClass.type}:output-schema`,
|
|
560
|
+
throwOnError: true
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
this.RED.validator.validate(msg, rawSchema, {
|
|
565
|
+
cacheKey: rawSchema.$id || `${NodeClass.type}:output-schema`,
|
|
608
566
|
throwOnError: true
|
|
609
567
|
});
|
|
610
568
|
}
|
|
611
569
|
} else {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
570
|
+
const schemaArray = Object.values(rawSchema);
|
|
571
|
+
const msgs = msg;
|
|
572
|
+
for (let i = 0; i < schemaArray.length; i++) {
|
|
573
|
+
if (msgs[i] == null) continue;
|
|
574
|
+
this.RED.validator.validate(msgs[i], schemaArray[i], {
|
|
575
|
+
cacheKey: schemaArray[i].$id || `${NodeClass.type}:output-schema:${i}`,
|
|
576
|
+
throwOnError: true
|
|
577
|
+
});
|
|
578
|
+
}
|
|
616
579
|
}
|
|
617
580
|
this.log("Output is valid");
|
|
618
581
|
}
|
|
619
|
-
if (this
|
|
620
|
-
this
|
|
582
|
+
if (this.#send) {
|
|
583
|
+
this.#send(msg);
|
|
621
584
|
} else {
|
|
622
585
|
this.node.send(msg);
|
|
623
586
|
}
|
|
624
587
|
}
|
|
625
|
-
// ---
|
|
626
|
-
|
|
627
|
-
get _baseOutputs() {
|
|
588
|
+
// --- Built-in port management ---
|
|
589
|
+
get baseOutputs() {
|
|
628
590
|
return this.constructor.outputs ?? 0;
|
|
629
591
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
let count = this.
|
|
633
|
-
if (
|
|
634
|
-
if (
|
|
635
|
-
if (
|
|
592
|
+
get totalOutputs() {
|
|
593
|
+
const config = this.config;
|
|
594
|
+
let count = this.baseOutputs;
|
|
595
|
+
if (config.errorPort) count++;
|
|
596
|
+
if (config.completePort) count++;
|
|
597
|
+
if (config.statusPort) count++;
|
|
636
598
|
return count;
|
|
637
599
|
}
|
|
638
|
-
/**
|
|
639
|
-
|
|
640
|
-
|
|
600
|
+
/**
|
|
601
|
+
* Send a message to a specific output port by index or name.
|
|
602
|
+
* Built-in ports: `"error"`, `"complete"`, `"status"` — resolved automatically
|
|
603
|
+
* based on the node's built-in port configuration.
|
|
604
|
+
* Custom named ports are resolved from `outputsSchema` when it is a record.
|
|
605
|
+
* Numeric indices refer to the base output ports (0-based).
|
|
606
|
+
*/
|
|
607
|
+
sendToPort(port, msg) {
|
|
608
|
+
this.#sendToPort(port, msg);
|
|
609
|
+
}
|
|
610
|
+
#sendToPort(port, msg) {
|
|
611
|
+
let portIndex;
|
|
612
|
+
if (typeof port === "number") {
|
|
613
|
+
portIndex = port;
|
|
614
|
+
} else if (port === "error" || port === "complete" || port === "status") {
|
|
615
|
+
portIndex = this.#getBuiltinPortIndex(port);
|
|
616
|
+
if (portIndex === null) return;
|
|
617
|
+
} else {
|
|
618
|
+
portIndex = this.#getNamedPortIndex(port);
|
|
619
|
+
if (portIndex === null) return;
|
|
620
|
+
}
|
|
621
|
+
const out = Array(this.totalOutputs).fill(null);
|
|
641
622
|
out[portIndex] = msg;
|
|
642
623
|
this.node.send(out);
|
|
643
624
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (!
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
_nodeSource() {
|
|
625
|
+
#getNamedPortIndex(name) {
|
|
626
|
+
const schema = this.constructor.outputsSchema;
|
|
627
|
+
if (!schema || Array.isArray(schema) || isSchemaLike(schema)) return null;
|
|
628
|
+
const idx = Object.keys(schema).indexOf(name);
|
|
629
|
+
return idx === -1 ? null : idx;
|
|
630
|
+
}
|
|
631
|
+
#getBuiltinPortIndex(name) {
|
|
632
|
+
const config = this.config;
|
|
633
|
+
if (name === "error") {
|
|
634
|
+
return config.errorPort ? this.baseOutputs : null;
|
|
635
|
+
}
|
|
636
|
+
let idx = this.baseOutputs;
|
|
637
|
+
if (config.errorPort) idx++;
|
|
638
|
+
if (name === "complete") {
|
|
639
|
+
return config.completePort ? idx : null;
|
|
640
|
+
}
|
|
641
|
+
if (config.completePort) idx++;
|
|
642
|
+
return config.statusPort ? idx : null;
|
|
643
|
+
}
|
|
644
|
+
#nodeSource() {
|
|
665
645
|
return {
|
|
666
646
|
id: this.id,
|
|
667
647
|
type: this.constructor.type,
|
|
@@ -670,23 +650,19 @@ var IONode = class extends Node {
|
|
|
670
650
|
}
|
|
671
651
|
status(status) {
|
|
672
652
|
this.node.status(status);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
this
|
|
676
|
-
|
|
677
|
-
source: this._nodeSource()
|
|
678
|
-
});
|
|
679
|
-
}
|
|
653
|
+
this.#sendToPort("status", {
|
|
654
|
+
status,
|
|
655
|
+
source: this.#nodeSource()
|
|
656
|
+
});
|
|
680
657
|
}
|
|
681
658
|
error(message, msg) {
|
|
682
659
|
super.error(message, msg);
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
this._sendToPort(portIdx, {
|
|
660
|
+
if (msg) {
|
|
661
|
+
this.#sendToPort("error", {
|
|
686
662
|
...msg,
|
|
687
663
|
error: {
|
|
688
664
|
message,
|
|
689
|
-
source: this
|
|
665
|
+
source: this.#nodeSource()
|
|
690
666
|
}
|
|
691
667
|
});
|
|
692
668
|
}
|
|
@@ -718,12 +694,6 @@ var IONode = class extends Node {
|
|
|
718
694
|
var ConfigNode = class extends Node {
|
|
719
695
|
static category = "config";
|
|
720
696
|
context;
|
|
721
|
-
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
722
|
-
/** @internal */
|
|
723
|
-
static _registered(RED) {
|
|
724
|
-
this.validateSettings(RED);
|
|
725
|
-
return this.registered?.(RED);
|
|
726
|
-
}
|
|
727
697
|
constructor(RED, node, config, credentials) {
|
|
728
698
|
super(RED, node, config, credentials);
|
|
729
699
|
const context = node.context();
|
|
@@ -765,8 +735,7 @@ function defineIONode(def) {
|
|
|
765
735
|
static outputsSchema = def.outputsSchema;
|
|
766
736
|
static validateInput = def.validateInput ?? false;
|
|
767
737
|
static validateOutput = def.validateOutput ?? false;
|
|
768
|
-
static
|
|
769
|
-
this.validateSettings(RED);
|
|
738
|
+
static registered(RED) {
|
|
770
739
|
return def.registered?.(RED);
|
|
771
740
|
}
|
|
772
741
|
async input(msg) {
|
|
@@ -794,8 +763,7 @@ function defineConfigNode(def) {
|
|
|
794
763
|
static configSchema = def.configSchema;
|
|
795
764
|
static credentialsSchema = def.credentialsSchema;
|
|
796
765
|
static settingsSchema = def.settingsSchema;
|
|
797
|
-
static
|
|
798
|
-
this.validateSettings(RED);
|
|
766
|
+
static registered(RED) {
|
|
799
767
|
return def.registered?.(RED);
|
|
800
768
|
}
|
|
801
769
|
async created() {
|
|
@@ -815,29 +783,208 @@ function defineConfigNode(def) {
|
|
|
815
783
|
return NodeClass;
|
|
816
784
|
}
|
|
817
785
|
|
|
818
|
-
// src/core/
|
|
786
|
+
// src/core/validator.ts
|
|
787
|
+
var import_ajv = __toESM(require("ajv"), 1);
|
|
788
|
+
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
|
789
|
+
var import_ajv_errors = __toESM(require("ajv-errors"), 1);
|
|
790
|
+
var Validator = class {
|
|
791
|
+
ajv;
|
|
792
|
+
constructor(options) {
|
|
793
|
+
const { customKeywords, customFormats, ...ajvOptions } = options || {};
|
|
794
|
+
this.ajv = new import_ajv.default({
|
|
795
|
+
allErrors: true,
|
|
796
|
+
code: {
|
|
797
|
+
source: false
|
|
798
|
+
},
|
|
799
|
+
coerceTypes: true,
|
|
800
|
+
removeAdditional: false,
|
|
801
|
+
strict: false,
|
|
802
|
+
strictSchema: false,
|
|
803
|
+
useDefaults: true,
|
|
804
|
+
validateFormats: true,
|
|
805
|
+
// NOTE: typebox handles validation via typescript
|
|
806
|
+
// NOTE: if true, types that are not serializable JSON, like Function, would not work
|
|
807
|
+
validateSchema: false,
|
|
808
|
+
verbose: true,
|
|
809
|
+
...ajvOptions
|
|
810
|
+
});
|
|
811
|
+
(0, import_ajv_formats.default)(this.ajv);
|
|
812
|
+
(0, import_ajv_errors.default)(this.ajv);
|
|
813
|
+
this.addCustomKeywords(customKeywords || []);
|
|
814
|
+
this.addCustomFormats(customFormats || {});
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Add custom keywords to the validator
|
|
818
|
+
*/
|
|
819
|
+
addCustomKeywords(keywords) {
|
|
820
|
+
if (!keywords) return;
|
|
821
|
+
keywords.forEach((keyword) => {
|
|
822
|
+
this.ajv.addKeyword(keyword);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Add custom formats to the validator
|
|
827
|
+
*/
|
|
828
|
+
addCustomFormats(formats) {
|
|
829
|
+
if (!formats) return;
|
|
830
|
+
Object.entries(formats).forEach(([name, validator]) => {
|
|
831
|
+
if (validator instanceof RegExp) {
|
|
832
|
+
this.ajv.addFormat(name, validator);
|
|
833
|
+
} else {
|
|
834
|
+
this.ajv.addFormat(name, { validate: validator });
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Create a validator function with caching
|
|
840
|
+
* @param schema - JSON Schema to validate against
|
|
841
|
+
* @param cacheKey - Optional cache key for reusing validators
|
|
842
|
+
*/
|
|
843
|
+
createValidator(schema, cacheKey) {
|
|
844
|
+
if (cacheKey && !schema.$id) {
|
|
845
|
+
schema.$id = cacheKey;
|
|
846
|
+
}
|
|
847
|
+
if (schema.$id) {
|
|
848
|
+
const cached = this.ajv.getSchema(schema.$id);
|
|
849
|
+
if (cached) return cached;
|
|
850
|
+
}
|
|
851
|
+
const validator = this.ajv.compile(schema);
|
|
852
|
+
return validator;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Validate data against a schema and return a structured result
|
|
856
|
+
*/
|
|
857
|
+
validate(data, schema, options) {
|
|
858
|
+
const validator = this.createValidator(schema, options?.cacheKey);
|
|
859
|
+
const valid = validator(data);
|
|
860
|
+
if (!valid) {
|
|
861
|
+
const errorMessage = this.formatErrors(validator.errors);
|
|
862
|
+
if (options?.throwOnError) {
|
|
863
|
+
throw new ValidationError(errorMessage, validator.errors || []);
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
valid: false,
|
|
867
|
+
errors: validator.errors || void 0,
|
|
868
|
+
errorMessage
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
valid: true,
|
|
873
|
+
data
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Format errors into a human-readable string
|
|
878
|
+
*/
|
|
879
|
+
formatErrors(errors, options) {
|
|
880
|
+
if (!errors || errors.length === 0) {
|
|
881
|
+
return "No errors";
|
|
882
|
+
}
|
|
883
|
+
return this.ajv.errorsText(errors, {
|
|
884
|
+
separator: "; ",
|
|
885
|
+
dataVar: "data",
|
|
886
|
+
...options
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Get detailed error information
|
|
891
|
+
*/
|
|
892
|
+
getDetailedErrors(errors) {
|
|
893
|
+
if (!errors || errors.length === 0) return [];
|
|
894
|
+
return errors.map((error) => ({
|
|
895
|
+
field: error.instancePath || "/",
|
|
896
|
+
message: error.message || "Validation failed",
|
|
897
|
+
keyword: error.keyword,
|
|
898
|
+
params: error.params,
|
|
899
|
+
schemaPath: error.schemaPath
|
|
900
|
+
}));
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Add a schema to the validator for reference
|
|
904
|
+
*/
|
|
905
|
+
addSchema(schema, key) {
|
|
906
|
+
this.ajv.addSchema(schema, key);
|
|
907
|
+
return this;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Remove a schema from the validator
|
|
911
|
+
*/
|
|
912
|
+
removeSchema(key) {
|
|
913
|
+
this.ajv.removeSchema(key);
|
|
914
|
+
return this;
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
var ValidationError = class _ValidationError extends Error {
|
|
918
|
+
constructor(message, errors) {
|
|
919
|
+
super(message);
|
|
920
|
+
this.errors = errors;
|
|
921
|
+
this.name = "ValidationError";
|
|
922
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
// src/core/server/validation.ts
|
|
927
|
+
function initValidator(RED) {
|
|
928
|
+
const nrg = {
|
|
929
|
+
validator: new Validator({
|
|
930
|
+
customKeywords: [
|
|
931
|
+
{
|
|
932
|
+
keyword: "x-nrg-skip-validation",
|
|
933
|
+
schemaType: "boolean",
|
|
934
|
+
valid: true
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
keyword: "x-nrg-node-type",
|
|
938
|
+
type: "string",
|
|
939
|
+
validate: (schemaValue, dataValue) => {
|
|
940
|
+
if (!dataValue) return true;
|
|
941
|
+
const node = RED.nodes.getNode(dataValue);
|
|
942
|
+
return node?.type === schemaValue;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
],
|
|
946
|
+
customFormats: {
|
|
947
|
+
"node-id": /^[a-zA-Z0-9-_]+$/,
|
|
948
|
+
"flow-id": /^[a-f0-9]{16}$/,
|
|
949
|
+
"topic-path": (data) => /^[a-zA-Z0-9/_-]+$/.test(data)
|
|
950
|
+
}
|
|
951
|
+
})
|
|
952
|
+
};
|
|
953
|
+
Object.defineProperty(RED, "_nrg", {
|
|
954
|
+
value: nrg,
|
|
955
|
+
writable: false,
|
|
956
|
+
enumerable: false,
|
|
957
|
+
configurable: false
|
|
958
|
+
});
|
|
959
|
+
Object.defineProperty(RED, "validator", {
|
|
960
|
+
get: () => nrg.validator,
|
|
961
|
+
enumerable: false,
|
|
962
|
+
configurable: false
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// src/core/server/api/assets.ts
|
|
819
967
|
var import_path = __toESM(require("path"), 1);
|
|
820
968
|
var import_fs = __toESM(require("fs"), 1);
|
|
821
|
-
var
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
router.get("/nrg/assets/*", handleNrgAsset);
|
|
969
|
+
var import_module = require("module");
|
|
970
|
+
function serveFile(filePath) {
|
|
971
|
+
return (_req, res, next) => {
|
|
972
|
+
if (!import_fs.default.existsSync(filePath)) return next();
|
|
973
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
974
|
+
import_fs.default.createReadStream(filePath).pipe(res);
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function initAssetsRoutes(router) {
|
|
978
|
+
const resourcesDir = import_path.default.resolve(__dirname, "./resources");
|
|
979
|
+
if (!import_fs.default.existsSync(resourcesDir)) return;
|
|
980
|
+
const _require = (0, import_module.createRequire)(import_path.default.join(__dirname, "package.json"));
|
|
981
|
+
const vueFile = process.env.NODE_ENV !== "production" ? _require.resolve("vue/dist/vue.esm-browser.js") : _require.resolve("vue/dist/vue.esm-browser.prod.js");
|
|
982
|
+
router.get(
|
|
983
|
+
"/nrg/assets/nrg-client.js",
|
|
984
|
+
serveFile(import_path.default.join(resourcesDir, "nrg-client.js"))
|
|
985
|
+
);
|
|
986
|
+
router.get("/nrg/assets/vue.esm-browser.prod.js", serveFile(vueFile));
|
|
987
|
+
router.get("/nrg/assets/vue.esm-browser.js", serveFile(vueFile));
|
|
841
988
|
}
|
|
842
989
|
|
|
843
990
|
// src/core/server/api/routes.ts
|
|
@@ -845,7 +992,42 @@ var _initialized = false;
|
|
|
845
992
|
function initRoutes(RED) {
|
|
846
993
|
if (_initialized) return;
|
|
847
994
|
_initialized = true;
|
|
848
|
-
|
|
995
|
+
initAssetsRoutes(RED.httpAdmin);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/core/server/registration.ts
|
|
999
|
+
async function registerType(RED, NodeClass) {
|
|
1000
|
+
RED.log.debug(`Registering Type: ${NodeClass.type}`);
|
|
1001
|
+
if (!(NodeClass.prototype instanceof Node)) {
|
|
1002
|
+
throw new NrgError(
|
|
1003
|
+
`${NodeClass.name} must extend IONode or ConfigNode classes`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
if (!NodeClass.type) {
|
|
1007
|
+
throw new NrgError("type must be provided when registering the node");
|
|
1008
|
+
}
|
|
1009
|
+
await NodeClass.register(RED);
|
|
1010
|
+
RED.log.debug(`Type registered: ${NodeClass.type}`);
|
|
1011
|
+
}
|
|
1012
|
+
function registerTypes(nodes) {
|
|
1013
|
+
const fn = Object.assign(
|
|
1014
|
+
async function(RED) {
|
|
1015
|
+
initValidator(RED);
|
|
1016
|
+
initRoutes(RED);
|
|
1017
|
+
try {
|
|
1018
|
+
RED.log.info("Registering node types in series");
|
|
1019
|
+
for (const NodeClass of nodes) {
|
|
1020
|
+
await registerType(RED, NodeClass);
|
|
1021
|
+
}
|
|
1022
|
+
RED.log.info("All node types registered in series");
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
RED.log.error("Error registering node types:", error);
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
{ nodes }
|
|
1029
|
+
);
|
|
1030
|
+
return fn;
|
|
849
1031
|
}
|
|
850
1032
|
|
|
851
1033
|
// src/core/constants.ts
|
|
@@ -867,17 +1049,17 @@ var TYPED_INPUT_TYPES = [
|
|
|
867
1049
|
];
|
|
868
1050
|
|
|
869
1051
|
// src/core/server/schemas/type.ts
|
|
870
|
-
var
|
|
1052
|
+
var import_typebox2 = require("@sinclair/typebox");
|
|
871
1053
|
var import_rules = require("ajv/dist/compile/rules");
|
|
872
1054
|
function NodeRef(nodeClass, options) {
|
|
873
1055
|
return {
|
|
874
|
-
...
|
|
1056
|
+
...import_typebox2.Type.String({
|
|
875
1057
|
description: options?.description || `Reference to ${nodeClass.type}`,
|
|
876
1058
|
format: "node-id"
|
|
877
1059
|
}),
|
|
878
1060
|
"x-nrg-node-type": nodeClass.type,
|
|
879
1061
|
...options,
|
|
880
|
-
[
|
|
1062
|
+
[import_typebox2.Kind]: "NodeRef"
|
|
881
1063
|
};
|
|
882
1064
|
}
|
|
883
1065
|
function TypedInput2(options) {
|
|
@@ -885,10 +1067,10 @@ function TypedInput2(options) {
|
|
|
885
1067
|
...TypedInputSchema,
|
|
886
1068
|
"x-nrg-typed-input": true,
|
|
887
1069
|
...options,
|
|
888
|
-
[
|
|
1070
|
+
[import_typebox2.Kind]: "TypedInput"
|
|
889
1071
|
};
|
|
890
1072
|
}
|
|
891
|
-
var SchemaType = Object.assign({},
|
|
1073
|
+
var SchemaType = Object.assign({}, import_typebox2.Type, {
|
|
892
1074
|
NodeRef,
|
|
893
1075
|
TypedInput: TypedInput2
|
|
894
1076
|
});
|
|
@@ -980,145 +1162,59 @@ var TypedInputSchema = SchemaType.Object(
|
|
|
980
1162
|
}
|
|
981
1163
|
}
|
|
982
1164
|
);
|
|
1165
|
+
var NodeSourceSchema = SchemaType.Object({
|
|
1166
|
+
id: SchemaType.String(),
|
|
1167
|
+
type: SchemaType.String(),
|
|
1168
|
+
name: SchemaType.String()
|
|
1169
|
+
});
|
|
1170
|
+
var ErrorPortSchema = SchemaType.Object({
|
|
1171
|
+
error: SchemaType.Object({
|
|
1172
|
+
message: SchemaType.String(),
|
|
1173
|
+
source: NodeSourceSchema
|
|
1174
|
+
})
|
|
1175
|
+
});
|
|
1176
|
+
var CompletePortSchema = SchemaType.Object({
|
|
1177
|
+
complete: SchemaType.Object({
|
|
1178
|
+
source: NodeSourceSchema
|
|
1179
|
+
})
|
|
1180
|
+
});
|
|
1181
|
+
var StatusPortSchema = SchemaType.Object({
|
|
1182
|
+
status: SchemaType.Object({
|
|
1183
|
+
fill: SchemaType.Optional(
|
|
1184
|
+
SchemaType.Union([
|
|
1185
|
+
SchemaType.Literal("red"),
|
|
1186
|
+
SchemaType.Literal("green")
|
|
1187
|
+
])
|
|
1188
|
+
),
|
|
1189
|
+
shape: SchemaType.Optional(
|
|
1190
|
+
SchemaType.Union([
|
|
1191
|
+
SchemaType.Literal("dot"),
|
|
1192
|
+
SchemaType.Literal("string")
|
|
1193
|
+
])
|
|
1194
|
+
),
|
|
1195
|
+
text: SchemaType.Optional(SchemaType.String())
|
|
1196
|
+
}),
|
|
1197
|
+
source: NodeSourceSchema
|
|
1198
|
+
});
|
|
983
1199
|
|
|
984
1200
|
// src/core/server/index.ts
|
|
985
|
-
async function registerType(RED, NodeClass) {
|
|
986
|
-
const NC = NodeClass;
|
|
987
|
-
RED.log.debug(`Registering Type: ${NC.type}`);
|
|
988
|
-
if (!(NC.prototype instanceof Node)) {
|
|
989
|
-
throw new NrgError(`${NC.name} must extend IONode or ConfigNode classes`);
|
|
990
|
-
}
|
|
991
|
-
if (!NC.type) {
|
|
992
|
-
throw new NrgError("type must be provided when registering the node");
|
|
993
|
-
}
|
|
994
|
-
if (NC.color && !/^#[0-9A-Fa-f]{6}$/.test(NC.color)) {
|
|
995
|
-
throw new NrgError(
|
|
996
|
-
`Invalid color for ${NodeClass.type}: ${NC.color} color must be in hex format`
|
|
997
|
-
);
|
|
998
|
-
}
|
|
999
|
-
RED.nodes.registerType(
|
|
1000
|
-
NC.type,
|
|
1001
|
-
function(config) {
|
|
1002
|
-
RED.nodes.createNode(this, config);
|
|
1003
|
-
const node = new NC(RED, this, config, this.credentials);
|
|
1004
|
-
Object.defineProperty(this, "_node", {
|
|
1005
|
-
value: node,
|
|
1006
|
-
writable: false,
|
|
1007
|
-
configurable: false,
|
|
1008
|
-
enumerable: false
|
|
1009
|
-
});
|
|
1010
|
-
const createdPromise = Promise.resolve(node.created?.()).catch(
|
|
1011
|
-
(error) => {
|
|
1012
|
-
this.error("Error during created hook: " + error.message);
|
|
1013
|
-
throw error;
|
|
1014
|
-
}
|
|
1015
|
-
);
|
|
1016
|
-
this.on(
|
|
1017
|
-
"input",
|
|
1018
|
-
async (msg, send, done) => {
|
|
1019
|
-
try {
|
|
1020
|
-
await createdPromise;
|
|
1021
|
-
} catch {
|
|
1022
|
-
done(new Error("Node failed to initialize"));
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
try {
|
|
1026
|
-
this.log("Calling input");
|
|
1027
|
-
await Promise.resolve(node._input(msg, send));
|
|
1028
|
-
const completeIdx = node._getCompletePortIndex();
|
|
1029
|
-
if (completeIdx !== null) {
|
|
1030
|
-
node._sendToPort(completeIdx, {
|
|
1031
|
-
...msg,
|
|
1032
|
-
complete: {
|
|
1033
|
-
source: { id: node.id, type: NC.type, name: node.name }
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
done();
|
|
1038
|
-
this.log("Input processed");
|
|
1039
|
-
} catch (error) {
|
|
1040
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error during input handling";
|
|
1041
|
-
const errorIdx = node._getErrorPortIndex();
|
|
1042
|
-
if (errorIdx !== null) {
|
|
1043
|
-
node._sendToPort(errorIdx, {
|
|
1044
|
-
...msg,
|
|
1045
|
-
error: {
|
|
1046
|
-
message: errorMsg,
|
|
1047
|
-
source: { id: node.id, type: NC.type, name: node.name }
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
if (error instanceof Error) {
|
|
1052
|
-
this.error("Error while processing input: " + error.message, msg);
|
|
1053
|
-
done(error);
|
|
1054
|
-
} else {
|
|
1055
|
-
this.error("Unknown error occurred during input handling", msg);
|
|
1056
|
-
done(new Error(errorMsg));
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
);
|
|
1061
|
-
this.on(
|
|
1062
|
-
"close",
|
|
1063
|
-
async (removed, done) => {
|
|
1064
|
-
try {
|
|
1065
|
-
this.log("Calling closed");
|
|
1066
|
-
await Promise.resolve(node._closed(removed));
|
|
1067
|
-
this.log("Node was closed");
|
|
1068
|
-
done();
|
|
1069
|
-
} catch (error) {
|
|
1070
|
-
if (error instanceof Error) {
|
|
1071
|
-
this.error("Error while closing node: " + error.message);
|
|
1072
|
-
done(error);
|
|
1073
|
-
} else {
|
|
1074
|
-
this.error("Unknown error occurred while closing node");
|
|
1075
|
-
done(new Error("Unknown error occurred while closing node"));
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
);
|
|
1080
|
-
},
|
|
1081
|
-
{
|
|
1082
|
-
credentials: NC.credentialsSchema ? getCredentialsFromSchema(NC.credentialsSchema) : {},
|
|
1083
|
-
settings: NC._settings?.()
|
|
1084
|
-
}
|
|
1085
|
-
);
|
|
1086
|
-
await Promise.resolve(NC._registered?.(RED));
|
|
1087
|
-
RED.log.debug(`Type registered: ${NC.type}`);
|
|
1088
|
-
}
|
|
1089
|
-
function registerTypes(nodes) {
|
|
1090
|
-
const fn = async function(RED) {
|
|
1091
|
-
initValidator(RED);
|
|
1092
|
-
initRoutes(RED);
|
|
1093
|
-
try {
|
|
1094
|
-
RED.log.info("Registering node types in series");
|
|
1095
|
-
for (const NodeClass of nodes) {
|
|
1096
|
-
await registerType(RED, NodeClass);
|
|
1097
|
-
}
|
|
1098
|
-
RED.log.info("All node types registered in series");
|
|
1099
|
-
} catch (error) {
|
|
1100
|
-
RED.log.error("Error registering node types:", error);
|
|
1101
|
-
throw error;
|
|
1102
|
-
}
|
|
1103
|
-
};
|
|
1104
|
-
fn.nodes = nodes;
|
|
1105
|
-
return fn;
|
|
1106
|
-
}
|
|
1107
1201
|
function defineModule(definition) {
|
|
1108
1202
|
return definition;
|
|
1109
1203
|
}
|
|
1110
1204
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1111
1205
|
0 && (module.exports = {
|
|
1206
|
+
CompletePortSchema,
|
|
1112
1207
|
ConfigNode,
|
|
1208
|
+
ErrorPortSchema,
|
|
1113
1209
|
IONode,
|
|
1114
1210
|
Node,
|
|
1115
1211
|
NrgError,
|
|
1116
1212
|
SchemaType,
|
|
1213
|
+
StatusPortSchema,
|
|
1117
1214
|
defineConfigNode,
|
|
1118
1215
|
defineIONode,
|
|
1119
1216
|
defineModule,
|
|
1120
1217
|
defineSchema,
|
|
1121
|
-
initValidator,
|
|
1122
1218
|
registerType,
|
|
1123
1219
|
registerTypes
|
|
1124
1220
|
});
|