@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/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
- function getCredentialsFromSchema(schema) {
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 Node = class {
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._cachedSettings = settings;
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._cachedSettings ?? {};
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
- return Array.isArray(s) ? s.length : 1;
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
- _send;
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
- // NOTE: used by the registered function. Had to be a different one to avoid calling the parent's input again
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._send = send;
531
+ this.#send = send;
413
532
  try {
414
533
  await Promise.resolve(this.input(msg));
415
534
  } finally {
416
- this._send = void 0;
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 schemas = NodeClass.outputsSchema;
425
- if (Array.isArray(schemas)) {
543
+ const rawSchema = NodeClass.outputsSchema;
544
+ if (Array.isArray(rawSchema)) {
426
545
  const msgs = msg;
427
- for (let i = 0; i < schemas.length; i++) {
546
+ for (let i = 0; i < rawSchema.length; i++) {
428
547
  if (msgs[i] == null) continue;
429
- this.RED.validator.validate(msgs[i], schemas[i], {
430
- cacheKey: schemas[i].$id || `${NodeClass.type}:output-schema:${i}`,
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 (Array.isArray(msg)) {
435
- for (let i = 0; i < msg.length; i++) {
436
- if (msg[i] == null) continue;
437
- this.RED.validator.validate(msg[i], schemas, {
438
- cacheKey: schemas.$id || `${NodeClass.type}:output-schema`,
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
- this.RED.validator.validate(msg, schemas, {
444
- cacheKey: schemas.$id || `${NodeClass.type}:output-schema`,
445
- throwOnError: true
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._send) {
451
- this._send(msg);
582
+ if (this.#send) {
583
+ this.#send(msg);
452
584
  } else {
453
585
  this.node.send(msg);
454
586
  }
455
587
  }
456
- // --- Emit port management ---
457
- /** @internal */
458
- get _baseOutputs() {
588
+ // --- Built-in port management ---
589
+ get baseOutputs() {
459
590
  return this.constructor.outputs ?? 0;
460
591
  }
461
- /** @internal */
462
- get _totalOutputs() {
463
- let count = this._baseOutputs;
464
- if (this.config.emitError) count++;
465
- if (this.config.emitComplete) count++;
466
- if (this.config.emitStatus) count++;
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
- * Named ports: `"error"`, `"complete"`, `"status"` — resolved automatically
472
- * based on the node's emit port configuration.
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._sendToPort(port, msg);
608
+ this.#sendToPort(port, msg);
477
609
  }
478
- /** @internal */
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._getEmitPortIndex(port);
618
+ portIndex = this.#getNamedPortIndex(port);
485
619
  if (portIndex === null) return;
486
620
  }
487
- const out = Array(this._totalOutputs).fill(null);
621
+ const out = Array(this.totalOutputs).fill(null);
488
622
  out[portIndex] = msg;
489
623
  this.node.send(out);
490
624
  }
491
- _getEmitPortIndex(name) {
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.emitError ? this._baseOutputs : null;
634
+ return config.errorPort ? this.baseOutputs : null;
495
635
  }
496
- let idx = this._baseOutputs;
497
- if (config.emitError) idx++;
636
+ let idx = this.baseOutputs;
637
+ if (config.errorPort) idx++;
498
638
  if (name === "complete") {
499
- return config.emitComplete ? idx : null;
639
+ return config.completePort ? idx : null;
500
640
  }
501
- if (config.emitComplete) idx++;
502
- return config.emitStatus ? idx : null;
641
+ if (config.completePort) idx++;
642
+ return config.statusPort ? idx : null;
503
643
  }
504
- _nodeSource() {
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._sendToPort("status", {
653
+ this.#sendToPort("status", {
514
654
  status,
515
- source: this._nodeSource()
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._sendToPort("error", {
661
+ this.#sendToPort("error", {
522
662
  ...msg,
523
663
  error: {
524
664
  message,
525
- source: this._nodeSource()
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 _registered(RED) {
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 _registered(RED) {
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 import_typebox = require("@sinclair/typebox");
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
- ...import_typebox.Type.String({
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
- [import_typebox.Kind]: "NodeRef"
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
- [import_typebox.Kind]: "TypedInput"
1070
+ [import_typebox2.Kind]: "TypedInput"
904
1071
  };
905
1072
  }
906
- var SchemaType = Object.assign({}, import_typebox.Type, {
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,