@bonsae/nrg 0.14.0 → 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/package.json +1 -1
- package/server/index.cjs +323 -238
- package/server/resources/nrg-client.js +1885 -1882
- package/test/index.js +28 -8
- package/types/server.d.ts +104 -41
- package/types/test.d.ts +3 -2
- package/vite/index.js +69 -12
package/server/index.cjs
CHANGED
|
@@ -30,11 +30,14 @@ 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,
|
|
@@ -44,20 +47,8 @@ __export(index_exports, {
|
|
|
44
47
|
});
|
|
45
48
|
module.exports = __toCommonJS(index_exports);
|
|
46
49
|
|
|
47
|
-
// src/core/server/utils.ts
|
|
48
|
-
|
|
49
|
-
const result = {};
|
|
50
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
51
|
-
const property = value;
|
|
52
|
-
result[key] = {
|
|
53
|
-
// NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
|
|
54
|
-
required: false,
|
|
55
|
-
type: property.format === "password" ? "password" : "text",
|
|
56
|
-
value: property.default ?? void 0
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
50
|
+
// src/core/server/nodes/utils.ts
|
|
51
|
+
var import_typebox = require("@sinclair/typebox");
|
|
61
52
|
|
|
62
53
|
// src/core/errors.ts
|
|
63
54
|
var NrgError = class _NrgError extends Error {
|
|
@@ -111,6 +102,9 @@ var TypedInput = class {
|
|
|
111
102
|
};
|
|
112
103
|
|
|
113
104
|
// src/core/server/nodes/utils.ts
|
|
105
|
+
function isSchemaLike(obj) {
|
|
106
|
+
return obj != null && typeof obj === "object" && import_typebox.Kind in obj;
|
|
107
|
+
}
|
|
114
108
|
function setupContext(context, store) {
|
|
115
109
|
return {
|
|
116
110
|
get: (key) => new Promise(
|
|
@@ -189,29 +183,32 @@ function setupConfigProxy(opts) {
|
|
|
189
183
|
return createProxy(config);
|
|
190
184
|
}
|
|
191
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
|
+
|
|
192
204
|
// src/core/server/nodes/node.ts
|
|
193
|
-
var
|
|
205
|
+
var cachedSettingsMap = /* @__PURE__ */ new WeakMap();
|
|
206
|
+
var Node = class _Node {
|
|
194
207
|
static type;
|
|
195
208
|
static category;
|
|
196
209
|
static configSchema;
|
|
197
210
|
static credentialsSchema;
|
|
198
211
|
static settingsSchema;
|
|
199
|
-
static _cachedSettings = null;
|
|
200
|
-
/** @internal */
|
|
201
|
-
static _settings() {
|
|
202
|
-
if (!this.settingsSchema) return;
|
|
203
|
-
const settings = {};
|
|
204
|
-
const prefix = this.type.replace(/-./g, (x) => x[1].toUpperCase());
|
|
205
|
-
for (const [key, prop] of Object.entries(this.settingsSchema.properties)) {
|
|
206
|
-
const settingKey = prefix + key.charAt(0).toUpperCase() + key.slice(1);
|
|
207
|
-
settings[settingKey] = {
|
|
208
|
-
value: prop.default,
|
|
209
|
-
exportable: prop.exportable ?? false
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
return settings;
|
|
213
|
-
}
|
|
214
|
-
// NOTE:
|
|
215
212
|
static validateSettings(RED) {
|
|
216
213
|
if (!this.settingsSchema) return;
|
|
217
214
|
RED.log.info("Validating settings");
|
|
@@ -237,9 +234,61 @@ var Node = class {
|
|
|
237
234
|
cacheKey: this.settingsSchema.$id || `${this.type}:settings`,
|
|
238
235
|
throwOnError: true
|
|
239
236
|
});
|
|
240
|
-
this
|
|
237
|
+
cachedSettingsMap.set(this, settings);
|
|
241
238
|
RED.log.info("Settings are valid");
|
|
242
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
|
+
}
|
|
243
292
|
RED;
|
|
244
293
|
node;
|
|
245
294
|
context;
|
|
@@ -289,6 +338,39 @@ var Node = class {
|
|
|
289
338
|
}
|
|
290
339
|
}
|
|
291
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
|
+
}
|
|
292
374
|
i18n(key, substitutions) {
|
|
293
375
|
const nodeType = this.constructor.type;
|
|
294
376
|
return this.RED._(`${nodeType}.${key}`, substitutions);
|
|
@@ -314,20 +396,6 @@ var Node = class {
|
|
|
314
396
|
clearInterval(interval);
|
|
315
397
|
this.intervals.delete(interval);
|
|
316
398
|
}
|
|
317
|
-
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's closed again
|
|
318
|
-
/** @internal */
|
|
319
|
-
async _closed(removed) {
|
|
320
|
-
try {
|
|
321
|
-
await Promise.resolve(this.closed?.(removed));
|
|
322
|
-
} finally {
|
|
323
|
-
this.log("clearing timers and intervals");
|
|
324
|
-
this.timers.forEach((t) => clearTimeout(t));
|
|
325
|
-
this.intervals.forEach((i) => clearInterval(i));
|
|
326
|
-
this.timers.clear();
|
|
327
|
-
this.intervals.clear();
|
|
328
|
-
this.log("timers and intervals cleared");
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
399
|
on(event, callback) {
|
|
332
400
|
this.node.on(event, callback);
|
|
333
401
|
}
|
|
@@ -354,7 +422,7 @@ var Node = class {
|
|
|
354
422
|
}
|
|
355
423
|
get settings() {
|
|
356
424
|
const constructor = this.constructor;
|
|
357
|
-
return constructor
|
|
425
|
+
return cachedSettingsMap.get(constructor) ?? {};
|
|
358
426
|
}
|
|
359
427
|
};
|
|
360
428
|
|
|
@@ -372,16 +440,20 @@ var IONode = class extends Node {
|
|
|
372
440
|
static get outputs() {
|
|
373
441
|
const s = this.outputsSchema;
|
|
374
442
|
if (!s) return 0;
|
|
375
|
-
|
|
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;
|
|
376
454
|
}
|
|
377
|
-
|
|
455
|
+
#send;
|
|
378
456
|
context;
|
|
379
|
-
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
380
|
-
/** @internal */
|
|
381
|
-
static _registered(RED) {
|
|
382
|
-
this.validateSettings(RED);
|
|
383
|
-
return this.registered?.(RED);
|
|
384
|
-
}
|
|
385
457
|
constructor(RED, node, config, credentials) {
|
|
386
458
|
super(RED, node, config, credentials);
|
|
387
459
|
const context = node.context();
|
|
@@ -394,11 +466,58 @@ var IONode = class extends Node {
|
|
|
394
466
|
fn.global = setupContext(context.global);
|
|
395
467
|
this.context = fn;
|
|
396
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
|
+
}
|
|
397
518
|
input(msg) {
|
|
398
519
|
}
|
|
399
|
-
|
|
400
|
-
/** @internal */
|
|
401
|
-
async _input(msg, send) {
|
|
520
|
+
async #input(msg, send) {
|
|
402
521
|
const NodeClass = this.constructor;
|
|
403
522
|
const shouldValidateInput = this.config.validateInput ?? NodeClass.validateInput;
|
|
404
523
|
if (shouldValidateInput && NodeClass.inputSchema) {
|
|
@@ -409,11 +528,11 @@ var IONode = class extends Node {
|
|
|
409
528
|
});
|
|
410
529
|
this.log("Input is valid");
|
|
411
530
|
}
|
|
412
|
-
this
|
|
531
|
+
this.#send = send;
|
|
413
532
|
try {
|
|
414
533
|
await Promise.resolve(this.input(msg));
|
|
415
534
|
} finally {
|
|
416
|
-
this
|
|
535
|
+
this.#send = void 0;
|
|
417
536
|
}
|
|
418
537
|
}
|
|
419
538
|
send(msg) {
|
|
@@ -421,87 +540,108 @@ var IONode = class extends Node {
|
|
|
421
540
|
const shouldValidateOutput = this.config.validateOutput ?? NodeClass.validateOutput;
|
|
422
541
|
if (shouldValidateOutput && NodeClass.outputsSchema) {
|
|
423
542
|
this.log("Validating output");
|
|
424
|
-
const
|
|
425
|
-
if (Array.isArray(
|
|
543
|
+
const rawSchema = NodeClass.outputsSchema;
|
|
544
|
+
if (Array.isArray(rawSchema)) {
|
|
426
545
|
const msgs = msg;
|
|
427
|
-
for (let i = 0; i <
|
|
546
|
+
for (let i = 0; i < rawSchema.length; i++) {
|
|
428
547
|
if (msgs[i] == null) continue;
|
|
429
|
-
this.RED.validator.validate(msgs[i],
|
|
430
|
-
cacheKey:
|
|
548
|
+
this.RED.validator.validate(msgs[i], rawSchema[i], {
|
|
549
|
+
cacheKey: rawSchema[i].$id || `${NodeClass.type}:output-schema:${i}`,
|
|
431
550
|
throwOnError: true
|
|
432
551
|
});
|
|
433
552
|
}
|
|
434
|
-
} else if (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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`,
|
|
439
566
|
throwOnError: true
|
|
440
567
|
});
|
|
441
568
|
}
|
|
442
569
|
} else {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
+
}
|
|
447
579
|
}
|
|
448
580
|
this.log("Output is valid");
|
|
449
581
|
}
|
|
450
|
-
if (this
|
|
451
|
-
this
|
|
582
|
+
if (this.#send) {
|
|
583
|
+
this.#send(msg);
|
|
452
584
|
} else {
|
|
453
585
|
this.node.send(msg);
|
|
454
586
|
}
|
|
455
587
|
}
|
|
456
|
-
// ---
|
|
457
|
-
|
|
458
|
-
get _baseOutputs() {
|
|
588
|
+
// --- Built-in port management ---
|
|
589
|
+
get baseOutputs() {
|
|
459
590
|
return this.constructor.outputs ?? 0;
|
|
460
591
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
let count = this.
|
|
464
|
-
if (
|
|
465
|
-
if (
|
|
466
|
-
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++;
|
|
467
598
|
return count;
|
|
468
599
|
}
|
|
469
600
|
/**
|
|
470
601
|
* Send a message to a specific output port by index or name.
|
|
471
|
-
*
|
|
472
|
-
* based on the node's
|
|
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.
|
|
473
605
|
* Numeric indices refer to the base output ports (0-based).
|
|
474
606
|
*/
|
|
475
607
|
sendToPort(port, msg) {
|
|
476
|
-
this
|
|
608
|
+
this.#sendToPort(port, msg);
|
|
477
609
|
}
|
|
478
|
-
|
|
479
|
-
_sendToPort(port, msg) {
|
|
610
|
+
#sendToPort(port, msg) {
|
|
480
611
|
let portIndex;
|
|
481
612
|
if (typeof port === "number") {
|
|
482
613
|
portIndex = port;
|
|
614
|
+
} else if (port === "error" || port === "complete" || port === "status") {
|
|
615
|
+
portIndex = this.#getBuiltinPortIndex(port);
|
|
616
|
+
if (portIndex === null) return;
|
|
483
617
|
} else {
|
|
484
|
-
portIndex = this
|
|
618
|
+
portIndex = this.#getNamedPortIndex(port);
|
|
485
619
|
if (portIndex === null) return;
|
|
486
620
|
}
|
|
487
|
-
const out = Array(this.
|
|
621
|
+
const out = Array(this.totalOutputs).fill(null);
|
|
488
622
|
out[portIndex] = msg;
|
|
489
623
|
this.node.send(out);
|
|
490
624
|
}
|
|
491
|
-
|
|
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) {
|
|
492
632
|
const config = this.config;
|
|
493
633
|
if (name === "error") {
|
|
494
|
-
return config.
|
|
634
|
+
return config.errorPort ? this.baseOutputs : null;
|
|
495
635
|
}
|
|
496
|
-
let idx = this.
|
|
497
|
-
if (config.
|
|
636
|
+
let idx = this.baseOutputs;
|
|
637
|
+
if (config.errorPort) idx++;
|
|
498
638
|
if (name === "complete") {
|
|
499
|
-
return config.
|
|
639
|
+
return config.completePort ? idx : null;
|
|
500
640
|
}
|
|
501
|
-
if (config.
|
|
502
|
-
return config.
|
|
641
|
+
if (config.completePort) idx++;
|
|
642
|
+
return config.statusPort ? idx : null;
|
|
503
643
|
}
|
|
504
|
-
|
|
644
|
+
#nodeSource() {
|
|
505
645
|
return {
|
|
506
646
|
id: this.id,
|
|
507
647
|
type: this.constructor.type,
|
|
@@ -510,19 +650,19 @@ var IONode = class extends Node {
|
|
|
510
650
|
}
|
|
511
651
|
status(status) {
|
|
512
652
|
this.node.status(status);
|
|
513
|
-
this
|
|
653
|
+
this.#sendToPort("status", {
|
|
514
654
|
status,
|
|
515
|
-
source: this
|
|
655
|
+
source: this.#nodeSource()
|
|
516
656
|
});
|
|
517
657
|
}
|
|
518
658
|
error(message, msg) {
|
|
519
659
|
super.error(message, msg);
|
|
520
660
|
if (msg) {
|
|
521
|
-
this
|
|
661
|
+
this.#sendToPort("error", {
|
|
522
662
|
...msg,
|
|
523
663
|
error: {
|
|
524
664
|
message,
|
|
525
|
-
source: this
|
|
665
|
+
source: this.#nodeSource()
|
|
526
666
|
}
|
|
527
667
|
});
|
|
528
668
|
}
|
|
@@ -554,12 +694,6 @@ var IONode = class extends Node {
|
|
|
554
694
|
var ConfigNode = class extends Node {
|
|
555
695
|
static category = "config";
|
|
556
696
|
context;
|
|
557
|
-
// NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
|
|
558
|
-
/** @internal */
|
|
559
|
-
static _registered(RED) {
|
|
560
|
-
this.validateSettings(RED);
|
|
561
|
-
return this.registered?.(RED);
|
|
562
|
-
}
|
|
563
697
|
constructor(RED, node, config, credentials) {
|
|
564
698
|
super(RED, node, config, credentials);
|
|
565
699
|
const context = node.context();
|
|
@@ -601,8 +735,7 @@ function defineIONode(def) {
|
|
|
601
735
|
static outputsSchema = def.outputsSchema;
|
|
602
736
|
static validateInput = def.validateInput ?? false;
|
|
603
737
|
static validateOutput = def.validateOutput ?? false;
|
|
604
|
-
static
|
|
605
|
-
this.validateSettings(RED);
|
|
738
|
+
static registered(RED) {
|
|
606
739
|
return def.registered?.(RED);
|
|
607
740
|
}
|
|
608
741
|
async input(msg) {
|
|
@@ -630,8 +763,7 @@ function defineConfigNode(def) {
|
|
|
630
763
|
static configSchema = def.configSchema;
|
|
631
764
|
static credentialsSchema = def.credentialsSchema;
|
|
632
765
|
static settingsSchema = def.settingsSchema;
|
|
633
|
-
static
|
|
634
|
-
this.validateSettings(RED);
|
|
766
|
+
static registered(RED) {
|
|
635
767
|
return def.registered?.(RED);
|
|
636
768
|
}
|
|
637
769
|
async created() {
|
|
@@ -863,6 +995,41 @@ function initRoutes(RED) {
|
|
|
863
995
|
initAssetsRoutes(RED.httpAdmin);
|
|
864
996
|
}
|
|
865
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;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
866
1033
|
// src/core/constants.ts
|
|
867
1034
|
var TYPED_INPUT_TYPES = [
|
|
868
1035
|
"msg",
|
|
@@ -882,17 +1049,17 @@ var TYPED_INPUT_TYPES = [
|
|
|
882
1049
|
];
|
|
883
1050
|
|
|
884
1051
|
// src/core/server/schemas/type.ts
|
|
885
|
-
var
|
|
1052
|
+
var import_typebox2 = require("@sinclair/typebox");
|
|
886
1053
|
var import_rules = require("ajv/dist/compile/rules");
|
|
887
1054
|
function NodeRef(nodeClass, options) {
|
|
888
1055
|
return {
|
|
889
|
-
...
|
|
1056
|
+
...import_typebox2.Type.String({
|
|
890
1057
|
description: options?.description || `Reference to ${nodeClass.type}`,
|
|
891
1058
|
format: "node-id"
|
|
892
1059
|
}),
|
|
893
1060
|
"x-nrg-node-type": nodeClass.type,
|
|
894
1061
|
...options,
|
|
895
|
-
[
|
|
1062
|
+
[import_typebox2.Kind]: "NodeRef"
|
|
896
1063
|
};
|
|
897
1064
|
}
|
|
898
1065
|
function TypedInput2(options) {
|
|
@@ -900,10 +1067,10 @@ function TypedInput2(options) {
|
|
|
900
1067
|
...TypedInputSchema,
|
|
901
1068
|
"x-nrg-typed-input": true,
|
|
902
1069
|
...options,
|
|
903
|
-
[
|
|
1070
|
+
[import_typebox2.Kind]: "TypedInput"
|
|
904
1071
|
};
|
|
905
1072
|
}
|
|
906
|
-
var SchemaType = Object.assign({},
|
|
1073
|
+
var SchemaType = Object.assign({}, import_typebox2.Type, {
|
|
907
1074
|
NodeRef,
|
|
908
1075
|
TypedInput: TypedInput2
|
|
909
1076
|
});
|
|
@@ -995,137 +1162,55 @@ var TypedInputSchema = SchemaType.Object(
|
|
|
995
1162
|
}
|
|
996
1163
|
}
|
|
997
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
|
+
});
|
|
998
1199
|
|
|
999
1200
|
// src/core/server/index.ts
|
|
1000
|
-
async function registerType(RED, NodeClass) {
|
|
1001
|
-
const NC = NodeClass;
|
|
1002
|
-
RED.log.debug(`Registering Type: ${NC.type}`);
|
|
1003
|
-
if (!(NC.prototype instanceof Node)) {
|
|
1004
|
-
throw new NrgError(`${NC.name} must extend IONode or ConfigNode classes`);
|
|
1005
|
-
}
|
|
1006
|
-
if (!NC.type) {
|
|
1007
|
-
throw new NrgError("type must be provided when registering the node");
|
|
1008
|
-
}
|
|
1009
|
-
if (NC.color && !/^#[0-9A-Fa-f]{6}$/.test(NC.color)) {
|
|
1010
|
-
throw new NrgError(
|
|
1011
|
-
`Invalid color for ${NodeClass.type}: ${NC.color} color must be in hex format`
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
RED.nodes.registerType(
|
|
1015
|
-
NC.type,
|
|
1016
|
-
function(config) {
|
|
1017
|
-
RED.nodes.createNode(this, config);
|
|
1018
|
-
const node = new NC(RED, this, config, this.credentials);
|
|
1019
|
-
Object.defineProperty(this, "_node", {
|
|
1020
|
-
value: node,
|
|
1021
|
-
writable: false,
|
|
1022
|
-
configurable: false,
|
|
1023
|
-
enumerable: false
|
|
1024
|
-
});
|
|
1025
|
-
const createdPromise = Promise.resolve(node.created?.()).catch(
|
|
1026
|
-
(error) => {
|
|
1027
|
-
this.error("Error during created hook: " + error.message);
|
|
1028
|
-
throw error;
|
|
1029
|
-
}
|
|
1030
|
-
);
|
|
1031
|
-
this.on(
|
|
1032
|
-
"input",
|
|
1033
|
-
async (msg, send, done) => {
|
|
1034
|
-
try {
|
|
1035
|
-
await createdPromise;
|
|
1036
|
-
} catch {
|
|
1037
|
-
done(new Error("Node failed to initialize"));
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
try {
|
|
1041
|
-
this.log("Calling input");
|
|
1042
|
-
await Promise.resolve(node._input(msg, send));
|
|
1043
|
-
node._sendToPort("complete", {
|
|
1044
|
-
...msg,
|
|
1045
|
-
complete: {
|
|
1046
|
-
source: { id: node.id, type: NC.type, name: node.name }
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
done();
|
|
1050
|
-
this.log("Input processed");
|
|
1051
|
-
} catch (error) {
|
|
1052
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error during input handling";
|
|
1053
|
-
const errorIdx = node._getErrorPortIndex();
|
|
1054
|
-
if (errorIdx !== null) {
|
|
1055
|
-
node._sendToPort(errorIdx, {
|
|
1056
|
-
...msg,
|
|
1057
|
-
error: {
|
|
1058
|
-
message: errorMsg,
|
|
1059
|
-
source: { id: node.id, type: NC.type, name: node.name }
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
if (error instanceof Error) {
|
|
1064
|
-
this.error("Error while processing input: " + error.message, msg);
|
|
1065
|
-
done(error);
|
|
1066
|
-
} else {
|
|
1067
|
-
this.error("Unknown error occurred during input handling", msg);
|
|
1068
|
-
done(new Error(errorMsg));
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
);
|
|
1073
|
-
this.on(
|
|
1074
|
-
"close",
|
|
1075
|
-
async (removed, done) => {
|
|
1076
|
-
try {
|
|
1077
|
-
this.log("Calling closed");
|
|
1078
|
-
await Promise.resolve(node._closed(removed));
|
|
1079
|
-
this.log("Node was closed");
|
|
1080
|
-
done();
|
|
1081
|
-
} catch (error) {
|
|
1082
|
-
if (error instanceof Error) {
|
|
1083
|
-
this.error("Error while closing node: " + error.message);
|
|
1084
|
-
done(error);
|
|
1085
|
-
} else {
|
|
1086
|
-
this.error("Unknown error occurred while closing node");
|
|
1087
|
-
done(new Error("Unknown error occurred while closing node"));
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
);
|
|
1092
|
-
},
|
|
1093
|
-
{
|
|
1094
|
-
credentials: NC.credentialsSchema ? getCredentialsFromSchema(NC.credentialsSchema) : {},
|
|
1095
|
-
settings: NC._settings?.()
|
|
1096
|
-
}
|
|
1097
|
-
);
|
|
1098
|
-
await Promise.resolve(NC._registered?.(RED));
|
|
1099
|
-
RED.log.debug(`Type registered: ${NC.type}`);
|
|
1100
|
-
}
|
|
1101
|
-
function registerTypes(nodes) {
|
|
1102
|
-
const fn = async function(RED) {
|
|
1103
|
-
initValidator(RED);
|
|
1104
|
-
initRoutes(RED);
|
|
1105
|
-
try {
|
|
1106
|
-
RED.log.info("Registering node types in series");
|
|
1107
|
-
for (const NodeClass of nodes) {
|
|
1108
|
-
await registerType(RED, NodeClass);
|
|
1109
|
-
}
|
|
1110
|
-
RED.log.info("All node types registered in series");
|
|
1111
|
-
} catch (error) {
|
|
1112
|
-
RED.log.error("Error registering node types:", error);
|
|
1113
|
-
throw error;
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
fn.nodes = nodes;
|
|
1117
|
-
return fn;
|
|
1118
|
-
}
|
|
1119
1201
|
function defineModule(definition) {
|
|
1120
1202
|
return definition;
|
|
1121
1203
|
}
|
|
1122
1204
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1123
1205
|
0 && (module.exports = {
|
|
1206
|
+
CompletePortSchema,
|
|
1124
1207
|
ConfigNode,
|
|
1208
|
+
ErrorPortSchema,
|
|
1125
1209
|
IONode,
|
|
1126
1210
|
Node,
|
|
1127
1211
|
NrgError,
|
|
1128
1212
|
SchemaType,
|
|
1213
|
+
StatusPortSchema,
|
|
1129
1214
|
defineConfigNode,
|
|
1130
1215
|
defineIONode,
|
|
1131
1216
|
defineModule,
|