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 +49 -0
- package/__tests__/influxdb3.test.js +188 -2
- package/influxdb3.html +30 -1
- package/influxdb3.js +45 -5
- package/package.json +3 -3
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('
|
|
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('
|
|
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 (
|
|
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
|
-
:
|
|
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)
|
|
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.
|
|
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.
|
|
36
|
+
"@influxdata/influxdb3-client": "^2.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"jest": "^30.
|
|
39
|
+
"jest": "^30.3.0"
|
|
40
40
|
}
|
|
41
41
|
}
|