@ti-engine/core 1.3.13 → 1.4.1

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.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
4
  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
5
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
6
  * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -14,45 +14,6 @@ const config = require( "#config" );
14
14
  const cache = require( "#cache" );
15
15
  const messageDispatcher = require( "#message-dispatcher" );
16
16
 
17
- /**
18
- * @typedef {Object} ServiceAddress
19
- * @property {string} serviceAlias A valid service alias.
20
- * @property {string} serviceDomainName A valid service domain name.
21
- * @property {number|undefined} serviceVersion Optional service version. If not provided, the latest version will be assumed as a target.
22
- */
23
-
24
- /**
25
- * @typedef {Object} ServiceExecContext
26
- * @property {string|undefined} authToken A valid authentication token that initialized the service call (if applicable).
27
- * @property {ServiceCallPredecessor|undefined} previousServiceCall The previous service call in the execution chain (if such exists).
28
- */
29
-
30
- /**
31
- * @typedef {Message} ServiceCallPredecessor
32
- * @property {string} predecessor The {@link Message.messageID} of the predecessor in the service call tree.
33
- * @property {ServiceAddress} serviceAddress The address of the service that has to process the service call.
34
- * @property {Object|undefined} serviceParams The named params to be provided to the API service.
35
- */
36
-
37
- /**
38
- * @typedef {ServiceCallPredecessor} ServiceCall
39
- * @property {string} authToken A valid authentication token that initialized the service call.
40
- * @property {number} createdOn A unix timestamp taken at creation time of the service call.
41
- * @property {number} executionTime The total execution time of this service call in milliseconds.
42
- * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise, it will be 'undefined'.
43
- * @property {number|undefined} finishedOn A unix timestamp taken at finish time of the service call.
44
- * @property {boolean} isCompleted Flag to indicate if this service call has been completed.
45
- * @property {boolean|undefined} isSuccessful A flag indicating if this service call can be considered successful or not. Will be 'undefined' until the service call is processed.
46
- * @property {string[]} successors The service call IDs of the successors in the service call tree.
47
- */
48
-
49
- /**
50
- * @typedef {Object} ServiceCallResult
51
- * @property {Exception|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise, it will be 'undefined'.
52
- * @property {boolean} isSuccessful A flag indicating if this service call can be considered successful or not.
53
- * @property {Object|string|undefined} payload The payload containing the results from the service call processing. If a string, it is ID of the payload in the memory cache instead.
54
- */
55
-
56
17
  /**
57
18
  * Used to assemble and prepare a new {@link ServiceCall} object.
58
19
  *
@@ -33,7 +33,7 @@ class ServiceConsumer extends ServiceInstance {
33
33
  * @constructor
34
34
  * @param {string} serviceDomainName The service domain name for this service instance.
35
35
  * @param {ServiceConfiguration} [serviceConfig] The JSON configuration for this service.
36
- * @throws {Exception.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
36
+ * @throws {TiException.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
37
37
  */
38
38
  constructor( serviceDomainName, serviceConfig ) {
39
39
  super( serviceDomainName, serviceConfig );
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
4
  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
5
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
6
  * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -15,21 +15,6 @@ const config = require( "#config" );
15
15
  const cache = require( "#cache" );
16
16
  const messageDispatcher = require( "#message-dispatcher" );
17
17
 
18
- /**
19
- * @typedef {Object} ServiceDefinition
20
- * @property {string} serviceAlias Service alias.
21
- * @property {string} serviceFile The JS file containing the service itself. This has to be exposed via package.json import structure!
22
- * @property {number} [serviceVersion] Service version.
23
- */
24
-
25
- /**
26
- * @typedef {Object.<string, ServiceInterfaceVersion>} ServiceInterface
27
- */
28
-
29
- /**
30
- * @typedef {Object.<number, ServiceHandlerMethod>} ServiceInterfaceVersion
31
- */
32
-
33
18
  /**
34
19
  * @callback VerifyAccessMethod
35
20
  * @param {string} authToken
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
4
  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
5
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
6
  * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -15,11 +15,6 @@ const exceptions = require( "#exceptions" );
15
15
  const cache = require( "#cache" );
16
16
  const messageDispatcher = require( "#message-dispatcher" );
17
17
 
18
- /**
19
- * @typedef {Object} ServiceConfiguration
20
- * @property {ServiceDefinition[]} [services] A list of service definitions to be registered with the {@link ServiceProvider}.
21
- */
22
-
23
18
  /**
24
19
  * Abstract class used to define a Service Instance behavior.
25
20
  * <br/>
@@ -43,8 +38,8 @@ class ServiceInstance {
43
38
  * @constructor
44
39
  * @param {string} serviceDomainName The service domain name for this service instance.
45
40
  * @param {ServiceConfiguration} [serviceConfig={ services: [] }] The JSON configuration for this service.
46
- * @throws {Exception.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
47
- * @throws {Exception.E_GEN_FEATURE_UNSUPPORTED} If multiple instances are started in the same process.
41
+ * @throws {TiException.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
42
+ * @throws {TiException.E_GEN_FEATURE_UNSUPPORTED} If multiple instances are started in the same process.
48
43
  */
49
44
  constructor( serviceDomainName, serviceConfig = { services: [] } ) {
50
45
  // Ensure this abstract class cannot be instantiated:
@@ -143,7 +138,7 @@ class ServiceInstance {
143
138
  * <br/>
144
139
  * NOTE: This method will be invoked automatically.
145
140
  * <br/>
146
- * NOTE: If you need to add more onStart logic you can override this method but make sure to call it in the
141
+ * NOTE: If you need to add more onStart logic, you can override this method but make sure to call it in the
147
142
  * overriding method using: super.onStart()
148
143
  *
149
144
  * @method
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
4
  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
5
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
6
  * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -38,7 +38,7 @@ class ServiceProvider extends ServiceConsumer {
38
38
  * @constructor
39
39
  * @param {string} serviceDomainName The service domain name for this service instance.
40
40
  * @param {ServiceConfiguration} [serviceConfig] The JSON configuration for this service.
41
- * @throws {Exception.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
41
+ * @throws {TiException.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
42
42
  */
43
43
  constructor( serviceDomainName, serviceConfig ) {
44
44
  super( serviceDomainName, serviceConfig );
@@ -56,7 +56,7 @@ class ServiceProvider extends ServiceConsumer {
56
56
  * <br/>
57
57
  * NOTE: This method will be invoked automatically.
58
58
  * <br/>
59
- * NOTE: If you need to add more onStart logic you can override this method but make sure to call it in the
59
+ * NOTE: If you need to add more onStart logic, you can override this method but make sure to call it in the
60
60
  * overriding method using: super.onStart()
61
61
  *
62
62
  * @method
@@ -88,7 +88,7 @@ class ServiceProvider extends ServiceConsumer {
88
88
  * <br/>
89
89
  * NOTE: This method will be invoked automatically.
90
90
  * <br/>
91
- * NOTE: If you need to add more onStop logic you can override this method but make sure to call it in the
91
+ * NOTE: If you need to add more onStop logic, you can override this method but make sure to call it in the
92
92
  * overriding method using: super.onStop()
93
93
  *
94
94
  * @method
@@ -110,7 +110,7 @@ class ServiceProvider extends ServiceConsumer {
110
110
  * Used to report health status of the service instance for external monitoring.
111
111
  * This is a scheduled job that will be executed at SERVICE_HEALTH_CHECK_INTERVAL time.
112
112
  * <br/>
113
- * NOTE: By default this method will update a Redis key with an expiration timer. You can override this
113
+ * NOTE: By default, this method will update a Redis key with an expiration timer. You can override this
114
114
  * functionality with something custom like calling an HTTP endpoint.
115
115
  *
116
116
  * @method
@@ -204,13 +204,14 @@ class ServiceProvider extends ServiceConsumer {
204
204
  let promises = [];
205
205
  _.forEach( serviceDefinitions, ( serviceDefinition ) => {
206
206
  // NOTE: we are not going to interrupt the service interface loading if one of the services fails to load or is not found!
207
- // If this happens, a corresponding log entry will be created but the loading process will continue. Therefore, the following
208
- // promise will always resolve (unless a programming error occurs in it, of course).
207
+ // If this happens, a corresponding log entry will be created, but the loading process will continue. Therefore, the following
208
+ // promise will always be resolved (unless a programming error occurs in it, of course).
209
209
  let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
210
210
  return new Promise( ( resolve ) => {
211
211
  this.registerService( serviceDefinition, defaultServiceHandler ).then( () => {
212
212
  resolve( true );
213
- } ).catch( () => {
213
+ } ).catch( ( error ) => {
214
+ logger.log( `Service registration failed for '${ serviceDefinition?.serviceAlias || "unknown-service" }'.`, logger.logSeverity.ERROR, error );
214
215
  resolve( false );
215
216
  } );
216
217
  } );
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
4
  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
5
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
6
  * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -18,6 +18,7 @@ const _ = require( "lodash" );
18
18
  *
19
19
  * @readonly
20
20
  * @enum {string}
21
+ * @typedef {string} TiRedisCommand
21
22
  */
22
23
  let cacheCommandsEnum = tools.enum( {
23
24
  ADD_TO_SET: [ "sadd", "add to set", "https://redis.io/docs/latest/commands/sadd/" ],
@@ -34,6 +35,8 @@ let cacheCommandsEnum = tools.enum( {
34
35
  IS_SET_MEMBER: [ "sismember", "", "https://redis.io/docs/latest/commands/sismember/" ],
35
36
  JSON_ARRAY_APPEND: [ "json.arrappend", "", "https://redis.io/docs/latest/commands/json.arrappend/" ],
36
37
  JSON_GET: [ "json.get", "", "https://redis.io/docs/latest/commands/json.get/" ],
38
+ JSON_MERGE: [ "json.merge", "", "https://redis.io/docs/latest/commands/json.merge/" ],
39
+ JSON_MGET: [ "json.mget", "", "https://redis.io/docs/latest/commands/json.mget/" ],
37
40
  JSON_SET: [ "json.set", "", "https://redis.io/docs/latest/commands/json.set/" ],
38
41
  KEYS: [ "keys", "(warning: O(N), use SCAN where possible)", "https://redis.io/docs/latest/commands/keys/" ],
39
42
  LIST_PUSH: [ "lpush", "list push", "https://redis.io/docs/latest/commands/lpush/" ],
@@ -43,12 +46,14 @@ let cacheCommandsEnum = tools.enum( {
43
46
  SET_VALUE: [ "set", "set value", "https://redis.io/docs/latest/commands/set/" ],
44
47
  UNION_OF_SETS: [ "sunion", "union of sets", "https://redis.io/docs/latest/commands/sunion/" ]
45
48
  } );
49
+ module.exports.cacheCommands = cacheCommandsEnum;
46
50
 
47
51
  /**
48
52
  * Enum for listing all client statuses.
49
53
  *
50
54
  * @readonly
51
55
  * @enum {number}
56
+ * @typedef {number} TiRedisClientStatus
52
57
  */
53
58
  let clientStatusEnum = tools.enum( {
54
59
  UNINITIALIZED: [ 0, "uninitialized", "Redis client is offline and not yet initialized." ],
@@ -58,31 +63,21 @@ let clientStatusEnum = tools.enum( {
58
63
  SHUTTING_DOWN: [ 4, "shutting down", "Redis client is shutting down." ],
59
64
  DISCONNECTED: [ 5, "disconnected", "Redis client is permanently disconnected from server." ]
60
65
  } );
66
+ module.exports.clientStatus = clientStatusEnum;
61
67
 
62
68
  /**
63
69
  * Enum for listing the Redis key override modes.
64
70
  *
65
71
  * @readonly
66
72
  * @enum {string}
73
+ * @typedef {string} TiRedisOverrideMode
67
74
  */
68
75
  let cacheOverrideModeEnum = tools.enum( {
69
76
  DEFAULT: [ "", "default", "Standard Redis behaviour when setting new key." ],
70
77
  NX: [ "nx", "nx", "Sets the key only if it does not already exist." ],
71
78
  XX: [ "xx", "xx", "Sets the key only if it already exists." ]
72
79
  } );
73
-
74
- /**
75
- * @typedef {string} TiRedisCommand
76
- */
77
- module.exports.cacheCommands = cacheCommandsEnum;
78
- /**
79
- * @typedef {string} TiRedisOverrideMode
80
- */
81
80
  module.exports.cacheOverrideMode = cacheOverrideModeEnum;
82
- /**
83
- * @typedef {number} TiRedisClientStatus
84
- */
85
- module.exports.clientStatus = clientStatusEnum;
86
81
 
87
82
  /**
88
83
  * Used to create a Redis Cache client.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.3.13",
3
+ "version": "1.4.1",
4
4
  "description": "The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.",
5
5
  "keywords": [
6
6
  "microservices",
@@ -35,7 +35,6 @@
35
35
  "#default-message-receiver": "./components/exchange/default/default-message-receiver.js",
36
36
  "#default-message-sender": "./components/exchange/default/default-message-sender.js",
37
37
  "#exceptions": "./utils/exceptions.js",
38
- "#gcloud-integration": "./integrations/gcloud-integration.js",
39
38
  "#labels": "./bin/localization/labels.json",
40
39
  "#localization": "./utils/localization.js",
41
40
  "#logger": "./utils/logger.js",
@@ -57,14 +56,13 @@
57
56
  "#tools": "./utils/tools.js"
58
57
  },
59
58
  "dependencies": {
60
- "@dotenvx/dotenvx": "^1.51.1",
61
- "blake2": "^5.0.0",
62
- "ioredis": "^5.8.2",
63
- "lodash": "^4.17.21",
59
+ "@dotenvx/dotenvx": "^1.55.1",
60
+ "blake2": "^5.0.1",
61
+ "ioredis": "^5.10.0",
62
+ "lodash": "^4.17.23",
64
63
  "node-schedule": "^2.1.1"
65
64
  },
66
65
  "optionalDependencies": {
67
- "@google-cloud/error-reporting": "^3.0.5",
68
66
  "zeromq": "^6.5.0"
69
67
  },
70
68
  "files": [
@@ -75,7 +73,7 @@
75
73
  "package.json",
76
74
  "README.md",
77
75
  "CHANGELOG.md",
78
- "LICENSE"
76
+ "LICENSE.md"
79
77
  ],
80
78
  "repository": {
81
79
  "type": "git",
@@ -87,6 +85,6 @@
87
85
  },
88
86
  "homepage": "https://github.com/Belleal/ti-engine/tree/master/packages/core#readme",
89
87
  "engines": {
90
- "node": ">=18.0.0"
88
+ "node": ">=20.0.0"
91
89
  }
92
- }
90
+ }
package/utils/cache.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
3
- * Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
4
4
  * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5
5
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6
6
  * You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -129,7 +129,7 @@ class CommonMemoryCache extends ConnectionObserver {
129
129
  *
130
130
  * @method
131
131
  * @param {string} identifier The identifier of the observed connection.
132
- * @throws {Exception.E_GEN_SYSTEM_CACHE_UNAVAILABLE} If the cache service is no longer available.
132
+ * @throws {TiException.E_GEN_SYSTEM_CACHE_UNAVAILABLE} If the cache service is no longer available.
133
133
  * @override
134
134
  * @public
135
135
  */
@@ -632,7 +632,7 @@ class CommonMemoryCache extends ConnectionObserver {
632
632
  * @method
633
633
  * @param {string} key
634
634
  * @param {Object} value
635
- * @param {string} [path='&']
635
+ * @param {string} [path="$"]
636
636
  * @param {number} [overrideMode=0] By default this allows full override for existing keys.
637
637
  * Option 1 will set the key only if it doesn't already exist. Option 2 will set it only if it already exists.
638
638
  * @returns {Promise}
@@ -642,7 +642,7 @@ class CommonMemoryCache extends ConnectionObserver {
642
642
  return new Promise( ( resolve, reject ) => {
643
643
  if ( this.#isOperational === true ) {
644
644
  if ( this.#redisClient.isJSONSupported ) {
645
- let commandArguments = [ redis.cacheCommands.JSON_SET, key, path, tools.stringifyJSON( value ) ];
645
+ let commandArguments = [ redis.cacheCommands.JSON_SET, key, this.#normalizeJSONPath( path ), tools.stringifyJSON( value ) ];
646
646
  if ( overrideMode !== 0 ) {
647
647
  commandArguments.push( overrideMode === 1 ? redis.cacheOverrideMode.NX : redis.cacheOverrideMode.XX );
648
648
  }
@@ -652,7 +652,7 @@ class CommonMemoryCache extends ConnectionObserver {
652
652
  reject( error );
653
653
  } );
654
654
  } else {
655
- reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ), { details: "No RedisJSON module installed on server." } );
655
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
656
656
  }
657
657
  } else {
658
658
  reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
@@ -667,7 +667,7 @@ class CommonMemoryCache extends ConnectionObserver {
667
667
  *
668
668
  * @method
669
669
  * @param {string} key
670
- * @param {string} path
670
+ * @param {string} [path="$"]
671
671
  * @returns {Promise<Object>}
672
672
  * @public
673
673
  */
@@ -675,14 +675,45 @@ class CommonMemoryCache extends ConnectionObserver {
675
675
  return new Promise( ( resolve, reject ) => {
676
676
  if ( this.#isOperational === true ) {
677
677
  if ( this.#redisClient.isJSONSupported ) {
678
- let commandArguments = [ redis.cacheCommands.JSON_GET, key, path ];
678
+ let commandArguments = [ redis.cacheCommands.JSON_GET, key, this.#normalizeJSONPath( path ) ];
679
679
  this.#redisClient.callCommand( commandArguments ).then( ( result ) => {
680
- resolve( tools.parseJSON( result ) );
680
+ resolve( result != null ? tools.parseJSON( String( result ) ) : null );
681
681
  } ).catch( ( error ) => {
682
682
  reject( error );
683
683
  } );
684
684
  } else {
685
- reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ), { details: "No RedisJSON module installed on server." } );
685
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
686
+ }
687
+ } else {
688
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
689
+ }
690
+ } );
691
+ }
692
+
693
+ /**
694
+ * Used to update/edit an existing JSON variable.
695
+ * <br/>
696
+ * NOTE: Requires ReJSON module installed on server to work.
697
+ *
698
+ * @method
699
+ * @param {string} key
700
+ * @param {Object} value
701
+ * @param {string} [path="$"]
702
+ * @returns {Promise}
703
+ * @public
704
+ */
705
+ editJSON( key, value, path = "$" ) {
706
+ return new Promise( ( resolve, reject ) => {
707
+ if ( this.#isOperational === true ) {
708
+ if ( this.#redisClient.isJSONSupported ) {
709
+ let commandArguments = [ redis.cacheCommands.JSON_MERGE, key, this.#normalizeJSONPath( path ), tools.stringifyJSON( value ) ];
710
+ this.#redisClient.callCommand( commandArguments ).then( () => {
711
+ resolve();
712
+ } ).catch( ( error ) => {
713
+ reject( error );
714
+ } );
715
+ } else {
716
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
686
717
  }
687
718
  } else {
688
719
  reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
@@ -698,7 +729,7 @@ class CommonMemoryCache extends ConnectionObserver {
698
729
  * @method
699
730
  * @param {string} key
700
731
  * @param {Object} value
701
- * @param {string} path
732
+ * @param {string} [path="$"]
702
733
  * @returns {Promise}
703
734
  * @public
704
735
  */
@@ -706,14 +737,14 @@ class CommonMemoryCache extends ConnectionObserver {
706
737
  return new Promise( ( resolve, reject ) => {
707
738
  if ( this.#isOperational === true ) {
708
739
  if ( this.#redisClient.isJSONSupported ) {
709
- let commandArguments = [ redis.cacheCommands.JSON_ARRAY_APPEND, key, path, tools.stringifyJSON( value ) ];
740
+ let commandArguments = [ redis.cacheCommands.JSON_ARRAY_APPEND, key, this.#normalizeJSONPath( path ), tools.stringifyJSON( value ) ];
710
741
  this.#redisClient.callCommand( commandArguments ).then( () => {
711
742
  resolve();
712
743
  } ).catch( ( error ) => {
713
744
  reject( error );
714
745
  } );
715
746
  } else {
716
- reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ), { details: "No RedisJSON module installed on server." } );
747
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
717
748
  }
718
749
  } else {
719
750
  reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
@@ -721,6 +752,20 @@ class CommonMemoryCache extends ConnectionObserver {
721
752
  } );
722
753
  }
723
754
 
755
+ /* Private interface */
756
+
757
+ /**
758
+ * Used to normalize a JSON path.
759
+ *
760
+ * @method
761
+ * @param {string} path
762
+ * @returns {string}
763
+ * @private
764
+ */
765
+ #normalizeJSONPath( path ) {
766
+ return ( path.startsWith( "$" ) === false ) ? ( "$." + path ) : path;
767
+ }
768
+
724
769
  }
725
770
 
726
771
  const instance = new CommonMemoryCache();