@yroshcha/node-red-contrib-redis-full 1.0.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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/icons/redis-icon.svg +7 -0
- package/icons/redis.png +0 -0
- package/package.json +38 -0
- package/redis.html +896 -0
- package/redis.js +1265 -0
package/redis.html
ADDED
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
"use strict";
|
|
3
|
+
/*global RED*/
|
|
4
|
+
|
|
5
|
+
// Shared visual identity for all nodes in this package.
|
|
6
|
+
var REDIS_CATEGORY = 'Redis Full';
|
|
7
|
+
// Redis Full visual identity: light Node-RED palette background + Redis mark.
|
|
8
|
+
var REDIS_COLOR = '#FFFFFF';
|
|
9
|
+
var REDIS_ICON = 'redis.png';
|
|
10
|
+
|
|
11
|
+
// -----------------------------------------------------------------
|
|
12
|
+
// yroshcha-redis-config
|
|
13
|
+
// -----------------------------------------------------------------
|
|
14
|
+
RED.nodes.registerType('yroshcha-redis-config', {
|
|
15
|
+
category: 'config',
|
|
16
|
+
defaults: {
|
|
17
|
+
name: { value: '' },
|
|
18
|
+
host: { value: '127.0.0.1', required: true },
|
|
19
|
+
port: { value: 6379, required: true, validate: RED.validators.number() },
|
|
20
|
+
db: { value: 0, validate: RED.validators.number() },
|
|
21
|
+
username: { value: '' },
|
|
22
|
+
tls: { value: false },
|
|
23
|
+
cluster: { value: false },
|
|
24
|
+
connectTimeout: { value: 10000, validate: RED.validators.number() },
|
|
25
|
+
commandTimeout: { value: 30000, validate: RED.validators.number() },
|
|
26
|
+
retryMaxDelay: { value: 30000, validate: RED.validators.number() }
|
|
27
|
+
},
|
|
28
|
+
credentials: {
|
|
29
|
+
password: { type: 'password' }
|
|
30
|
+
},
|
|
31
|
+
label: function () {
|
|
32
|
+
return this.name || (this.host + ':' + this.port + '/' + this.db);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<script type="text/html" data-template-name="yroshcha-redis-config">
|
|
38
|
+
<div class="form-row">
|
|
39
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
40
|
+
<input type="text" id="node-config-input-name">
|
|
41
|
+
</div>
|
|
42
|
+
<div class="form-row">
|
|
43
|
+
<label for="node-config-input-host"><i class="fa fa-server"></i> Host</label>
|
|
44
|
+
<input type="text" id="node-config-input-host">
|
|
45
|
+
</div>
|
|
46
|
+
<div class="form-row">
|
|
47
|
+
<label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
|
|
48
|
+
<input type="text" id="node-config-input-port">
|
|
49
|
+
</div>
|
|
50
|
+
<div class="form-row">
|
|
51
|
+
<label for="node-config-input-db"><i class="fa fa-database"></i> DB</label>
|
|
52
|
+
<input type="text" id="node-config-input-db">
|
|
53
|
+
</div>
|
|
54
|
+
<div class="form-row">
|
|
55
|
+
<label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
|
|
56
|
+
<input type="password" id="node-config-input-password">
|
|
57
|
+
</div>
|
|
58
|
+
<div class="form-row">
|
|
59
|
+
<label for="node-config-input-username">Username</label>
|
|
60
|
+
<input type="text" id="node-config-input-username" placeholder="optional Redis ACL user">
|
|
61
|
+
</div>
|
|
62
|
+
<div class="form-row">
|
|
63
|
+
<label for="node-config-input-tls" style="width:auto">
|
|
64
|
+
<input type="checkbox" id="node-config-input-tls" style="width:auto; margin-right:8px">TLS
|
|
65
|
+
</label>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="form-row">
|
|
68
|
+
<label for="node-config-input-cluster" style="width:auto">
|
|
69
|
+
<input type="checkbox" id="node-config-input-cluster" style="width:auto; margin-right:8px">Cluster mode
|
|
70
|
+
</label>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="form-row"><hr></div>
|
|
73
|
+
<div class="form-row"><b>Reliability</b></div>
|
|
74
|
+
<div class="form-row">
|
|
75
|
+
<label for="node-config-input-connectTimeout" title="Connection timeout in milliseconds">Connect timeout</label>
|
|
76
|
+
<input type="text" id="node-config-input-connectTimeout">
|
|
77
|
+
</div>
|
|
78
|
+
<div class="form-row">
|
|
79
|
+
<label for="node-config-input-commandTimeout" title="Redis command timeout in milliseconds">Command timeout</label>
|
|
80
|
+
<input type="text" id="node-config-input-commandTimeout">
|
|
81
|
+
</div>
|
|
82
|
+
<div class="form-row">
|
|
83
|
+
<label for="node-config-input-retryMaxDelay" title="Maximum reconnect delay in milliseconds">Retry max</label>
|
|
84
|
+
<input type="text" id="node-config-input-retryMaxDelay">
|
|
85
|
+
</div>
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<script type="text/javascript">
|
|
89
|
+
// -----------------------------------------------------------------
|
|
90
|
+
// yroshcha-redis-command — generic command
|
|
91
|
+
// -----------------------------------------------------------------
|
|
92
|
+
RED.nodes.registerType('yroshcha-redis-command', {
|
|
93
|
+
category: REDIS_CATEGORY,
|
|
94
|
+
color: REDIS_COLOR,
|
|
95
|
+
icon: REDIS_ICON,
|
|
96
|
+
// `l` controls per-node canvas label visibility in Node-RED 5.
|
|
97
|
+
// onadd affects only a newly dragged node; saved user choices remain intact.
|
|
98
|
+
showLabel: false,
|
|
99
|
+
defaults: {
|
|
100
|
+
name: { value: '' },
|
|
101
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
102
|
+
command: { value: 'GET' },
|
|
103
|
+
allowMsgCommand: { value: true },
|
|
104
|
+
block: { value: false }
|
|
105
|
+
},
|
|
106
|
+
inputs: 1,
|
|
107
|
+
outputs: 1,
|
|
108
|
+
paletteLabel: 'redis cmd',
|
|
109
|
+
label: function () {
|
|
110
|
+
return this.name || 'redis ' + (this.command || 'cmd').toLowerCase();
|
|
111
|
+
},
|
|
112
|
+
onadd: function () { this.l = true; }
|
|
113
|
+
});
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<script type="text/html" data-template-name="yroshcha-redis-command">
|
|
117
|
+
<div class="form-row">
|
|
118
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
119
|
+
<input type="text" id="node-input-server">
|
|
120
|
+
</div>
|
|
121
|
+
<div class="form-row">
|
|
122
|
+
<label for="node-input-command"><i class="fa fa-terminal"></i> Command</label>
|
|
123
|
+
<input type="text" id="node-input-command" list="yroshcha-redis-command-list" placeholder="e.g. GET, HSET, ZADD, XADD, EVAL...">
|
|
124
|
+
<datalist id="yroshcha-redis-command-list">
|
|
125
|
+
<option value="GET"><option value="SET"><option value="SETEX"><option value="SETNX">
|
|
126
|
+
<option value="MGET"><option value="MSET"><option value="INCR"><option value="INCRBY">
|
|
127
|
+
<option value="APPEND"><option value="STRLEN">
|
|
128
|
+
<option value="HGET"><option value="HSET"><option value="HGETALL"><option value="HDEL">
|
|
129
|
+
<option value="HMGET"><option value="HINCRBY"><option value="HEXISTS">
|
|
130
|
+
<option value="LPUSH"><option value="RPUSH"><option value="LPOP"><option value="RPOP">
|
|
131
|
+
<option value="LRANGE"><option value="LLEN"><option value="BLPOP"><option value="BRPOP">
|
|
132
|
+
<option value="BRPOPLPUSH">
|
|
133
|
+
<option value="SADD"><option value="SREM"><option value="SMEMBERS"><option value="SISMEMBER">
|
|
134
|
+
<option value="SUNION"><option value="SINTER">
|
|
135
|
+
<option value="ZADD"><option value="ZRANGE"><option value="ZRANGEBYSCORE"><option value="ZREM">
|
|
136
|
+
<option value="ZINCRBY"><option value="ZSCORE">
|
|
137
|
+
<option value="XLEN"><option value="XRANGE"><option value="XREVRANGE"><option value="XPENDING">
|
|
138
|
+
<option value="XINFO STREAM"><option value="XINFO GROUPS"><option value="XTRIM">
|
|
139
|
+
<option value="XGROUP DESTROY"><option value="XGROUP DELCONSUMER"><option value="XDEL">
|
|
140
|
+
<option value="DEL"><option value="EXISTS"><option value="EXPIRE"><option value="TTL">
|
|
141
|
+
<option value="TYPE"><option value="RENAME"><option value="KEYS">
|
|
142
|
+
<option value="EVAL"><option value="EVALSHA"><option value="SCRIPT LOAD">
|
|
143
|
+
<option value="INFO"><option value="DBSIZE"><option value="FLUSHDB"><option value="WAIT">
|
|
144
|
+
<option value="GEOADD"><option value="GEOSEARCH"><option value="SETBIT"><option value="GETBIT">
|
|
145
|
+
<option value="PFADD"><option value="PFCOUNT">
|
|
146
|
+
</datalist>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="form-row">
|
|
149
|
+
<label for="node-input-allowMsgCommand" title="Allow msg.command to override the configured command" style="width:auto">
|
|
150
|
+
<input type="checkbox" id="node-input-allowMsgCommand" style="width:auto; margin-right:8px">Allow msg.command
|
|
151
|
+
</label>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="form-row">
|
|
154
|
+
<label for="node-input-block" title="Use a dedicated Redis connection for blocking commands" style="width:auto">
|
|
155
|
+
<input type="checkbox" id="node-input-block" style="width:auto; margin-right:8px">Dedicated connection
|
|
156
|
+
</label>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="form-row">
|
|
159
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
160
|
+
<input type="text" id="node-input-name">
|
|
161
|
+
</div>
|
|
162
|
+
</script>
|
|
163
|
+
|
|
164
|
+
<script type="text/html" data-help-name="yroshcha-redis-command">
|
|
165
|
+
<p>Runs <b>any</b> Redis command through <code>ioredis.call()</code>.</p>
|
|
166
|
+
<p><code>msg.args</code> is an array of arguments. Compound commands such as <code>XINFO STREAM</code> are supported.</p>
|
|
167
|
+
<p>Enable <b>Block</b> for blocking commands (<code>BLPOP</code>, <code>BRPOP</code>, <code>WAIT</code>) to use a dedicated connection rather than occupy the shared config connection.</p>
|
|
168
|
+
<p>The raw ioredis result is placed in <code>msg.payload</code>.</p>
|
|
169
|
+
</script>
|
|
170
|
+
|
|
171
|
+
<script type="text/javascript">
|
|
172
|
+
// -----------------------------------------------------------------
|
|
173
|
+
// yroshcha-redis-subscribe — Pub/Sub
|
|
174
|
+
// -----------------------------------------------------------------
|
|
175
|
+
RED.nodes.registerType('yroshcha-redis-subscribe', {
|
|
176
|
+
category: REDIS_CATEGORY,
|
|
177
|
+
color: REDIS_COLOR,
|
|
178
|
+
icon: REDIS_ICON,
|
|
179
|
+
showLabel: false,
|
|
180
|
+
defaults: {
|
|
181
|
+
name: { value: '' },
|
|
182
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
183
|
+
mode: { value: 'subscribe' },
|
|
184
|
+
channels: { value: '' }
|
|
185
|
+
},
|
|
186
|
+
inputs: 1,
|
|
187
|
+
outputs: 1,
|
|
188
|
+
paletteLabel: 'redis sub',
|
|
189
|
+
label: function () {
|
|
190
|
+
return this.name || 'redis ' + this.mode;
|
|
191
|
+
},
|
|
192
|
+
onadd: function () { this.l = true; }
|
|
193
|
+
});
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
<script type="text/html" data-template-name="yroshcha-redis-subscribe">
|
|
197
|
+
<div class="form-row">
|
|
198
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
199
|
+
<input type="text" id="node-input-server">
|
|
200
|
+
</div>
|
|
201
|
+
<div class="form-row">
|
|
202
|
+
<label for="node-input-mode">Mode</label>
|
|
203
|
+
<select id="node-input-mode">
|
|
204
|
+
<option value="subscribe">SUBSCRIBE (exact channels)</option>
|
|
205
|
+
<option value="psubscribe">PSUBSCRIBE (patterns)</option>
|
|
206
|
+
</select>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="form-row">
|
|
209
|
+
<label for="node-input-channels">Channels</label>
|
|
210
|
+
<input type="text" id="node-input-channels" placeholder="comma-separated, e.g. app:notify, app:alerts">
|
|
211
|
+
</div>
|
|
212
|
+
<div class="form-row">
|
|
213
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
214
|
+
<input type="text" id="node-input-name">
|
|
215
|
+
</div>
|
|
216
|
+
</script>
|
|
217
|
+
|
|
218
|
+
<script type="text/html" data-help-name="yroshcha-redis-subscribe">
|
|
219
|
+
<p>Subscribes with <code>SUBSCRIBE</code>/<code>PSUBSCRIBE</code> on a dedicated connection.</p>
|
|
220
|
+
<p>Output: <code>msg.topic</code>, <code>msg.payload</code>, and <code>msg.pattern</code> for pattern subscriptions.</p>
|
|
221
|
+
<p>Dynamic control: <code>msg.subscribe</code>/<code>msg.unsubscribe</code> are arrays of channels.</p>
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<script type="text/javascript">
|
|
225
|
+
// -----------------------------------------------------------------
|
|
226
|
+
// yroshcha-redis-multi — MULTI/EXEC
|
|
227
|
+
// -----------------------------------------------------------------
|
|
228
|
+
RED.nodes.registerType('yroshcha-redis-multi', {
|
|
229
|
+
category: REDIS_CATEGORY,
|
|
230
|
+
color: REDIS_COLOR,
|
|
231
|
+
icon: REDIS_ICON,
|
|
232
|
+
showLabel: false,
|
|
233
|
+
defaults: {
|
|
234
|
+
name: { value: '' },
|
|
235
|
+
server: { type: 'yroshcha-redis-config', required: true }
|
|
236
|
+
},
|
|
237
|
+
inputs: 1,
|
|
238
|
+
outputs: 1,
|
|
239
|
+
paletteLabel: 'redis multi',
|
|
240
|
+
label: function () {
|
|
241
|
+
return this.name || 'redis multi/exec';
|
|
242
|
+
},
|
|
243
|
+
onadd: function () { this.l = true; }
|
|
244
|
+
});
|
|
245
|
+
</script>
|
|
246
|
+
|
|
247
|
+
<script type="text/html" data-template-name="yroshcha-redis-multi">
|
|
248
|
+
<div class="form-row">
|
|
249
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
250
|
+
<input type="text" id="node-input-server">
|
|
251
|
+
</div>
|
|
252
|
+
<div class="form-row">
|
|
253
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
254
|
+
<input type="text" id="node-input-name">
|
|
255
|
+
</div>
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
<script type="text/html" data-help-name="yroshcha-redis-multi">
|
|
259
|
+
<p>Atomic transaction. <code>msg.commands = [["set","k","v"],["incr","c"]]</code> → <code>MULTI/EXEC</code>.</p>
|
|
260
|
+
<p>Output: <code>msg.payload</code> is an array of results. A command error is sent to the error handler.</p>
|
|
261
|
+
</script>
|
|
262
|
+
|
|
263
|
+
<script type="text/javascript">
|
|
264
|
+
// -----------------------------------------------------------------
|
|
265
|
+
// yroshcha-redis-scan — SCAN/HSCAN/SSCAN/ZSCAN
|
|
266
|
+
// -----------------------------------------------------------------
|
|
267
|
+
RED.nodes.registerType('yroshcha-redis-scan', {
|
|
268
|
+
category: REDIS_CATEGORY,
|
|
269
|
+
color: REDIS_COLOR,
|
|
270
|
+
icon: REDIS_ICON,
|
|
271
|
+
showLabel: false,
|
|
272
|
+
defaults: {
|
|
273
|
+
name: { value: '' },
|
|
274
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
275
|
+
scanType: { value: 'SCAN' },
|
|
276
|
+
count: { value: 100, validate: RED.validators.number() }
|
|
277
|
+
},
|
|
278
|
+
inputs: 1,
|
|
279
|
+
outputs: 1,
|
|
280
|
+
paletteLabel: 'redis scan',
|
|
281
|
+
label: function () {
|
|
282
|
+
return this.name || 'redis ' + this.scanType.toLowerCase();
|
|
283
|
+
},
|
|
284
|
+
onadd: function () { this.l = true; }
|
|
285
|
+
});
|
|
286
|
+
</script>
|
|
287
|
+
|
|
288
|
+
<script type="text/html" data-template-name="yroshcha-redis-scan">
|
|
289
|
+
<div class="form-row">
|
|
290
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
291
|
+
<input type="text" id="node-input-server">
|
|
292
|
+
</div>
|
|
293
|
+
<div class="form-row">
|
|
294
|
+
<label for="node-input-scanType">Type</label>
|
|
295
|
+
<select id="node-input-scanType">
|
|
296
|
+
<option value="SCAN">SCAN (keys)</option>
|
|
297
|
+
<option value="HSCAN">HSCAN (hash fields)</option>
|
|
298
|
+
<option value="SSCAN">SSCAN (set members)</option>
|
|
299
|
+
<option value="ZSCAN">ZSCAN (sorted set members)</option>
|
|
300
|
+
</select>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="form-row">
|
|
303
|
+
<label for="node-input-count">COUNT hint</label>
|
|
304
|
+
<input type="text" id="node-input-count">
|
|
305
|
+
</div>
|
|
306
|
+
<div class="form-row">
|
|
307
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
308
|
+
<input type="text" id="node-input-name">
|
|
309
|
+
</div>
|
|
310
|
+
</script>
|
|
311
|
+
|
|
312
|
+
<script type="text/html" data-help-name="yroshcha-redis-scan">
|
|
313
|
+
<p>Full cursor-based non-blocking scan. HSCAN/SSCAN/ZSCAN require <code>msg.key</code>.</p>
|
|
314
|
+
<p>Output: <code>msg.payload</code> is an array; HSCAN/ZSCAN return <code>{member, value}</code>.</p>
|
|
315
|
+
</script>
|
|
316
|
+
|
|
317
|
+
<script type="text/javascript">
|
|
318
|
+
// -----------------------------------------------------------------
|
|
319
|
+
// yroshcha-redis-stream-out — XADD
|
|
320
|
+
// -----------------------------------------------------------------
|
|
321
|
+
RED.nodes.registerType('yroshcha-redis-stream-out', {
|
|
322
|
+
category: REDIS_CATEGORY,
|
|
323
|
+
color: REDIS_COLOR,
|
|
324
|
+
icon: REDIS_ICON,
|
|
325
|
+
showLabel: false,
|
|
326
|
+
defaults: {
|
|
327
|
+
name: { value: '' },
|
|
328
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
329
|
+
streamKey: { value: '' },
|
|
330
|
+
maxlen: { value: '' },
|
|
331
|
+
approxTrim: { value: true },
|
|
332
|
+
unsafeTrim: { value: false }
|
|
333
|
+
},
|
|
334
|
+
inputs: 1,
|
|
335
|
+
outputs: 1,
|
|
336
|
+
paletteLabel: 'redis xadd',
|
|
337
|
+
label: function () {
|
|
338
|
+
return this.name || 'redis xadd ' + (this.streamKey || '');
|
|
339
|
+
},
|
|
340
|
+
onadd: function () { this.l = true; }
|
|
341
|
+
});
|
|
342
|
+
</script>
|
|
343
|
+
|
|
344
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-out">
|
|
345
|
+
<div class="form-row">
|
|
346
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
347
|
+
<input type="text" id="node-input-server">
|
|
348
|
+
</div>
|
|
349
|
+
<div class="form-row">
|
|
350
|
+
<label for="node-input-streamKey">Stream key</label>
|
|
351
|
+
<input type="text" id="node-input-streamKey" placeholder="e.g. app:events">
|
|
352
|
+
</div>
|
|
353
|
+
<div class="form-row">
|
|
354
|
+
<label for="node-input-maxlen">MAXLEN</label>
|
|
355
|
+
<input type="text" id="node-input-maxlen" placeholder="optional, e.g. 100000">
|
|
356
|
+
</div>
|
|
357
|
+
<div class="form-row">
|
|
358
|
+
<label for="node-input-approxTrim" title="Use approximate MAXLEN trimming (~)" style="width:auto">
|
|
359
|
+
<input type="checkbox" id="node-input-approxTrim" style="width:auto; margin-right:8px">Approx. trim
|
|
360
|
+
</label>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="form-row">
|
|
363
|
+
<label for="node-input-unsafeTrim" title="MAXLEN may delete records that are still pending; enable only with a retention plan" style="width:auto">
|
|
364
|
+
<input type="checkbox" id="node-input-unsafeTrim" style="width:auto; margin-right:8px">Allow unsafe MAXLEN trim
|
|
365
|
+
</label>
|
|
366
|
+
</div>
|
|
367
|
+
<div class="form-row">
|
|
368
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
369
|
+
<input type="text" id="node-input-name">
|
|
370
|
+
</div>
|
|
371
|
+
</script>
|
|
372
|
+
|
|
373
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-out">
|
|
374
|
+
<p>Runs <code>XADD</code>. <code>msg.payload</code> is a flat fields object. <code>msg.stream</code>/<code>msg.maxlen</code> override the configuration.</p>
|
|
375
|
+
<p><b>Production safety:</b> MAXLEN trimming is disabled until <b>Allow unsafe MAXLEN trim</b> or <code>msg.allowUnsafeTrim = true</code> is explicitly set. Trimming may remove an entry that is still in a consumer-group PEL.</p>
|
|
376
|
+
<p>Output: <code>msg.streamId</code>, <code>msg.stream</code>.</p>
|
|
377
|
+
</script>
|
|
378
|
+
|
|
379
|
+
<script type="text/javascript">
|
|
380
|
+
// -----------------------------------------------------------------
|
|
381
|
+
// yroshcha-redis-stream-in — XREADGROUP consumer
|
|
382
|
+
// -----------------------------------------------------------------
|
|
383
|
+
RED.nodes.registerType('yroshcha-redis-stream-in', {
|
|
384
|
+
category: REDIS_CATEGORY,
|
|
385
|
+
color: REDIS_COLOR,
|
|
386
|
+
icon: REDIS_ICON,
|
|
387
|
+
showLabel: false,
|
|
388
|
+
defaults: {
|
|
389
|
+
name: { value: '' },
|
|
390
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
391
|
+
streamKey: { value: '', required: true },
|
|
392
|
+
group: { value: '', required: true },
|
|
393
|
+
consumer: { value: '' },
|
|
394
|
+
count: { value: 100, validate: RED.validators.number() },
|
|
395
|
+
blockMs: { value: 5000, validate: RED.validators.number() },
|
|
396
|
+
readIntervalMs: { value: 0, validate: RED.validators.number() },
|
|
397
|
+
rateLimitPerSecond: { value: 0, validate: RED.validators.number() },
|
|
398
|
+
batchWindowMs: { value: 0, validate: RED.validators.number() },
|
|
399
|
+
batchIntervalMs: { value: 0, validate: RED.validators.number() },
|
|
400
|
+
autoAck: { value: false },
|
|
401
|
+
startPaused: { value: false },
|
|
402
|
+
startId: { value: '$' },
|
|
403
|
+
maxPending: { value: 1000, required: true, validate: RED.validators.number() },
|
|
404
|
+
capacityPollMs: { value: 500, required: true, validate: RED.validators.number() },
|
|
405
|
+
capacityCheckIntervalMs: { value: 250, required: true, validate: RED.validators.number() },
|
|
406
|
+
autoClaim: { value: true },
|
|
407
|
+
reclaimIdleMs: { value: 60000, required: true, validate: RED.validators.number() },
|
|
408
|
+
reclaimIntervalMs: { value: 30000, required: true, validate: RED.validators.number() },
|
|
409
|
+
reclaimCount: { value: 100, required: true, validate: RED.validators.number() },
|
|
410
|
+
maxDeliveries: { value: 5, required: true, validate: RED.validators.number() },
|
|
411
|
+
deadLetterStream: { value: '' },
|
|
412
|
+
initialBackoffMs: { value: 500, validate: RED.validators.number() },
|
|
413
|
+
maxBackoffMs: { value: 30000, validate: RED.validators.number() },
|
|
414
|
+
backoffMultiplier: { value: 2, validate: RED.validators.number() },
|
|
415
|
+
pelAlertThreshold: { value: 0, validate: RED.validators.number() },
|
|
416
|
+
pelCheckIntervalMs: { value: 30000, validate: RED.validators.number() }
|
|
417
|
+
},
|
|
418
|
+
inputs: 0,
|
|
419
|
+
outputs: 1,
|
|
420
|
+
paletteLabel: 'redis xreadgroup',
|
|
421
|
+
label: function () {
|
|
422
|
+
return this.name || 'redis xreadgroup ' + (this.streamKey || '');
|
|
423
|
+
},
|
|
424
|
+
onadd: function () { this.l = true; }
|
|
425
|
+
});
|
|
426
|
+
</script>
|
|
427
|
+
|
|
428
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-in">
|
|
429
|
+
<div class="form-row">
|
|
430
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
431
|
+
<input type="text" id="node-input-server">
|
|
432
|
+
</div>
|
|
433
|
+
<div class="form-row">
|
|
434
|
+
<label for="node-input-streamKey">Stream key</label>
|
|
435
|
+
<input type="text" id="node-input-streamKey" placeholder="e.g. app:events">
|
|
436
|
+
</div>
|
|
437
|
+
<div class="form-row">
|
|
438
|
+
<label for="node-input-group">Group</label>
|
|
439
|
+
<input type="text" id="node-input-group" placeholder="e.g. app-workers">
|
|
440
|
+
</div>
|
|
441
|
+
<div class="form-row">
|
|
442
|
+
<label for="node-input-consumer" title="Consumer name">Consumer</label>
|
|
443
|
+
<input type="text" id="node-input-consumer" placeholder="empty = hostname + node id">
|
|
444
|
+
</div>
|
|
445
|
+
<div class="form-row">
|
|
446
|
+
<label for="node-input-count">COUNT</label>
|
|
447
|
+
<input type="text" id="node-input-count">
|
|
448
|
+
</div>
|
|
449
|
+
<div class="form-row">
|
|
450
|
+
<label for="node-input-blockMs">BLOCK (ms)</label>
|
|
451
|
+
<input type="text" id="node-input-blockMs">
|
|
452
|
+
</div>
|
|
453
|
+
<div class="form-row">
|
|
454
|
+
<label for="node-input-readIntervalMs" title="Minimum delay between XREADGROUP calls; 0 means read as fast as Redis responds">Read interval (ms)</label>
|
|
455
|
+
<input type="text" id="node-input-readIntervalMs" placeholder="0 = no limit; 1000 = once/second">
|
|
456
|
+
</div>
|
|
457
|
+
<div class="form-row">
|
|
458
|
+
<label for="node-input-rateLimitPerSecond" title="Maximum messages delivered downstream per second; 0 means unlimited">Rate limit (msg/s)</label>
|
|
459
|
+
<input type="text" id="node-input-rateLimitPerSecond" placeholder="0 = unlimited">
|
|
460
|
+
</div>
|
|
461
|
+
<div class="form-row">
|
|
462
|
+
<label for="node-input-batchWindowMs" title="When greater than 0, emit a batch when COUNT is reached or this wait expires">Batch wait (ms)</label>
|
|
463
|
+
<input type="text" id="node-input-batchWindowMs" placeholder="0 = emit each message">
|
|
464
|
+
</div>
|
|
465
|
+
<div class="form-row">
|
|
466
|
+
<label for="node-input-batchIntervalMs" title="Minimum delay between emitted batches; 0 means no fixed pacing">Batch interval (ms)</label>
|
|
467
|
+
<input type="text" id="node-input-batchIntervalMs" placeholder="0 = no fixed pacing">
|
|
468
|
+
</div>
|
|
469
|
+
<div class="form-row">
|
|
470
|
+
<label for="node-input-startId" title="Start ID when a consumer group is created">Start ID</label>
|
|
471
|
+
<input type="text" id="node-input-startId" placeholder="$ = only new, 0 = from beginning">
|
|
472
|
+
</div>
|
|
473
|
+
<div class="form-row">
|
|
474
|
+
<label for="node-input-autoAck" title="Unsafe for asynchronous business processing; use redis xack downstream" style="width:auto">
|
|
475
|
+
<input type="checkbox" id="node-input-autoAck" style="width:auto; margin-right:8px">Auto ACK (unsafe)
|
|
476
|
+
</label>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="form-row">
|
|
479
|
+
<label for="node-input-startPaused" title="Do not call XREADGROUP after startup until redis streams API receives resume" style="width:auto">
|
|
480
|
+
<input type="checkbox" id="node-input-startPaused" style="width:auto; margin-right:8px">Start paused (wait for resume)
|
|
481
|
+
</label>
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
<div class="form-row"><hr></div>
|
|
485
|
+
<div class="form-row"><b>Reliability (advanced)</b></div>
|
|
486
|
+
|
|
487
|
+
<div class="form-row">
|
|
488
|
+
<label for="node-input-maxPending" title="Group-wide PEL ceiling; intake pauses when reached">Max pending</label>
|
|
489
|
+
<input type="text" id="node-input-maxPending">
|
|
490
|
+
</div>
|
|
491
|
+
<div class="form-row">
|
|
492
|
+
<label for="node-input-capacityPollMs" title="Wait between PEL capacity checks in milliseconds">Poll delay</label>
|
|
493
|
+
<input type="text" id="node-input-capacityPollMs">
|
|
494
|
+
</div>
|
|
495
|
+
<div class="form-row">
|
|
496
|
+
<label for="node-input-capacityCheckIntervalMs" title="How often PEL capacity is sampled while consuming, in milliseconds">PEL sample</label>
|
|
497
|
+
<input type="text" id="node-input-capacityCheckIntervalMs">
|
|
498
|
+
</div>
|
|
499
|
+
<div class="form-row">
|
|
500
|
+
<label for="node-input-autoClaim" title="Automatically reclaim abandoned messages from dead or stalled consumers" style="width:auto">
|
|
501
|
+
<input type="checkbox" id="node-input-autoClaim" style="width:auto; margin-right:8px">Auto recovery
|
|
502
|
+
</label>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="form-row">
|
|
505
|
+
<label for="node-input-reclaimIdleMs" title="A pending message must be idle this long before automatic reclaim, in milliseconds">Recovery idle</label>
|
|
506
|
+
<input type="text" id="node-input-reclaimIdleMs">
|
|
507
|
+
</div>
|
|
508
|
+
<div class="form-row">
|
|
509
|
+
<label for="node-input-reclaimIntervalMs" title="How often automatic recovery runs, in milliseconds">Recovery check</label>
|
|
510
|
+
<input type="text" id="node-input-reclaimIntervalMs">
|
|
511
|
+
</div>
|
|
512
|
+
<div class="form-row">
|
|
513
|
+
<label for="node-input-reclaimCount" title="Maximum abandoned messages claimed in one automatic recovery run">Recovery batch</label>
|
|
514
|
+
<input type="text" id="node-input-reclaimCount">
|
|
515
|
+
</div>
|
|
516
|
+
<div class="form-row">
|
|
517
|
+
<label for="node-input-maxDeliveries" title="Move a repeatedly delivered message to the DLQ after this count; 0 disables">Max deliveries</label>
|
|
518
|
+
<input type="text" id="node-input-maxDeliveries">
|
|
519
|
+
</div>
|
|
520
|
+
<div class="form-row">
|
|
521
|
+
<label for="node-input-deadLetterStream" title="Defaults to <stream>:dlq; use the same Redis Cluster hash tag as the source stream">DLQ stream</label>
|
|
522
|
+
<input type="text" id="node-input-deadLetterStream" placeholder="empty = <stream>:dlq">
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<div class="form-row">
|
|
526
|
+
<label for="node-input-initialBackoffMs" title="Initial reconnect backoff in milliseconds">Initial delay</label>
|
|
527
|
+
<input type="text" id="node-input-initialBackoffMs">
|
|
528
|
+
</div>
|
|
529
|
+
<div class="form-row">
|
|
530
|
+
<label for="node-input-maxBackoffMs" title="Maximum reconnect backoff in milliseconds">Maximum delay</label>
|
|
531
|
+
<input type="text" id="node-input-maxBackoffMs">
|
|
532
|
+
</div>
|
|
533
|
+
<div class="form-row">
|
|
534
|
+
<label for="node-input-backoffMultiplier" title="Reconnect backoff multiplier">Multiplier</label>
|
|
535
|
+
<input type="text" id="node-input-backoffMultiplier">
|
|
536
|
+
</div>
|
|
537
|
+
<div class="form-row">
|
|
538
|
+
<label for="node-input-pelAlertThreshold" title="Pending Entries List alert threshold">PEL limit</label>
|
|
539
|
+
<input type="text" id="node-input-pelAlertThreshold" placeholder="0 = disabled">
|
|
540
|
+
</div>
|
|
541
|
+
<div class="form-row">
|
|
542
|
+
<label for="node-input-pelCheckIntervalMs" title="Pending Entries List check interval in milliseconds">PEL check</label>
|
|
543
|
+
<input type="text" id="node-input-pelCheckIntervalMs">
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
<div class="form-row">
|
|
547
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
548
|
+
<input type="text" id="node-input-name">
|
|
549
|
+
</div>
|
|
550
|
+
</script>
|
|
551
|
+
|
|
552
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-in">
|
|
553
|
+
<p>Consumer on a dedicated blocking connection. Creates its group with <code>MKSTREAM</code>. On startup it reads its own pending entries (<code>0</code>), then new entries (<code>></code>).</p>
|
|
554
|
+
<p><b>Production:</b> keep <b>Auto ACK</b> disabled and use <code>redis xack</code> only after business logic completes. Auto ACK can lose an event if the process fails after sending it downstream.</p>
|
|
555
|
+
<p><b>Backpressure:</b> intake stops when the group PEL reaches <b>Max pending</b>. After <b>Max deliveries</b>, an entry is atomically moved to the DLQ and ACKed. In Redis Cluster, source and DLQ must share a hash tag.</p>
|
|
556
|
+
<p><b>Read interval:</b> limits how often the node calls <code>XREADGROUP</code>, regardless of queue depth. <code>0</code> reads as fast as Redis responds; <code>1000</code> reads at most once per second; <code>60000</code> at most once per minute. <b>COUNT</b> still limits entries returned by each read.</p>
|
|
557
|
+
<p><b>Rate limit:</b> <b>Rate limit (msg/s)</b> limits messages delivered downstream. <code>0</code> is unlimited; for example, <code>500</code> limits average delivery to 500 messages per second. Redis reads may reserve entries in the PEL up to <b>Max pending</b> while the downstream rate is limited.</p>
|
|
558
|
+
<p><b>Batch wait:</b> with <b>Batch wait (ms)</b> set to <code>0</code>, each entry is emitted immediately. When it is greater than <code>0</code>, the node emits an array when either <b>COUNT</b> entries are collected or the wait expires. In batch mode, <code>msg.payload</code> and <code>msg.streamId</code> are arrays; <code>redis xack</code> accepts the ID array directly.</p>
|
|
559
|
+
<p><b>Batch interval:</b> sets the minimum time between emitted batches, regardless of queue depth. For exactly one batch of up to 50 entries per second, use <b>COUNT = 50</b> and <b>Batch interval = 1000</b>. If Batch wait is left at <code>0</code>, it follows the interval automatically.</p>
|
|
560
|
+
<p><b>Auto recovery:</b> enabled by default. The node periodically runs <code>XAUTOCLAIM</code> for entries left without ACK longer than <b>Recovery idle</b> by a dead or stalled consumer. The separate <code>redis xautoclaim</code> node remains available for manual recovery.</p>
|
|
561
|
+
<p><b>Worker control:</b> use <code>redis streams API</code> for global <code>pause</code>, <code>resume</code>, <code>status</code> and <code>drain</code>. The consumer node remains a clean source node.</p>
|
|
562
|
+
<p><b>Start paused:</b> creates or verifies the consumer group, but does not call <code>XREADGROUP</code> until a <code>resume</code> control request arrives. Use it to let a pod pass readiness checks before it accepts stream work.</p>
|
|
563
|
+
<p><b>Backoff/jitter:</b> after a loop failure, delay grows exponentially from <b>initial</b> to <b>max</b> (× <b>multiplier</b>) with up to +30% random jitter. It prevents an HPA pool from reconnecting in lockstep during a Redis outage, and resets after the first successful call.</p>
|
|
564
|
+
<p><b>PEL alert:</b> when <b>PEL alert threshold</b> is greater than 0, the node periodically checks <code>XPENDING</code>. Above the threshold it emits <code>node.warn</code> and shows a yellow status until the lag subsides.</p>
|
|
565
|
+
<p><b>Multi-pod / same host:</b> leave <b>Consumer</b> empty to generate <code>HOSTNAME-node.id</code>. This is unique across pods and across several consumer nodes in one Node-RED runtime. <u>Do not use the same static value</u> for multiple workers in one group.</p>
|
|
566
|
+
<p>Output: <code>msg.payload</code>, <code>msg.streamId</code>, <code>msg.stream</code>, <code>msg.group</code>, <code>msg.consumer</code>. With Batch wait enabled, payload and streamId are arrays.</p>
|
|
567
|
+
</script>
|
|
568
|
+
|
|
569
|
+
<script type="text/javascript">
|
|
570
|
+
// -----------------------------------------------------------------
|
|
571
|
+
// yroshcha-redis-stream-ack — XACK
|
|
572
|
+
// -----------------------------------------------------------------
|
|
573
|
+
RED.nodes.registerType('yroshcha-redis-stream-ack', {
|
|
574
|
+
category: REDIS_CATEGORY,
|
|
575
|
+
color: REDIS_COLOR,
|
|
576
|
+
icon: REDIS_ICON,
|
|
577
|
+
showLabel: false,
|
|
578
|
+
defaults: {
|
|
579
|
+
name: { value: '' },
|
|
580
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
581
|
+
streamKey: { value: '' },
|
|
582
|
+
group: { value: '' },
|
|
583
|
+
deleteAfterAck: { value: false }
|
|
584
|
+
},
|
|
585
|
+
inputs: 1,
|
|
586
|
+
outputs: 1,
|
|
587
|
+
paletteLabel: 'redis xack',
|
|
588
|
+
label: function () {
|
|
589
|
+
return this.name || 'redis xack';
|
|
590
|
+
},
|
|
591
|
+
onadd: function () { this.l = true; }
|
|
592
|
+
});
|
|
593
|
+
</script>
|
|
594
|
+
|
|
595
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-ack">
|
|
596
|
+
<div class="form-row">
|
|
597
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
598
|
+
<input type="text" id="node-input-server">
|
|
599
|
+
</div>
|
|
600
|
+
<div class="form-row">
|
|
601
|
+
<label for="node-input-streamKey">Stream key</label>
|
|
602
|
+
<input type="text" id="node-input-streamKey" placeholder="optional, else msg._streamKey">
|
|
603
|
+
</div>
|
|
604
|
+
<div class="form-row">
|
|
605
|
+
<label for="node-input-group">Group</label>
|
|
606
|
+
<input type="text" id="node-input-group" placeholder="optional, else msg._streamGroup">
|
|
607
|
+
</div>
|
|
608
|
+
<div class="form-row">
|
|
609
|
+
<label for="node-input-deleteAfterAck" title="Deletes the stream entry after ACK; use only when no other consumer group needs it" style="width:auto">
|
|
610
|
+
<input type="checkbox" id="node-input-deleteAfterAck" style="width:auto; margin-right:8px">Delete after ACK
|
|
611
|
+
</label>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="form-row">
|
|
614
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
615
|
+
<input type="text" id="node-input-name">
|
|
616
|
+
</div>
|
|
617
|
+
</script>
|
|
618
|
+
|
|
619
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-ack">
|
|
620
|
+
<p>Runs <code>XACK</code>. <code>msg.streamId</code> can be one ID or an array. When Stream key or Group is empty, it uses <code>msg._streamKey</code> and <code>msg._streamGroup</code> emitted by <code>redis xreadgroup</code>.</p>
|
|
621
|
+
<p><b>Delete after ACK</b> also runs <code>XDEL</code>. Enable it only when replay and reading this stream from other consumer groups are not required. Override it per message with <code>msg.deleteAfterAck = true</code> or <code>false</code>.</p>
|
|
622
|
+
</script>
|
|
623
|
+
|
|
624
|
+
<script type="text/javascript">
|
|
625
|
+
// -----------------------------------------------------------------
|
|
626
|
+
// yroshcha-redis-stream-claim — XAUTOCLAIM
|
|
627
|
+
// -----------------------------------------------------------------
|
|
628
|
+
RED.nodes.registerType('yroshcha-redis-stream-claim', {
|
|
629
|
+
category: REDIS_CATEGORY,
|
|
630
|
+
color: REDIS_COLOR,
|
|
631
|
+
icon: REDIS_ICON,
|
|
632
|
+
showLabel: false,
|
|
633
|
+
defaults: {
|
|
634
|
+
name: { value: '' },
|
|
635
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
636
|
+
streamKey: { value: '' },
|
|
637
|
+
group: { value: '' },
|
|
638
|
+
consumer: { value: '' },
|
|
639
|
+
minIdleMs: { value: 60000, validate: RED.validators.number() },
|
|
640
|
+
count: { value: 100, validate: RED.validators.number() },
|
|
641
|
+
maxMessages: { value: 1000, validate: RED.validators.number() }
|
|
642
|
+
},
|
|
643
|
+
inputs: 1,
|
|
644
|
+
outputs: 1,
|
|
645
|
+
paletteLabel: 'redis xautoclaim',
|
|
646
|
+
label: function () {
|
|
647
|
+
return this.name || 'redis xautoclaim';
|
|
648
|
+
},
|
|
649
|
+
onadd: function () { this.l = true; }
|
|
650
|
+
});
|
|
651
|
+
</script>
|
|
652
|
+
|
|
653
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-claim">
|
|
654
|
+
<div class="form-row">
|
|
655
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
656
|
+
<input type="text" id="node-input-server">
|
|
657
|
+
</div>
|
|
658
|
+
<div class="form-row">
|
|
659
|
+
<label for="node-input-streamKey">Stream key</label>
|
|
660
|
+
<input type="text" id="node-input-streamKey">
|
|
661
|
+
</div>
|
|
662
|
+
<div class="form-row">
|
|
663
|
+
<label for="node-input-group">Group</label>
|
|
664
|
+
<input type="text" id="node-input-group">
|
|
665
|
+
</div>
|
|
666
|
+
<div class="form-row">
|
|
667
|
+
<label for="node-input-consumer" title="Consumer that receives claimed messages">Claim consumer</label>
|
|
668
|
+
<input type="text" id="node-input-consumer" placeholder="e.g. recovery-worker">
|
|
669
|
+
</div>
|
|
670
|
+
<div class="form-row">
|
|
671
|
+
<label for="node-input-minIdleMs" title="Minimum idle time in milliseconds">Min. idle</label>
|
|
672
|
+
<input type="text" id="node-input-minIdleMs">
|
|
673
|
+
</div>
|
|
674
|
+
<div class="form-row">
|
|
675
|
+
<label for="node-input-count" title="Maximum messages to claim per call">Claim count</label>
|
|
676
|
+
<input type="text" id="node-input-count">
|
|
677
|
+
</div>
|
|
678
|
+
<div class="form-row">
|
|
679
|
+
<label for="node-input-maxMessages" title="Hard limit for messages returned by one recovery run">Run limit</label>
|
|
680
|
+
<input type="text" id="node-input-maxMessages">
|
|
681
|
+
</div>
|
|
682
|
+
<div class="form-row">
|
|
683
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
684
|
+
<input type="text" id="node-input-name">
|
|
685
|
+
</div>
|
|
686
|
+
</script>
|
|
687
|
+
|
|
688
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-claim">
|
|
689
|
+
<p>Runs from an input message (inject/timer). Claims entries left in the PEL longer than <code>minIdleMs</code>. One run is bounded by <b>Run limit</b> to avoid loading the whole PEL into Node-RED memory.</p>
|
|
690
|
+
<p>Output: <code>msg.payload</code> is an array of <code>{streamId, payload}</code>. Process entries and acknowledge them with <code>redis xack</code>.</p>
|
|
691
|
+
</script>
|
|
692
|
+
|
|
693
|
+
<script type="text/javascript">
|
|
694
|
+
// -----------------------------------------------------------------
|
|
695
|
+
// yroshcha-redis-stream-gc — remove dead consumer records
|
|
696
|
+
// -----------------------------------------------------------------
|
|
697
|
+
RED.nodes.registerType('yroshcha-redis-stream-gc', {
|
|
698
|
+
category: REDIS_CATEGORY,
|
|
699
|
+
color: REDIS_COLOR,
|
|
700
|
+
icon: REDIS_ICON,
|
|
701
|
+
showLabel: false,
|
|
702
|
+
defaults: {
|
|
703
|
+
name: { value: '' },
|
|
704
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
705
|
+
streamKey: { value: '' },
|
|
706
|
+
group: { value: '' },
|
|
707
|
+
minIdleMs: { value: 600000, validate: RED.validators.number() }
|
|
708
|
+
},
|
|
709
|
+
inputs: 1,
|
|
710
|
+
outputs: 1,
|
|
711
|
+
paletteLabel: 'redis consumer gc',
|
|
712
|
+
label: function () {
|
|
713
|
+
return this.name || 'redis consumer gc';
|
|
714
|
+
},
|
|
715
|
+
onadd: function () { this.l = true; }
|
|
716
|
+
});
|
|
717
|
+
</script>
|
|
718
|
+
|
|
719
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-gc">
|
|
720
|
+
<div class="form-row">
|
|
721
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
722
|
+
<input type="text" id="node-input-server">
|
|
723
|
+
</div>
|
|
724
|
+
<div class="form-row">
|
|
725
|
+
<label for="node-input-streamKey">Stream key</label>
|
|
726
|
+
<input type="text" id="node-input-streamKey" placeholder="optional, else msg.stream">
|
|
727
|
+
</div>
|
|
728
|
+
<div class="form-row">
|
|
729
|
+
<label for="node-input-group">Group</label>
|
|
730
|
+
<input type="text" id="node-input-group" placeholder="optional, else msg.group">
|
|
731
|
+
</div>
|
|
732
|
+
<div class="form-row">
|
|
733
|
+
<label for="node-input-minIdleMs" title="Minimum idle time before cleanup in milliseconds">Min. idle</label>
|
|
734
|
+
<input type="text" id="node-input-minIdleMs" placeholder="10 min default">
|
|
735
|
+
</div>
|
|
736
|
+
<div class="form-row">
|
|
737
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
738
|
+
<input type="text" id="node-input-name">
|
|
739
|
+
</div>
|
|
740
|
+
</script>
|
|
741
|
+
|
|
742
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-gc">
|
|
743
|
+
<p>Removes dead consumers listed by <code>XINFO CONSUMERS</code> with <code>XGROUP DELCONSUMER</code>.</p>
|
|
744
|
+
<p><b>Why:</b> each HPA pod restart creates a new consumer when the name uses <code>$HOSTNAME</code>, while old records remain in <code>XINFO CONSUMERS</code> until explicitly removed.</p>
|
|
745
|
+
<p><b>Safety:</b> only consumers with <code>pending = 0</code> and <code>idle</code> longer than <b>Min idle</b> are removed, so unprocessed entries and recently restarted active pods are not affected.</p>
|
|
746
|
+
<p>Run it from a scheduled <code>inject</code> node, for example once an hour, rather than as a permanent background process.</p>
|
|
747
|
+
<p>Output: <code>msg.payload = {removed: [...], kept: [...]}</code>.</p>
|
|
748
|
+
</script>
|
|
749
|
+
|
|
750
|
+
<script type="text/javascript">
|
|
751
|
+
// -----------------------------------------------------------------
|
|
752
|
+
// yroshcha-redis-stream-metrics — XINFO GROUPS + XPENDING snapshot
|
|
753
|
+
// -----------------------------------------------------------------
|
|
754
|
+
RED.nodes.registerType('yroshcha-redis-stream-metrics', {
|
|
755
|
+
category: REDIS_CATEGORY,
|
|
756
|
+
color: REDIS_COLOR,
|
|
757
|
+
icon: REDIS_ICON,
|
|
758
|
+
showLabel: false,
|
|
759
|
+
defaults: {
|
|
760
|
+
name: { value: '' },
|
|
761
|
+
server: { type: 'yroshcha-redis-config', required: true },
|
|
762
|
+
streamKey: { value: '', required: true },
|
|
763
|
+
group: { value: '', required: true },
|
|
764
|
+
deadLetterStream: { value: '' }
|
|
765
|
+
},
|
|
766
|
+
inputs: 1,
|
|
767
|
+
outputs: 1,
|
|
768
|
+
paletteLabel: 'redis stream metrics',
|
|
769
|
+
label: function () {
|
|
770
|
+
return this.name || 'redis stream metrics';
|
|
771
|
+
},
|
|
772
|
+
onadd: function () { this.l = true; }
|
|
773
|
+
});
|
|
774
|
+
</script>
|
|
775
|
+
|
|
776
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-metrics">
|
|
777
|
+
<div class="form-row">
|
|
778
|
+
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
|
|
779
|
+
<input type="text" id="node-input-server">
|
|
780
|
+
</div>
|
|
781
|
+
<div class="form-row">
|
|
782
|
+
<label for="node-input-streamKey">Stream key</label>
|
|
783
|
+
<input type="text" id="node-input-streamKey" placeholder="e.g. app:events">
|
|
784
|
+
</div>
|
|
785
|
+
<div class="form-row">
|
|
786
|
+
<label for="node-input-group">Group</label>
|
|
787
|
+
<input type="text" id="node-input-group" placeholder="e.g. app-workers">
|
|
788
|
+
</div>
|
|
789
|
+
<div class="form-row">
|
|
790
|
+
<label for="node-input-deadLetterStream">DLQ stream</label>
|
|
791
|
+
<input type="text" id="node-input-deadLetterStream" placeholder="optional: e.g. app:events:dlq">
|
|
792
|
+
</div>
|
|
793
|
+
<div class="form-row">
|
|
794
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
795
|
+
<input type="text" id="node-input-name">
|
|
796
|
+
</div>
|
|
797
|
+
</script>
|
|
798
|
+
|
|
799
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-metrics">
|
|
800
|
+
<p>Collects a consumer-group snapshot with <code>XINFO GROUPS</code> and <code>XPENDING</code>. Run it from Inject or a schedule and pass <code>msg.payload</code> to a monitoring flow.</p>
|
|
801
|
+
<p>Optionally set <b>DLQ stream</b> to collect its <code>XLEN</code> as well. Output: <code>{stream, group, pending, deadLetterStream, deadLetterLength, consumers, lag, entriesRead, lastDeliveredId, sampledAt}</code>. <code>lag</code> may be <code>null</code> on older Redis versions.</p>
|
|
802
|
+
</script>
|
|
803
|
+
|
|
804
|
+
<script type="text/javascript">
|
|
805
|
+
// -----------------------------------------------------------------
|
|
806
|
+
// yroshcha-redis-stream-control — global runtime pause/drain control
|
|
807
|
+
// -----------------------------------------------------------------
|
|
808
|
+
RED.nodes.registerType('yroshcha-redis-stream-control', {
|
|
809
|
+
category: REDIS_CATEGORY,
|
|
810
|
+
color: REDIS_COLOR,
|
|
811
|
+
icon: REDIS_ICON,
|
|
812
|
+
showLabel: false,
|
|
813
|
+
defaults: {
|
|
814
|
+
name: { value: '' }
|
|
815
|
+
},
|
|
816
|
+
inputs: 1,
|
|
817
|
+
outputs: 1,
|
|
818
|
+
paletteLabel: 'redis streams control',
|
|
819
|
+
label: function () {
|
|
820
|
+
return this.name || 'redis streams control';
|
|
821
|
+
},
|
|
822
|
+
onadd: function () { this.l = true; }
|
|
823
|
+
});
|
|
824
|
+
</script>
|
|
825
|
+
|
|
826
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-control">
|
|
827
|
+
<div class="form-row">
|
|
828
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
829
|
+
<input type="text" id="node-input-name">
|
|
830
|
+
</div>
|
|
831
|
+
</script>
|
|
832
|
+
|
|
833
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-control">
|
|
834
|
+
<p>Controls all <code>redis xreadgroup</code> nodes in <b>this Node-RED runtime</b>. Accepts <code>msg.action</code>: <code>pause</code>, <code>resume</code>, <code>status</code>, <code>drain</code>.</p>
|
|
835
|
+
<p><code>status</code> returns every local consumer state, including <code>nodeId</code>, stream, group, PEL and paused. <code>pause</code> without filters stops all of them. To isolate one consumer, pass <code>msg.nodeId</code>; <code>msg.stream</code> and <code>msg.group</code> are also available.</p>
|
|
836
|
+
<p><code>drain</code> pauses selected consumers and waits until their own PEL reaches 0. Output contains <code>{drained, consumers: [...]}</code>.</p>
|
|
837
|
+
</script>
|
|
838
|
+
|
|
839
|
+
<script type="text/javascript">
|
|
840
|
+
// -----------------------------------------------------------------
|
|
841
|
+
// yroshcha-redis-stream-api — protected HTTP control plane
|
|
842
|
+
// -----------------------------------------------------------------
|
|
843
|
+
RED.nodes.registerType('yroshcha-redis-stream-api', {
|
|
844
|
+
category: REDIS_CATEGORY,
|
|
845
|
+
color: REDIS_COLOR,
|
|
846
|
+
icon: REDIS_ICON,
|
|
847
|
+
showLabel: false,
|
|
848
|
+
defaults: {
|
|
849
|
+
name: { value: '' },
|
|
850
|
+
enabled: { value: false },
|
|
851
|
+
requireToken: { value: false }
|
|
852
|
+
},
|
|
853
|
+
credentials: {
|
|
854
|
+
token: { type: 'password' }
|
|
855
|
+
},
|
|
856
|
+
inputs: 0,
|
|
857
|
+
outputs: 1,
|
|
858
|
+
paletteLabel: 'redis streams API',
|
|
859
|
+
label: function () {
|
|
860
|
+
return this.name || 'redis streams API';
|
|
861
|
+
},
|
|
862
|
+
onadd: function () { this.l = true; }
|
|
863
|
+
});
|
|
864
|
+
</script>
|
|
865
|
+
|
|
866
|
+
<script type="text/html" data-template-name="yroshcha-redis-stream-api">
|
|
867
|
+
<div class="form-row">
|
|
868
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
869
|
+
<input type="text" id="node-input-name" placeholder="Redis Streams control API">
|
|
870
|
+
</div>
|
|
871
|
+
<div class="form-row">
|
|
872
|
+
<label for="node-input-enabled" style="width:auto">
|
|
873
|
+
<input type="checkbox" id="node-input-enabled" style="width:auto; margin-right:8px">Enable HTTP API
|
|
874
|
+
</label>
|
|
875
|
+
</div>
|
|
876
|
+
<div class="form-row">
|
|
877
|
+
<label for="node-input-token"><i class="fa fa-key"></i> API token</label>
|
|
878
|
+
<input type="password" id="node-input-token" autocomplete="new-password">
|
|
879
|
+
</div>
|
|
880
|
+
<div class="form-row">
|
|
881
|
+
<label for="node-input-requireToken" style="width:auto" title="Require X-API-Key or Authorization: Bearer on every request">
|
|
882
|
+
<input type="checkbox" id="node-input-requireToken" style="width:auto; margin-right:8px">Require API token
|
|
883
|
+
</label>
|
|
884
|
+
</div>
|
|
885
|
+
<div class="form-tips" style="white-space:normal; overflow-wrap:anywhere; box-sizing:border-box;">
|
|
886
|
+
The API is disabled by default. After enabling, no token is required.
|
|
887
|
+
</div>
|
|
888
|
+
</script>
|
|
889
|
+
|
|
890
|
+
<script type="text/html" data-help-name="yroshcha-redis-stream-api">
|
|
891
|
+
<p>Self-contained HTTP control plane for all <code>redis xreadgroup</code> nodes in this Node-RED runtime. Use one node. The HTTP API is <b>disabled</b> by default; enable <b>Enable HTTP API</b> first.</p>
|
|
892
|
+
<p>After enabling, the API works without a token for a private network. If needed, enable <b>Require API token</b> and send it through <code>X-API-Key: <token></code> or <code>Authorization: Bearer <token></code>. If token protection is enabled but the token is empty, endpoints do not start.</p>
|
|
893
|
+
<p><code>GET /redis/streams/status</code> returns every local consumer state: <code>nodeId</code>, stream, group, PEL, consumer and paused.</p>
|
|
894
|
+
<p><code>POST /redis/streams/control</code> accepts JSON: <code>{"action":"pause"}</code> for all, or <code>{"action":"pause","nodeId":"..."}</code> for one node. <code>resume</code>, <code>status</code>, <code>drain</code>, <code>stream</code>/<code>group</code> filters and <code>drainTimeoutMs</code> are also available.</p>
|
|
895
|
+
<p>The node has no input; it only publishes the HTTP API. Its output mirrors HTTP results for debug or monitoring. Endpoints cover this runtime only; call each replica separately.</p>
|
|
896
|
+
</script>
|