node-red-contrib-redis-variable 1.5.1 → 1.5.2
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/package.json +1 -1
- package/redis-variable-config.js +15 -3
- package/redis-variable.js +51 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-redis-variable",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "A comprehensive Node-RED node for Redis operations with universal payload-based configuration, automatic JSON handling, SSL/TLS support, and advanced pattern matching with pagination",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
package/redis-variable-config.js
CHANGED
|
@@ -385,9 +385,21 @@ module.exports = function (RED) {
|
|
|
385
385
|
// Create Redis client
|
|
386
386
|
let client;
|
|
387
387
|
if (this.cluster) {
|
|
388
|
-
//
|
|
389
|
-
const clusterNodes =
|
|
390
|
-
|
|
388
|
+
// ioredis Cluster: first arg = startup nodes [{ host, port }], second = options with redisOptions
|
|
389
|
+
const clusterNodes = [{ host: options.host, port: parseInt(options.port, 10) }];
|
|
390
|
+
const clusterOptions = {
|
|
391
|
+
enableReadyCheck: false,
|
|
392
|
+
lazyConnect: true,
|
|
393
|
+
maxRetriesPerRequest: 3,
|
|
394
|
+
redisOptions: {
|
|
395
|
+
username: options.username || undefined,
|
|
396
|
+
password: options.password || undefined,
|
|
397
|
+
db: 0,
|
|
398
|
+
connectTimeout: connectionOptions.connectTimeout || 5000,
|
|
399
|
+
tls: connectionOptions.tls || undefined,
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
client = new Redis.Cluster(clusterNodes, clusterOptions);
|
|
391
403
|
} else {
|
|
392
404
|
client = new Redis(connectionOptions);
|
|
393
405
|
}
|
package/redis-variable.js
CHANGED
|
@@ -309,6 +309,21 @@ module.exports = function (RED) {
|
|
|
309
309
|
send = send || function() { node.send.apply(node, arguments) };
|
|
310
310
|
done = done || function(err) { if(err) node.error(err, msg); };
|
|
311
311
|
|
|
312
|
+
// Ensure we only send and call done once per message
|
|
313
|
+
let completed = false;
|
|
314
|
+
const finish = (err) => {
|
|
315
|
+
if (completed) return;
|
|
316
|
+
completed = true;
|
|
317
|
+
if (err) node.error(err.message || err, msg);
|
|
318
|
+
done(err);
|
|
319
|
+
};
|
|
320
|
+
const sendAndDone = (m) => {
|
|
321
|
+
if (completed) return;
|
|
322
|
+
completed = true;
|
|
323
|
+
send(m);
|
|
324
|
+
done();
|
|
325
|
+
};
|
|
326
|
+
|
|
312
327
|
if (!running) {
|
|
313
328
|
running = true;
|
|
314
329
|
}
|
|
@@ -317,8 +332,7 @@ module.exports = function (RED) {
|
|
|
317
332
|
if (!redisConfig) {
|
|
318
333
|
node.error("Redis configuration not found", msg);
|
|
319
334
|
msg.payload = { error: "Redis configuration not found" };
|
|
320
|
-
|
|
321
|
-
done();
|
|
335
|
+
sendAndDone(msg);
|
|
322
336
|
return;
|
|
323
337
|
}
|
|
324
338
|
|
|
@@ -337,8 +351,7 @@ module.exports = function (RED) {
|
|
|
337
351
|
} catch (configError) {
|
|
338
352
|
node.error(configError.message, msg);
|
|
339
353
|
msg.payload = { error: configError.message };
|
|
340
|
-
|
|
341
|
-
done();
|
|
354
|
+
sendAndDone(msg);
|
|
342
355
|
return;
|
|
343
356
|
}
|
|
344
357
|
|
|
@@ -350,43 +363,50 @@ module.exports = function (RED) {
|
|
|
350
363
|
node.warn("Redis configuration not available. Operation skipped.");
|
|
351
364
|
node.status({ fill: "yellow", shape: "ring", text: "no config" });
|
|
352
365
|
msg.payload = { error: "Redis configuration not available" };
|
|
353
|
-
|
|
354
|
-
done();
|
|
366
|
+
sendAndDone(msg);
|
|
355
367
|
return;
|
|
356
368
|
}
|
|
357
369
|
}
|
|
358
370
|
|
|
359
|
-
//
|
|
371
|
+
// Ensure client is connected before running commands
|
|
360
372
|
if (client.status !== 'ready' && client.status !== 'connect') {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
373
|
+
const CONNECT_WAIT_MS = 15000;
|
|
374
|
+
const waitForReady = () => new Promise((resolve, reject) => {
|
|
375
|
+
const timeout = setTimeout(() => reject(new Error('Connection timeout')), CONNECT_WAIT_MS);
|
|
376
|
+
const onReady = () => { clearTimeout(timeout); cleanup(); resolve(); };
|
|
377
|
+
const onError = (e) => { clearTimeout(timeout); cleanup(); reject(e); };
|
|
378
|
+
const cleanup = () => {
|
|
379
|
+
client.removeListener('ready', onReady);
|
|
380
|
+
client.removeListener('connect', onReady);
|
|
381
|
+
client.removeListener('error', onError);
|
|
382
|
+
};
|
|
383
|
+
client.once('ready', onReady);
|
|
384
|
+
client.once('connect', onReady);
|
|
385
|
+
client.once('error', onError);
|
|
386
|
+
});
|
|
387
|
+
try {
|
|
388
|
+
if (client.status === 'connecting') {
|
|
389
|
+
// Already connecting: wait for ready, do not call connect() again
|
|
390
|
+
await waitForReady();
|
|
391
|
+
} else if (typeof client.connect === 'function') {
|
|
392
|
+
await client.connect();
|
|
393
|
+
}
|
|
394
|
+
} catch (connectErr) {
|
|
395
|
+
const msg = connectErr.message || String(connectErr);
|
|
396
|
+
if (msg.includes('already connecting') || msg.includes('already connected')) {
|
|
397
|
+
// Race: another message triggered connect; wait for ready
|
|
398
|
+
await waitForReady();
|
|
399
|
+
} else {
|
|
369
400
|
redisConfig.forceDisconnect(node.id);
|
|
370
401
|
client = null;
|
|
371
|
-
|
|
372
|
-
if (!client) {
|
|
373
|
-
node.warn("Redis configuration not available during reconnection. Operation skipped.");
|
|
374
|
-
node.status({ fill: "yellow", shape: "ring", text: "no config" });
|
|
375
|
-
msg.payload = { error: "Redis configuration not available" };
|
|
376
|
-
send(msg);
|
|
377
|
-
done();
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
} catch (reconnectError) {
|
|
381
|
-
throw new Error(`Failed to reconnect: ${reconnectError.message}`);
|
|
402
|
+
throw new Error(`Failed to connect: ${msg}`);
|
|
382
403
|
}
|
|
383
404
|
}
|
|
384
405
|
}
|
|
385
406
|
} catch (err) {
|
|
386
407
|
node.error(err.message, msg);
|
|
387
408
|
msg.payload = { error: err.message };
|
|
388
|
-
|
|
389
|
-
done();
|
|
409
|
+
sendAndDone(msg);
|
|
390
410
|
return;
|
|
391
411
|
}
|
|
392
412
|
|
|
@@ -770,22 +790,19 @@ module.exports = function (RED) {
|
|
|
770
790
|
retryable: false
|
|
771
791
|
};
|
|
772
792
|
}
|
|
773
|
-
|
|
774
|
-
done();
|
|
793
|
+
sendAndDone(msg);
|
|
775
794
|
return;
|
|
776
795
|
}
|
|
777
796
|
|
|
778
797
|
// Update node status on success
|
|
779
798
|
node.status({ fill: "green", shape: "dot", text: node.operation });
|
|
780
|
-
|
|
781
|
-
done();
|
|
799
|
+
sendAndDone(msg);
|
|
782
800
|
|
|
783
801
|
} catch (error) {
|
|
784
802
|
// Handle general errors (validation, etc.)
|
|
785
803
|
node.error(error.message, msg);
|
|
786
804
|
msg.payload = { error: error.message };
|
|
787
|
-
|
|
788
|
-
done();
|
|
805
|
+
sendAndDone(msg);
|
|
789
806
|
}
|
|
790
807
|
});
|
|
791
808
|
}
|