node-red-contrib-questdb 0.6.22 → 0.6.23

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.
@@ -110,14 +110,30 @@ module.exports = function (RED) {
110
110
  }
111
111
 
112
112
  node.on('input', async function (msg) {
113
- // Connection checks
113
+ // Connection checks (rate-limit error logging to once per 5s)
114
114
  if (!connection.connected && !connection.connecting) {
115
115
  connection.connect();
116
- sendError(msg, 'Not connected to QuestDB, reconnecting...');
116
+ var now = Date.now();
117
+ if (now - connection.lastNotConnectedLog > 5000) {
118
+ connection.lastNotConnectedLog = now;
119
+ sendError(msg, 'Not connected to QuestDB, reconnecting...');
120
+ } else {
121
+ var errMsg = RED.util.cloneMessage(msg);
122
+ errMsg.payload = { success: false, error: 'Not connected to QuestDB, reconnecting...' };
123
+ node.send([null, errMsg]);
124
+ }
117
125
  return;
118
126
  }
119
127
  if (!connection.connected || !connection.sender) {
120
- sendError(msg, 'Not connected to QuestDB');
128
+ var now2 = Date.now();
129
+ if (now2 - connection.lastNotConnectedLog > 5000) {
130
+ connection.lastNotConnectedLog = now2;
131
+ sendError(msg, 'Not connected to QuestDB');
132
+ } else {
133
+ var errMsg2 = RED.util.cloneMessage(msg);
134
+ errMsg2.payload = { success: false, error: 'Not connected to QuestDB' };
135
+ node.send([null, errMsg2]);
136
+ }
121
137
  return;
122
138
  }
123
139
  if (
@@ -213,6 +229,25 @@ module.exports = function (RED) {
213
229
  connection.sender.at(timestampMicros);
214
230
  }
215
231
 
232
+ // Track rows and flush at threshold
233
+ connection.rowCount++;
234
+ if (connection.flushRows && connection.rowCount >= connection.flushRows) {
235
+ try {
236
+ await connection.sender.flush();
237
+ connection.rowCount = 0;
238
+ } catch (flushErr) {
239
+ var flushErrMsg = formatError(flushErr, { operation: 'flush', table: tableName });
240
+ RED.log.error('[QuestDB] Flush failed: ' + flushErrMsg);
241
+ if (isDisconnectionError(flushErr)) {
242
+ connection.updateState(false, flushErr.message);
243
+ connection.sender = null;
244
+ connection.connect();
245
+ }
246
+ sendError(msg, flushErrMsg);
247
+ return;
248
+ }
249
+ }
250
+
216
251
  var label = type + ': ' + String(castedValue);
217
252
  if (label.length > 25) label = label.substring(0, 25) + '...';
218
253
  node.status({ fill: 'green', shape: 'dot', text: tableName + ' ' + label });
package/nodes/questdb.js CHANGED
@@ -31,17 +31,9 @@ module.exports = function (RED) {
31
31
  }
32
32
  }
33
33
 
34
- // Auto-flush options
35
- if (configNode.autoFlush === false) {
36
- connStr += `auto_flush=off;`;
37
- } else {
38
- if (configNode.autoFlushRows) {
39
- connStr += `auto_flush_rows=${configNode.autoFlushRows};`;
40
- }
41
- if (configNode.autoFlushInterval) {
42
- connStr += `auto_flush_interval=${configNode.autoFlushInterval};`;
43
- }
44
- }
34
+ // Always disable sender's built-in auto-flush to prevent uncaught exceptions.
35
+ // We run our own flush timer with error handling in the connection state.
36
+ connStr += `auto_flush=off;`;
45
37
 
46
38
  // Buffer options
47
39
  if (configNode.initBufSize) {
@@ -110,8 +102,11 @@ module.exports = function (RED) {
110
102
  connecting: false,
111
103
  users: 0,
112
104
  reconnectTimer: null,
105
+ flushTimer: null,
106
+ rowCount: 0,
113
107
  emitter: new EventEmitter(),
114
108
  lastError: null,
109
+ lastNotConnectedLog: 0,
115
110
 
116
111
  // Method to update state and emit event
117
112
  updateState: function (newStatus, error = null) {
@@ -171,6 +166,32 @@ module.exports = function (RED) {
171
166
 
172
167
  connectionState.updateState(true);
173
168
 
169
+ // Start managed flush timer (replaces sender's built-in auto-flush)
170
+ if (connectionState.flushTimer) {
171
+ clearInterval(connectionState.flushTimer);
172
+ }
173
+ const flushInterval = configNode.autoFlushInterval || 1000;
174
+ connectionState.flushTimer = setInterval(async () => {
175
+ if (!connectionState.connected || !connectionState.sender) return;
176
+ if (connectionState.rowCount === 0) return;
177
+ try {
178
+ await connectionState.sender.flush();
179
+ connectionState.rowCount = 0;
180
+ } catch (flushErr) {
181
+ const flushErrMsg = formatError(flushErr, { operation: 'auto-flush' });
182
+ RED.log.error(`[QuestDB] Flush failed: ${flushErrMsg}`);
183
+ connectionState.emitter.emit('flushError', flushErr);
184
+ if (isDisconnectionError(flushErr)) {
185
+ connectionState.updateState(false, flushErr.message);
186
+ connectionState.sender = null;
187
+ connectionState.connect();
188
+ }
189
+ }
190
+ }, flushInterval);
191
+
192
+ // Also flush when row count threshold is reached
193
+ connectionState.flushRows = configNode.autoFlushRows || 500;
194
+
174
195
  RED.log.info(
175
196
  `[QuestDB] Connected to ${configNode.protocol}://${configNode.host}:${configNode.port}`
176
197
  );
@@ -196,6 +217,11 @@ module.exports = function (RED) {
196
217
  };
197
218
 
198
219
  connectionState.disconnect = async function () {
220
+ if (connectionState.flushTimer) {
221
+ clearInterval(connectionState.flushTimer);
222
+ connectionState.flushTimer = null;
223
+ }
224
+
199
225
  if (connectionState.reconnectTimer) {
200
226
  clearTimeout(connectionState.reconnectTimer);
201
227
  connectionState.reconnectTimer = null;
@@ -310,15 +336,32 @@ module.exports = function (RED) {
310
336
  node.send([null, errMsg]);
311
337
  }
312
338
 
313
- // Auto-reconnect if disconnected
339
+ // Auto-reconnect if disconnected (rate-limit error logging to once per 5s)
314
340
  if (!connection.connected && !connection.connecting) {
315
341
  connection.connect();
316
- sendError('Not connected to QuestDB, reconnecting...');
342
+ var now = Date.now();
343
+ if (now - connection.lastNotConnectedLog > 5000) {
344
+ connection.lastNotConnectedLog = now;
345
+ sendError('Not connected to QuestDB, reconnecting...');
346
+ } else {
347
+ // Still send to error output but don't log
348
+ var errMsg = RED.util.cloneMessage(msg);
349
+ errMsg.payload = { success: false, error: 'Not connected to QuestDB, reconnecting...' };
350
+ node.send([null, errMsg]);
351
+ }
317
352
  return;
318
353
  }
319
354
 
320
355
  if (!connection.connected || !connection.sender) {
321
- sendError('Not connected to QuestDB');
356
+ var now2 = Date.now();
357
+ if (now2 - connection.lastNotConnectedLog > 5000) {
358
+ connection.lastNotConnectedLog = now2;
359
+ sendError('Not connected to QuestDB');
360
+ } else {
361
+ var errMsg2 = RED.util.cloneMessage(msg);
362
+ errMsg2.payload = { success: false, error: 'Not connected to QuestDB' };
363
+ node.send([null, errMsg2]);
364
+ }
322
365
  return;
323
366
  }
324
367
 
@@ -540,6 +583,25 @@ module.exports = function (RED) {
540
583
  }
541
584
  }
542
585
 
586
+ // Track rows and flush at threshold
587
+ connection.rowCount++;
588
+ if (connection.flushRows && connection.rowCount >= connection.flushRows) {
589
+ try {
590
+ await connection.sender.flush();
591
+ connection.rowCount = 0;
592
+ } catch (flushErr) {
593
+ const flushErrMsg = formatError(flushErr, { operation: 'flush', table: tableName });
594
+ RED.log.error(`[QuestDB] Flush failed: ${flushErrMsg}`);
595
+ if (isDisconnectionError(flushErr)) {
596
+ connection.updateState(false, flushErr.message);
597
+ connection.sender = null;
598
+ connection.connect();
599
+ }
600
+ sendError(flushErrMsg);
601
+ return;
602
+ }
603
+ }
604
+
543
605
  node.status({ fill: 'green', shape: 'dot', text: `sent: ${tableName}` });
544
606
  msg.payload = { success: true, table: tableName };
545
607
  node.send([msg, null]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-questdb",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
4
4
  "description": "Node-RED nodes for writing high-performance time-series data to QuestDB using Influx Line Protocol (ILP). Supports IoT, industrial monitoring, smart buildings, fleet telematics, healthcare, agriculture, and more.",
5
5
  "author": {
6
6
  "name": "Holger Amort"