Node.js Example Pack for Attune
A complete example pack demonstrating Node.js actions, a stateful counter sensor with keystore integration, and HTTP requests using the node-fetch library.
Purpose
This pack exercises as many parts of the Attune SDLC as possible:
- Node.js actions with the wrapper-based execution model
- Node.js sensor with RabbitMQ rule lifecycle integration (amqplib)
- Trigger types with structured payload schemas
- Rules connecting triggers to actions with parameter mapping
- Keystore integration for persistent sensor state across restarts
- External Node.js dependencies (
node-fetch,amqplib) - Per-rule scoped state — each rule subscription gets its own counter
Components
Actions
| Ref | Description |
|---|---|
nodejs_example.hello |
Returns "Hello, Node.js" — minimal action |
nodejs_example.http_example |
Uses node-fetch to GET https://example.com |
nodejs_example.read_counter |
Consumes a counter value and returns a formatted message |
Triggers
| Ref | Description |
|---|---|
nodejs_example.counter |
Fires periodically with an incrementing counter per rule |
Sensors
| Ref | Description |
|---|---|
nodejs_example.counter_sensor |
Manages per-rule counters stored in the Attune keystore |
Rules
| Ref | Description |
|---|---|
nodejs_example.count_and_log |
Wires the counter trigger to the read_counter action |
Installation
As a Git Pack (recommended)
# Install via the Attune CLI from a git repository
attune pack install https://github.com/attune-automation/pack-nodejs-example.git
# Or via the API
curl -X POST "http://localhost:8080/api/v1/packs/install" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"source": "git", "url": "https://github.com/attune-automation/pack-nodejs-example.git"}'
Local Development (submodule)
If you're developing against the Attune repository:
cd attune
# Add as a git submodule in packs.examples/
git submodule add <your-repo-url> packs.examples/nodejs_example
# Or if you already have the directory, initialize it:
cd packs.examples/nodejs_example
git init
git remote add origin <your-repo-url>
Manual / Volume Mount
Copy or symlink the pack into your Attune packs directory:
cp -r nodejs_example /opt/attune/packs/nodejs_example
# Then restart services to pick it up, or use the dev packs volume
Dependencies
Declared in package.json:
node-fetch@^2.7.0— HTTP client for thehttp_exampleaction (CJS-compatible v2)amqplib@^0.10.4— RabbitMQ client for the counter sensor
These are installed automatically when the pack is loaded by a Node.js worker with dependency management enabled.
How It Works
Counter Sensor Flow
┌──────────────────────────────────────────────────────────┐
│ counter_sensor.js │
│ │
│ 1. Startup: fetch active rules from GET /api/v1/rules │
│ 2. Listen: RabbitMQ queue sensor.nodejs_example.* │
│ for rule.created / rule.enabled / rule.disabled / │
│ rule.deleted messages │
│ 3. Per active rule, spawn a setInterval timer: │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Timer (1 tick/sec per rule) │ │
│ │ │ │
│ │ GET /api/v1/keys/{key} → read counter │ │
│ │ counter += 1 │ │
│ │ PUT /api/v1/keys/{key} → write back │ │
│ │ POST /api/v1/events → emit event │ │
│ └────────────────────────────────────────┘ │
│ │
│ 4. On shutdown: clearTimeout all timers gracefully │
└──────────────────────────────────────────────────────────┘
Keystore Key Naming
Each rule gets its own counter key:
nodejs_example.counter.<rule_ref_with_dots_replaced_by_underscores>
For example, a rule with ref nodejs_example.count_and_log stores its counter at:
nodejs_example.counter.nodejs_example_count_and_log
Event Payload
Each emitted event has this structure:
{
"counter": 42,
"rule_ref": "nodejs_example.count_and_log",
"sensor_ref": "nodejs_example.counter_sensor",
"fired_at": "2025-01-15T12:00:00.000Z"
}
Rule Parameter Mapping
The included count_and_log rule maps trigger payload fields to action parameters:
action_params:
counter: "{{ trigger.payload.counter }}"
rule_ref: "{{ trigger.payload.rule_ref }}"
The read_counter action then returns:
{
"message": "Counter value is 42 (from rule: nodejs_example.count_and_log)",
"counter": 42,
"rule_ref": "nodejs_example.count_and_log"
}
Testing Individual Components
Test the hello action
attune action execute nodejs_example.hello
# Output: {"message": "Hello, Node.js"}
Test the HTTP action
attune action execute nodejs_example.http_example
# Output: {"status_code": 200, "url": "https://example.com", ...}
Test the read_counter action directly
attune action execute nodejs_example.read_counter --param counter=99 --param rule_ref=test
# Output: {"message": "Counter value is 99 (from rule: test)", ...}
Enable the rule to start the counter sensor loop
# The rule is enabled by default when the pack is loaded.
# To manually enable/disable:
attune rule enable nodejs_example.count_and_log
attune rule disable nodejs_example.count_and_log
# Monitor executions produced by the rule:
attune execution list --action nodejs_example.read_counter
Configuration
The pack supports the following configuration in pack.yaml:
| Setting | Default | Description |
|---|---|---|
counter_key_prefix |
nodejs_example.counter |
Prefix for keystore keys |
The sensor supports these parameters:
| Parameter | Default | Description |
|---|---|---|
default_interval_seconds |
1 |
Default tick interval per rule |
key_prefix |
nodejs_example.counter |
Keystore key prefix |
The trigger supports per-rule configuration:
| Parameter | Default | Description |
|---|---|---|
interval_seconds |
1 |
Seconds between counter ticks |
Development
# Install dependencies locally
npm install
# Run the sensor manually for testing
export ATTUNE_API_URL=http://localhost:8080
export ATTUNE_API_TOKEN=<your-token>
export ATTUNE_MQ_URL=amqp://guest:guest@localhost:5672/
node sensors/counter_sensor.js
# Run an action manually (direct execution)
node actions/hello.js
Comparison with Python Example Pack
This pack is a direct Node.js equivalent of the python_example pack. Both exercise the same Attune features but use their respective language ecosystems:
| Feature | Python Pack | Node.js Pack |
|---|---|---|
| HTTP client | requests |
node-fetch |
| RabbitMQ client | pika |
amqplib |
| Concurrency model | threading.Thread + threading.Event |
setTimeout + EventEmitter |
| Sensor API calls | requests (same lib as actions) |
Built-in http/https (no extra deps) |
| Dependency file | requirements.txt |
package.json |
| Module exports | def run(**kwargs) |
module.exports = { run } |
License
MIT