@bonsae/nrg-runtime 0.38.0 → 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 +287 -61
- 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,18 +906,22 @@ 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_$]*$/;
|
|
794
915
|
var INPUT_KEY = "input";
|
|
795
916
|
var SOURCE_KEY = "source";
|
|
917
|
+
var MSGID_KEY = "_msgid";
|
|
796
918
|
var RESERVED_RETURN_PROPERTIES = /* @__PURE__ */ new Set([
|
|
797
919
|
SOURCE_KEY,
|
|
798
920
|
INPUT_KEY,
|
|
799
921
|
"error",
|
|
800
922
|
"complete",
|
|
801
923
|
"status",
|
|
802
|
-
|
|
924
|
+
MSGID_KEY
|
|
803
925
|
]);
|
|
804
926
|
var IONode = class _IONode extends Node {
|
|
805
927
|
static align;
|
|
@@ -892,7 +1014,10 @@ var IONode = class _IONode extends Node {
|
|
|
892
1014
|
this.#sendToPort("complete", {
|
|
893
1015
|
...result !== void 0 ? { complete: result } : {},
|
|
894
1016
|
[SOURCE_KEY]: this.#nodeSource(),
|
|
895
|
-
[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)
|
|
896
1021
|
});
|
|
897
1022
|
}
|
|
898
1023
|
done();
|
|
@@ -915,7 +1040,10 @@ var IONode = class _IONode extends Node {
|
|
|
915
1040
|
...stack ? { stack } : {}
|
|
916
1041
|
},
|
|
917
1042
|
[SOURCE_KEY]: this.#nodeSource(),
|
|
918
|
-
[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)
|
|
919
1047
|
});
|
|
920
1048
|
this.node.error(errorMsg);
|
|
921
1049
|
}
|
|
@@ -945,20 +1073,65 @@ var IONode = class _IONode extends Node {
|
|
|
945
1073
|
this.node.log("Input is valid");
|
|
946
1074
|
}
|
|
947
1075
|
}
|
|
1076
|
+
this.#setupChannels(msg);
|
|
948
1077
|
return await _IONode.#invocation.run(
|
|
949
1078
|
store,
|
|
950
1079
|
() => Promise.resolve(this.input(msg))
|
|
951
1080
|
);
|
|
952
1081
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
+
}
|
|
960
1109
|
});
|
|
961
|
-
|
|
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
|
+
}
|
|
962
1135
|
}
|
|
963
1136
|
#deliver(out) {
|
|
964
1137
|
const send = _IONode.#invocation.getStore()?.send;
|
|
@@ -1058,13 +1231,43 @@ var IONode = class _IONode extends Node {
|
|
|
1058
1231
|
const key = this.#returnPropertyKey(port);
|
|
1059
1232
|
const input = _IONode.#invocation.getStore()?.inputMsg ?? {};
|
|
1060
1233
|
const source = this.#outputSource(port);
|
|
1061
|
-
const frame = (msg) =>
|
|
1062
|
-
|
|
1063
|
-
|
|
1234
|
+
const frame = (msg) => this.#withMsgid(
|
|
1235
|
+
Object.keys(msg).length ? { [key]: value, [SOURCE_KEY]: source, [INPUT_KEY]: msg } : { [key]: value, [SOURCE_KEY]: source }
|
|
1236
|
+
);
|
|
1237
|
+
if (mode === "reset")
|
|
1238
|
+
return this.#withMsgid({ [key]: value, [SOURCE_KEY]: source });
|
|
1239
|
+
if (mode === "trace") return frame({ ...input });
|
|
1064
1240
|
const lastOnly = { ...input };
|
|
1065
1241
|
delete lastOnly[INPUT_KEY];
|
|
1066
1242
|
return frame(lastOnly);
|
|
1067
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Preserve Node-RED's message-lineage id: a node's output inherits the
|
|
1246
|
+
* `_msgid` of the message it is processing (Node-RED assigns a fresh one only
|
|
1247
|
+
* when absent — see `Node.prototype.send`). Copying it onto every outgoing
|
|
1248
|
+
* message — in every context mode and on every port — stops the id forking at
|
|
1249
|
+
* each hop, which would otherwise break the message-flow debugger, Catch/
|
|
1250
|
+
* Complete grouping, and any `_msgid`-based correlation. Reads the current
|
|
1251
|
+
* invocation's input; a send outside any input() call (e.g. a source node)
|
|
1252
|
+
* has none, so nothing is stamped and Node-RED assigns one as usual.
|
|
1253
|
+
*/
|
|
1254
|
+
#withMsgid(frame) {
|
|
1255
|
+
const msgid = _IONode.#invocation.getStore()?.inputMsg?.[MSGID_KEY];
|
|
1256
|
+
return msgid !== void 0 ? { ...frame, [MSGID_KEY]: msgid } : frame;
|
|
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
|
+
}
|
|
1068
1271
|
/**
|
|
1069
1272
|
* Provenance stamped on every data-port output under `msg.source`: the
|
|
1070
1273
|
* producing node plus the port the message was sent on (with the named-port
|
|
@@ -1108,32 +1311,51 @@ var IONode = class _IONode extends Node {
|
|
|
1108
1311
|
* framework and cannot be sent to directly. Use `this.status()` for status,
|
|
1109
1312
|
* throw an error or call `this.error()` for the error port, and the complete
|
|
1110
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.
|
|
1111
1320
|
*/
|
|
1112
|
-
|
|
1321
|
+
send(port, msg, channels) {
|
|
1113
1322
|
if (port === "error" || port === "complete" || port === "status") {
|
|
1114
1323
|
throw new NrgError(
|
|
1115
|
-
`
|
|
1324
|
+
`send("${port}") is not allowed. Built-in ports are managed by the framework.`
|
|
1116
1325
|
);
|
|
1117
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
|
+
}
|
|
1118
1339
|
const portIndex = typeof port === "number" ? port : this.#getNamedPortIndex(port);
|
|
1119
1340
|
if (typeof port === "string" && portIndex === null) {
|
|
1120
1341
|
const keys = this.#namedPortKeys();
|
|
1121
1342
|
throw new NrgError(
|
|
1122
|
-
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.`
|
|
1123
1344
|
);
|
|
1124
1345
|
}
|
|
1125
1346
|
const idx = portIndex ?? 0;
|
|
1126
1347
|
if (msg == null) {
|
|
1127
|
-
this.#sendToPort(port, msg);
|
|
1348
|
+
this.#sendToPort(port, msg, channels);
|
|
1128
1349
|
return;
|
|
1129
1350
|
}
|
|
1130
1351
|
this.#validatePort(msg, idx);
|
|
1131
1352
|
this.#sendToPort(
|
|
1132
1353
|
port,
|
|
1133
|
-
this.#wrapOutgoing(msg, this.#resolveContextMode(idx), idx)
|
|
1354
|
+
this.#wrapOutgoing(msg, this.#resolveContextMode(idx), idx),
|
|
1355
|
+
channels
|
|
1134
1356
|
);
|
|
1135
1357
|
}
|
|
1136
|
-
#sendToPort(port, msg) {
|
|
1358
|
+
#sendToPort(port, msg, channels) {
|
|
1137
1359
|
let portIndex;
|
|
1138
1360
|
if (typeof port === "number") {
|
|
1139
1361
|
portIndex = port;
|
|
@@ -1147,8 +1369,11 @@ var IONode = class _IONode extends Node {
|
|
|
1147
1369
|
}
|
|
1148
1370
|
}
|
|
1149
1371
|
const out = new Array(this.#totalOutputs);
|
|
1150
|
-
out[portIndex] = msg;
|
|
1151
|
-
|
|
1372
|
+
out[portIndex] = msg !== null && typeof msg === "object" ? this.#withMsgid(msg) : msg;
|
|
1373
|
+
if (channels?.protected || channels?.private) {
|
|
1374
|
+
this.#writeChannels(channels.protected, channels.private, out);
|
|
1375
|
+
}
|
|
1376
|
+
this.#deliver(out);
|
|
1152
1377
|
}
|
|
1153
1378
|
/** The declared named output ports (from the injected `Output`-generic
|
|
1154
1379
|
* topology's `Port<T>` record), or null when the node has none (a
|
|
@@ -1497,6 +1722,7 @@ function defineSchema(properties, options) {
|
|
|
1497
1722
|
var import_typebox3 = require("@sinclair/typebox");
|
|
1498
1723
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1499
1724
|
0 && (module.exports = {
|
|
1725
|
+
Channels,
|
|
1500
1726
|
ConfigNode,
|
|
1501
1727
|
IONode,
|
|
1502
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",
|