@rosepetal/node-red-contrib-async-function 1.0.3 → 1.1.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/README.md +5 -3
- package/nodes/lib/child-process-script.js +1 -1
- package/nodes/lib/module-installer.js +6 -6
- package/nodes/lib/worker-script.js +1 -1
- package/nodes/{async-function.html → worker-function.html} +7 -7
- package/nodes/{async-function.js → worker-function.js} +6 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Run heavy computations in Node-RED without slowing down your flows. This node wo
|
|
|
20
20
|
|
|
21
21
|
## How It Works
|
|
22
22
|
|
|
23
|
-
Drop
|
|
23
|
+
Drop a **worker function** node into your flow. Write your code just like you would in a regular function node. The difference? Your code runs in a separate worker thread by default (or a child process if configured), so heavy operations won't freeze Node-RED.
|
|
24
24
|
|
|
25
25
|
## When to Use This
|
|
26
26
|
|
|
@@ -66,7 +66,7 @@ return msg;
|
|
|
66
66
|
|
|
67
67
|
## Typical Flow
|
|
68
68
|
|
|
69
|
-
1. Add
|
|
69
|
+
1. Add a **worker function** node to your workspace.
|
|
70
70
|
2. Connect an Inject node (input) and a Debug node (output).
|
|
71
71
|
3. Write a simple script:
|
|
72
72
|
```javascript
|
|
@@ -194,7 +194,9 @@ npm install @rosepetal/node-red-contrib-async-function
|
|
|
194
194
|
|
|
195
195
|
Restart Node-RED and find the node in the **function** category.
|
|
196
196
|
|
|
197
|
-
## Migration
|
|
197
|
+
## Migration
|
|
198
|
+
|
|
199
|
+
### From minWorkers/maxWorkers
|
|
198
200
|
|
|
199
201
|
If you're upgrading from a version that used `minWorkers` and `maxWorkers`:
|
|
200
202
|
- Your existing flows will automatically migrate to use the new `numWorkers` parameter
|
|
@@ -226,7 +226,7 @@ async function loadConfiguredModules(libs) {
|
|
|
226
226
|
moduleVars.push(lib.var);
|
|
227
227
|
moduleValues.push(loadedModules[lib.var]);
|
|
228
228
|
} catch (err) {
|
|
229
|
-
console.error(`[
|
|
229
|
+
console.error(`[worker-function] Failed to load module ${lib.module}: ${err.message}`);
|
|
230
230
|
failedModules.push({ module: lib.module, var: lib.var, error: err.message });
|
|
231
231
|
}
|
|
232
232
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module Installer
|
|
3
3
|
*
|
|
4
|
-
* Handles automatic installation of npm modules for the
|
|
4
|
+
* Handles automatic installation of npm modules for the worker-function node.
|
|
5
5
|
* Modules are installed in the Node-RED user directory (~/.node-red).
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -43,21 +43,21 @@ async function getNodeRedUserDir() {
|
|
|
43
43
|
*/
|
|
44
44
|
async function installModule(moduleName) {
|
|
45
45
|
if (!moduleName || typeof moduleName !== 'string') {
|
|
46
|
-
console.error('[
|
|
46
|
+
console.error('[worker-function] Invalid module name');
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Sanitize module name to prevent command injection
|
|
51
51
|
const sanitizedName = moduleName.trim();
|
|
52
52
|
if (!/^(@[\w-]+\/)?[\w.-]+(@[\w.-]+)?$/.test(sanitizedName)) {
|
|
53
|
-
console.error(`[
|
|
53
|
+
console.error(`[worker-function] Invalid module name format: ${sanitizedName}`);
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const userDir = await getNodeRedUserDir();
|
|
58
58
|
|
|
59
59
|
try {
|
|
60
|
-
console.log(`[
|
|
60
|
+
console.log(`[worker-function] Installing module: ${sanitizedName} in ${userDir}`);
|
|
61
61
|
|
|
62
62
|
await new Promise((resolve, reject) => {
|
|
63
63
|
const child = spawn('npm', ['install', sanitizedName], {
|
|
@@ -98,11 +98,11 @@ async function installModule(moduleName) {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
console.log(`[
|
|
101
|
+
console.log(`[worker-function] Successfully installed: ${sanitizedName}`);
|
|
102
102
|
return true;
|
|
103
103
|
|
|
104
104
|
} catch (err) {
|
|
105
|
-
console.error(`[
|
|
105
|
+
console.error(`[worker-function] Failed to install ${sanitizedName}: ${err.message}`);
|
|
106
106
|
return false;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -233,7 +233,7 @@ async function loadConfiguredModules() {
|
|
|
233
233
|
moduleVars.push(lib.var);
|
|
234
234
|
moduleValues.push(loadedModules[lib.var]);
|
|
235
235
|
} catch (err) {
|
|
236
|
-
console.error(`[
|
|
236
|
+
console.error(`[worker-function] Failed to load module ${lib.module}: ${err.message}`);
|
|
237
237
|
failedModules.push({ module: lib.module, var: lib.var, error: err.message });
|
|
238
238
|
}
|
|
239
239
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<!--
|
|
1
|
+
<!-- Worker Function Node - Node-RED Editor Configuration -->
|
|
2
2
|
|
|
3
3
|
<script type="text/javascript">
|
|
4
|
-
RED.nodes.registerType('
|
|
4
|
+
RED.nodes.registerType('worker-function', {
|
|
5
5
|
category: 'function',
|
|
6
6
|
color: '#d7d9dc',
|
|
7
7
|
defaults: {
|
|
8
8
|
name: { value: '' },
|
|
9
9
|
func: {
|
|
10
|
-
value: '// Write your
|
|
10
|
+
value: '// Write your code here\n// The code runs in a worker thread or child process\n// Available: msg, return, async/await, require(), node.warn/error/log,\n// flow/global/context (snapshot-based)\n\nreturn msg;'
|
|
11
11
|
},
|
|
12
12
|
outputs: { value: 1, validate: RED.validators.number() },
|
|
13
13
|
timeout: { value: 30000, validate: RED.validators.number() },
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
outputs: 1,
|
|
22
22
|
icon: 'function.svg',
|
|
23
23
|
label: function() {
|
|
24
|
-
return this.name || '
|
|
24
|
+
return this.name || 'worker function';
|
|
25
25
|
},
|
|
26
26
|
labelStyle: function() {
|
|
27
27
|
return this.name ? 'node_label_italic' : '';
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
status.text('Restarting...').css('color', '#666');
|
|
251
251
|
|
|
252
252
|
$.ajax({
|
|
253
|
-
url: '
|
|
253
|
+
url: 'worker-function/' + nodeId + '/restart',
|
|
254
254
|
type: 'POST',
|
|
255
255
|
success: function(data) {
|
|
256
256
|
status.text('✓ Workers restarted').css('color', 'green');
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
});
|
|
347
347
|
</script>
|
|
348
348
|
|
|
349
|
-
<script type="text/html" data-template-name="
|
|
349
|
+
<script type="text/html" data-template-name="worker-function">
|
|
350
350
|
<style>
|
|
351
351
|
.async-func-tabs-row {
|
|
352
352
|
margin-bottom: 0;
|
|
@@ -519,7 +519,7 @@
|
|
|
519
519
|
</div>
|
|
520
520
|
</script>
|
|
521
521
|
|
|
522
|
-
<script type="text/html" data-help-name="
|
|
522
|
+
<script type="text/html" data-help-name="worker-function">
|
|
523
523
|
<p>Execute custom JavaScript code in a worker thread or child process to prevent event loop blocking.</p>
|
|
524
524
|
|
|
525
525
|
<h3>Inputs</h3>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Worker Function Node
|
|
3
3
|
*
|
|
4
4
|
* A Node-RED function node that executes user code in worker threads
|
|
5
|
-
* to prevent event loop blocking.
|
|
5
|
+
* or child processes to prevent event loop blocking.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
@@ -387,7 +387,7 @@ function applyPerformanceMetrics(node, originalMsg, targetMsg, performance) {
|
|
|
387
387
|
return;
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
const label = (typeof node.name === 'string' && node.name.trim()) ? node.name.trim() : '
|
|
390
|
+
const label = (typeof node.name === 'string' && node.name.trim()) ? node.name.trim() : 'worker function';
|
|
391
391
|
if (!label) {
|
|
392
392
|
return;
|
|
393
393
|
}
|
|
@@ -656,7 +656,7 @@ module.exports = function(RED) {
|
|
|
656
656
|
}
|
|
657
657
|
|
|
658
658
|
// Log error
|
|
659
|
-
node.error(`
|
|
659
|
+
node.error(`Worker function error: ${err.message}`, msg);
|
|
660
660
|
|
|
661
661
|
// Propagate error to Catch node
|
|
662
662
|
done(err);
|
|
@@ -703,12 +703,12 @@ module.exports = function(RED) {
|
|
|
703
703
|
}
|
|
704
704
|
|
|
705
705
|
// Register the node type
|
|
706
|
-
RED.nodes.registerType('
|
|
706
|
+
RED.nodes.registerType('worker-function', AsyncFunctionNode, {
|
|
707
707
|
dynamicModuleList: 'libs'
|
|
708
708
|
});
|
|
709
709
|
|
|
710
710
|
// HTTP endpoint to restart workers for a specific node
|
|
711
|
-
RED.httpAdmin.post('/
|
|
711
|
+
RED.httpAdmin.post('/worker-function/:id/restart', async function(req, res) {
|
|
712
712
|
const node = RED.nodes.getNode(req.params.id);
|
|
713
713
|
if (!node || !node.pool) {
|
|
714
714
|
return res.status(404).json({ error: 'Node not found or pool not initialized' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rosepetal/node-red-contrib-async-function",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A Node-RED function node that runs code in worker threads to keep your flows responsive",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"keywords": [
|
|
10
10
|
"node-red",
|
|
11
11
|
"function",
|
|
12
|
-
"
|
|
12
|
+
"worker-function",
|
|
13
13
|
"worker-threads",
|
|
14
14
|
"non-blocking",
|
|
15
15
|
"performance",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"node-red": {
|
|
35
35
|
"version": ">=2.0.0",
|
|
36
36
|
"nodes": {
|
|
37
|
-
"
|
|
37
|
+
"worker-function": "nodes/worker-function.js"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {}
|