@bonsae/nrg 0.26.3 → 0.28.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.
@@ -0,0 +1,48 @@
1
+ // src/eslint/index.ts
2
+ var nodeTypeMatchesFilename = {
3
+ meta: {
4
+ type: "problem",
5
+ docs: {
6
+ description: "require a node's static `type` to equal its filename in server/nodes"
7
+ },
8
+ schema: [],
9
+ messages: {
10
+ mismatch: `Node static type "{{type}}" must match the filename "{{expected}}.ts" \u2014 the type string keys the node's schema, component, icon and locale folder.`
11
+ }
12
+ },
13
+ create(context) {
14
+ const match = /[\\/]server[\\/]nodes[\\/]([^\\/]+)\.ts$/.exec(
15
+ context.filename
16
+ );
17
+ if (!match) return {};
18
+ const expected = match[1];
19
+ return {
20
+ "PropertyDefinition[static=true]"(node) {
21
+ if (node.key?.type === "Identifier" && node.key.name === "type" && node.value?.type === "Literal" && typeof node.value.value === "string" && node.value.value !== expected) {
22
+ context.report({
23
+ node: node.value,
24
+ messageId: "mismatch",
25
+ data: { type: node.value.value, expected }
26
+ });
27
+ }
28
+ }
29
+ };
30
+ }
31
+ };
32
+ var plugin = {
33
+ meta: { name: "@bonsae/nrg" },
34
+ rules: { "node-type-matches-filename": nodeTypeMatchesFilename }
35
+ };
36
+ var nrgConventions = {
37
+ plugins: { "@bonsae/nrg": plugin },
38
+ rules: {
39
+ "@bonsae/nrg/node-type-matches-filename": "error"
40
+ }
41
+ };
42
+ var index_default = nrgConventions;
43
+ export {
44
+ index_default as default,
45
+ nodeTypeMatchesFilename,
46
+ nrgConventions,
47
+ plugin
48
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.26.3",
3
+ "version": "0.28.0",
4
4
  "description": "NRG framework — build Node-RED nodes with Vue 3, TypeScript, and JSON Schema",
5
5
  "author": "Allan Oricil <allanoricil@duck.com>",
6
6
  "license": "MIT",
@@ -50,6 +50,7 @@
50
50
  "types": "./types/vite.d.ts",
51
51
  "default": "./vite/index.js"
52
52
  },
53
+ "./eslint": "./eslint/index.js",
53
54
  "./test/server/unit": {
54
55
  "types": "./types/test-server-unit.d.ts",
55
56
  "default": "./test/server/unit/index.js"
package/server/index.cjs CHANGED
@@ -128,7 +128,8 @@ function setupConfigProxy(opts) {
128
128
  if (typeof prop === "symbol") return target[prop];
129
129
  if (SKIP_PROPS.has(prop)) return target[prop];
130
130
  const value = target[prop];
131
- if (typeof value === "string" && value.length > 0 && nodeRefProps.has(prop)) {
131
+ if (nodeRefProps.has(prop)) {
132
+ if (typeof value !== "string" || value.length === 0) return void 0;
132
133
  return RED.nodes.getNode(value)?._node ?? value;
133
134
  }
134
135
  if (typedInputProps.has(prop) && value && typeof value === "object" && "type" in value && "value" in value) {
@@ -161,10 +162,16 @@ function getCredentialsFromSchema(schema) {
161
162
  const result = {};
162
163
  for (const [key, value] of Object.entries(schema.properties)) {
163
164
  const property = value;
165
+ const isPassword = property.format === "password";
166
+ if (!isPassword) {
167
+ console.warn(
168
+ `[nrg] credential "${key}" has no format:"password" \u2014 it is stored as a visible (cleartext-in-editor) credential. Add { format: "password" } to mask it.`
169
+ );
170
+ }
164
171
  result[key] = {
165
172
  // NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
166
173
  required: false,
167
- type: property.format === "password" ? "password" : "text",
174
+ type: isPassword ? "password" : "text",
168
175
  value: property.default ?? void 0
169
176
  };
170
177
  }
@@ -253,6 +260,7 @@ var Node = class _Node {
253
260
  }
254
261
  );
255
262
  createdPromise.catch(() => {
263
+ this.status({ fill: "red", shape: "ring", text: "created() failed" });
256
264
  });
257
265
  node[WIRE_HANDLERS](this, createdPromise);
258
266
  },
@@ -453,14 +461,16 @@ function setupContext(context, store) {
453
461
  await set(key, next);
454
462
  return next;
455
463
  };
456
- const run = (chains.get(lockKey) ?? Promise.resolve()).then(task, task);
457
- chains.set(
458
- lockKey,
459
- run.then(
460
- () => void 0,
461
- () => void 0
462
- )
464
+ const previous = chains.get(lockKey) ?? Promise.resolve();
465
+ const run = previous.then(task, task);
466
+ const settled = run.then(
467
+ () => void 0,
468
+ () => void 0
463
469
  );
470
+ chains.set(lockKey, settled);
471
+ void settled.then(() => {
472
+ if (chains.get(lockKey) === settled) chains.delete(lockKey);
473
+ });
464
474
  return run;
465
475
  };
466
476
  const increment = (key, by = 1) => {
@@ -579,36 +589,43 @@ var IONode = class _IONode extends Node {
579
589
  async (msg, send, done) => {
580
590
  try {
581
591
  await createdPromise;
582
- } catch {
583
- done(new Error("Node failed to initialize"));
592
+ } catch (initError) {
593
+ done(
594
+ initError instanceof Error ? initError : new Error(String(initError))
595
+ );
584
596
  return;
585
597
  }
598
+ const store = { inputMsg: msg, send };
586
599
  try {
587
600
  nodeRedNode.log("Calling input");
588
- const result = await this.#input(msg, send);
589
- this.#sendToPort("complete", {
590
- ...msg,
591
- ...result !== void 0 ? { output: result } : {},
592
- complete: {
593
- source: this.#nodeSource()
594
- },
595
- [INPUT_KEY]: msg
596
- });
601
+ const result = await this.#input(msg, store);
602
+ if (this.config.completePort) {
603
+ this.#sendToPort("complete", {
604
+ ...msg,
605
+ ...result !== void 0 ? { output: result } : {},
606
+ complete: {
607
+ source: this.#nodeSource()
608
+ },
609
+ [INPUT_KEY]: msg
610
+ });
611
+ }
597
612
  done();
598
613
  nodeRedNode.log("Input processed");
599
614
  } catch (error) {
600
615
  const errorMsg = error instanceof Error ? error.message : "Unknown error during input handling";
601
- const errorData = error && typeof error === "object" ? { ...error } : {};
602
- this.#sendToPort("error", {
603
- ...msg,
604
- error: {
605
- ...errorData,
606
- name: error?.name ?? "Error",
607
- message: errorMsg,
608
- source: this.#nodeSource()
609
- },
610
- [INPUT_KEY]: msg
611
- });
616
+ if (this.config.errorPort && !store.errorEmitted) {
617
+ const errorData = error && typeof error === "object" ? { ...error } : {};
618
+ this.#sendToPort("error", {
619
+ ...msg,
620
+ error: {
621
+ ...errorData,
622
+ name: error?.name ?? "Error",
623
+ message: errorMsg,
624
+ source: this.#nodeSource()
625
+ },
626
+ [INPUT_KEY]: msg
627
+ });
628
+ }
612
629
  if (error instanceof Error) {
613
630
  nodeRedNode.error(
614
631
  "Error while processing input: " + error.message,
@@ -629,7 +646,7 @@ var IONode = class _IONode extends Node {
629
646
  input(msg) {
630
647
  return void 0;
631
648
  }
632
- async #input(msg, send) {
649
+ async #input(msg, store) {
633
650
  const NodeClass = this.constructor;
634
651
  const shouldValidateInput = this.config.validateInput ?? NodeClass.validateInput;
635
652
  if (shouldValidateInput && NodeClass.inputSchema) {
@@ -641,7 +658,7 @@ var IONode = class _IONode extends Node {
641
658
  this.log("Input is valid");
642
659
  }
643
660
  return await _IONode.#invocation.run(
644
- { inputMsg: msg, send },
661
+ store,
645
662
  () => Promise.resolve(this.input(msg))
646
663
  );
647
664
  }
@@ -811,15 +828,20 @@ var IONode = class _IONode extends Node {
811
828
  }
812
829
  error(message, msg) {
813
830
  super.error(message, msg);
814
- if (msg) {
831
+ if (msg && this.config.errorPort) {
815
832
  this.#sendToPort("error", {
816
833
  ...msg,
817
834
  error: {
835
+ // `name` keeps the error-port payload Catch-node compatible and
836
+ // consistent with the auto-emit from a thrown error.
837
+ name: "Error",
818
838
  message,
819
839
  source: this.#nodeSource()
820
840
  },
821
841
  [INPUT_KEY]: msg
822
842
  });
843
+ const store = _IONode.#invocation.getStore();
844
+ if (store) store.errorEmitted = true;
823
845
  }
824
846
  }
825
847
  updateWires(wires) {
@@ -1101,9 +1123,7 @@ function initValidator(RED) {
1101
1123
  }
1102
1124
  ],
1103
1125
  customFormats: {
1104
- "node-id": /^[a-zA-Z0-9-_]+$/,
1105
- "flow-id": /^[a-f0-9]{16}$/,
1106
- "topic-path": (data) => /^[a-zA-Z0-9/_-]+$/.test(data)
1126
+ "node-id": /^[a-zA-Z0-9-_]+$/
1107
1127
  }
1108
1128
  })
1109
1129
  };
@@ -1205,35 +1225,140 @@ var TYPED_INPUT_TYPES = [
1205
1225
  "cred"
1206
1226
  ];
1207
1227
 
1208
- // src/core/server/schemas/type.ts
1228
+ // src/core/shared/schemas/base.ts
1209
1229
  var import_typebox2 = require("@sinclair/typebox");
1210
- var import_rules = require("ajv/dist/compile/rules.js");
1211
- function NodeRef(nodeClass, options) {
1230
+ var NodeConfigSchema = import_typebox2.Type.Object({
1231
+ id: import_typebox2.Type.String(),
1232
+ type: import_typebox2.Type.String(),
1233
+ name: import_typebox2.Type.String(),
1234
+ z: import_typebox2.Type.Optional(import_typebox2.Type.String())
1235
+ });
1236
+ var ConfigNodeConfigSchema = import_typebox2.Type.Object({
1237
+ ...NodeConfigSchema.properties,
1238
+ _users: import_typebox2.Type.Array(import_typebox2.Type.String())
1239
+ });
1240
+ var IONodeConfigSchema = import_typebox2.Type.Object({
1241
+ ...NodeConfigSchema.properties,
1242
+ wires: import_typebox2.Type.Array(import_typebox2.Type.Array(import_typebox2.Type.String(), { default: [] }), {
1243
+ default: [[]]
1244
+ }),
1245
+ x: import_typebox2.Type.Number(),
1246
+ y: import_typebox2.Type.Number(),
1247
+ g: import_typebox2.Type.Optional(import_typebox2.Type.String())
1248
+ });
1249
+ var TypedInputSchema = import_typebox2.Type.Object(
1250
+ {
1251
+ value: import_typebox2.Type.Union(
1252
+ [
1253
+ import_typebox2.Type.String(),
1254
+ import_typebox2.Type.Number(),
1255
+ import_typebox2.Type.Boolean(),
1256
+ import_typebox2.Type.Null()
1257
+ ],
1258
+ {
1259
+ description: "The actual value entered or selected.",
1260
+ default: ""
1261
+ }
1262
+ ),
1263
+ type: import_typebox2.Type.Union(
1264
+ TYPED_INPUT_TYPES.map((type) => import_typebox2.Type.Literal(type)),
1265
+ {
1266
+ description: "The type of the value (string, number, message property, etc.)",
1267
+ default: "str"
1268
+ }
1269
+ )
1270
+ },
1271
+ {
1272
+ description: "Represents a Node-RED TypedInput value and its type.",
1273
+ default: {
1274
+ type: "str",
1275
+ value: ""
1276
+ }
1277
+ }
1278
+ );
1279
+ var NodeSourceSchema = import_typebox2.Type.Object({
1280
+ id: import_typebox2.Type.String(),
1281
+ type: import_typebox2.Type.String(),
1282
+ name: import_typebox2.Type.String()
1283
+ });
1284
+ var ErrorPortSchema = import_typebox2.Type.Object({
1285
+ error: import_typebox2.Type.Object({
1286
+ message: import_typebox2.Type.String(),
1287
+ source: NodeSourceSchema
1288
+ })
1289
+ });
1290
+ var CompletePortSchema = import_typebox2.Type.Object({
1291
+ complete: import_typebox2.Type.Object({
1292
+ source: NodeSourceSchema
1293
+ })
1294
+ });
1295
+ var StatusPortSchema = import_typebox2.Type.Object({
1296
+ status: import_typebox2.Type.Union([
1297
+ import_typebox2.Type.Object({
1298
+ fill: import_typebox2.Type.Optional(
1299
+ import_typebox2.Type.Union([
1300
+ import_typebox2.Type.Literal("red"),
1301
+ import_typebox2.Type.Literal("green"),
1302
+ import_typebox2.Type.Literal("yellow"),
1303
+ import_typebox2.Type.Literal("blue"),
1304
+ import_typebox2.Type.Literal("grey"),
1305
+ import_typebox2.Type.Literal("gray")
1306
+ ])
1307
+ ),
1308
+ shape: import_typebox2.Type.Optional(
1309
+ import_typebox2.Type.Union([import_typebox2.Type.Literal("ring"), import_typebox2.Type.Literal("dot")])
1310
+ ),
1311
+ text: import_typebox2.Type.Optional(import_typebox2.Type.String())
1312
+ }),
1313
+ import_typebox2.Type.String()
1314
+ ]),
1315
+ source: NodeSourceSchema
1316
+ });
1317
+
1318
+ // src/core/shared/schemas/type.ts
1319
+ var import_typebox3 = require("@sinclair/typebox");
1320
+ var JSON_TYPES = /* @__PURE__ */ new Set([
1321
+ "null",
1322
+ "boolean",
1323
+ "object",
1324
+ "array",
1325
+ "number",
1326
+ "integer",
1327
+ "string"
1328
+ ]);
1329
+ function isJSONType(value) {
1330
+ return typeof value === "string" && JSON_TYPES.has(value);
1331
+ }
1332
+ function NodeRef(type, options) {
1212
1333
  return {
1213
- ...import_typebox2.Type.String({
1214
- description: options?.description || `Reference to ${nodeClass.type}`,
1215
- format: "node-id"
1334
+ ...import_typebox3.Type.String({
1335
+ description: options?.description || `Reference to ${type}`
1216
1336
  }),
1217
- "x-nrg-node-type": nodeClass.type,
1337
+ // `...options` first so user options (description, x-nrg-form, …) apply, but
1338
+ // the framework keys below win — a caller can't clobber `format`/
1339
+ // `x-nrg-node-type`/`Kind` and silently break NodeRef resolution.
1218
1340
  ...options,
1219
- [import_typebox2.Kind]: "NodeRef"
1341
+ format: "node-id",
1342
+ "x-nrg-node-type": type,
1343
+ [import_typebox3.Kind]: "NodeRef"
1220
1344
  };
1221
1345
  }
1222
1346
  function TypedInput2(options) {
1223
1347
  return {
1224
1348
  ...TypedInputSchema,
1225
- "x-nrg-typed-input": true,
1349
+ // `...options` first; the framework key below wins (see NodeRef).
1226
1350
  ...options,
1227
- [import_typebox2.Kind]: "TypedInput"
1351
+ "x-nrg-typed-input": true,
1352
+ [import_typebox3.Kind]: "TypedInput"
1228
1353
  };
1229
1354
  }
1230
1355
  function NrgString(options) {
1231
- return import_typebox2.Type.String(options);
1356
+ return import_typebox3.Type.String(options);
1232
1357
  }
1233
1358
  function OutputReturnProperties(options) {
1234
- return import_typebox2.Type.Record(
1235
- import_typebox2.Type.Number(),
1236
- import_typebox2.Type.String({ pattern: "^[A-Za-z_$][A-Za-z0-9_$]*$" }),
1359
+ return import_typebox3.Type.Record(
1360
+ import_typebox3.Type.Number(),
1361
+ import_typebox3.Type.String({ pattern: "^[A-Za-z_$][A-Za-z0-9_$]*$" }),
1237
1362
  {
1238
1363
  description: "Per-port return property, keyed by output port index. A missing entry falls back to `output`.",
1239
1364
  default: {},
@@ -1242,12 +1367,12 @@ function OutputReturnProperties(options) {
1242
1367
  );
1243
1368
  }
1244
1369
  function OutputContextModes(options) {
1245
- return import_typebox2.Type.Record(
1246
- import_typebox2.Type.Number(),
1247
- import_typebox2.Type.Union([
1248
- import_typebox2.Type.Literal("carry"),
1249
- import_typebox2.Type.Literal("trace"),
1250
- import_typebox2.Type.Literal("reset")
1370
+ return import_typebox3.Type.Record(
1371
+ import_typebox3.Type.Number(),
1372
+ import_typebox3.Type.Union([
1373
+ import_typebox3.Type.Literal("carry"),
1374
+ import_typebox3.Type.Literal("trace"),
1375
+ import_typebox3.Type.Literal("reset")
1251
1376
  ]),
1252
1377
  {
1253
1378
  description: "Per-port context mode, keyed by output port index. A missing entry falls back to `carry`.",
@@ -1256,16 +1381,21 @@ function OutputContextModes(options) {
1256
1381
  }
1257
1382
  );
1258
1383
  }
1259
- var SchemaType = Object.assign({}, import_typebox2.Type, {
1384
+ function NrgUnsafe(options) {
1385
+ return import_typebox3.Type.Unsafe(options);
1386
+ }
1387
+ var NrgBuilders = {
1260
1388
  String: NrgString,
1389
+ Unsafe: NrgUnsafe,
1261
1390
  NodeRef,
1262
1391
  TypedInput: TypedInput2,
1263
1392
  OutputReturnProperties,
1264
1393
  OutputContextModes
1265
- });
1394
+ };
1395
+ var SchemaType = Object.assign({}, import_typebox3.Type, NrgBuilders);
1266
1396
  function markNonValidatable(schema) {
1267
1397
  const type = schema.type;
1268
- const hasInvalidType = type !== void 0 && (Array.isArray(type) ? !type.every(import_rules.isJSONType) : !(0, import_rules.isJSONType)(type));
1398
+ const hasInvalidType = type !== void 0 && (Array.isArray(type) ? !type.every(isJSONType) : !isJSONType(type));
1269
1399
  if (hasInvalidType) {
1270
1400
  schema["x-nrg-skip-validation"] = true;
1271
1401
  if (schema.default !== void 0) {
@@ -1279,6 +1409,14 @@ function markNonValidatable(schema) {
1279
1409
  markNonValidatable(prop);
1280
1410
  }
1281
1411
  }
1412
+ if (schema.patternProperties) {
1413
+ for (const prop of Object.values(schema.patternProperties)) {
1414
+ markNonValidatable(prop);
1415
+ }
1416
+ }
1417
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
1418
+ markNonValidatable(schema.additionalProperties);
1419
+ }
1282
1420
  if (schema.items) {
1283
1421
  markNonValidatable(schema.items);
1284
1422
  }
@@ -1298,97 +1436,6 @@ function defineSchema(properties, options) {
1298
1436
  return markNonValidatable(schema);
1299
1437
  }
1300
1438
 
1301
- // src/core/server/schemas/base.ts
1302
- var NodeConfigSchema = SchemaType.Object({
1303
- id: SchemaType.String(),
1304
- type: SchemaType.String(),
1305
- name: SchemaType.String(),
1306
- z: SchemaType.Optional(SchemaType.String())
1307
- });
1308
- var ConfigNodeConfigSchema = SchemaType.Object({
1309
- ...NodeConfigSchema.properties,
1310
- _users: SchemaType.Array(SchemaType.String())
1311
- });
1312
- var IONodeConfigSchema = SchemaType.Object({
1313
- ...NodeConfigSchema.properties,
1314
- wires: SchemaType.Array(
1315
- SchemaType.Array(SchemaType.String(), { default: [] }),
1316
- {
1317
- default: [[]]
1318
- }
1319
- ),
1320
- x: SchemaType.Number(),
1321
- y: SchemaType.Number(),
1322
- g: SchemaType.Optional(SchemaType.String())
1323
- });
1324
- var TypedInputSchema = SchemaType.Object(
1325
- {
1326
- value: SchemaType.Union(
1327
- [
1328
- SchemaType.String(),
1329
- SchemaType.Number(),
1330
- SchemaType.Boolean(),
1331
- SchemaType.Null()
1332
- ],
1333
- {
1334
- description: "The actual value entered or selected.",
1335
- default: ""
1336
- }
1337
- ),
1338
- type: SchemaType.Union(
1339
- TYPED_INPUT_TYPES.map((type) => SchemaType.Literal(type)),
1340
- {
1341
- description: "The type of the value (string, number, message property, etc.)",
1342
- default: "str"
1343
- }
1344
- )
1345
- },
1346
- {
1347
- description: "Represents a Node-RED TypedInput value and its type.",
1348
- default: {
1349
- type: "str",
1350
- value: ""
1351
- }
1352
- }
1353
- );
1354
- var NodeSourceSchema = SchemaType.Object({
1355
- id: SchemaType.String(),
1356
- type: SchemaType.String(),
1357
- name: SchemaType.String()
1358
- });
1359
- var ErrorPortSchema = SchemaType.Object({
1360
- error: SchemaType.Object({
1361
- message: SchemaType.String(),
1362
- source: NodeSourceSchema
1363
- })
1364
- });
1365
- var CompletePortSchema = SchemaType.Object({
1366
- complete: SchemaType.Object({
1367
- source: NodeSourceSchema
1368
- })
1369
- });
1370
- var StatusPortSchema = SchemaType.Object({
1371
- status: SchemaType.Union([
1372
- SchemaType.Object({
1373
- fill: SchemaType.Optional(
1374
- SchemaType.Union([
1375
- SchemaType.Literal("red"),
1376
- SchemaType.Literal("green")
1377
- ])
1378
- ),
1379
- shape: SchemaType.Optional(
1380
- SchemaType.Union([
1381
- SchemaType.Literal("dot"),
1382
- SchemaType.Literal("string")
1383
- ])
1384
- ),
1385
- text: SchemaType.Optional(SchemaType.String())
1386
- }),
1387
- SchemaType.String()
1388
- ]),
1389
- source: NodeSourceSchema
1390
- });
1391
-
1392
1439
  // src/core/server/index.ts
1393
1440
  function defineModule(definition) {
1394
1441
  return definition;