iotagent-node-lib 4.10.0 → 4.12.0
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/CHANGES_NEXT_RELEASE +0 -1
- package/Changelog +13 -0
- package/doc/admin.md +10 -25
- package/doc/api.md +62 -55
- package/doc/deprecated.md +33 -14
- package/doc/devel/northboundinteractions.md +35 -23
- package/lib/commonConfig.js +13 -0
- package/lib/jexlTranformsMap.js +15 -0
- package/lib/model/dbConn.js +57 -58
- package/lib/services/devices/registrationUtils.js +1 -1
- package/lib/services/ngsi/ngsiService.js +15 -1
- package/lib/services/northBound/contextServer-NGSI-v2.js +4 -2
- package/package.json +1 -1
- package/test/functional/testCases.js +73 -0
- package/test/unit/expressions/jexlExpression-test.js +30 -0
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -1
- package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json +1 -1
- package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json +1 -1
- package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json +1 -1
- package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json +1 -1
package/CHANGES_NEXT_RELEASE
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/Changelog
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
4.12.0 (Jan 28th, 2026)
|
|
2
|
+
|
|
3
|
+
- Fix: set default value for defaultTransport to allow set polling and transport for autoprovisioned devices when no IOTA_DEFAULT_TRANSPORT en var is used (#1751)
|
|
4
|
+
- Fix: allow match auto-provisioned devices and groups when the same type is used by several groups (which always uses different apikeys) (#1713)
|
|
5
|
+
|
|
6
|
+
4.11.0 (Oct 23th, 2025)
|
|
7
|
+
|
|
8
|
+
- Add: IOTA_MONGO_URI env var
|
|
9
|
+
- Add: locale string number jexl function (#1737)
|
|
10
|
+
- Deprecated: IOTA_MONGO_ different from IOTA_MONGO_URI (use IOTA_MONGO_URI instead)
|
|
11
|
+
- Fix: broken notification command mode (it was not working without providing targetEntityId and targetEntityType in the notification, which belongs to another command mode: advancedNotification)
|
|
12
|
+
- Fix: use normalized in attrsFormat for commands subscriptions (#1743)
|
|
13
|
+
|
|
1
14
|
4.10.0 (Oct 6th, 2025)
|
|
2
15
|
|
|
3
16
|
- Add: command modes (new field cmdMode): legacy, notification and advancedNotification (#1732)
|
package/doc/admin.md
CHANGED
|
@@ -248,15 +248,8 @@ the `mongob` section (as described bellow). E.g.:
|
|
|
248
248
|
|
|
249
249
|
#### `mongodb`
|
|
250
250
|
|
|
251
|
-
It configures the MongoDB driver for those repositories with 'mongodb' type
|
|
252
|
-
|
|
253
|
-
`replicaSet` should contain the Replica Set name. If the database requires authentication, username (`user`), password
|
|
254
|
-
(`password`) and authSource (`authSource`) can be set. If the database requires TLS/SSL connection but any validation of
|
|
255
|
-
the certificate chain is not mandatory, all you need is to set the ssl (`ssl`) option as `true` to connect the database.
|
|
256
|
-
If you need to add more complex option(s) such as `retryWrites=true` or `w=majority` when connection database, extraArgs
|
|
257
|
-
(`extraArgs`) can be used to perform it. For The MongoBD driver will retry the connection at startup time `retries`
|
|
258
|
-
times, waiting `retryTime` seconds between attempts, if those attributes are present (default values are 5 and 5
|
|
259
|
-
respectively). E.g.:
|
|
251
|
+
It configures the MongoDB driver for those repositories with 'mongodb' type, using the `uri` parameter (which format is
|
|
252
|
+
available in [this reference](http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html)).
|
|
260
253
|
|
|
261
254
|
```javascript
|
|
262
255
|
{
|
|
@@ -454,12 +447,14 @@ This flag is overwritten by `useCBflowControl` flag in group or device. This fla
|
|
|
454
447
|
|
|
455
448
|
#### `cmdMode`
|
|
456
449
|
|
|
457
|
-
Set command mode for the IoTAgent instance (it can be overriden by the `cmdMode` at group or device level). Possible
|
|
450
|
+
Set command mode for the IoTAgent instance (it can be overriden by the `cmdMode` at group or device level). Possible
|
|
451
|
+
values are:
|
|
458
452
|
|
|
459
|
-
|
|
460
|
-
mechanims.
|
|
461
|
-
|
|
462
|
-
|
|
453
|
+
- `legacy` (used as default if this setting is not defined): IoTAgent commands will use Context Broker registers
|
|
454
|
+
mechanims.
|
|
455
|
+
- `notification`: IoTAgent commands will use subscriptions to be notified for Context Broker commands.
|
|
456
|
+
- `advancedNotification`: IoTAgent commands will use subscriptions to be notified for Context Broker commands (but in
|
|
457
|
+
a different way as in `notification` mode)
|
|
463
458
|
|
|
464
459
|
Have a look to [this document](devel/northboundinteractions.md) for more detail on how this modes work.
|
|
465
460
|
|
|
@@ -505,17 +500,7 @@ overrides.
|
|
|
505
500
|
| IOTA_IOTAM_AGENTPATH | `iotManager.agentPath` |
|
|
506
501
|
| IOTA_IOTAM_PROTOCOL | `iotManager.protocol` |
|
|
507
502
|
| IOTA_IOTAM_DESCRIPTION | `iotManager.description` |
|
|
508
|
-
|
|
|
509
|
-
| IOTA_MONGO_PORT | `mongodb.port` |
|
|
510
|
-
| IOTA_MONGO_DB | `mongodb.db` |
|
|
511
|
-
| IOTA_MONGO_REPLICASET | `mongodb.replicaSet` |
|
|
512
|
-
| IOTA_MONGO_USER | `mongodb.user` |
|
|
513
|
-
| IOTA_MONGO_PASSWORD | `mongodb.password` |
|
|
514
|
-
| IOTA_MONGO_AUTH_SOURCE | `mongodb.authSource` |
|
|
515
|
-
| IOTA_MONGO_RETRIES | `mongodb.retries` |
|
|
516
|
-
| IOTA_MONGO_RETRY_TIME | `mongodb.retryTime` |
|
|
517
|
-
| IOTA_MONGO_SSL | `mongodb.ssl` |
|
|
518
|
-
| IOTA_MONGO_EXTRAARGS | `mongodb.extraArgs` |
|
|
503
|
+
| IOTA_MONGO_URI | `mongodb.uri` |
|
|
519
504
|
| IOTA_POLLING_EXPIRATION | `pollingExpiration` |
|
|
520
505
|
| IOTA_POLLING_DAEMON_FREQ | `pollingDaemonFrequency` |
|
|
521
506
|
| IOTA_MULTI_CORE | `multiCore` |
|
package/doc/api.md
CHANGED
|
@@ -279,12 +279,16 @@ IOTAgent, you can create it in advance using the Context Broker
|
|
|
279
279
|
|
|
280
280
|
## Entity creation when `cmdMode` is `notification`
|
|
281
281
|
|
|
282
|
-
Even when an entity should not be created (see [above section](#device-autoprovision-and-entity-creation)), when the
|
|
282
|
+
Even when an entity should not be created (see [above section](#device-autoprovision-and-entity-creation)), when the
|
|
283
|
+
device uses `cmdMode` set to `notification` an entity is created. In particular:
|
|
283
284
|
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
- An entity is created at device provision time
|
|
286
|
+
- That entity is created with an attribute corresponding to each commands. The value of that attribute at creation
|
|
287
|
+
time is `null` (specifying in some way that the command has not been triggered yet). Note that if the attribute
|
|
288
|
+
doesn't have any command, the entity is not created (even if `cmdMode` is `notification`).
|
|
286
289
|
|
|
287
|
-
For instance, if device has commands `ping` and `switch` the entity corresponding to that device will be created at
|
|
290
|
+
For instance, if device has commands `ping` and `switch` the entity corresponding to that device will be created at
|
|
291
|
+
provising time with the following attributes:
|
|
288
292
|
|
|
289
293
|
```
|
|
290
294
|
...
|
|
@@ -300,7 +304,8 @@ For instance, if device has commands `ping` and `switch` the entity correspondin
|
|
|
300
304
|
}
|
|
301
305
|
```
|
|
302
306
|
|
|
303
|
-
**NOTE:** `command` is the usual type for attributes associated to commands, but the one used at provisioning time
|
|
307
|
+
**NOTE:** `command` is the usual type for attributes associated to commands, but the one used at provisioning time
|
|
308
|
+
(`"command": [ ...]` field) will be actually used.
|
|
304
309
|
|
|
305
310
|
## Entity Name expression support
|
|
306
311
|
|
|
@@ -708,52 +713,53 @@ to incorporate new transformations from the IoT Agent configuration file in a fa
|
|
|
708
713
|
|
|
709
714
|
Current common transformation set:
|
|
710
715
|
|
|
711
|
-
| JEXL Transformation
|
|
712
|
-
|
|
|
713
|
-
| jsonparse: (str)
|
|
714
|
-
| jsonstringify: (obj)
|
|
715
|
-
| indexOf: (val, char)
|
|
716
|
-
| length: (val)
|
|
717
|
-
| trim: (val)
|
|
718
|
-
| substr: (val, int1, int2)
|
|
719
|
-
| addreduce: (arr)
|
|
720
|
-
| lengtharray: (arr)
|
|
721
|
-
| typeof: (val)
|
|
722
|
-
| isarray: (arr)
|
|
723
|
-
| isnan: (val)
|
|
724
|
-
| parseint: (val)
|
|
725
|
-
| parsefloat: (val)
|
|
726
|
-
| toisodate: (val)
|
|
727
|
-
| timeoffset:(isostr)
|
|
728
|
-
| tostring: (val)
|
|
729
|
-
| urlencode: (val)
|
|
730
|
-
| urldecode: (val)
|
|
731
|
-
| replacestr: (str, from, to)
|
|
732
|
-
| replaceregexp: (str, reg, to)
|
|
733
|
-
| replaceallstr: (str, from, to)
|
|
734
|
-
| replaceallregexp: (str, reg, to)
|
|
735
|
-
| split: (str, ch)
|
|
736
|
-
| joinarrtostr: (arr, ch)
|
|
737
|
-
| concatarr: (arr, arr2)
|
|
738
|
-
| mapper: (val, values, choices)
|
|
739
|
-
| thmapper: (val, values, choices)
|
|
740
|
-
| bitwisemask: (i,mask,op,shf)
|
|
741
|
-
| slice: (arr, init, end)
|
|
742
|
-
| addset: (arr, x)
|
|
743
|
-
| removeset: (arr, x)
|
|
744
|
-
| touppercase: (val)
|
|
745
|
-
| tolowercase: (val)
|
|
746
|
-
| round: (val)
|
|
747
|
-
| floor: (val)
|
|
748
|
-
| ceil: (val)
|
|
749
|
-
| tofixed: (val, decimals)
|
|
750
|
-
| gettime: (d)
|
|
751
|
-
| toisostring: (d)
|
|
752
|
-
| localestring: (d, timezone, options)
|
|
753
|
-
|
|
|
754
|
-
|
|
|
755
|
-
|
|
|
756
|
-
|
|
|
716
|
+
| JEXL Transformation | Equivalent JavaScript Function |
|
|
717
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
718
|
+
| jsonparse: (str) | `JSON.parse(str);` |
|
|
719
|
+
| jsonstringify: (obj) | `JSON.stringify(obj);` |
|
|
720
|
+
| indexOf: (val, char) | `String(val).indexOf(char);` |
|
|
721
|
+
| length: (val) | `String(val).length;` |
|
|
722
|
+
| trim: (val) | `String(val).trim();` |
|
|
723
|
+
| substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
|
|
724
|
+
| addreduce: (arr) | <code>arr.reduce((i, v) | i + v));</code> |
|
|
725
|
+
| lengtharray: (arr) | `arr.length;` |
|
|
726
|
+
| typeof: (val) | `typeof val;` |
|
|
727
|
+
| isarray: (arr) | `Array.isArray(arr);` |
|
|
728
|
+
| isnan: (val) | `isNaN(val);` |
|
|
729
|
+
| parseint: (val) | `parseInt(val);` |
|
|
730
|
+
| parsefloat: (val) | `parseFloat(val);` |
|
|
731
|
+
| toisodate: (val) | `new Date(val).toISOString();` |
|
|
732
|
+
| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
|
|
733
|
+
| tostring: (val) | `val.toString();` |
|
|
734
|
+
| urlencode: (val) | `encodeURI(val);` |
|
|
735
|
+
| urldecode: (val) | `decodeURI(val);` |
|
|
736
|
+
| replacestr: (str, from, to) | `str.replace(from, to);` |
|
|
737
|
+
| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
|
|
738
|
+
| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
|
|
739
|
+
| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
|
|
740
|
+
| split: (str, ch) | `str.split(ch);` |
|
|
741
|
+
| joinarrtostr: (arr, ch) | `arr.join(ch);` |
|
|
742
|
+
| concatarr: (arr, arr2) | `arr.concat(arr2);` |
|
|
743
|
+
| mapper: (val, values, choices) | <code>choices[values.findIndex((target) | target == val)]);</code> |
|
|
744
|
+
| thmapper: (val, values, choices) | <code>choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)];</code> |
|
|
745
|
+
| bitwisemask: (i,mask,op,shf) | <code>(op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf;</code> |
|
|
746
|
+
| slice: (arr, init, end) | `arr.slice(init,end);` |
|
|
747
|
+
| addset: (arr, x) | <code>{ return Array.from((new Set(arr)).add(x)) }</code> |
|
|
748
|
+
| removeset: (arr, x) | <code>{ let s = new Set(arr); s.delete(x); return Array.from(s) }</code> |
|
|
749
|
+
| touppercase: (val) | `String(val).toUpperCase()` |
|
|
750
|
+
| tolowercase: (val) | `String(val).toLowerCase()` |
|
|
751
|
+
| round: (val) | `Math.round(val)` |
|
|
752
|
+
| floor: (val) | `Math.floor(val)` |
|
|
753
|
+
| ceil: (val) | `Math.ceil(val)` |
|
|
754
|
+
| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
|
|
755
|
+
| gettime: (d) | `new Date(d).getTime()` |
|
|
756
|
+
| toisostring: (d) | `new Date(d).toISOString()` |
|
|
757
|
+
| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
|
|
758
|
+
| localestringnumber: (n, locale, options) | `n.toLocaleStringNumber(locale, options)` |
|
|
759
|
+
| now: () | `Date.now()` |
|
|
760
|
+
| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
|
|
761
|
+
| valuePicker: (val,pick) | <code>valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k)</code> |
|
|
762
|
+
| valuePickerMulti: (val,pick) | <code>valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k)</code> |
|
|
757
763
|
|
|
758
764
|
You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can
|
|
759
765
|
test all the functions described above.
|
|
@@ -1349,7 +1355,8 @@ as `command` in the [config group](#config-group-datamodel) or in the [device pr
|
|
|
1349
1355
|
attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be
|
|
1350
1356
|
sent (HTTP/MQTT) with the `transport` parameter at the provisioning process.
|
|
1351
1357
|
|
|
1352
|
-
**NOTE**: in `advancedNotification` mode the command is not triggered updating an attribute, but creating a command
|
|
1358
|
+
**NOTE**: in `advancedNotification` mode the command is not triggered updating an attribute, but creating a command
|
|
1359
|
+
execution entity. However, this mode has not been implemented yet so details are to be clarified.
|
|
1353
1360
|
|
|
1354
1361
|
For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in
|
|
1355
1362
|
the Context Broker will send a command to your device. For instance, to send the `ping` command with value
|
|
@@ -1812,7 +1819,7 @@ Config group is represented by a JSON object with the following fields:
|
|
|
1812
1819
|
| `endpoint` | ✓ | `string` | | Endpoint where the group of device is going to receive commands, if any. |
|
|
1813
1820
|
| `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. See more info [in this section](admin.md#storelastmeasure). False by default |
|
|
1814
1821
|
| `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default |
|
|
1815
|
-
| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md).
|
|
1822
|
+
| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). |
|
|
1816
1823
|
|
|
1817
1824
|
### Config group operations
|
|
1818
1825
|
|
|
@@ -2036,7 +2043,7 @@ the API resource fields and the same fields in the database model.
|
|
|
2036
2043
|
| `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. Useful just for debugging purpose. See more info [in this section](admin.md#storelastmeasure). False by default. |
|
|
2037
2044
|
| `lastMeasure` | ✓ | `object` | | last measure stored on device when `storeLastMeasure` is enabled. See more info [in this section](admin.md#storelastmeasure). This field can be cleared using `{}` in a device update request. In that case, `lastMeasure` is removed from device (until a next measure is received and `lastMesuare` gets created again). |
|
|
2038
2045
|
| `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default. |
|
|
2039
|
-
| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md).
|
|
2046
|
+
| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). |
|
|
2040
2047
|
|
|
2041
2048
|
### Device operations
|
|
2042
2049
|
|
|
@@ -2493,4 +2500,4 @@ updateEntityRequestsError 5
|
|
|
2493
2500
|
[18]:
|
|
2494
2501
|
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22value%22%20%3A%206%2C%0A%20%20%22ts%22%3A%201637245214901%2C%0A%20%22name%22%3A%20%22DevId629%22%2C%0A%20%22object%22%3A%7Bname%3A%20%22John%22%2C%20surname%3A%20%22Doe%22%7D%2C%0A%20%20%22array%22%3A%5B1%2C3%5D%0A%7D&input=name%7Csubstr(0%2Cname%7CindexOf(%22e%22)%2B1)&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
|
|
2495
2502
|
[99]:
|
|
2496
|
-
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%20%7B%0A%20%20%20%20jsonparse%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.parse)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20jsonstringify%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.stringify)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v))(arr)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20Array.isArray%2C%0A%20%20%20%20isnan%3A%20isNaN%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber((val)%20%3D%3E%20parseInt(val%2C%2010))(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber(parseFloat)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20timeoffset%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTimezoneOffset())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20val.toString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20urlencode%3A%20encodeURI%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(decodeURI)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg%2C%20'g')%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5Bvalues.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20!%3D%3D%20null%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20i%20%3A%20null)%2C%20null)%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20Array.from(new%20Set(arr).add(x))%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20Math.floor%2C%0A%20%20%20%20ceil%3A%20Math.ceil%2C%0A%20%20%20%20round%3A%20Math.round%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20Number.parseFloat(val)%3B%0A%20%20%20%20%20%20%20%20const%20dec%20%3D%20Number.parseInt(decimals)%3B%0A%20%20%20%20%20%20%20%20return%20isNaN(num)%20%7C%7C%20isNaN(dec)%20%3F%20null%20%3A%20num.toFixed(dec)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20gettime%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTime())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options))(d%2C%20timezone%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20now%3A%20Date.now%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20val%20!%3D%3D%20'string'%20%7C%7C%20!%2F%5E%5B0-9a-fA-F%5D%2B%24%2F.test(val)%20%7C%7C%20val.length%20%25%202%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%3B%0A%20%20%20%20%20%20%20%20%7D)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20valuePicker%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v))%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%0A%7D
|
|
2503
|
+
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%20%7B%0A%20%20%20%20jsonparse%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.parse)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20jsonstringify%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.stringify)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v))(arr)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20Array.isArray%2C%0A%20%20%20%20isnan%3A%20isNaN%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber((val)%20%3D%3E%20parseInt(val%2C%2010))(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber(parseFloat)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20timeoffset%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTimezoneOffset())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20val.toString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20urlencode%3A%20encodeURI%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(decodeURI)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg%2C%20'g')%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5Bvalues.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20!%3D%3D%20null%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20i%20%3A%20null)%2C%20null)%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20Array.from(new%20Set(arr).add(x))%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20Math.floor%2C%0A%20%20%20%20ceil%3A%20Math.ceil%2C%0A%20%20%20%20round%3A%20Math.round%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20Number.parseFloat(val)%3B%0A%20%20%20%20%20%20%20%20const%20dec%20%3D%20Number.parseInt(decimals)%3B%0A%20%20%20%20%20%20%20%20return%20isNaN(num)%20%7C%7C%20isNaN(dec)%20%3F%20null%20%3A%20num.toFixed(dec)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20gettime%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTime())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options))(d%2C%20timezone%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestringnumber%3A%20(n%2C%20locale%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20(fn)%20%3D%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((n%2C%20locale%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20n%20!%3D%3D%20'number'%20%7C%7C%20isNaN(n))%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20n.toLocaleString(locale%2C%20options)%3B%0A%20%20%20%20%20%20%20%20%7D)(n%2C%20locale%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20now%3A%20Date.now%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20val%20!%3D%3D%20'string'%20%7C%7C%20!%2F%5E%5B0-9a-fA-F%5D%2B%24%2F.test(val)%20%7C%7C%20val.length%20%25%202%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%3B%0A%20%20%20%20%20%20%20%20%7D)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20valuePicker%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v))%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%0A%7D
|
package/doc/deprecated.md
CHANGED
|
@@ -12,12 +12,17 @@ longer. In particular:
|
|
|
12
12
|
|
|
13
13
|
A list of deprecated features and the version in which they were deprecated follows:
|
|
14
14
|
|
|
15
|
+
- Config `mongodb.*` different from `mongodb.uri` (and associated env vars `IOTA_MONGO_*` except `IOTA_MONGO_URI`) in
|
|
16
|
+
4.11.0
|
|
15
17
|
- Support to NGSI v1 (finally removed in 2.18.0)
|
|
16
18
|
- Support to Node.js v4 in iotagent-node-lib 2.8.1 (finally removed in 2.9.0)
|
|
17
19
|
- Support to Node.js v6 in iotagent-node-lib 2.9.0 (finally removed in 2.10.0)
|
|
18
20
|
- Support to Node.js v8 in iotagent-node-lib 2.12.0 (finally removed in 2.13.0)
|
|
19
21
|
- Support to Node.js v10 in iotagent-node-lib 2.15.0 (finally removed in 2.16.0)
|
|
20
22
|
- Support to Node.js v12 in iotagent-node-lib 2.24.0 (finally removed in 2.25.0)
|
|
23
|
+
- Support to Node.js v14 in iotagent-node-lib 3.0.0 (finally removed in 3.1.0)
|
|
24
|
+
- Support to Node.js v16 in iotagent-node-lib 4.9.0 (finally removed in 4.10.0)
|
|
25
|
+
- Support to Node.js v18 in iotagent-node-lib 4.9.0 (finally removed in 4.10.0)
|
|
21
26
|
- Support to NGSI-LD v1.3 in iotagent-node-lib 2.25.0 (finally removed in 2.26.0)
|
|
22
27
|
- Support groups (provision) statically defined by configuration
|
|
23
28
|
- Support to in-memory registry (i.e.`deviceRegistry.type=memory`)
|
|
@@ -47,17 +52,31 @@ information in the case you want to use old versions:
|
|
|
47
52
|
|
|
48
53
|
The following table provides information about the last iotagent-node-lib version supporting currently removed features:
|
|
49
54
|
|
|
50
|
-
| **Removed feature**
|
|
51
|
-
|
|
|
52
|
-
| NGSI v1 API
|
|
53
|
-
| Support to Node.js v4
|
|
54
|
-
| Support to Node.js v6
|
|
55
|
-
| Support to Node.js v8
|
|
56
|
-
| Support to Node.js v10
|
|
57
|
-
| Support to Node.js v12
|
|
58
|
-
| Support to
|
|
59
|
-
|
|
|
60
|
-
| Support to
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
55
|
+
| **Removed feature** | **Last iotagent-node-lib version supporting feature** | **That version release date** |
|
|
56
|
+
| -------------------------------------------------------------- | ----------------------------------------------------- | ----------------------------- |
|
|
57
|
+
| NGSI v1 API | 2.17.0 | August 30th, 2021 |
|
|
58
|
+
| Support to Node.js v4 | 2.8.1 | December 19th, 2018 |
|
|
59
|
+
| Support to Node.js v6 | 2.9.0 | May 22nd, 2019 |
|
|
60
|
+
| Support to Node.js v8 | 2.12.0 | April 7th, 2020 |
|
|
61
|
+
| Support to Node.js v10 | 2.15.0 | February 18th, 2021 |
|
|
62
|
+
| Support to Node.js v12 | 2.24.0 | September 2nd, 2022 |
|
|
63
|
+
| Support to Node.js v14 | 3.0.0 | March 30th, 2023 |
|
|
64
|
+
| Support to Node.js v16 | 4.9.0 | August 22nd, 2025 |
|
|
65
|
+
| Support to Node.js v18 | 4.9.0 | August 22nd, 2025 |
|
|
66
|
+
| Support to NGSI-LD 1.3 | 2.25.0 | January 24th, 2023 |
|
|
67
|
+
| eventType configuration | 2.26.0 | March 15th, 2023 |
|
|
68
|
+
| Support to Legacy Expressions | 3.1.0 | April 25th, 2023 |
|
|
69
|
+
| bidirectional plugin | 3.3.0 | August 24th, 2023 |
|
|
70
|
+
| appendMode configuration (`IOTA_APPEND_MODE` env var) | 3.3.0 | August 24th, 2023 |
|
|
71
|
+
| push-mode stats | 4.5.0 | June 11th, 2024 |
|
|
72
|
+
| config `mongodb.host` (env var `IOTA_MONGO_HOST`) | Not yet | Not yet |
|
|
73
|
+
| config `mongodb.port` (env var `IOTA_MONGO_PORT`) | Not yet | Not yet |
|
|
74
|
+
| config `mongodb.db` (env var `IOTA_MONGO_DB`) | Not yet | Not yet |
|
|
75
|
+
| config `mongodb.replicaSet` (env var `IOTA_MONGO_REPLICASET`) | Not yet | Not yet |
|
|
76
|
+
| config `mongodb.user` (env var `IOTA_MONGO_USER`) | Not yet | Not yet |
|
|
77
|
+
| config `mongodb.password` (env var `IOTA_MONGO_PASSWORD`) | Not yet | Not yet |
|
|
78
|
+
| config `mongodb.authSource` (env var `IOTA_MONGO_AUTH_SOURCE`) | Not yet | Not yet |
|
|
79
|
+
| config `mongodb.retries` (env var `IOTA_MONGO_RETRIES`) | Not yet | Not yet |
|
|
80
|
+
| config `mongodb.retryTime` (env var `IOTA_MONGO_RETRY_TIME`) | Not yet | Not yet |
|
|
81
|
+
| config `mongodb.ssl` (env var `IOTA_MONGO_SSL`) | Not yet | Not yet |
|
|
82
|
+
| config `mongodb.extraArgs` (env var `IOTA_MONGO_EXTRAARGS`) | Not yet | Not yet |
|
|
@@ -346,7 +346,8 @@ queries (and thus P2 and R2 payloads).
|
|
|
346
346
|
|
|
347
347
|

|
|
348
348
|
|
|
349
|
-
**FIXME:** this scenario describes the registration-based commanding mechanism, which is currently deprecated. It should
|
|
349
|
+
**FIXME:** this scenario describes the registration-based commanding mechanism, which is currently deprecated. It should
|
|
350
|
+
be reworked
|
|
350
351
|
|
|
351
352
|
This scenario requires that the attributes that are going to be requested are marked as provided by the IoT Agent,
|
|
352
353
|
through a registration process (NGSIv9). Examples of this registration process will be provided in the practical section
|
|
@@ -733,11 +734,14 @@ error, that error must follow the NGSI payloads described in the Scenario 1 erro
|
|
|
733
734
|
|
|
734
735
|
The interactions depend on the command mode (`cmdMode`):
|
|
735
736
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
737
|
+
- `legacy`
|
|
738
|
+
- `notification`
|
|
739
|
+
- `advancedNotification`
|
|
739
740
|
|
|
740
|
-
The way of setting up Context Broker to IotAgent communication and the interaction between Context Broker and IoTAgent
|
|
741
|
+
The way of setting up Context Broker to IotAgent communication and the interaction between Context Broker and IoTAgent
|
|
742
|
+
when the command is executed depends on the mode (thus specific subsections about it are provided next for the three
|
|
743
|
+
modes). However, the way in which the command result is provided is the same to all modes (so it is described in a
|
|
744
|
+
[common section](#result-reporting)).
|
|
741
745
|
|
|
742
746
|
#### `legacy` mode
|
|
743
747
|
|
|
@@ -766,7 +770,8 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
766
770
|
}' "https://<platform-ip>:1026/v2/registrations"
|
|
767
771
|
```
|
|
768
772
|
|
|
769
|
-
If everything has gone OK, the Context Broker will return 201 Created with the ID of the registration in the `Location`
|
|
773
|
+
If everything has gone OK, the Context Broker will return 201 Created with the ID of the registration in the `Location`
|
|
774
|
+
header:
|
|
770
775
|
|
|
771
776
|
```
|
|
772
777
|
HTTP/1.1 201 Created
|
|
@@ -831,8 +836,8 @@ Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
|
|
|
831
836
|
}
|
|
832
837
|
```
|
|
833
838
|
|
|
834
|
-
The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 No Content
|
|
835
|
-
payload).
|
|
839
|
+
The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 No Content
|
|
840
|
+
(without payload).
|
|
836
841
|
|
|
837
842
|
This response just indicates that the IoT Agent has received the command successfully, and gives no information about
|
|
838
843
|
the requested information or command execution.
|
|
@@ -867,13 +872,14 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
867
872
|
"http": {
|
|
868
873
|
"url": "<value of the IOTA_PROVIDER_URL>/notify"
|
|
869
874
|
},
|
|
870
|
-
"attrsFormat": "
|
|
875
|
+
"attrsFormat": "normalized",
|
|
871
876
|
"attrs": [ "switch" ]
|
|
872
877
|
}
|
|
873
878
|
}' "https://<platform-ip>:1026/v2/subscriptions"
|
|
874
879
|
```
|
|
875
880
|
|
|
876
|
-
If everything has gone OK, the Context Broker will return 201 Created with the ID of the subscription in the `Location`
|
|
881
|
+
If everything has gone OK, the Context Broker will return 201 Created with the ID of the subscription in the `Location`
|
|
882
|
+
header:
|
|
877
883
|
|
|
878
884
|
```
|
|
879
885
|
HTTP/1.1 201 Created
|
|
@@ -908,8 +914,8 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
908
914
|
} ' "https://<platform-ip>:1026/v2/op/update"
|
|
909
915
|
```
|
|
910
916
|
|
|
911
|
-
The Context Broker receives this update and detects that it triggers the subscription, so it
|
|
912
|
-
|
|
917
|
+
The Context Broker receives this update and detects that it triggers the subscription, so it notifies to the IoT Agent,
|
|
918
|
+
as follows:
|
|
913
919
|
|
|
914
920
|
```bash
|
|
915
921
|
POST /notify HTTP/1.1
|
|
@@ -919,17 +925,20 @@ Fiware-ServicePath: /iota2ngsi
|
|
|
919
925
|
Accept: application/json
|
|
920
926
|
Content-length: ...
|
|
921
927
|
Content-type: application/json; charset=utf-8
|
|
922
|
-
Ngsiv2-Attrsformat:
|
|
928
|
+
Ngsiv2-Attrsformat: normalized
|
|
923
929
|
Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
|
|
924
930
|
|
|
925
931
|
{
|
|
926
|
-
"
|
|
927
|
-
"
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
932
|
+
"subscriptionId": "60b0cedd497e8b681d40b58e",
|
|
933
|
+
"data": [{
|
|
934
|
+
"id": "Dev0001",
|
|
935
|
+
"type": "device",
|
|
936
|
+
"switch": {
|
|
937
|
+
"type": "command",
|
|
938
|
+
"value": "54, 12",
|
|
939
|
+
"metadata": {}
|
|
940
|
+
}
|
|
941
|
+
}]
|
|
933
942
|
}
|
|
934
943
|
```
|
|
935
944
|
|
|
@@ -946,7 +955,8 @@ request will be.
|
|
|
946
955
|
|
|
947
956
|
##### Set up ContextBroker to IOTA comunication mechanism
|
|
948
957
|
|
|
949
|
-
The communication mechanism will be based on subscriptions, although a different one than the one used in `notification`
|
|
958
|
+
The communication mechanism will be based on subscriptions, although a different one than the one used in `notification`
|
|
959
|
+
mode. Note this mode has not been implemented yet, so following should be taken as a draft:
|
|
950
960
|
|
|
951
961
|
```bash
|
|
952
962
|
curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Fiware-Service: workshop" \
|
|
@@ -974,7 +984,8 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
974
984
|
|
|
975
985
|
##### Command Execution
|
|
976
986
|
|
|
977
|
-
Command execution involves to create a
|
|
987
|
+
Command execution involves to create a _command execution entity_ (details are yet to be implemented), which in sequence
|
|
988
|
+
triggers a notification to IoTAgent as this one (draft)
|
|
978
989
|
|
|
979
990
|
```bash
|
|
980
991
|
POST /notify HTTP/1.1
|
|
@@ -1072,7 +1083,8 @@ the requested information or command execution.
|
|
|
1072
1083
|
At this point, the command has been issued to the IoTAgent and the User doesn't still know what the result of its
|
|
1073
1084
|
request will be.
|
|
1074
1085
|
|
|
1075
|
-
As mentioned before, advanced notification mode has not been yet implemented. The following considerations are gap in
|
|
1086
|
+
As mentioned before, advanced notification mode has not been yet implemented. The following considerations are gap in
|
|
1087
|
+
the current implementation at IoT Agent side that should be addressed:
|
|
1076
1088
|
|
|
1077
1089
|
- Fields others than `targetEntityId`, `targetEntityType`, `cmd` and `params` (i.e. `execTs`, `status`, `info`,
|
|
1078
1090
|
`onDelivered`, `onOk`, `onError`, `onInfo`, `cmdExecution` and `dataExpiration`), are not actually used. By the
|
package/lib/commonConfig.js
CHANGED
|
@@ -173,6 +173,9 @@ function processEnvironmentVariables() {
|
|
|
173
173
|
'IOTA_IOTAM_AGENTPATH'
|
|
174
174
|
];
|
|
175
175
|
const mongoVariables = [
|
|
176
|
+
'IOTA_MONGO_URI',
|
|
177
|
+
// FIXME: the following IOTA_MONGO_ env vars are deprecated and will
|
|
178
|
+
// be eventually removed (use IOTA_MONGO_URI)
|
|
176
179
|
'IOTA_MONGO_HOST',
|
|
177
180
|
'IOTA_MONGO_PORT',
|
|
178
181
|
'IOTA_MONGO_DB',
|
|
@@ -365,6 +368,12 @@ function processEnvironmentVariables() {
|
|
|
365
368
|
// Default transport
|
|
366
369
|
if (process.env.IOTA_DEFAULT_TRANSPORT !== undefined) {
|
|
367
370
|
config.defaultTransport = process.env.IOTA_DEFAULT_TRANSPORT;
|
|
371
|
+
} else {
|
|
372
|
+
// defaultTransport is "MQTT" by default even when no ENV VAR is used
|
|
373
|
+
// and should be available in iotagent-library to allow
|
|
374
|
+
// set polling and transport for autoprovisioned devices
|
|
375
|
+
// at lib/services/devices/deviceService.js#L302
|
|
376
|
+
config.defaultTransport = 'MQTT';
|
|
368
377
|
}
|
|
369
378
|
|
|
370
379
|
// Default explicitAttrs
|
|
@@ -409,6 +418,10 @@ function processEnvironmentVariables() {
|
|
|
409
418
|
config.mongodb = {};
|
|
410
419
|
}
|
|
411
420
|
|
|
421
|
+
if (process.env.IOTA_MONGO_URI) {
|
|
422
|
+
config.mongodb.uri = process.env.IOTA_MONGO_URI;
|
|
423
|
+
}
|
|
424
|
+
|
|
412
425
|
if (process.env.IOTA_MONGO_HOST) {
|
|
413
426
|
config.mongodb.host = process.env.IOTA_MONGO_HOST;
|
|
414
427
|
}
|
package/lib/jexlTranformsMap.js
CHANGED
|
@@ -204,6 +204,21 @@ const map = {
|
|
|
204
204
|
options
|
|
205
205
|
);
|
|
206
206
|
},
|
|
207
|
+
localestringnumber: (n, locale, options) => {
|
|
208
|
+
const safeOperation =
|
|
209
|
+
(fn) =>
|
|
210
|
+
(...args) => {
|
|
211
|
+
try {
|
|
212
|
+
return fn(...args);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
return safeOperation((n, locale, options) => {
|
|
218
|
+
if (typeof n !== 'number' || isNaN(n)) return null;
|
|
219
|
+
return n.toLocaleString(locale, options);
|
|
220
|
+
})(n, locale, options);
|
|
221
|
+
},
|
|
207
222
|
now: Date.now,
|
|
208
223
|
hextostring: (val) => {
|
|
209
224
|
const safeOperation =
|
package/lib/model/dbConn.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* please contact with::daniel.moranjimenez@telefonica.com
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/*
|
|
25
25
|
* This module sets up the connection with the mongodb through mongoose. This connection will be used
|
|
26
26
|
* in mongoose schemas to persist objects.
|
|
27
27
|
*/
|
|
@@ -49,8 +49,7 @@ function loadModels() {
|
|
|
49
49
|
*
|
|
50
50
|
* @this Reference to the dbConn module itself.
|
|
51
51
|
*/
|
|
52
|
-
|
|
53
|
-
function init(host, db, port, options, callback) {
|
|
52
|
+
function init(host, db, port, options, callback, fullUri = null) {
|
|
54
53
|
let url;
|
|
55
54
|
let retries = 0;
|
|
56
55
|
let lastError;
|
|
@@ -60,21 +59,25 @@ function init(host, db, port, options, callback) {
|
|
|
60
59
|
return `${item}:${port}`;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
if (fullUri) {
|
|
63
|
+
url = fullUri;
|
|
64
|
+
} else {
|
|
65
|
+
url = 'mongodb://';
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if (options.auth) {
|
|
68
|
+
url += `${encodeURIComponent(options.auth.user)}:${encodeURIComponent(options.auth.password)}@`;
|
|
69
|
+
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
const hosts = host.split(',').map(addPort).join(',');
|
|
72
|
+
url += `${hosts}/${db}`;
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (options.extraArgs) {
|
|
75
|
+
const query = new URLSearchParams(options.extraArgs).toString();
|
|
76
|
+
if (query) {
|
|
77
|
+
url += `?${query}`;
|
|
78
|
+
}
|
|
79
|
+
delete options.extraArgs;
|
|
76
80
|
}
|
|
77
|
-
delete options.extraArgs;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
function connectionAttempt(callback) {
|
|
@@ -91,32 +94,17 @@ function init(host, db, port, options, callback) {
|
|
|
91
94
|
lastError = error;
|
|
92
95
|
alarms.raise(constants.MONGO_ALARM, error);
|
|
93
96
|
});
|
|
94
|
-
|
|
95
|
-
defaultDb.on('
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
defaultDb.on('
|
|
99
|
-
logger.debug(context, 'Mongo Driver connected');
|
|
100
|
-
});
|
|
101
|
-
defaultDb.on('reconnected', function () {
|
|
102
|
-
logger.debug(context, 'Mongo Driver reconnected');
|
|
103
|
-
});
|
|
104
|
-
defaultDb.on('disconnected', function () {
|
|
105
|
-
logger.debug(context, 'Mongo Driver disconnected');
|
|
106
|
-
});
|
|
107
|
-
defaultDb.on('reconnectFailed', function () {
|
|
97
|
+
defaultDb.on('connecting', () => logger.debug(context, 'Mongo Driver connecting'));
|
|
98
|
+
defaultDb.on('connected', () => logger.debug(context, 'Mongo Driver connected'));
|
|
99
|
+
defaultDb.on('reconnected', () => logger.debug(context, 'Mongo Driver reconnected'));
|
|
100
|
+
defaultDb.on('disconnected', () => logger.debug(context, 'Mongo Driver disconnected'));
|
|
101
|
+
defaultDb.on('reconnectFailed', () => {
|
|
108
102
|
logger.error(context, 'MONGODB-004: MongoDB connection was lost');
|
|
109
103
|
process.exit(1);
|
|
110
104
|
});
|
|
111
|
-
defaultDb.on('disconnecting',
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
defaultDb.on('open', function () {
|
|
115
|
-
logger.debug(context, 'Mongo Driver open');
|
|
116
|
-
});
|
|
117
|
-
defaultDb.on('close', function () {
|
|
118
|
-
logger.debug(context, 'Mongo Driver close');
|
|
119
|
-
});
|
|
105
|
+
defaultDb.on('disconnecting', () => logger.debug(context, 'Mongo Driver disconnecting'));
|
|
106
|
+
defaultDb.on('open', () => logger.debug(context, 'Mongo Driver open'));
|
|
107
|
+
defaultDb.on('close', () => logger.debug(context, 'Mongo Driver close'));
|
|
120
108
|
callback();
|
|
121
109
|
})
|
|
122
110
|
.catch((err) => {
|
|
@@ -144,34 +132,45 @@ function init(host, db, port, options, callback) {
|
|
|
144
132
|
|
|
145
133
|
function configureDb(callback) {
|
|
146
134
|
const currentConfig = config.getConfig();
|
|
135
|
+
|
|
147
136
|
if (currentConfig.deviceRegistry?.type === 'mongodb') {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} else {
|
|
152
|
-
const dbName = currentConfig.mongodb.db || DEFAULT_DB_NAME;
|
|
153
|
-
const port = currentConfig.mongodb.port || 27017;
|
|
137
|
+
const mongoCfg = currentConfig.mongodb;
|
|
138
|
+
|
|
139
|
+
if (mongoCfg?.uri) {
|
|
154
140
|
const options = {};
|
|
141
|
+
logger.info(context, `Using full MongoDB URI from configuration`);
|
|
142
|
+
init(null, null, null, options, callback, mongoCfg.uri);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
155
145
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
if (!mongoCfg?.host) {
|
|
147
|
+
logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
|
|
148
|
+
callback(new errors.BadConfiguration('No host found for MongoDB driver'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
159
151
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
const dbName = mongoCfg.db || DEFAULT_DB_NAME;
|
|
153
|
+
const port = mongoCfg.port || 27017;
|
|
154
|
+
const options = {};
|
|
155
|
+
|
|
156
|
+
if (mongoCfg.replicaSet) options.replicaSet = mongoCfg.replicaSet;
|
|
157
|
+
if (mongoCfg.ssl) options.ssl = mongoCfg.ssl;
|
|
158
|
+
if (mongoCfg.extraArgs) options.extraArgs = mongoCfg.extraArgs;
|
|
159
|
+
|
|
160
|
+
if (mongoCfg.user && mongoCfg.password) {
|
|
161
|
+
options.auth = {
|
|
162
|
+
user: mongoCfg.user,
|
|
163
|
+
password: mongoCfg.password
|
|
164
|
+
};
|
|
165
|
+
if (mongoCfg.authSource) {
|
|
166
|
+
options.extraArgs = {
|
|
167
|
+
...options.extraArgs,
|
|
168
|
+
authSource: mongoCfg.authSource
|
|
164
169
|
};
|
|
165
|
-
if (currentConfig.mongodb.authSource) {
|
|
166
|
-
options.extraArgs = {
|
|
167
|
-
...options.extraArgs,
|
|
168
|
-
authSource: currentConfig.mongodb.authSource
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
170
|
}
|
|
172
|
-
|
|
173
|
-
init(currentConfig.mongodb.host, dbName, port, options, callback);
|
|
174
171
|
}
|
|
172
|
+
|
|
173
|
+
init(mongoCfg.host, dbName, port, options, callback);
|
|
175
174
|
} else {
|
|
176
175
|
callback();
|
|
177
176
|
}
|
|
@@ -341,7 +341,7 @@ function sendSubscriptionsNgsi2(unregister, deviceData, callback) {
|
|
|
341
341
|
}
|
|
342
342
|
const trigger = attrs; // one subscription for all commands
|
|
343
343
|
const content = attrs;
|
|
344
|
-
const attrsFormat = '
|
|
344
|
+
const attrsFormat = 'normalized';
|
|
345
345
|
|
|
346
346
|
logger.debug(
|
|
347
347
|
context,
|
|
@@ -168,7 +168,21 @@ function executeWithDeviceInformation(operationFunction) {
|
|
|
168
168
|
deviceInformation
|
|
169
169
|
);
|
|
170
170
|
const currentType = type ? type : deviceInformation.type;
|
|
171
|
-
|
|
171
|
+
|
|
172
|
+
function getConfiguration(currentType, deviceInformation, callback) {
|
|
173
|
+
config
|
|
174
|
+
.getGroupRegistry()
|
|
175
|
+
.getSilently(deviceInformation.resource, deviceInformation.apikey, function (error, deviceGroup) {
|
|
176
|
+
if (error) {
|
|
177
|
+
config.getGroupRegistry().getTypeSilently(currentType, function (error, deviceGroup) {
|
|
178
|
+
callback(error, deviceGroup);
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
callback(null, deviceGroup);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
getConfiguration(currentType, deviceInformation, function (error, deviceGroup) {
|
|
172
186
|
let typeInformation;
|
|
173
187
|
const configDeviceInfo = config.getConfig().types[currentType];
|
|
174
188
|
if (error) {
|
|
@@ -293,8 +293,10 @@ function handleNotificationNgsi2(req, res, next) {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
logger.debug(context, 'extracted atts %j from dataElement %j', atts, dataElement);
|
|
296
|
-
|
|
297
|
-
var
|
|
296
|
+
// FIXME: command execution entity for advancedNotification is still not implemented
|
|
297
|
+
var id = dataElement.id;
|
|
298
|
+
var type = dataElement.type;
|
|
299
|
+
|
|
298
300
|
if (dataElement.targetEntityId && dataElement.targetEntityId.value) {
|
|
299
301
|
id = dataElement.targetEntityId.value;
|
|
300
302
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "iotagent-node-lib",
|
|
3
3
|
"license": "AGPL-3.0-only",
|
|
4
4
|
"description": "IoT Agent library to interface with NGSI Context Broker",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.12.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -2235,6 +2235,79 @@ const testCases = [
|
|
|
2235
2235
|
}
|
|
2236
2236
|
]
|
|
2237
2237
|
},
|
|
2238
|
+
{
|
|
2239
|
+
describeName: '0201 - Simple groups with commands',
|
|
2240
|
+
provision: {
|
|
2241
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
2242
|
+
method: 'POST',
|
|
2243
|
+
json: {
|
|
2244
|
+
services: [
|
|
2245
|
+
{
|
|
2246
|
+
resource: '/iot/json',
|
|
2247
|
+
apikey: globalEnv.apikey,
|
|
2248
|
+
entity_type: globalEnv.entity_type,
|
|
2249
|
+
commands: [
|
|
2250
|
+
{
|
|
2251
|
+
name: 'cmd1',
|
|
2252
|
+
type: 'command'
|
|
2253
|
+
}
|
|
2254
|
+
],
|
|
2255
|
+
endpoint: 'http://myendpoint.com',
|
|
2256
|
+
transport: 'http',
|
|
2257
|
+
lazy: [],
|
|
2258
|
+
attributes: [],
|
|
2259
|
+
static_attributes: []
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
resource: '/iot/json',
|
|
2263
|
+
apikey: globalEnv.apikey + 'OTHER',
|
|
2264
|
+
entity_type: globalEnv.entity_type,
|
|
2265
|
+
commands: [
|
|
2266
|
+
{
|
|
2267
|
+
name: 'cmd1',
|
|
2268
|
+
type: 'command'
|
|
2269
|
+
}
|
|
2270
|
+
],
|
|
2271
|
+
transport: 'mqtt',
|
|
2272
|
+
lazy: [],
|
|
2273
|
+
attributes: [],
|
|
2274
|
+
static_attributes: []
|
|
2275
|
+
}
|
|
2276
|
+
]
|
|
2277
|
+
},
|
|
2278
|
+
headers: {
|
|
2279
|
+
'fiware-service': globalEnv.service,
|
|
2280
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
should: [
|
|
2284
|
+
{
|
|
2285
|
+
shouldName:
|
|
2286
|
+
'A - WHEN sending not provisioned object_ids (measures) through http IT should store commands into Context Broker',
|
|
2287
|
+
type: 'single',
|
|
2288
|
+
skip: '!lib', // there is not CB registration mock
|
|
2289
|
+
measure: {
|
|
2290
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2291
|
+
method: 'POST',
|
|
2292
|
+
qs: {
|
|
2293
|
+
i: globalEnv.deviceId,
|
|
2294
|
+
k: globalEnv.apikey
|
|
2295
|
+
},
|
|
2296
|
+
json: {
|
|
2297
|
+
b: 10
|
|
2298
|
+
}
|
|
2299
|
+
},
|
|
2300
|
+
expectation: {
|
|
2301
|
+
id: globalEnv.entity_name,
|
|
2302
|
+
type: globalEnv.entity_type,
|
|
2303
|
+
b: {
|
|
2304
|
+
value: 10,
|
|
2305
|
+
type: 'Text'
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
]
|
|
2310
|
+
},
|
|
2238
2311
|
// 0300 - STATIC ATTRIBUTES TESTS
|
|
2239
2312
|
{
|
|
2240
2313
|
describeName:
|
|
@@ -398,6 +398,36 @@ describe('Jexl expression interpreter', function () {
|
|
|
398
398
|
});
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
+
describe('When number localization functions are executed', function () {
|
|
402
|
+
it('should return the localized string representation of the number', function (done) {
|
|
403
|
+
const scope = {
|
|
404
|
+
theNumber: 1234567.89,
|
|
405
|
+
locale: 'es-ES',
|
|
406
|
+
options: { minimumFractionDigits: 2, maximumFractionDigits: 2 }
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) {
|
|
410
|
+
should.not.exist(error);
|
|
411
|
+
result.should.equal('1.234.567,89');
|
|
412
|
+
done();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('should return null for invalid number input', function (done) {
|
|
417
|
+
const scope = {
|
|
418
|
+
theNumber: 'not_a_number',
|
|
419
|
+
locale: 'en-US',
|
|
420
|
+
options: {}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) {
|
|
424
|
+
should.not.exist(error);
|
|
425
|
+
should.equal(result, null);
|
|
426
|
+
done();
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
401
431
|
// Check errors
|
|
402
432
|
describe('When invalid inputs are used', function () {
|
|
403
433
|
describe('When an invalid JSON string is parsed', function () {
|
|
@@ -243,7 +243,7 @@ describe('NGSI-v2 - In memory device registry', function () {
|
|
|
243
243
|
should.exist(device.type);
|
|
244
244
|
device.name.should.equal('name5');
|
|
245
245
|
device.type.should.equal('Light5');
|
|
246
|
-
Object.keys(device).length.should.equal(
|
|
246
|
+
Object.keys(device).length.should.equal(14);
|
|
247
247
|
done();
|
|
248
248
|
});
|
|
249
249
|
});
|