node-red-contrib-influxdb3 1.0.5 → 1.0.6

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 CHANGED
@@ -118,6 +118,55 @@ msg.payload = {
118
118
  return msg;
119
119
  ```
120
120
 
121
+ #### 4. Array of Multiple Measurements
122
+
123
+ Send an array to write multiple measurements in a single message:
124
+
125
+ ```javascript
126
+ // Array of objects
127
+ msg.payload = [
128
+ {
129
+ measurement: "temperature",
130
+ fields: { value: 21.5 },
131
+ tags: { location: "room1" }
132
+ },
133
+ {
134
+ measurement: "temperature",
135
+ fields: { value: 19.8 },
136
+ tags: { location: "room2" }
137
+ },
138
+ {
139
+ measurement: "humidity",
140
+ fields: { value: 65 },
141
+ tags: { location: "room1" }
142
+ }
143
+ ];
144
+ return msg;
145
+ ```
146
+
147
+ ```javascript
148
+ // Array of line protocol strings
149
+ msg.payload = [
150
+ "temperature,location=room1 value=21.5",
151
+ "temperature,location=room2 value=19.8",
152
+ "humidity,location=room1 value=65"
153
+ ];
154
+ return msg;
155
+ ```
156
+
157
+ ```javascript
158
+ // Mixed array (objects and strings)
159
+ msg.payload = [
160
+ "temperature,location=room1 value=21.5",
161
+ {
162
+ measurement: "humidity",
163
+ fields: { value: 65 },
164
+ tags: { location: "room1" }
165
+ }
166
+ ];
167
+ return msg;
168
+ ```
169
+
121
170
  ### Data Types
122
171
 
123
172
  **Important:** By default, **all numbers are written as floats** to avoid schema conflicts in InfluxDB. This is because JavaScript doesn't distinguish between `1.0` and `1` (both equal `1`), which can cause issues when InfluxDB expects a float but receives an integer.
@@ -737,7 +737,20 @@ describe('buildLineProtocol – error cases', () => {
737
737
  expect(done.mock.calls[0][0].message).toContain('Line protocol string is empty');
738
738
  });
739
739
 
740
- test('invalid payload type (array) produces error', async () => {
740
+ test('empty array produces error', async () => {
741
+ const { writeNode } = createWriteNode();
742
+ const msg = {
743
+ payload: []
744
+ };
745
+ const send = jest.fn();
746
+ const done = jest.fn();
747
+ await writeNode._handlers.input(msg, send, done);
748
+
749
+ expect(done).toHaveBeenCalledWith(expect.any(Error));
750
+ expect(done.mock.calls[0][0].message).toContain('Payload array is empty');
751
+ });
752
+
753
+ test('array with invalid item type produces error', async () => {
741
754
  const { writeNode } = createWriteNode();
742
755
  const msg = {
743
756
  payload: [1, 2, 3]
@@ -747,7 +760,180 @@ describe('buildLineProtocol – error cases', () => {
747
760
  await writeNode._handlers.input(msg, send, done);
748
761
 
749
762
  expect(done).toHaveBeenCalledWith(expect.any(Error));
750
- expect(done.mock.calls[0][0].message).toContain('Invalid payload format');
763
+ expect(done.mock.calls[0][0].message).toContain('Array item 0 has invalid format');
764
+ });
765
+ });
766
+
767
+ describe('Array payload support', () => {
768
+ test('writes array of line protocol strings', async () => {
769
+ const { writeNode, influxModule } = createWriteNode();
770
+ const msg = {
771
+ payload: [
772
+ 'temperature,location=room1 value=21.5',
773
+ 'temperature,location=room2 value=19.8',
774
+ 'humidity,location=room1 value=65'
775
+ ]
776
+ };
777
+ const send = jest.fn();
778
+ const done = jest.fn();
779
+ await writeNode._handlers.input(msg, send, done);
780
+
781
+ const client = influxModule.__getLastClientInstance();
782
+ expect(client.write).toHaveBeenCalledWith(
783
+ 'temperature,location=room1 value=21.5\ntemperature,location=room2 value=19.8\nhumidity,location=room1 value=65',
784
+ 'metrics'
785
+ );
786
+ expect(send).toHaveBeenCalledWith(msg);
787
+ expect(done).toHaveBeenCalled();
788
+ });
789
+
790
+ test('writes array of object payloads', async () => {
791
+ const { writeNode, influxModule } = createWriteNode();
792
+ const msg = {
793
+ payload: [
794
+ {
795
+ measurement: 'temperature',
796
+ fields: { value: 21.5 },
797
+ tags: { location: 'room1' }
798
+ },
799
+ {
800
+ measurement: 'temperature',
801
+ fields: { value: 19.8 },
802
+ tags: { location: 'room2' }
803
+ },
804
+ {
805
+ measurement: 'humidity',
806
+ fields: { value: 65 },
807
+ tags: { location: 'room1' }
808
+ }
809
+ ]
810
+ };
811
+ const send = jest.fn();
812
+ const done = jest.fn();
813
+ await writeNode._handlers.input(msg, send, done);
814
+
815
+ const client = influxModule.__getLastClientInstance();
816
+ expect(client.write).toHaveBeenCalled();
817
+ expect(send).toHaveBeenCalledWith(msg);
818
+ expect(done).toHaveBeenCalled();
819
+ });
820
+
821
+ test('writes mixed array of objects and line protocol strings', async () => {
822
+ const { writeNode, influxModule } = createWriteNode();
823
+ const msg = {
824
+ payload: [
825
+ 'temperature,location=room1 value=21.5',
826
+ {
827
+ measurement: 'humidity',
828
+ fields: { value: 65 },
829
+ tags: { location: 'room1' }
830
+ },
831
+ 'pressure,location=room1 value=1013.25'
832
+ ]
833
+ };
834
+ const send = jest.fn();
835
+ const done = jest.fn();
836
+ await writeNode._handlers.input(msg, send, done);
837
+
838
+ const client = influxModule.__getLastClientInstance();
839
+ expect(client.write).toHaveBeenCalled();
840
+ expect(send).toHaveBeenCalledWith(msg);
841
+ expect(done).toHaveBeenCalled();
842
+ });
843
+
844
+ test('array objects can use msg.measurement as fallback', async () => {
845
+ const { writeNode, influxModule } = createWriteNode();
846
+ const msg = {
847
+ measurement: 'temperature',
848
+ payload: [
849
+ {
850
+ fields: { value: 21.5 },
851
+ tags: { location: 'room1' }
852
+ },
853
+ {
854
+ fields: { value: 19.8 },
855
+ tags: { location: 'room2' }
856
+ }
857
+ ]
858
+ };
859
+ const send = jest.fn();
860
+ const done = jest.fn();
861
+ await writeNode._handlers.input(msg, send, done);
862
+
863
+ const client = influxModule.__getLastClientInstance();
864
+ expect(client.write).toHaveBeenCalled();
865
+ expect(send).toHaveBeenCalledWith(msg);
866
+ expect(done).toHaveBeenCalled();
867
+ });
868
+
869
+ test('array item error includes item index', async () => {
870
+ const { RED, influxModule } = setup();
871
+ const ConfigCtor = RED._types['influxdb3-config'];
872
+ const WriteCtor = RED._types['influxdb3-write'];
873
+
874
+ const configNode = new ConfigCtor({
875
+ host: 'https://example.com',
876
+ database: 'metrics',
877
+ name: 'Test',
878
+ credentials: { token: 'token' }
879
+ });
880
+
881
+ // Create node without default measurement
882
+ const writeNode = new WriteCtor({
883
+ influxdb: configNode,
884
+ measurement: '', // No default measurement
885
+ database: ''
886
+ });
887
+
888
+ const msg = {
889
+ payload: [
890
+ 'temperature,location=room1 value=21.5',
891
+ {
892
+ // Missing measurement and no fallback
893
+ fields: { value: 19.8 }
894
+ }
895
+ ]
896
+ };
897
+ const send = jest.fn();
898
+ const done = jest.fn();
899
+ await writeNode._handlers.input(msg, send, done);
900
+
901
+ expect(done).toHaveBeenCalledWith(expect.any(Error));
902
+ expect(done.mock.calls[0][0].message).toContain('Array item 1');
903
+ });
904
+
905
+ test('array with empty string produces error', async () => {
906
+ const { writeNode } = createWriteNode();
907
+ const msg = {
908
+ payload: [
909
+ 'temperature,location=room1 value=21.5',
910
+ ' ',
911
+ 'humidity,location=room1 value=65'
912
+ ]
913
+ };
914
+ const send = jest.fn();
915
+ const done = jest.fn();
916
+ await writeNode._handlers.input(msg, send, done);
917
+
918
+ expect(done).toHaveBeenCalledWith(expect.any(Error));
919
+ expect(done.mock.calls[0][0].message).toContain('Array item 1 is an empty string');
920
+ });
921
+
922
+ test('array with invalid line protocol produces error with index', async () => {
923
+ const { writeNode } = createWriteNode();
924
+ const msg = {
925
+ payload: [
926
+ 'temperature,location=room1 value=21.5',
927
+ 'invalid line protocol no equals',
928
+ 'humidity,location=room1 value=65'
929
+ ]
930
+ };
931
+ const send = jest.fn();
932
+ const done = jest.fn();
933
+ await writeNode._handlers.input(msg, send, done);
934
+
935
+ expect(done).toHaveBeenCalledWith(expect.any(Error));
936
+ expect(done.mock.calls[0][0].message).toContain('Array item 1');
751
937
  });
752
938
  });
753
939
 
package/influxdb3.html CHANGED
@@ -117,11 +117,12 @@
117
117
 
118
118
  <h3>Inputs</h3>
119
119
  <dl class="message-properties">
120
- <dt>payload <span class="property-type">string | object</span></dt>
120
+ <dt>payload <span class="property-type">string | object | array</span></dt>
121
121
  <dd>The data to write. Can be either:
122
122
  <ul>
123
123
  <li>A string in line protocol format</li>
124
124
  <li>An object with <code>fields</code> (required) and optionally <code>tags</code> and <code>timestamp</code></li>
125
+ <li>An array of strings (line protocol) or objects (multiple measurements in one write)</li>
125
126
  </ul>
126
127
  </dd>
127
128
  <dt class="optional">measurement <span class="property-type">string</span></dt>
@@ -233,6 +234,34 @@ return msg;</pre>
233
234
  <pre>msg.payload = "weather,location=garden temperature=18.5,humidity=75";
234
235
  return msg;</pre>
235
236
 
237
+ <h4>Example 4: Array of multiple measurements</h4>
238
+ <pre>msg.payload = [
239
+ {
240
+ "measurement": "temperature",
241
+ "fields": { "value": 21.5 },
242
+ "tags": { "location": "room1" }
243
+ },
244
+ {
245
+ "measurement": "temperature",
246
+ "fields": { "value": 19.8 },
247
+ "tags": { "location": "room2" }
248
+ },
249
+ {
250
+ "measurement": "humidity",
251
+ "fields": { "value": 65 },
252
+ "tags": { "location": "room1" }
253
+ }
254
+ ];
255
+ return msg;</pre>
256
+
257
+ <h4>Example 5: Array of line protocol strings</h4>
258
+ <pre>msg.payload = [
259
+ "temperature,location=room1 value=21.5",
260
+ "temperature,location=room2 value=19.8",
261
+ "humidity,location=room1 value=65"
262
+ ];
263
+ return msg;</pre>
264
+
236
265
  <h3>References</h3>
237
266
  <ul>
238
267
  <li><a href="https://github.com/InfluxCommunity/influxdb3-js">InfluxDB v3 JavaScript Client</a></li>
package/influxdb3.js CHANGED
@@ -407,7 +407,49 @@ module.exports = function(RED) {
407
407
  if (validationError) {
408
408
  throw new Error(validationError);
409
409
  }
410
- } else if (msg.payload && typeof msg.payload === 'object' && !Array.isArray(msg.payload)) {
410
+ } else if (Array.isArray(msg.payload)) {
411
+ // Handle array of measurements
412
+ if (msg.payload.length === 0) {
413
+ throw new Error('Payload array is empty');
414
+ }
415
+
416
+ const lineProtocols = [];
417
+ for (let i = 0; i < msg.payload.length; i++) {
418
+ const item = msg.payload[i];
419
+
420
+ if (typeof item === 'string') {
421
+ // String line protocol
422
+ const lp = item.trim();
423
+ if (!lp) {
424
+ throw new Error(`Array item ${i} is an empty string`);
425
+ }
426
+ const validationError = validateLineProtocol(lp);
427
+ if (validationError) {
428
+ throw new Error(`Array item ${i}: ${validationError}`);
429
+ }
430
+ lineProtocols.push(lp);
431
+ } else if (item && typeof item === 'object' && !Array.isArray(item)) {
432
+ // Object payload - build line protocol
433
+ const tempMsg = {
434
+ ...msg,
435
+ payload: item,
436
+ measurement: item.measurement || msg.measurement || node.measurement
437
+ };
438
+ const result = buildLineProtocol(tempMsg);
439
+ if (result.error) {
440
+ throw new Error(`Array item ${i}: ${result.error}`);
441
+ }
442
+ lineProtocols.push(result.lineProtocol);
443
+ } else {
444
+ throw new Error(
445
+ `Array item ${i} has invalid format. Expected string (line protocol) or object with fields. ` +
446
+ `Received: ${typeof item}`
447
+ );
448
+ }
449
+ }
450
+
451
+ lineProtocol = lineProtocols.join('\n');
452
+ } else if (msg.payload && typeof msg.payload === 'object') {
411
453
  const result = buildLineProtocol(msg);
412
454
  if (result.error) {
413
455
  throw new Error(result.error);
@@ -419,11 +461,9 @@ module.exports = function(RED) {
419
461
  ? 'null'
420
462
  : msg.payload === undefined
421
463
  ? 'undefined'
422
- : Array.isArray(msg.payload)
423
- ? `Array (length ${msg.payload.length}): ${safeStringify(msg.payload)}`
424
- : `${actualType}${msg.payload && msg.payload.constructor ? ` [${msg.payload.constructor.name}]` : ''}: ${safeStringify(msg.payload)}`;
464
+ : `${actualType}${msg.payload && msg.payload.constructor ? ` [${msg.payload.constructor.name}]` : ''}: ${safeStringify(msg.payload)}`;
425
465
  throw new Error(
426
- `Invalid payload format. Expected string (line protocol) or object with fields. ` +
466
+ `Invalid payload format. Expected string (line protocol), object with fields, or array of objects/strings. ` +
427
467
  `Received: ${detail}`
428
468
  );
429
469
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-influxdb3",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Node-RED nodes for InfluxDB v3 integration",
5
5
  "main": "influxdb3.js",
6
6
  "scripts": {
@@ -33,9 +33,9 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@influxdata/influxdb3-client": "^2.0.0"
36
+ "@influxdata/influxdb3-client": "^2.1.0"
37
37
  },
38
38
  "devDependencies": {
39
- "jest": "^30.2.0"
39
+ "jest": "^30.3.0"
40
40
  }
41
41
  }