@bonsae/nrg-runtime 0.38.1 → 0.39.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/index.cjs +265 -57
- package/package.json +1 -1
package/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/sdk/lib/runtime.ts
|
|
31
31
|
var runtime_exports = {};
|
|
32
32
|
__export(runtime_exports, {
|
|
33
|
+
Channels: () => Channels,
|
|
33
34
|
ConfigNode: () => ConfigNode,
|
|
34
35
|
IONode: () => IONode,
|
|
35
36
|
Kind: () => import_typebox3.Kind,
|
|
@@ -43,13 +44,20 @@ __export(runtime_exports, {
|
|
|
43
44
|
});
|
|
44
45
|
module.exports = __toCommonJS(runtime_exports);
|
|
45
46
|
|
|
46
|
-
// src/sdk/lib/server/
|
|
47
|
+
// src/sdk/lib/server/symbols/node.ts
|
|
47
48
|
var NRG_SETUP_CLOSE_HANDLER = Symbol.for("nrg.setupCloseHandler");
|
|
48
49
|
var NRG_SETUP_INPUT_HANDLER = Symbol.for("nrg.setupInputHandler");
|
|
49
50
|
var NRG_NODE = Symbol.for("nrg.node");
|
|
50
51
|
var NRG_CONFIG_NODE = Symbol.for("nrg.configNode");
|
|
51
52
|
var NRG_PORTS = Symbol.for("nrg.ports");
|
|
52
53
|
|
|
54
|
+
// src/sdk/lib/server/symbols/channels-store.ts
|
|
55
|
+
var NRG_PROTECTED_CHANNEL = Symbol.for("nrg.protectedChannel");
|
|
56
|
+
var NRG_PRIVATE_CHANNEL = Symbol.for("nrg.privateChannel");
|
|
57
|
+
var NRG_MODULE_PRIVATE_CHANNEL = Symbol.for(
|
|
58
|
+
"nrg.modulePrivateChannel"
|
|
59
|
+
);
|
|
60
|
+
|
|
53
61
|
// src/sdk/lib/shared/errors.ts
|
|
54
62
|
var NrgError = class _NrgError extends Error {
|
|
55
63
|
constructor(message) {
|
|
@@ -61,6 +69,7 @@ var NrgError = class _NrgError extends Error {
|
|
|
61
69
|
|
|
62
70
|
// src/sdk/lib/server/module.ts
|
|
63
71
|
function defineModule(definition) {
|
|
72
|
+
const packagePrivateChannel = Symbol("nrg.package.private");
|
|
64
73
|
for (const NodeClass of definition.nodes) {
|
|
65
74
|
if (!NodeClass[NRG_NODE]) {
|
|
66
75
|
const name = NodeClass?.name || String(NodeClass);
|
|
@@ -68,6 +77,7 @@ function defineModule(definition) {
|
|
|
68
77
|
`defineModule: "${name}" is not an nrg node class \u2014 extend IONode/ConfigNode.`
|
|
69
78
|
);
|
|
70
79
|
}
|
|
80
|
+
NodeClass[NRG_MODULE_PRIVATE_CHANNEL] = packagePrivateChannel;
|
|
71
81
|
}
|
|
72
82
|
return definition;
|
|
73
83
|
}
|
|
@@ -249,44 +259,108 @@ var ValidationError = class _ValidationError extends Error {
|
|
|
249
259
|
}
|
|
250
260
|
};
|
|
251
261
|
|
|
252
|
-
// src/sdk/lib/server/
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
262
|
+
// src/sdk/lib/server/channels-store.ts
|
|
263
|
+
var ASSIGN_MESSAGE = "Cannot assign to a message channel. Channel data is written on send \u2014 `send(port, value, { protected, private })`. The `msg[Channels]` accessor is read + delete only (e.g. `delete msg[Channels].private.x`).";
|
|
264
|
+
var ChannelStore = class {
|
|
265
|
+
#byMsgid = /* @__PURE__ */ new Map();
|
|
266
|
+
#ttlMs;
|
|
267
|
+
#sweepMs;
|
|
268
|
+
#sweeper;
|
|
269
|
+
constructor(options = {}) {
|
|
270
|
+
this.#ttlMs = options.ttlMs ?? 5 * 6e4;
|
|
271
|
+
this.#sweepMs = options.sweepMs ?? 6e4;
|
|
272
|
+
}
|
|
273
|
+
#channel(msgid, partition, create) {
|
|
274
|
+
let entry = this.#byMsgid.get(msgid);
|
|
275
|
+
if (!entry) {
|
|
276
|
+
if (!create) return void 0;
|
|
277
|
+
entry = { lastTouched: Date.now(), partitions: /* @__PURE__ */ new Map() };
|
|
278
|
+
this.#byMsgid.set(msgid, entry);
|
|
279
|
+
this.#ensureSweeper();
|
|
280
|
+
} else {
|
|
281
|
+
entry.lastTouched = Date.now();
|
|
282
|
+
}
|
|
283
|
+
let channel = entry.partitions.get(partition);
|
|
284
|
+
if (!channel && create) {
|
|
285
|
+
channel = /* @__PURE__ */ new Map();
|
|
286
|
+
entry.partitions.set(partition, channel);
|
|
287
|
+
}
|
|
288
|
+
return channel;
|
|
289
|
+
}
|
|
290
|
+
/** Merge a producer's `{ … }` bag into a channel (send's protected/private args). */
|
|
291
|
+
merge(msgid, partition, data) {
|
|
292
|
+
const channel = this.#channel(msgid, partition, true);
|
|
293
|
+
for (const [key, value] of Object.entries(data)) channel.set(key, value);
|
|
294
|
+
}
|
|
295
|
+
get(msgid, partition, key) {
|
|
296
|
+
return this.#channel(msgid, partition, false)?.get(key);
|
|
297
|
+
}
|
|
298
|
+
set(msgid, partition, key, value) {
|
|
299
|
+
this.#channel(msgid, partition, true).set(key, value);
|
|
300
|
+
}
|
|
301
|
+
has(msgid, partition, key) {
|
|
302
|
+
return this.#channel(msgid, partition, false)?.has(key) ?? false;
|
|
303
|
+
}
|
|
304
|
+
/** Remove one entry — framework bookkeeping only; the resource owns release. */
|
|
305
|
+
delete(msgid, partition, key) {
|
|
306
|
+
this.#channel(msgid, partition, false)?.delete(key);
|
|
307
|
+
}
|
|
308
|
+
keys(msgid, partition) {
|
|
309
|
+
return [...this.#channel(msgid, partition, false)?.keys() ?? []];
|
|
310
|
+
}
|
|
311
|
+
#ensureSweeper() {
|
|
312
|
+
if (this.#sweeper) return;
|
|
313
|
+
this.#sweeper = setInterval(() => this.#sweep(), this.#sweepMs);
|
|
314
|
+
this.#sweeper.unref?.();
|
|
315
|
+
}
|
|
316
|
+
#sweep() {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
for (const [msgid, entry] of this.#byMsgid) {
|
|
319
|
+
if (now - entry.lastTouched > this.#ttlMs) this.#byMsgid.delete(msgid);
|
|
320
|
+
}
|
|
321
|
+
if (this.#byMsgid.size === 0 && this.#sweeper) {
|
|
322
|
+
clearInterval(this.#sweeper);
|
|
323
|
+
this.#sweeper = void 0;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
function channelProxy(store, msgid, partition) {
|
|
328
|
+
if (msgid == null) {
|
|
329
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
330
|
+
get: () => void 0,
|
|
331
|
+
set: () => {
|
|
332
|
+
throw new NrgError(ASSIGN_MESSAGE);
|
|
333
|
+
},
|
|
334
|
+
deleteProperty: () => true,
|
|
335
|
+
has: () => false,
|
|
336
|
+
ownKeys: () => [],
|
|
337
|
+
getOwnPropertyDescriptor: () => void 0
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
341
|
+
get: (_t, key) => typeof key === "string" ? store.get(msgid, partition, key) : void 0,
|
|
342
|
+
// Assignment is a misuse — channels are written on send, not by mutating the
|
|
343
|
+
// incoming message. Throw loudly rather than silently writing a second path.
|
|
344
|
+
set: () => {
|
|
345
|
+
throw new NrgError(ASSIGN_MESSAGE);
|
|
346
|
+
},
|
|
347
|
+
deleteProperty: (_t, key) => {
|
|
348
|
+
if (typeof key === "string") store.delete(msgid, partition, key);
|
|
349
|
+
return true;
|
|
350
|
+
},
|
|
351
|
+
has: (_t, key) => typeof key === "string" ? store.has(msgid, partition, key) : false,
|
|
352
|
+
ownKeys: () => store.keys(msgid, partition),
|
|
353
|
+
getOwnPropertyDescriptor: (_t, key) => typeof key === "string" && store.has(msgid, partition, key) ? {
|
|
354
|
+
enumerable: true,
|
|
355
|
+
configurable: true,
|
|
356
|
+
writable: false,
|
|
357
|
+
value: store.get(msgid, partition, key)
|
|
358
|
+
} : void 0
|
|
288
359
|
});
|
|
289
360
|
}
|
|
361
|
+
function packageChannel(nodeClass) {
|
|
362
|
+
return nodeClass[NRG_MODULE_PRIVATE_CHANNEL] ?? NRG_PRIVATE_CHANNEL;
|
|
363
|
+
}
|
|
290
364
|
|
|
291
365
|
// src/sdk/lib/server/api/assets.ts
|
|
292
366
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
@@ -320,6 +394,51 @@ function initRoutes(RED) {
|
|
|
320
394
|
initAssetsRoutes(RED.httpAdmin);
|
|
321
395
|
}
|
|
322
396
|
|
|
397
|
+
// src/sdk/lib/server/init.ts
|
|
398
|
+
function initValidator(RED) {
|
|
399
|
+
if (RED.validator) return;
|
|
400
|
+
const validator = new Validator({
|
|
401
|
+
customKeywords: [
|
|
402
|
+
{
|
|
403
|
+
keyword: "x-nrg-skip-validation",
|
|
404
|
+
schemaType: "boolean",
|
|
405
|
+
valid: true
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
keyword: "x-nrg-node-type",
|
|
409
|
+
type: "string",
|
|
410
|
+
validate: (schemaValue, dataValue) => {
|
|
411
|
+
if (!dataValue) return true;
|
|
412
|
+
const node = RED.nodes.getNode(dataValue);
|
|
413
|
+
return node?.type === schemaValue;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
],
|
|
417
|
+
customFormats: {
|
|
418
|
+
"node-id": /^[a-zA-Z0-9-_]+$/
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
Object.defineProperty(RED, "validator", {
|
|
422
|
+
get: () => validator,
|
|
423
|
+
enumerable: false,
|
|
424
|
+
configurable: false
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function initChannelStore(RED) {
|
|
428
|
+
if (RED.channelStore) return;
|
|
429
|
+
const channelStore = new ChannelStore();
|
|
430
|
+
Object.defineProperty(RED, "channelStore", {
|
|
431
|
+
get: () => channelStore,
|
|
432
|
+
enumerable: false,
|
|
433
|
+
configurable: false
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function init(RED) {
|
|
437
|
+
initValidator(RED);
|
|
438
|
+
initChannelStore(RED);
|
|
439
|
+
initRoutes(RED);
|
|
440
|
+
}
|
|
441
|
+
|
|
323
442
|
// src/sdk/lib/shared/schemas/utils.ts
|
|
324
443
|
function getCredentialsFromSchema(schema) {
|
|
325
444
|
if (!schema?.properties) return void 0;
|
|
@@ -416,8 +535,7 @@ async function registerType(RED, NodeClass) {
|
|
|
416
535
|
function registerTypes(nodes) {
|
|
417
536
|
const fn = Object.assign(
|
|
418
537
|
async function(RED) {
|
|
419
|
-
|
|
420
|
-
initRoutes(RED);
|
|
538
|
+
init(RED);
|
|
421
539
|
try {
|
|
422
540
|
RED.log.info("Registering node types in series");
|
|
423
541
|
for (const NodeClass of nodes) {
|
|
@@ -788,6 +906,9 @@ function setupContext(context, store) {
|
|
|
788
906
|
return { get, set, keys, update, increment };
|
|
789
907
|
}
|
|
790
908
|
|
|
909
|
+
// src/sdk/lib/server/nodes/types/ports.ts
|
|
910
|
+
var Channels = Symbol.for("nrg.channels");
|
|
911
|
+
|
|
791
912
|
// src/sdk/lib/server/nodes/io-node.ts
|
|
792
913
|
var import_node_async_hooks = require("node:async_hooks");
|
|
793
914
|
var RETURN_PROPERTY_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -893,7 +1014,10 @@ var IONode = class _IONode extends Node {
|
|
|
893
1014
|
this.#sendToPort("complete", {
|
|
894
1015
|
...result !== void 0 ? { complete: result } : {},
|
|
895
1016
|
[SOURCE_KEY]: this.#nodeSource(),
|
|
896
|
-
[INPUT_KEY]: msg
|
|
1017
|
+
[INPUT_KEY]: msg,
|
|
1018
|
+
// Runs after input() resolved (outside the ALS scope), so inherit
|
|
1019
|
+
// the incoming `_msgid` explicitly to keep the lineage id intact.
|
|
1020
|
+
...this.#sourceMsgid(msg)
|
|
897
1021
|
});
|
|
898
1022
|
}
|
|
899
1023
|
done();
|
|
@@ -916,7 +1040,10 @@ var IONode = class _IONode extends Node {
|
|
|
916
1040
|
...stack ? { stack } : {}
|
|
917
1041
|
},
|
|
918
1042
|
[SOURCE_KEY]: this.#nodeSource(),
|
|
919
|
-
[INPUT_KEY]: msg
|
|
1043
|
+
[INPUT_KEY]: msg,
|
|
1044
|
+
// Runs in the catch (outside the ALS scope), so inherit the
|
|
1045
|
+
// incoming `_msgid` explicitly to keep the lineage id intact.
|
|
1046
|
+
...this.#sourceMsgid(msg)
|
|
920
1047
|
});
|
|
921
1048
|
this.node.error(errorMsg);
|
|
922
1049
|
}
|
|
@@ -946,20 +1073,65 @@ var IONode = class _IONode extends Node {
|
|
|
946
1073
|
this.node.log("Input is valid");
|
|
947
1074
|
}
|
|
948
1075
|
}
|
|
1076
|
+
this.#setupChannels(msg);
|
|
949
1077
|
return await _IONode.#invocation.run(
|
|
950
1078
|
store,
|
|
951
1079
|
() => Promise.resolve(this.input(msg))
|
|
952
1080
|
);
|
|
953
1081
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1082
|
+
/**
|
|
1083
|
+
* Add the `[Channels]` accessor to the incoming message. SYMBOL-keyed, so it can
|
|
1084
|
+
* never collide with an author's own message fields and is already invisible to
|
|
1085
|
+
* JSON / `Object.keys` / the debug panel (the `enumerable: false` keeps it out of
|
|
1086
|
+
* a symbol-aware clone too). Reading `msg[Channels]` yields `{ protected, private }`
|
|
1087
|
+
* — proxies over the channel store keyed by the message's `_msgid`, `private`
|
|
1088
|
+
* partitioned by this node's package. A core function node gets the bare message
|
|
1089
|
+
* and has no accessor — the channels are structurally invisible to the flow author.
|
|
1090
|
+
*
|
|
1091
|
+
* Mutates `msg` in place (the caller already holds it as `TInput`). A non-object
|
|
1092
|
+
* message (never produced by real Node-RED, which always delivers an object) is
|
|
1093
|
+
* left untouched.
|
|
1094
|
+
*/
|
|
1095
|
+
#setupChannels(msg) {
|
|
1096
|
+
if (msg == null || typeof msg !== "object") return;
|
|
1097
|
+
const store = this.RED.channelStore;
|
|
1098
|
+
const pkg = packageChannel(this.constructor);
|
|
1099
|
+
Object.defineProperty(msg, Channels, {
|
|
1100
|
+
configurable: true,
|
|
1101
|
+
enumerable: false,
|
|
1102
|
+
get() {
|
|
1103
|
+
const msgid = this._msgid;
|
|
1104
|
+
return {
|
|
1105
|
+
protected: channelProxy(store, msgid, NRG_PROTECTED_CHANNEL),
|
|
1106
|
+
private: channelProxy(store, msgid, pkg)
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
961
1109
|
});
|
|
962
|
-
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Merge the `protected`/`private` args into this message's channels. Keyed by the
|
|
1113
|
+
* outgoing `_msgid`: an input-triggered send inherits it; a source send (no
|
|
1114
|
+
* invocation) mints one and stamps it on the outgoing frames so a downstream
|
|
1115
|
+
* node reads the same channel. Contributions are STICKY — they persist for the
|
|
1116
|
+
* message's journey, so a middle node's plain `send()` keeps them alive.
|
|
1117
|
+
*/
|
|
1118
|
+
#writeChannels(protectedData, privateData, out) {
|
|
1119
|
+
if (!protectedData && !privateData) return;
|
|
1120
|
+
const existing = _IONode.#invocation.getStore()?.inputMsg?.[MSGID_KEY];
|
|
1121
|
+
const msgid = existing ?? this.RED.util.generateId();
|
|
1122
|
+
if (!existing) {
|
|
1123
|
+
for (const m of out) {
|
|
1124
|
+
if (m && typeof m === "object") {
|
|
1125
|
+
const rec = m;
|
|
1126
|
+
if (rec[MSGID_KEY] === void 0) rec[MSGID_KEY] = msgid;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const store = this.RED.channelStore;
|
|
1131
|
+
if (protectedData) store.merge(msgid, NRG_PROTECTED_CHANNEL, protectedData);
|
|
1132
|
+
if (privateData) {
|
|
1133
|
+
store.merge(msgid, packageChannel(this.constructor), privateData);
|
|
1134
|
+
}
|
|
963
1135
|
}
|
|
964
1136
|
#deliver(out) {
|
|
965
1137
|
const send = _IONode.#invocation.getStore()?.send;
|
|
@@ -1064,7 +1236,7 @@ var IONode = class _IONode extends Node {
|
|
|
1064
1236
|
);
|
|
1065
1237
|
if (mode === "reset")
|
|
1066
1238
|
return this.#withMsgid({ [key]: value, [SOURCE_KEY]: source });
|
|
1067
|
-
if (mode === "trace") return frame(input);
|
|
1239
|
+
if (mode === "trace") return frame({ ...input });
|
|
1068
1240
|
const lastOnly = { ...input };
|
|
1069
1241
|
delete lastOnly[INPUT_KEY];
|
|
1070
1242
|
return frame(lastOnly);
|
|
@@ -1083,6 +1255,19 @@ var IONode = class _IONode extends Node {
|
|
|
1083
1255
|
const msgid = _IONode.#invocation.getStore()?.inputMsg?.[MSGID_KEY];
|
|
1084
1256
|
return msgid !== void 0 ? { ...frame, [MSGID_KEY]: msgid } : frame;
|
|
1085
1257
|
}
|
|
1258
|
+
/**
|
|
1259
|
+
* The source message's `_msgid` as a spreadable frame fragment — for the
|
|
1260
|
+
* built-in complete/error auto-emits, which run AFTER `input()` has resolved,
|
|
1261
|
+
* i.e. OUTSIDE the invocation ALS scope, so {@link #withMsgid} (which reads the
|
|
1262
|
+
* scope) can't recover it. Stamping it here keeps the lifecycle frame on the
|
|
1263
|
+
* SAME lineage id as the data-port emits, so Catch/Complete grouping and any
|
|
1264
|
+
* `_msgid` correlation still work. `{}` when the source carries none (a source
|
|
1265
|
+
* node) — Node-RED then assigns one, exactly as {@link #withMsgid} documents.
|
|
1266
|
+
*/
|
|
1267
|
+
#sourceMsgid(src) {
|
|
1268
|
+
const id = src?.[MSGID_KEY];
|
|
1269
|
+
return typeof id === "string" ? { [MSGID_KEY]: id } : {};
|
|
1270
|
+
}
|
|
1086
1271
|
/**
|
|
1087
1272
|
* Provenance stamped on every data-port output under `msg.source`: the
|
|
1088
1273
|
* producing node plus the port the message was sent on (with the named-port
|
|
@@ -1126,32 +1311,51 @@ var IONode = class _IONode extends Node {
|
|
|
1126
1311
|
* framework and cannot be sent to directly. Use `this.status()` for status,
|
|
1127
1312
|
* throw an error or call `this.error()` for the error port, and the complete
|
|
1128
1313
|
* port is sent automatically on successful input processing.
|
|
1314
|
+
*
|
|
1315
|
+
* The optional `channels` argument writes off-the-wire data to the message's
|
|
1316
|
+
* channel store (keyed by its `_msgid`): `protected` targets the package-shared
|
|
1317
|
+
* partition, `private` this package's own. It is reachable downstream via
|
|
1318
|
+
* `msg[Channels]` and never rides the serialized message. A single object (not
|
|
1319
|
+
* positional args) so new channels stay additive.
|
|
1129
1320
|
*/
|
|
1130
|
-
|
|
1321
|
+
send(port, msg, channels) {
|
|
1131
1322
|
if (port === "error" || port === "complete" || port === "status") {
|
|
1132
1323
|
throw new NrgError(
|
|
1133
|
-
`
|
|
1324
|
+
`send("${port}") is not allowed. Built-in ports are managed by the framework.`
|
|
1134
1325
|
);
|
|
1135
1326
|
}
|
|
1327
|
+
if (typeof port === "number") {
|
|
1328
|
+
if (!Number.isInteger(port) || port < 0) {
|
|
1329
|
+
throw new NrgError(
|
|
1330
|
+
`send(${port}) \u2014 a numeric output port must be a non-negative integer index.`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
if (port >= this.#baseOutputs && port < this.#totalOutputs) {
|
|
1334
|
+
throw new NrgError(
|
|
1335
|
+
`send(${port}) targets a framework-managed built-in port slot (error/complete/status).` + (this.#baseOutputs > 0 ? ` Send to a base output port (0..${this.#baseOutputs - 1}),` : ` Send to a base output port,`) + ` or use this.status() / this.error().`
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1136
1339
|
const portIndex = typeof port === "number" ? port : this.#getNamedPortIndex(port);
|
|
1137
1340
|
if (typeof port === "string" && portIndex === null) {
|
|
1138
1341
|
const keys = this.#namedPortKeys();
|
|
1139
1342
|
throw new NrgError(
|
|
1140
|
-
keys && keys.length ? `
|
|
1343
|
+
keys && keys.length ? `send("${port}") \u2014 unknown output port. Valid named ports: ${keys.map((n) => `"${n}"`).join(", ")}.` : `send("${port}") \u2014 this node has no named output ports. Declare named ports with a Port<T> record in the node's Output generic, or send to a numeric port index.`
|
|
1141
1344
|
);
|
|
1142
1345
|
}
|
|
1143
1346
|
const idx = portIndex ?? 0;
|
|
1144
1347
|
if (msg == null) {
|
|
1145
|
-
this.#sendToPort(port, msg);
|
|
1348
|
+
this.#sendToPort(port, msg, channels);
|
|
1146
1349
|
return;
|
|
1147
1350
|
}
|
|
1148
1351
|
this.#validatePort(msg, idx);
|
|
1149
1352
|
this.#sendToPort(
|
|
1150
1353
|
port,
|
|
1151
|
-
this.#wrapOutgoing(msg, this.#resolveContextMode(idx), idx)
|
|
1354
|
+
this.#wrapOutgoing(msg, this.#resolveContextMode(idx), idx),
|
|
1355
|
+
channels
|
|
1152
1356
|
);
|
|
1153
1357
|
}
|
|
1154
|
-
#sendToPort(port, msg) {
|
|
1358
|
+
#sendToPort(port, msg, channels) {
|
|
1155
1359
|
let portIndex;
|
|
1156
1360
|
if (typeof port === "number") {
|
|
1157
1361
|
portIndex = port;
|
|
@@ -1166,7 +1370,10 @@ var IONode = class _IONode extends Node {
|
|
|
1166
1370
|
}
|
|
1167
1371
|
const out = new Array(this.#totalOutputs);
|
|
1168
1372
|
out[portIndex] = msg !== null && typeof msg === "object" ? this.#withMsgid(msg) : msg;
|
|
1169
|
-
|
|
1373
|
+
if (channels?.protected || channels?.private) {
|
|
1374
|
+
this.#writeChannels(channels.protected, channels.private, out);
|
|
1375
|
+
}
|
|
1376
|
+
this.#deliver(out);
|
|
1170
1377
|
}
|
|
1171
1378
|
/** The declared named output ports (from the injected `Output`-generic
|
|
1172
1379
|
* topology's `Port<T>` record), or null when the node has none (a
|
|
@@ -1515,6 +1722,7 @@ function defineSchema(properties, options) {
|
|
|
1515
1722
|
var import_typebox3 = require("@sinclair/typebox");
|
|
1516
1723
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1517
1724
|
0 && (module.exports = {
|
|
1725
|
+
Channels,
|
|
1518
1726
|
ConfigNode,
|
|
1519
1727
|
IONode,
|
|
1520
1728
|
Kind,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bonsae/nrg-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "The runtime for Node-RED nodes built with @bonsae/nrg — node base classes, schemas, AJV validator, and the editor client. Carries no build tooling.",
|
|
5
5
|
"author": "Allan Oricil <allanoricil@duck.com>",
|
|
6
6
|
"license": "MIT",
|