node-red-contrib-questdb 0.6.22 → 0.6.24

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
 
@@ -485,19 +528,7 @@ module.exports = function (RED) {
485
528
  } else if (typeof value === 'boolean') {
486
529
  connection.sender.booleanColumn(key, value);
487
530
  } else if (typeof value === 'string') {
488
- // Check if it's an ISO date string
489
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
490
- const dateValue = new Date(value);
491
- if (!isNaN(dateValue.getTime())) {
492
- // Convert to microseconds for QuestDB
493
- const microSeconds = BigInt(dateValue.getTime()) * 1000n;
494
- connection.sender.timestampColumn(key, microSeconds);
495
- } else {
496
- connection.sender.stringColumn(key, value);
497
- }
498
- } else {
499
- connection.sender.stringColumn(key, value);
500
- }
531
+ connection.sender.stringColumn(key, value);
501
532
  } else if (value instanceof Date) {
502
533
  // Convert to microseconds for QuestDB
503
534
  const microSeconds = BigInt(value.getTime()) * 1000n;
@@ -540,6 +571,25 @@ module.exports = function (RED) {
540
571
  }
541
572
  }
542
573
 
574
+ // Track rows and flush at threshold
575
+ connection.rowCount++;
576
+ if (connection.flushRows && connection.rowCount >= connection.flushRows) {
577
+ try {
578
+ await connection.sender.flush();
579
+ connection.rowCount = 0;
580
+ } catch (flushErr) {
581
+ const flushErrMsg = formatError(flushErr, { operation: 'flush', table: tableName });
582
+ RED.log.error(`[QuestDB] Flush failed: ${flushErrMsg}`);
583
+ if (isDisconnectionError(flushErr)) {
584
+ connection.updateState(false, flushErr.message);
585
+ connection.sender = null;
586
+ connection.connect();
587
+ }
588
+ sendError(flushErrMsg);
589
+ return;
590
+ }
591
+ }
592
+
543
593
  node.status({ fill: 'green', shape: 'dot', text: `sent: ${tableName}` });
544
594
  msg.payload = { success: true, table: tableName };
545
595
  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.24",
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"