node-red-contrib-qrusty 0.19.20 → 0.20.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 +133 -26
- package/lib/common.js +94 -0
- package/package.json +37 -7
- package/qrusty-ack-batch.html +68 -0
- package/qrusty-ack-batch.js +37 -9
- package/qrusty-ack.html +66 -0
- package/qrusty-ack.js +32 -11
- package/qrusty-consume.html +76 -0
- package/qrusty-consume.js +37 -8
- package/qrusty-create-queue.html +82 -0
- package/qrusty-create-queue.js +39 -13
- package/qrusty-delete-all.html +38 -0
- package/qrusty-delete-all.js +19 -5
- package/qrusty-delete-queue.html +51 -0
- package/qrusty-delete-queue.js +21 -5
- package/qrusty-health.html +37 -0
- package/qrusty-health.js +19 -5
- package/qrusty-list-queues.html +37 -0
- package/qrusty-list-queues.js +19 -5
- package/qrusty-nack-batch.html +67 -0
- package/qrusty-nack-batch.js +37 -9
- package/qrusty-nack.html +66 -0
- package/qrusty-nack.js +31 -11
- package/qrusty-publish.html +92 -0
- package/qrusty-publish.js +37 -10
- package/qrusty-purge-all.html +41 -0
- package/qrusty-purge-all.js +19 -5
- package/qrusty-purge-queue.html +51 -0
- package/qrusty-purge-queue.js +21 -5
- package/qrusty-queue-metrics.html +51 -0
- package/qrusty-queue-metrics.js +21 -6
- package/qrusty-queue-stats.html +51 -0
- package/qrusty-queue-stats.js +21 -5
- package/qrusty-server.html +64 -0
- package/qrusty-server.js +20 -0
- package/qrusty-stats.html +37 -0
- package/qrusty-stats.js +19 -5
- package/qrusty-update-queue.html +77 -0
- package/qrusty-update-queue.js +37 -12
package/README.md
CHANGED
|
@@ -1,28 +1,135 @@
|
|
|
1
1
|
# node-red-contrib-qrusty
|
|
2
2
|
|
|
3
|
-
Node-RED
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- `qrusty
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
Node-RED custom nodes for the [Qrusty](https://github.com/greeng3/qrusty)
|
|
4
|
+
priority queue server.
|
|
5
|
+
|
|
6
|
+
Wraps the [`qrusty-client`](https://www.npmjs.com/package/qrusty-client)
|
|
7
|
+
Node.js client in fully-typed, HTML-templated Node-RED nodes so you can
|
|
8
|
+
drag-and-drop queue operations into your flows instead of hand-rolling
|
|
9
|
+
`function` nodes.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
From the Node-RED palette manager, search for `node-red-contrib-qrusty`
|
|
14
|
+
and install. Or from the command line in your Node-RED user directory
|
|
15
|
+
(typically `~/.node-red`):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install node-red-contrib-qrusty
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requires Node.js 18 or newer and Node-RED 3.0 or newer.
|
|
22
|
+
|
|
23
|
+
## Nodes
|
|
24
|
+
|
|
25
|
+
One config node plus 17 action nodes. All action nodes reference a
|
|
26
|
+
shared `qrusty-server` config node so you only enter the host/port once
|
|
27
|
+
per environment.
|
|
28
|
+
|
|
29
|
+
### Config node
|
|
30
|
+
|
|
31
|
+
| Node | Purpose |
|
|
32
|
+
| --------------- | ----------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `qrusty-server` | Holds host, port, and `useHttps` for a Qrusty server. Computes `baseUrl = http(s)://host:port`. |
|
|
34
|
+
|
|
35
|
+
### Message operations
|
|
36
|
+
|
|
37
|
+
| Node | HTTP endpoint | Purpose |
|
|
38
|
+
| ------------------- | ------------------------- | -------------------------------------- |
|
|
39
|
+
| `qrusty-publish` | `POST /publish` | Publish `msg.payload` to a queue. |
|
|
40
|
+
| `qrusty-consume` | `POST /consume/:queue` | Pop one message (or null). |
|
|
41
|
+
| `qrusty-ack` | `POST /ack/:queue/:id` | Acknowledge a message. |
|
|
42
|
+
| `qrusty-nack` | `POST /nack/:queue/:id` | Negatively acknowledge (retry or DLQ). |
|
|
43
|
+
| `qrusty-ack-batch` | `POST /ack-batch/:queue` | Ack many ids in one request. |
|
|
44
|
+
| `qrusty-nack-batch` | `POST /nack-batch/:queue` | Nack many ids in one request. |
|
|
45
|
+
|
|
46
|
+
### Queue management
|
|
47
|
+
|
|
48
|
+
| Node | HTTP endpoint | Purpose |
|
|
49
|
+
| --------------------- | ----------------------------- | ------------------------------------------------ |
|
|
50
|
+
| `qrusty-create-queue` | `POST /create-queue` | Create a queue with ordering / duplicate policy. |
|
|
51
|
+
| `qrusty-update-queue` | `POST /update-queue` | Rename or toggle duplicate protection. |
|
|
52
|
+
| `qrusty-delete-queue` | `DELETE /delete-queue/:queue` | Delete a queue and its messages. |
|
|
53
|
+
| `qrusty-purge-queue` | `POST /purge-queue/:queue` | Empty a queue without deleting it. |
|
|
54
|
+
| `qrusty-purge-all` | `POST /purge-all` | Empty every queue. |
|
|
55
|
+
| `qrusty-delete-all` | `POST /delete-all` | Delete every queue. |
|
|
56
|
+
|
|
57
|
+
### Observability
|
|
58
|
+
|
|
59
|
+
| Node | HTTP endpoint | Purpose |
|
|
60
|
+
| ---------------------- | ---------------------------- | -------------------------------- |
|
|
61
|
+
| `qrusty-stats` | `GET /stats` | Aggregate stats for every queue. |
|
|
62
|
+
| `qrusty-queue-stats` | `GET /queue-stats/:queue` | Detailed stats for one queue. |
|
|
63
|
+
| `qrusty-queue-metrics` | `GET /queues/:queue/metrics` | Per-second time-series metrics. |
|
|
64
|
+
| `qrusty-list-queues` | `GET /queues` | Names of all existing queues. |
|
|
65
|
+
| `qrusty-health` | `GET /health` | Server health check. |
|
|
66
|
+
|
|
67
|
+
## Config-vs-msg fallback
|
|
68
|
+
|
|
69
|
+
Every action node supports two ways of supplying its parameters:
|
|
70
|
+
|
|
71
|
+
1. **In the editor panel** — values set on the node itself (e.g. a hard-coded
|
|
72
|
+
`queue` field) take precedence.
|
|
73
|
+
2. **At runtime via the incoming `msg`** — leave the field blank on the node
|
|
74
|
+
and supply `msg.queue`, `msg.priority`, etc.
|
|
75
|
+
|
|
76
|
+
This lets you build generic flows (`msg.queue` decided upstream) or
|
|
77
|
+
explicit flows (queue pinned in the editor), or a mix of both.
|
|
78
|
+
|
|
79
|
+
## Error handling
|
|
80
|
+
|
|
81
|
+
- A green status dot with `"ok"` means the HTTP call succeeded.
|
|
82
|
+
- A blue dot with `"requesting"` shows the call is in flight.
|
|
83
|
+
- A red ring with `"HTTP 4xx"` / `"HTTP 5xx"` / `"no server config"` means
|
|
84
|
+
the node failed; the error is forwarded via `done(err)` so a downstream
|
|
85
|
+
`catch` node will pick it up.
|
|
86
|
+
|
|
87
|
+
## Example flow
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
[
|
|
91
|
+
{
|
|
92
|
+
"id": "srv",
|
|
93
|
+
"type": "qrusty-server",
|
|
94
|
+
"name": "dev",
|
|
95
|
+
"host": "localhost",
|
|
96
|
+
"port": "6784",
|
|
97
|
+
"useHttps": false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "inject",
|
|
101
|
+
"type": "inject",
|
|
102
|
+
"payload": "hello from node-red",
|
|
103
|
+
"payloadType": "str",
|
|
104
|
+
"wires": [["pub"]]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "pub",
|
|
108
|
+
"type": "qrusty-publish",
|
|
109
|
+
"name": "",
|
|
110
|
+
"server": "srv",
|
|
111
|
+
"queue": "orders",
|
|
112
|
+
"priority": "10",
|
|
113
|
+
"wires": [[]]
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cd integrations/node-red
|
|
122
|
+
npm install
|
|
123
|
+
npm test # runs mocha against node-red-node-test-helper
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The test suite in `test/` uses the official
|
|
127
|
+
[`node-red-node-test-helper`](https://www.npmjs.com/package/node-red-node-test-helper)
|
|
128
|
+
to load every node into a real Node-RED runtime and exercise it end-to-end
|
|
129
|
+
against a mocked HTTP server.
|
|
130
|
+
|
|
131
|
+
## Versioning
|
|
132
|
+
|
|
133
|
+
This package tracks the Qrusty server version. When the server bumps
|
|
134
|
+
(e.g. `0.19.x`), this package is bumped in lockstep and its
|
|
135
|
+
`qrusty-client` dependency is pinned to the matching major.minor.
|
package/lib/common.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// lib/common.js
|
|
2
|
+
//
|
|
3
|
+
// Shared helpers for all Qrusty Node-RED action nodes.
|
|
4
|
+
// Wraps the qrusty-client HTTP client with standard Node-RED patterns:
|
|
5
|
+
// status updates, async error handling, and config-vs-msg fallback.
|
|
6
|
+
|
|
7
|
+
const { QrustyClient } = require("qrusty-client");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pick a value: prefer the node config, fall back to `msg.<name>`, then
|
|
11
|
+
* to `fallback`. Treats empty string / null / undefined as "not set".
|
|
12
|
+
*
|
|
13
|
+
* @param {*} configVal value from the Node-RED node config (may be "")
|
|
14
|
+
* @param {*} msgVal value from the incoming msg
|
|
15
|
+
* @param {*} [fallback] optional default when neither is set
|
|
16
|
+
* @returns {*}
|
|
17
|
+
*/
|
|
18
|
+
function fromConfigOrMsg(configVal, msgVal, fallback) {
|
|
19
|
+
if (configVal !== undefined && configVal !== null && configVal !== "") {
|
|
20
|
+
return configVal;
|
|
21
|
+
}
|
|
22
|
+
if (msgVal !== undefined && msgVal !== null) {
|
|
23
|
+
return msgVal;
|
|
24
|
+
}
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Coerce a value to a number if it looks numeric, else return undefined.
|
|
30
|
+
* Used for priority / maxRetries / timeoutSeconds config fields which are
|
|
31
|
+
* rendered as `<input type="number">` but arrive as strings.
|
|
32
|
+
*/
|
|
33
|
+
function toNumberOrUndefined(value) {
|
|
34
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
35
|
+
const n = Number(value);
|
|
36
|
+
return Number.isNaN(n) ? undefined : n;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register an async input handler on `node` that:
|
|
41
|
+
* 1. instantiates a QrustyClient from the server config's baseUrl,
|
|
42
|
+
* 2. runs the supplied `action(client, msg)` function,
|
|
43
|
+
* 3. stores the result in msg.payload and emits the msg,
|
|
44
|
+
* 4. updates node.status with blue → green (ok) / red (error),
|
|
45
|
+
* 5. forwards errors via `done(err)` for proper Node-RED catch semantics.
|
|
46
|
+
*
|
|
47
|
+
* If the server config is missing or invalid, the node enters a permanent
|
|
48
|
+
* "no server config" red-ring status and every subsequent input fails fast
|
|
49
|
+
* without making HTTP calls.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} node the Node-RED node instance
|
|
52
|
+
* @param {object} serverConfig the resolved qrusty-server config node
|
|
53
|
+
* @param {Function} action async (client, msg) => any
|
|
54
|
+
*/
|
|
55
|
+
function setupActionNode(node, serverConfig, action) {
|
|
56
|
+
if (!serverConfig || !serverConfig.baseUrl) {
|
|
57
|
+
node.status({ fill: "red", shape: "ring", text: "no server config" });
|
|
58
|
+
node.on("input", function (msg, send, done) {
|
|
59
|
+
const err = new Error(
|
|
60
|
+
"qrusty-server config is required but not configured",
|
|
61
|
+
);
|
|
62
|
+
if (done) {
|
|
63
|
+
done(err);
|
|
64
|
+
} else {
|
|
65
|
+
node.error(err, msg);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
node.on("input", async function (msg, send, done) {
|
|
72
|
+
node.status({ fill: "blue", shape: "dot", text: "requesting" });
|
|
73
|
+
try {
|
|
74
|
+
const client = new QrustyClient(serverConfig.baseUrl);
|
|
75
|
+
const result = await action(client, msg);
|
|
76
|
+
msg.payload = result;
|
|
77
|
+
node.status({ fill: "green", shape: "dot", text: "ok" });
|
|
78
|
+
send(msg);
|
|
79
|
+
if (done) done();
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const statusText = err?.response?.status
|
|
82
|
+
? `HTTP ${err.response.status}`
|
|
83
|
+
: err?.message || String(err);
|
|
84
|
+
node.status({ fill: "red", shape: "ring", text: statusText });
|
|
85
|
+
if (done) {
|
|
86
|
+
done(err);
|
|
87
|
+
} else {
|
|
88
|
+
node.error(err, msg);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { setupActionNode, fromConfigOrMsg, toNumberOrUndefined };
|
package/package.json
CHANGED
|
@@ -1,33 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-qrusty",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Node-RED nodes for Qrusty priority queue server",
|
|
3
|
+
"version": "0.20.0",
|
|
4
|
+
"description": "Node-RED nodes for the Qrusty priority queue server",
|
|
5
5
|
"author": "Gordon Greene <greeng3@obscure-reference.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/greeng3/qrusty.git",
|
|
10
|
+
"directory": "integrations/node-red"
|
|
11
|
+
},
|
|
7
12
|
"keywords": [
|
|
8
13
|
"node-red",
|
|
9
14
|
"qrusty",
|
|
10
15
|
"queue",
|
|
11
|
-
"priority"
|
|
16
|
+
"priority-queue",
|
|
17
|
+
"message-queue"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"qrusty-client": "^0.20.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"mocha": "^11.0.0",
|
|
27
|
+
"nock": "^14.0.0",
|
|
28
|
+
"node-red": "^4.0.0",
|
|
29
|
+
"node-red-node-test-helper": "^0.3.4"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "mocha \"test/**/*_spec.js\" --timeout 10000"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"lib/",
|
|
36
|
+
"qrusty-*.js",
|
|
37
|
+
"qrusty-*.html",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
12
40
|
],
|
|
13
41
|
"node-red": {
|
|
42
|
+
"version": ">=3.0.0",
|
|
14
43
|
"nodes": {
|
|
44
|
+
"qrusty-server": "qrusty-server.js",
|
|
15
45
|
"qrusty-publish": "qrusty-publish.js",
|
|
16
46
|
"qrusty-consume": "qrusty-consume.js",
|
|
17
47
|
"qrusty-ack": "qrusty-ack.js",
|
|
18
48
|
"qrusty-nack": "qrusty-nack.js",
|
|
49
|
+
"qrusty-ack-batch": "qrusty-ack-batch.js",
|
|
50
|
+
"qrusty-nack-batch": "qrusty-nack-batch.js",
|
|
19
51
|
"qrusty-create-queue": "qrusty-create-queue.js",
|
|
20
52
|
"qrusty-update-queue": "qrusty-update-queue.js",
|
|
21
53
|
"qrusty-delete-queue": "qrusty-delete-queue.js",
|
|
22
54
|
"qrusty-purge-queue": "qrusty-purge-queue.js",
|
|
55
|
+
"qrusty-purge-all": "qrusty-purge-all.js",
|
|
56
|
+
"qrusty-delete-all": "qrusty-delete-all.js",
|
|
23
57
|
"qrusty-stats": "qrusty-stats.js",
|
|
24
58
|
"qrusty-queue-stats": "qrusty-queue-stats.js",
|
|
25
59
|
"qrusty-queue-metrics": "qrusty-queue-metrics.js",
|
|
26
60
|
"qrusty-list-queues": "qrusty-list-queues.js",
|
|
27
|
-
"qrusty-purge-all": "qrusty-purge-all.js",
|
|
28
|
-
"qrusty-delete-all": "qrusty-delete-all.js",
|
|
29
|
-
"qrusty-ack-batch": "qrusty-ack-batch.js",
|
|
30
|
-
"qrusty-nack-batch": "qrusty-nack-batch.js",
|
|
31
61
|
"qrusty-health": "qrusty-health.js"
|
|
32
62
|
}
|
|
33
63
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType("qrusty-ack-batch", {
|
|
3
|
+
category: "qrusty",
|
|
4
|
+
color: "#d78a4e",
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: "" },
|
|
7
|
+
server: { value: "", type: "qrusty-server", required: true },
|
|
8
|
+
queue: { value: "" },
|
|
9
|
+
consumerId: { value: "" },
|
|
10
|
+
},
|
|
11
|
+
inputs: 1,
|
|
12
|
+
outputs: 1,
|
|
13
|
+
icon: "font-awesome/fa-check-square",
|
|
14
|
+
label: function () {
|
|
15
|
+
return this.name || "qrusty ack batch";
|
|
16
|
+
},
|
|
17
|
+
paletteLabel: "ack batch",
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script type="text/html" data-template-name="qrusty-ack-batch">
|
|
22
|
+
<div class="form-row">
|
|
23
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
24
|
+
<input type="text" id="node-input-name" placeholder="optional" />
|
|
25
|
+
</div>
|
|
26
|
+
<div class="form-row">
|
|
27
|
+
<label for="node-input-server"><i class="fa fa-server"></i> Server</label>
|
|
28
|
+
<input type="text" id="node-input-server" />
|
|
29
|
+
</div>
|
|
30
|
+
<div class="form-row">
|
|
31
|
+
<label for="node-input-queue"><i class="fa fa-list"></i> Queue</label>
|
|
32
|
+
<input
|
|
33
|
+
type="text"
|
|
34
|
+
id="node-input-queue"
|
|
35
|
+
placeholder="leave blank to use msg.queue"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-row">
|
|
39
|
+
<label for="node-input-consumerId"
|
|
40
|
+
><i class="fa fa-user"></i> Consumer ID</label
|
|
41
|
+
>
|
|
42
|
+
<input
|
|
43
|
+
type="text"
|
|
44
|
+
id="node-input-consumerId"
|
|
45
|
+
placeholder="default node-red-consumer"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script type="text/html" data-help-name="qrusty-ack-batch">
|
|
51
|
+
<p>Acknowledges multiple messages at once.</p>
|
|
52
|
+
<h3>Inputs</h3>
|
|
53
|
+
<dl class="message-properties">
|
|
54
|
+
<dt>messageIds <span class="property-type">string[]</span></dt>
|
|
55
|
+
<dd>
|
|
56
|
+
The ids to ack. Supplied via <code>msg.messageIds</code>,
|
|
57
|
+
<code>msg.message_ids</code>, or <code>msg.payload</code> (if it
|
|
58
|
+
is itself an array).
|
|
59
|
+
</dd>
|
|
60
|
+
<dt class="optional">queue <span class="property-type">string</span></dt>
|
|
61
|
+
<dd>Queue holding the messages.</dd>
|
|
62
|
+
</dl>
|
|
63
|
+
<h3>Outputs</h3>
|
|
64
|
+
<dl class="message-properties">
|
|
65
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
66
|
+
<dd>Server response with counts of acked / not-found ids.</dd>
|
|
67
|
+
</dl>
|
|
68
|
+
</script>
|
package/qrusty-ack-batch.js
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// qrusty-ack-batch.js
|
|
2
|
+
//
|
|
3
|
+
// Batch-acknowledges multiple messages in one HTTP round trip.
|
|
4
|
+
|
|
5
|
+
const { setupActionNode, fromConfigOrMsg } = require("./lib/common");
|
|
6
|
+
|
|
7
|
+
module.exports = function (RED) {
|
|
8
|
+
function QrustyAckBatchNode(n) {
|
|
9
|
+
RED.nodes.createNode(this, n);
|
|
10
|
+
const node = this;
|
|
11
|
+
const server = RED.nodes.getNode(n.server);
|
|
12
|
+
|
|
13
|
+
setupActionNode(node, server, async (client, msg) => {
|
|
14
|
+
const queue = fromConfigOrMsg(n.queue, msg.queue);
|
|
15
|
+
if (!queue) throw new Error("queue is required");
|
|
16
|
+
|
|
17
|
+
const ids = Array.isArray(msg.messageIds)
|
|
18
|
+
? msg.messageIds
|
|
19
|
+
: Array.isArray(msg.message_ids)
|
|
20
|
+
? msg.message_ids
|
|
21
|
+
: Array.isArray(msg.payload)
|
|
22
|
+
? msg.payload
|
|
23
|
+
: null;
|
|
24
|
+
if (!ids || ids.length === 0) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"messageIds array is required (set msg.messageIds, msg.message_ids, or msg.payload)",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const consumerId =
|
|
31
|
+
fromConfigOrMsg(n.consumerId, msg.consumer_id) || "node-red-consumer";
|
|
32
|
+
|
|
33
|
+
return await client.ackBatch(queue, consumerId, ids);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
RED.nodes.registerType("qrusty-ack-batch", QrustyAckBatchNode);
|
|
8
38
|
};
|
|
9
|
-
msg.headers = { "Content-Type": "application/json" };
|
|
10
|
-
return msg;
|
package/qrusty-ack.html
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType("qrusty-ack", {
|
|
3
|
+
category: "qrusty",
|
|
4
|
+
color: "#d78a4e",
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: "" },
|
|
7
|
+
server: { value: "", type: "qrusty-server", required: true },
|
|
8
|
+
queue: { value: "" },
|
|
9
|
+
consumerId: { value: "" },
|
|
10
|
+
},
|
|
11
|
+
inputs: 1,
|
|
12
|
+
outputs: 1,
|
|
13
|
+
icon: "font-awesome/fa-check",
|
|
14
|
+
label: function () {
|
|
15
|
+
return this.name || "qrusty ack";
|
|
16
|
+
},
|
|
17
|
+
paletteLabel: "ack",
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script type="text/html" data-template-name="qrusty-ack">
|
|
22
|
+
<div class="form-row">
|
|
23
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
24
|
+
<input type="text" id="node-input-name" placeholder="optional" />
|
|
25
|
+
</div>
|
|
26
|
+
<div class="form-row">
|
|
27
|
+
<label for="node-input-server"><i class="fa fa-server"></i> Server</label>
|
|
28
|
+
<input type="text" id="node-input-server" />
|
|
29
|
+
</div>
|
|
30
|
+
<div class="form-row">
|
|
31
|
+
<label for="node-input-queue"><i class="fa fa-list"></i> Queue</label>
|
|
32
|
+
<input
|
|
33
|
+
type="text"
|
|
34
|
+
id="node-input-queue"
|
|
35
|
+
placeholder="leave blank to use msg.queue"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-row">
|
|
39
|
+
<label for="node-input-consumerId"
|
|
40
|
+
><i class="fa fa-user"></i> Consumer ID</label
|
|
41
|
+
>
|
|
42
|
+
<input
|
|
43
|
+
type="text"
|
|
44
|
+
id="node-input-consumerId"
|
|
45
|
+
placeholder="default node-red-consumer"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script type="text/html" data-help-name="qrusty-ack">
|
|
51
|
+
<p>Acknowledges a Qrusty message.</p>
|
|
52
|
+
<h3>Inputs</h3>
|
|
53
|
+
<dl class="message-properties">
|
|
54
|
+
<dt>messageId / id <span class="property-type">string</span></dt>
|
|
55
|
+
<dd>The id of the message to ack (either <code>msg.messageId</code> or <code>msg.id</code>).</dd>
|
|
56
|
+
<dt class="optional">queue <span class="property-type">string</span></dt>
|
|
57
|
+
<dd>Queue holding the message (if not set on the node).</dd>
|
|
58
|
+
<dt class="optional">consumer_id <span class="property-type">string</span></dt>
|
|
59
|
+
<dd>Consumer identity that originally locked the message.</dd>
|
|
60
|
+
</dl>
|
|
61
|
+
<h3>Outputs</h3>
|
|
62
|
+
<dl class="message-properties">
|
|
63
|
+
<dt>payload <span class="property-type">boolean</span></dt>
|
|
64
|
+
<dd><code>true</code> on success, <code>false</code> if the server returned 404.</dd>
|
|
65
|
+
</dl>
|
|
66
|
+
</script>
|
package/qrusty-ack.js
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// qrusty-ack.js
|
|
2
|
+
//
|
|
3
|
+
// Acknowledges a previously-consumed message. msg.payload becomes
|
|
4
|
+
// `true` on success or `false` if the server returned 404 (message
|
|
5
|
+
// already acked, lock expired, or id unknown).
|
|
6
|
+
|
|
7
|
+
const { setupActionNode, fromConfigOrMsg } = require("./lib/common");
|
|
8
|
+
|
|
9
|
+
module.exports = function (RED) {
|
|
10
|
+
function QrustyAckNode(n) {
|
|
11
|
+
RED.nodes.createNode(this, n);
|
|
12
|
+
const node = this;
|
|
13
|
+
const server = RED.nodes.getNode(n.server);
|
|
14
|
+
|
|
15
|
+
setupActionNode(node, server, async (client, msg) => {
|
|
16
|
+
const queue = fromConfigOrMsg(n.queue, msg.queue);
|
|
17
|
+
if (!queue) throw new Error("queue is required");
|
|
18
|
+
const messageId = fromConfigOrMsg(
|
|
19
|
+
undefined,
|
|
20
|
+
msg.messageId ?? msg.id ?? msg.message_id,
|
|
21
|
+
);
|
|
22
|
+
if (!messageId) {
|
|
23
|
+
throw new Error("messageId is required (set msg.messageId or msg.id)");
|
|
24
|
+
}
|
|
25
|
+
const consumerId =
|
|
26
|
+
fromConfigOrMsg(n.consumerId, msg.consumer_id) || "node-red-consumer";
|
|
27
|
+
|
|
28
|
+
return await client.ack(queue, messageId, consumerId);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
RED.nodes.registerType("qrusty-ack", QrustyAckNode);
|
|
10
33
|
};
|
|
11
|
-
msg.headers = { "Content-Type": "application/json" };
|
|
12
|
-
return msg;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType("qrusty-consume", {
|
|
3
|
+
category: "qrusty",
|
|
4
|
+
color: "#d78a4e",
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: "" },
|
|
7
|
+
server: { value: "", type: "qrusty-server", required: true },
|
|
8
|
+
queue: { value: "" },
|
|
9
|
+
consumerId: { value: "" },
|
|
10
|
+
timeoutSeconds: { value: "" },
|
|
11
|
+
},
|
|
12
|
+
inputs: 1,
|
|
13
|
+
outputs: 1,
|
|
14
|
+
icon: "font-awesome/fa-inbox",
|
|
15
|
+
label: function () {
|
|
16
|
+
return this.name || "qrusty consume";
|
|
17
|
+
},
|
|
18
|
+
paletteLabel: "consume",
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script type="text/html" data-template-name="qrusty-consume">
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
25
|
+
<input type="text" id="node-input-name" placeholder="optional" />
|
|
26
|
+
</div>
|
|
27
|
+
<div class="form-row">
|
|
28
|
+
<label for="node-input-server"><i class="fa fa-server"></i> Server</label>
|
|
29
|
+
<input type="text" id="node-input-server" />
|
|
30
|
+
</div>
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-input-queue"><i class="fa fa-list"></i> Queue</label>
|
|
33
|
+
<input
|
|
34
|
+
type="text"
|
|
35
|
+
id="node-input-queue"
|
|
36
|
+
placeholder="leave blank to use msg.queue"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="form-row">
|
|
40
|
+
<label for="node-input-consumerId"
|
|
41
|
+
><i class="fa fa-user"></i> Consumer ID</label
|
|
42
|
+
>
|
|
43
|
+
<input
|
|
44
|
+
type="text"
|
|
45
|
+
id="node-input-consumerId"
|
|
46
|
+
placeholder="default node-red-consumer"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="form-row">
|
|
50
|
+
<label for="node-input-timeoutSeconds"
|
|
51
|
+
><i class="fa fa-clock-o"></i> Lock timeout (s)</label
|
|
52
|
+
>
|
|
53
|
+
<input type="number" id="node-input-timeoutSeconds" placeholder="30" />
|
|
54
|
+
</div>
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<script type="text/html" data-help-name="qrusty-consume">
|
|
58
|
+
<p>Pops one message from a Qrusty queue.</p>
|
|
59
|
+
<h3>Inputs</h3>
|
|
60
|
+
<dl class="message-properties">
|
|
61
|
+
<dt class="optional">queue <span class="property-type">string</span></dt>
|
|
62
|
+
<dd>Queue to read from (if not set on the node).</dd>
|
|
63
|
+
<dt class="optional">consumer_id <span class="property-type">string</span></dt>
|
|
64
|
+
<dd>Consumer identity used by the lock ownership check.</dd>
|
|
65
|
+
<dt class="optional">timeout_seconds <span class="property-type">number</span></dt>
|
|
66
|
+
<dd>How long the server locks the popped message before auto-unlock.</dd>
|
|
67
|
+
</dl>
|
|
68
|
+
<h3>Outputs</h3>
|
|
69
|
+
<dl class="message-properties">
|
|
70
|
+
<dt>payload <span class="property-type">object | null</span></dt>
|
|
71
|
+
<dd>
|
|
72
|
+
The popped message (<code>{ id, payload, retry_count }</code>) or
|
|
73
|
+
<code>null</code> when the queue is empty.
|
|
74
|
+
</dd>
|
|
75
|
+
</dl>
|
|
76
|
+
</script>
|