@nrutils/node-red-utils 0.0.0-stage → 0.2.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +190 -0
  3. package/README.md +187 -2
  4. package/index.js +50 -0
  5. package/node-red-base/NodeRedNodeBase.js +143 -0
  6. package/node-red-base/README.md +215 -0
  7. package/node-red-editorcomms/EditorCommsBackend.js +155 -0
  8. package/node-red-editorcomms/EditorCommsFrontend.js +105 -0
  9. package/node-red-editorcomms/README.md +164 -0
  10. package/node-red-gpu/GpuBuffer.js +29 -0
  11. package/node-red-gpu/Kernel.js +35 -0
  12. package/node-red-gpu/README.md +220 -0
  13. package/node-red-gpu/backends/CpuBackend.js +109 -0
  14. package/node-red-gpu/backends/VulkanBackend.js +232 -0
  15. package/node-red-gpu/errors.js +48 -0
  16. package/node-red-gpu/index.js +6 -0
  17. package/node-red-gpu/log.js +18 -0
  18. package/node-red-gpu/native/binding.gyp +14 -0
  19. package/node-red-gpu/native/src/gpu_addon.cpp +1066 -0
  20. package/node-red-gpu/nodeRedGpuMain.js +39 -0
  21. package/node-red-gpu/nodeRedGpuWorker.js +242 -0
  22. package/node-red-gpu/transpiler/emitGlsl.js +497 -0
  23. package/node-red-gpu/transpiler/glslNames.js +60 -0
  24. package/node-red-gpu/transpiler/index.js +81 -0
  25. package/node-red-gpu/transpiler/inferTypes.js +302 -0
  26. package/node-red-gpu/transpiler/validate.js +461 -0
  27. package/node-red-popup/NodeRedPopupBackend.js +69 -0
  28. package/node-red-popup/NodeRedPopupFrontend.js +508 -0
  29. package/node-red-popup/README.md +113 -0
  30. package/node-red-pubsub-bus/NodeRedPubsubBus.js +617 -0
  31. package/node-red-pubsub-bus/NodeRedPubsubBusWorkerProxy.js +239 -0
  32. package/node-red-pubsub-bus/README.md +201 -0
  33. package/node-red-resourceendpoint/NodeRedResourceEndpoint.js +205 -0
  34. package/node-red-resourceendpoint/README.md +119 -0
  35. package/node-red-singleton/NodeRedSingletonBackend.js +30 -0
  36. package/node-red-singleton/NodeRedSingletonFrontend.js +54 -0
  37. package/node-red-singleton/README.md +72 -0
  38. package/node-red-worker/NodeRedWorker.js +443 -0
  39. package/node-red-worker/NodeRedWorkerRuntime.js +659 -0
  40. package/node-red-worker/README.md +273 -0
  41. package/node-red-worker/workerRuntimeLoader.js +55 -0
  42. package/package.json +70 -5
@@ -0,0 +1,119 @@
1
+ # node-red-resourceendpoint
2
+
3
+ Serve files from your node package to the editor: scripts, stylesheets, images, JSON.
4
+
5
+ ## Usage
6
+
7
+ ```js
8
+ const path = require('path');
9
+ const { NodeRedNodeBase, nodeRedResourceEndpoint } = require('@nrutils/node-red-utils');
10
+
11
+ module.exports = NodeRedNodeBase.createNode('chart-node', [nodeRedResourceEndpoint], (RED, thisNode, config) => {
12
+ // the runtime code of the node
13
+ }, {
14
+ resources: [
15
+ path.join(__dirname, 'public'), // every file under public/
16
+ path.join(__dirname, 'icons', 'chart.svg') // one file
17
+ ]
18
+ });
19
+ ```
20
+
21
+ Put the paths in `resources` in the fourth argument of `createNode`. They are registered when Node-RED loads the node, before any deploy, so the editor can load the files at once. `init()` exists for paths that depend on `config`; they are served only after the first deploy. `createNode` does not pass `resources` on to `RED.nodes.registerType`; the other keys (`credentials`, `settings`) still go there. The mixin must be in the `mixins` array.
22
+
23
+ The files are now served under `node-red-resourceendpoint/chart-node/` relative to the editor URL. In the node's `.html`, use relative URLs so that `httpAdminRoot` and reverse proxies work:
24
+
25
+ ```html
26
+ <script type="text/javascript" src="node-red-resourceendpoint/chart-node/chart-lib.js"></script>
27
+ <link rel="stylesheet" href="node-red-resourceendpoint/chart-node/chart.css">
28
+ <img src="node-red-resourceendpoint/chart-node/chart.svg">
29
+ ```
30
+
31
+ ## Example: a model list for the edit dialog
32
+
33
+ The edit dialog of an I2C sensor node fills a dropdown from a JSON file of the package, and sets the address of the selected model. This works for a new node that is not deployed yet, because the files are served from when Node-RED loads the node. [`nodeRedEditorComms`](../node-red-editorcomms/README.md) cannot do this: it needs a deployed node.
34
+
35
+ `i2c-sensor.js`:
36
+
37
+ ```js
38
+ const path = require('path');
39
+ const { NodeRedNodeBase, nodeRedResourceEndpoint } = require('@nrutils/node-red-utils');
40
+
41
+ module.exports = NodeRedNodeBase.createNode('i2c-sensor', [nodeRedResourceEndpoint], (RED, thisNode, config) => {
42
+ thisNode.on('input', (msg, send, done) => {
43
+ msg.payload = { model: config.model, address: Number(config.address) }; // read the device here
44
+ send(msg);
45
+ done();
46
+ });
47
+ }, {
48
+ resources: [path.join(__dirname, 'editor')] // editor/models.json and anything else in editor/
49
+ });
50
+ ```
51
+
52
+ `editor/models.json`:
53
+
54
+ ```json
55
+ [
56
+ { "id": "bme280", "label": "Bosch BME280", "address": 118 },
57
+ { "id": "sht31", "label": "Sensirion SHT31", "address": 68 }
58
+ ]
59
+ ```
60
+
61
+ `i2c-sensor.html`:
62
+
63
+ ```html
64
+ <script type="text/javascript">
65
+ RED.nodes.registerType('i2c-sensor', {
66
+ category: 'input',
67
+ color: '#e2d96e',
68
+ defaults: { model: { value: 'bme280' }, address: { value: 118 } },
69
+ inputs: 1,
70
+ outputs: 1,
71
+ label: function () { return this.model; },
72
+ oneditprepare: function () {
73
+ const node = this;
74
+ const select = $('#node-input-model');
75
+ // A file of this package: it loads also for a new node that is not deployed
76
+ $.getJSON('node-red-resourceendpoint/i2c-sensor/models.json', function (models) {
77
+ for (const m of models) select.append($('<option>').val(m.id).text(m.label));
78
+ select.val(node.model).on('change', function () {
79
+ const model = models.find((m) => m.id === select.val());
80
+ if (model) $('#node-input-address').val(model.address);
81
+ });
82
+ });
83
+ }
84
+ });
85
+ </script>
86
+
87
+ <script type="text/html" data-template-name="i2c-sensor">
88
+ <div class="form-row">
89
+ <label for="node-input-model">Model</label>
90
+ <select id="node-input-model"></select>
91
+ </div>
92
+ <div class="form-row">
93
+ <label for="node-input-address">Address</label>
94
+ <input type="text" id="node-input-address">
95
+ </div>
96
+ </script>
97
+ ```
98
+
99
+ ## URL rules
100
+
101
+ URLs have the form `node-red-resourceendpoint/<nodeType>/<path>`.
102
+
103
+ - A registered file is served under its base name: `icons/chart.svg` above becomes `.../chart-node/chart.svg`.
104
+ - A registered directory serves every file inside it, also in subdirectories: `public/js/app.js` becomes `.../chart-node/js/app.js`.
105
+ - Anything else returns 404 and logs a warning.
106
+
107
+ ## Warnings
108
+
109
+ Registration logs a warning with `RED.log.warn` when:
110
+
111
+ - a registered path does not exist, so its URLs would return 404;
112
+ - two different npm packages register files for the same `nodeType`. Both sets of files are served under the same URL prefix, and for the same file name the first registration wins. Use your node type, which is unique, as `nodeType`.
113
+
114
+ ## Security
115
+
116
+ The route has no auth check, also when `adminAuth` is on.
117
+ Serve only files that are safe to make public: editor scripts, styles, images.
118
+
119
+ [Back to node-red-utils](../README.md)
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright 2024 Contributors
3
+ * Licensed under the Apache License, Version 2.0
4
+ * http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { NodeRedResourceEndpoint } = require('../node-red-resourceendpoint/NodeRedResourceEndpoint.js');
11
+
12
+ /**
13
+ * NodeRedSingleton - Mixin that ensures only one instance of a node type can exist in a flow
14
+ *
15
+ * This mixin simply provides a resource endpoint for the frontend script that will
16
+ * actually enforces the singleton pattern in the editor.
17
+ *
18
+ * @param {class} superclass - The class to extend with singleton capabilities
19
+ * @returns {class} A new class that extends the superclass with singleton functionality
20
+ */
21
+ const nodeRedSingleton = (superclass) => class extends superclass {
22
+ // Serve the frontend script before the first deploy
23
+ static onRegisterType(RED) {
24
+ super.onRegisterType?.(RED);
25
+ new NodeRedResourceEndpoint(RED).registerResources('node-red-singleton',
26
+ path.join(__dirname, 'NodeRedSingletonFrontend.js'));
27
+ }
28
+ };
29
+
30
+ module.exports.nodeRedSingleton = nodeRedSingleton;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Copyright 2024 Contributors
3
+ * Licensed under the Apache License, Version 2.0
4
+ * http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+
7
+ //
8
+ // NodeRedSingletonFrontend.js
9
+ //
10
+ // Frontend handler for Node-RED singleton nodes.
11
+ // A node type with "singleton: true" in its definition can have one node in the editor.
12
+ // - A new node that one user action added alone (palette drop, quick add, paste of one node):
13
+ // the script undoes that action, so links, groups and the changed state come back too.
14
+ // - Other duplicates (an import of several nodes, a saved flow with two nodes):
15
+ // the script does not delete them. It shows a warning.
16
+ //
17
+
18
+ if (!window.NodeRedSingletonInitialized) {
19
+ window.NodeRedSingletonInitialized = true;
20
+
21
+ const instances = {}; // node type -> Set of node ids in the editor
22
+ const warned = new Set(); // node types with a warning in the current operation
23
+
24
+ // The node ids that a history event adds, also inside a "multi" event
25
+ const addedIds = (ev) => !ev ? [] : [...(ev.t === 'add' && ev.nodes) || [], ...(ev.events || []).flatMap(addedIds)];
26
+
27
+ function check(node) {
28
+ const ids = instances[node.type];
29
+ if (!ids || ids.size < 2 || !ids.has(node.id)) return;
30
+
31
+ const added = addedIds(RED.history.peek());
32
+ if (added.length === 1 && added[0] === node.id) {
33
+ RED.history.pop();
34
+ RED.view.redraw(true);
35
+ RED.notify(`Only one "${node.type}" node is allowed. The new node was removed.`, 'warning');
36
+ } else if (!warned.has(node.type)) {
37
+ warned.add(node.type);
38
+ setTimeout(() => warned.delete(node.type), 0);
39
+ RED.notify(`Only one "${node.type}" node is allowed, but the flows have ${ids.size}. Delete the extra nodes.`, 'warning');
40
+ }
41
+ }
42
+
43
+ RED.events.on('nodes:add', (node) => {
44
+ if (!node || !node.type || RED.nodes.getType(node.type)?.singleton !== true) return;
45
+ const ids = instances[node.type] ??= new Set();
46
+ ids.add(node.id);
47
+ // Wait until the editor has recorded the action in its history
48
+ if (ids.size > 1) setTimeout(() => check(node), 0);
49
+ });
50
+
51
+ RED.events.on('nodes:remove', (node) => {
52
+ if (node && node.type) instances[node.type]?.delete(node.id);
53
+ });
54
+ }
@@ -0,0 +1,72 @@
1
+ # node-red-singleton
2
+
3
+ EXPERIMENTAL. The API and the behavior can change in a minor release.
4
+
5
+ Allow only one node of a type in the editor. Useful for a node that owns a device, a port or another resource that two nodes cannot share.
6
+
7
+ The check runs only in the editor. The runtime does not check, so a flow deployed through the admin API, or a flow file edited by hand, can still hold two nodes. If two nodes would break your node, also check in the runtime, as the example below does.
8
+
9
+ ## Usage
10
+
11
+ A node that receives sensor data on a fixed UDP port. Two nodes cannot bind the same port, so the editor allows one node, and the runtime checks again:
12
+
13
+ ```js
14
+ const dgram = require('dgram');
15
+ const { NodeRedNodeBase, nodeRedSingleton } = require('@nrutils/node-red-utils');
16
+
17
+ const PORT = 41234; // the sensors send here; only one socket can bind this port
18
+ let owner = null; // id of the node that has the socket
19
+
20
+ module.exports = NodeRedNodeBase.createNode('udp-sensors', [nodeRedSingleton], (RED, thisNode, config) => {
21
+ // A flow deployed through the admin API skips the editor check, so check here too
22
+ if (owner) {
23
+ thisNode.status({ fill: 'red', shape: 'ring', text: 'not started: one node only' });
24
+ thisNode.error(`Node ${owner} has UDP port ${PORT}. Only one udp-sensors node can run.`);
25
+ return;
26
+ }
27
+ owner = thisNode.id;
28
+ const socket = dgram.createSocket('udp4');
29
+ socket.on('message', (data, rinfo) => thisNode.send({ payload: data.toString(), sender: rinfo.address }));
30
+ socket.on('error', (err) => thisNode.error(err));
31
+ socket.bind(PORT);
32
+
33
+ thisNode.on('close', (done) => {
34
+ owner = null;
35
+ socket.close(() => done());
36
+ });
37
+ });
38
+ ```
39
+
40
+ ```html
41
+ <script type="text/javascript" src="node-red-resourceendpoint/node-red-singleton/NodeRedSingletonFrontend.js"></script>
42
+
43
+ <script type="text/javascript">
44
+ RED.nodes.registerType('udp-sensors', {
45
+ category: 'function',
46
+ color: '#ffcccc',
47
+ singleton: true,
48
+ defaults: { name: { value: '' } },
49
+ inputs: 0,
50
+ outputs: 1,
51
+ label: function () { return this.name || 'udp-sensors'; }
52
+ });
53
+ </script>
54
+
55
+ <script type="text/html" data-template-name="udp-sensors">
56
+ <div class="form-row">
57
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
58
+ <input type="text" id="node-input-name" placeholder="Name">
59
+ </div>
60
+ </script>
61
+ ```
62
+
63
+ `singleton: true` goes at the top level of the definition, not in `defaults`.
64
+
65
+ ## Behavior
66
+
67
+ - When one user action adds only a second node of the type (drag from the palette, quick add, paste or import of that one node), the editor shows a warning and undoes that action. Thus the new node goes away with its links, and the changed state of the workspace is as before. Redo adds it again, and the check undoes it again.
68
+ - When an import or paste adds a second node together with other nodes, the editor does not delete anything. It shows a warning that asks you to delete the extra nodes. The same warning shows when the editor loads saved flows that have two or more nodes of the type.
69
+ - The limit covers the whole editor: all flow tabs, not one tab.
70
+ - When you delete the existing node, you can add a new one.
71
+
72
+ [Back to node-red-utils](../README.md)