@rosepetal/node-red-contrib-async-function 1.0.1 → 1.0.3

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 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 an **async 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, so heavy operations won't freeze Node-RED.
23
+ Drop an **async 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
 
@@ -31,7 +31,7 @@ Drop an **async function** node into your flow. Write your code just like you wo
31
31
 
32
32
  **Skip It For:**
33
33
  - Simple math or quick transformations (the regular function node is faster).
34
- - When you need `context`, `flow`, or `global` storage (coming in v2.0).
34
+ - Flows that require live context reads/writes during execution (context is snapshot-based).
35
35
 
36
36
  ## Node Options
37
37
 
@@ -40,9 +40,10 @@ Drop an **async function** node into your flow. Write your code just like you wo
40
40
  - **Function** – Your JavaScript code. Works with `async/await`, `return`, and `require()`.
41
41
  - **Outputs** – How many output wires (0-10). Return an array for multiple outputs.
42
42
  - **Timeout** – Maximum seconds to wait before killing the worker. Default: 30 seconds.
43
+ - **Runtime** – Worker Threads (default, fastest) or Child Process (for native modules like `gl`).
43
44
 
44
45
  ### Worker Pool
45
- - **Workers** – Fixed number of worker threads (1-16). Each node maintains exactly this many workers. Default: 3.
46
+ - **Workers** – Fixed number of workers (1-16). Each node maintains exactly this many workers. Default: 3.
46
47
  - **Queue Size** – Messages to queue when all workers are occupied. Default: 100.
47
48
 
48
49
  ### Modules
@@ -61,7 +62,7 @@ return msg;
61
62
  ```
62
63
 
63
64
  ### Buffer Handling
64
- - **Buffers** – Any `Buffer` in `msg` is transferred through shared memory (`/dev/shm` on Linux, otherwise `os.tmpdir()`), with base64 fallback if needed.
65
+ - **Buffers** – Worker Threads use zero-copy transfer when possible. Child Process mode and non-transferable buffers fall back to shared memory (`/dev/shm` on Linux, otherwise `os.tmpdir()`), with base64 fallback if needed.
65
66
 
66
67
  ## Typical Flow
67
68
 
@@ -85,9 +86,11 @@ return msg;
85
86
  - `console` – Logging functions
86
87
  - `setTimeout`, `setInterval` – Timers
87
88
 
88
- **Not Available (Yet):**
89
- - `context`, `flow`, `global` Coming in v2.0
90
- - `node` Node instance methods
89
+ **Notes:**
90
+ - `context`, `flow`, `global` are snapshot-based: reads are from the snapshot, writes are applied after the function completes
91
+ - Snapshot includes only literal keys found in `flow.get("key")` / `global.get("key")` / `context.get("key")`
92
+ - Context store selection is not supported (default store only)
93
+ - `node.warn/error/log` are collected and forwarded to the main thread
91
94
  - Non-serializable objects (functions, symbols, etc.)
92
95
 
93
96
  ## Code Examples
@@ -165,11 +168,11 @@ The node shows you what's happening in real time:
165
168
 
166
169
  ## Performance Notes
167
170
 
168
- - Worker threads add about 5-10ms overhead per message.
171
+ - Worker threads add about 5-10ms overhead per message (child process mode is higher).
169
172
  - Best for operations taking more than 10ms to run.
170
173
  - Each node maintains a fixed pool of workers—no startup delay or dynamic scaling overhead.
171
174
  - Workers are dedicated per-node, ensuring predictable performance.
172
- - **Binary Fast Path**: Buffers use shared memory transfer (base64 fallback), keeping messages responsive even with large payloads.
175
+ - **Binary Fast Path**: Worker threads use zero-copy transfer when possible; shared memory is the fallback.
173
176
  - Event loop never blocks, even when processing multi-MB binary data (images, files, etc.).
174
177
 
175
178
  ## Error Handling
@@ -7,10 +7,11 @@
7
7
  defaults: {
8
8
  name: { value: '' },
9
9
  func: {
10
- value: '// Write your async code here\n// The code runs in a worker thread\n// Available: msg, return, async/await, require()\n// Not available: context, flow, global, node\n\nreturn msg;'
10
+ value: '// Write your async 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() },
14
+ executionMode: { value: 'worker_threads' },
14
15
  numWorkers: { value: 3, validate: RED.validators.number() },
15
16
  maxQueueSize: { value: 100, validate: RED.validators.number() },
16
17
  libs: { value: [] },
@@ -239,6 +240,32 @@
239
240
  // Ensure configured module vars are registered in the editor on load
240
241
  updateEditorModules();
241
242
 
243
+ // Restart Workers button click handler
244
+ $('#node-restart-workers').on('click', function() {
245
+ var btn = $(this);
246
+ var status = $('#node-restart-status');
247
+ var nodeId = node.id;
248
+
249
+ btn.prop('disabled', true);
250
+ status.text('Restarting...').css('color', '#666');
251
+
252
+ $.ajax({
253
+ url: 'async-function/' + nodeId + '/restart',
254
+ type: 'POST',
255
+ success: function(data) {
256
+ status.text('✓ Workers restarted').css('color', 'green');
257
+ setTimeout(function() { status.text(''); }, 3000);
258
+ },
259
+ error: function(xhr) {
260
+ var msg = xhr.responseJSON ? xhr.responseJSON.error : 'Restart failed';
261
+ status.text('✗ ' + msg).css('color', 'red');
262
+ },
263
+ complete: function() {
264
+ btn.prop('disabled', false);
265
+ }
266
+ });
267
+ });
268
+
242
269
  // Resize editor on dialog resize
243
270
  $('#dialog-form').on('dialogresize', function() {
244
271
  const rows = $('#dialog-form>div:not(#async-func-tabs-content)');
@@ -412,6 +439,13 @@
412
439
  <input id="node-input-timeout" style="width:120px;" value="30000">
413
440
  <span style="margin-left:5px;">ms</span>
414
441
  </div>
442
+ <div class="form-row">
443
+ <label for="node-input-executionMode"><i class="fa fa-bolt"></i> Runtime</label>
444
+ <select id="node-input-executionMode" style="width: 260px;">
445
+ <option value="worker_threads">Worker Threads (fastest)</option>
446
+ <option value="child_process">Child Process (native modules)</option>
447
+ </select>
448
+ </div>
415
449
  </div>
416
450
 
417
451
  <!-- Worker Pool Section -->
@@ -440,6 +474,22 @@
440
474
  <ol id="node-input-libs-container"></ol>
441
475
  </div>
442
476
  </div>
477
+
478
+ <!-- Actions Section -->
479
+ <div class="async-func-section">
480
+ <div class="async-func-section-header">
481
+ <i class="fa fa-refresh"></i> Actions
482
+ </div>
483
+ <div class="form-row" style="margin-bottom: 0;">
484
+ <button type="button" class="red-ui-button" id="node-restart-workers">
485
+ <i class="fa fa-refresh"></i> Restart Workers
486
+ </button>
487
+ <span id="node-restart-status" style="margin-left: 10px;"></span>
488
+ <div style="margin-top: 8px; color: #999; font-size: 12px;">
489
+ Restarts all worker processes/threads to reload modules and reset state.
490
+ </div>
491
+ </div>
492
+ </div>
443
493
  </div>
444
494
 
445
495
  <!-- Function Tab -->
@@ -456,12 +506,13 @@
456
506
  <div class="form-tips">
457
507
  <b>Tips:</b>
458
508
  <ul style="margin-top:5px; margin-bottom:0;">
459
- <li>Code runs in a worker thread to prevent blocking</li>
460
- <li>Available: <code>msg</code>, <code>return</code>, <code>async/await</code>, <code>require()</code></li>
461
- <li>Not available: <code>context</code>, <code>flow</code>, <code>global</code>, <code>node</code></li>
462
- <li>Return <code>msg</code> for single output, or <code>[msg1, msg2]</code> for multiple outputs</li>
463
- <li>Return <code>null</code> to stop the flow</li>
464
- </ul>
509
+ <li>Code runs in a worker thread or child process to prevent blocking</li>
510
+ <li>Available: <code>msg</code>, <code>return</code>, <code>async/await</code>, <code>require()</code>, <code>node</code>, <code>flow</code>, <code>global</code>, <code>context</code></li>
511
+ <li>Context access is snapshot-based (writes apply after execution)</li>
512
+ <li>Use "Child Process" runtime for native modules that require main thread (e.g., <code>gl</code>)</li>
513
+ <li>Return <code>msg</code> for single output, or <code>[msg1, msg2]</code> for multiple outputs</li>
514
+ <li>Return <code>null</code> to stop the flow</li>
515
+ </ul>
465
516
  </div>
466
517
  </div>
467
518
 
@@ -469,7 +520,7 @@
469
520
  </script>
470
521
 
471
522
  <script type="text/html" data-help-name="async-function">
472
- <p>Execute custom JavaScript code in a worker thread to prevent event loop blocking.</p>
523
+ <p>Execute custom JavaScript code in a worker thread or child process to prevent event loop blocking.</p>
473
524
 
474
525
  <h3>Inputs</h3>
475
526
  <dl class="message-properties">
@@ -486,8 +537,8 @@
486
537
  </dl>
487
538
 
488
539
  <h3>Details</h3>
489
- <p>This node executes JavaScript code in a worker thread, preventing CPU-intensive operations
490
- from blocking the Node-RED event loop.</p>
540
+ <p>This node executes JavaScript code in a worker thread or child process, preventing CPU-intensive
541
+ operations from blocking the Node-RED event loop.</p>
491
542
 
492
543
  <h4>Available in Code</h4>
493
544
  <ul>
@@ -495,14 +546,14 @@
495
546
  <li><code>return</code> - Return modified message or array of messages</li>
496
547
  <li><code>async/await</code> - Use async operations</li>
497
548
  <li><code>require()</code> - Import Node.js modules</li>
549
+ <li><code>node</code> - Node helpers (<code>warn</code>, <code>error</code>, <code>log</code>)</li>
550
+ <li><code>flow</code>, <code>global</code>, <code>context</code> - Snapshot-based context access (writes apply after execution)</li>
498
551
  <li><code>console</code> - Logging functions</li>
499
552
  <li><code>setTimeout</code>, <code>setInterval</code> - Timers</li>
500
553
  </ul>
501
554
 
502
555
  <h4>Not Available</h4>
503
556
  <ul>
504
- <li><code>context</code>, <code>flow</code>, <code>global</code> - Context storage (v2.0 feature)</li>
505
- <li><code>node</code> - Node instance methods</li>
506
557
  <li>Non-serializable objects in <code>msg</code></li>
507
558
  </ul>
508
559
 
@@ -554,12 +605,14 @@ return msg;</pre>
554
605
  <dd>Number of output ports (0-10). Use array return for multiple outputs.</dd>
555
606
  <dt>Timeout</dt>
556
607
  <dd>Maximum execution time in milliseconds (1000-300000). Worker is terminated if exceeded. Default: 30000 (30s).</dd>
608
+ <dt>Runtime</dt>
609
+ <dd>Worker Threads (default, fastest) or Child Process (useful for native modules like <code>gl</code>).</dd>
557
610
  <dt>Workers</dt>
558
- <dd>Fixed number of worker threads (1-16). Each node maintains exactly this many workers. Default: 3.</dd>
611
+ <dd>Fixed number of workers (1-16). Each node maintains exactly this many workers. Default: 3.</dd>
559
612
  <dt>Queue Size</dt>
560
613
  <dd>Maximum number of messages queued when all workers are busy (10-1000). Default: 100.</dd>
561
614
  <dt>Buffers</dt>
562
- <dd>All Buffers are transferred via shared memory when possible (falls back to base64 if needed).</dd>
615
+ <dd>Worker threads use zero-copy transfer when possible; shared memory is used as a fallback.</dd>
563
616
  </dl>
564
617
 
565
618
  <h3>Status Display</h3>
@@ -582,13 +635,15 @@ return msg;</pre>
582
635
 
583
636
  <h3>Performance Notes</h3>
584
637
  <ul>
585
- <li>Worker threads add ~5-10ms overhead per message</li>
638
+ <li>Worker threads add ~5-10ms overhead per message (child process mode is higher)</li>
586
639
  <li>Best for CPU-intensive operations (>10ms execution time)</li>
587
640
  <li>For simple operations, use the standard function node</li>
588
641
  <li>Workers are pooled and reused for efficiency</li>
589
- <li><b>Buffer Transfer:</b> Buffers are streamed through shared memory (base64 fallback)</li>
642
+ <li><b>Buffer Transfer:</b> Worker threads use zero-copy transfer when possible; shared memory is the fallback</li>
590
643
  <li>Event loop never blocks, even when processing multi-MB binary data (images, files, etc.)</li>
591
644
  <li>Message transfer is optimized by copying only referenced <code>msg.*</code> keys when possible</li>
645
+ <li>Context snapshots include only literal keys found in <code>flow.get("key")</code> etc.</li>
646
+ <li>Context snapshots use the default context store only</li>
592
647
  <li>Timing is recorded on <code>msg.performance</code> under the node label</li>
593
648
  </ul>
594
649