@ti-engine/core 1.6.1 → 1.7.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  This document contains the list of changes made to the framework. The format is based on the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
4
4
 
5
+ ## Version 1.7.1
6
+
7
+ * fix(exchange): preserve a configured-but-falsy `securityHashKey` — only a truly absent (`null`/`undefined`) value falls back to the empty key, so a configured `0`/`false` is no longer silently downgraded to the insecure empty-key path (PR #83, CodeRabbit)
8
+ * build(release): bump package version from `1.7.0` to `1.7.1`
9
+
10
+ ## Version 1.7.0
11
+
12
+ * refactor(core)!: load the `.env` file via native `process.loadEnvFile` instead of `@dotenvx/dotenvx`
13
+ * build(deps)!: remove the `@dotenvx/dotenvx` dependency — core now has three runtime dependencies (`ioredis`, `lodash`, `node-schedule`)
14
+ * build(core)!: raise the minimum Node.js version to `>=20.12.0` (required for `process.loadEnvFile`)
15
+
16
+ > The native loader matches the prior behavior: a missing `.env` file is tolerated (ENOENT is ignored, as with the previous `quiet: true`) and existing environment variables are not overridden. The `--env` / `--env-file` / `--dotenv` / `--dotenv-path` / `-e` CLI aliases for choosing the env-file path are unchanged. Encrypted `.env` files (a dotenvx-only feature this framework never used) are not supported. Consumers on Node 20.0–20.11 must upgrade to Node ≥ 20.12.
17
+
5
18
  ## Version 1.6.1
6
19
 
7
20
  * build(deps): update `@dotenvx/dotenvx` from ^1.73.1 to ^1.75.1
package/README.md CHANGED
@@ -35,7 +35,7 @@ Being a messaging system, the **ti-engine** relies on a message broker for the a
35
35
 
36
36
  To run the basic **ti-engine** framework, you will need a couple of things:
37
37
 
38
- * A local [node.js installation](https://nodejs.org/en/download/) with a minimum version of **18.0.0**
38
+ * A local [node.js installation](https://nodejs.org/en/download/) with a minimum version of **20.12.0** (the `core` package requires `process.loadEnvFile`)
39
39
  * A local or remote [Redis cache installation](https://redis.io/download) with a minimum version of **5.0.14**
40
40
 
41
41
  If you are working under Windows 10+ OS and you need to install Redis, take a look at this [guide](https://redis.com/blog/redis-on-windows-10/). You could also use [Redis Cloud](https://app.redislabs.com/) for development as it offers a free basic account.
@@ -140,17 +140,50 @@ See the following sections for more information on each of them.
140
140
 
141
141
  ### Tier 1 - Message exchange
142
142
 
143
- This is the lowest framework tier, unless we count the actual data objects processed by the framework. As you already know, the foundational **ti-engine** concept is that of a messaging system. Therefore, the first tier provides an abstraction over a chosen message broker (Redis by default). That abstraction makes it easy to switch between message brokers whenever you want to without having to change anything above tier 1. It also provides several bonuses that can speed up your work—message encryption, message tracing, message observers, and others. More details about each of these features will be covered in the section [Using the framework](#using-the-framework).
143
+ This is the lowest framework tier, unless we count the actual data objects processed by the framework. As you already know, the foundational **ti-engine** concept is that of a messaging system. Therefore, the first tier provides an abstraction over a chosen message broker (Redis by default). That abstraction makes it easy to switch between message brokers whenever you want to without having to change anything above tier 1. It also provides several bonuses that can speed up your work—message integrity hashing, message tracing, message observers, and others. More details about each of these features will be covered in the section [Using the framework](#using-the-framework).
144
144
 
145
145
  Another important aspect for you to remember is that the message exchange is entirely _asynchronous_. This helps reduce the system load and optimizes the usage of the available resources. Even so, each node.js process can handle a limited load. Therefore, you should plan for running multiple identical senders and receives to scale your solution. But more on that later.
146
146
 
147
- For now, take a look at the following diagram:
147
+ The sequence below shows a full service-call round trip through the default Redis exchange. Each message is split into a lightweight **envelope** (metadata, carrying the integrity hash) and a **payload** (the operational data): the payload is parked in a shared Redis hash while only the envelope travels through the queue.
148
+
149
+ ```mermaid
150
+ sequenceDiagram
151
+ autonumber
152
+ participant Caller as ServiceConsumer<br/>(ServiceCaller)
153
+ participant SOut as MessageSender<br/>(requests-out)
154
+ participant Redis as Redis<br/>(list queues + payload hash)
155
+ participant RIn as MessageReceiver<br/>(requests-in)
156
+ participant Exec as ServiceProvider<br/>(ServiceExecutor)
157
+
158
+ rect rgb(232, 243, 255)
159
+ Note over Caller,Exec: Request path (blue)
160
+ Caller->>SOut: callService() builds the request envelope
161
+ SOut->>SOut: stamp HMAC-SHA256 hash over the message
162
+ SOut->>Redis: HSET payload into ti:messages:store (field = storeID)
163
+ SOut->>Redis: LPUSH envelope onto ti:messages:pending:{destination}
164
+ RIn->>Redis: BRPOP ti:messages:pending:{domain} (blocks until a message)
165
+ Redis-->>RIn: envelope (payload = storeID)
166
+ RIn->>Redis: HGET + HDEL storeID from ti:messages:store
167
+ Redis-->>RIn: payload, reassembled into the full message
168
+ RIn->>RIn: recompute HMAC, constant-time verify
169
+ RIn->>Exec: deliver the verified message
170
+ Exec->>Exec: resolve handler by alias + version, then run it
171
+ end
172
+
173
+ rect rgb(255, 235, 235)
174
+ Note over Caller,Exec: Response path (red, mirrored route)
175
+ Exec->>Redis: HSET result payload, LPUSH onto ti:messages:processed:{source}:{instanceID}
176
+ Caller->>Redis: BRPOP ti:messages:processed:{source}:{instanceID} (blocks)
177
+ Redis-->>Caller: response envelope, reassembled and verified
178
+ Caller->>Caller: resolve the awaiting Promise with the ServiceCallResult
179
+ end
180
+ ```
148
181
 
149
- ![Message Exchange](https://raw.githubusercontent.com/Belleal/ti-engine/master/packages/core/docs/diagram1.png)
182
+ If the recomputed hash does not match the one on the envelope, the receiver rejects the message with `E_SEC_MESSAGE_TAMPERING_DETECTED` instead of delivering it.
150
183
 
151
- It shows the standard flow of a message exchange between one sender and _n_ identical message receivers. The sender splits each message into an _envelope_ and a _payload_, then stores the payload in the shared cache and enqueues the envelope in the requests (destination) queue. Receivers can subscribe to that queue to fetch enqueued messages and process their contents. During the fetch sequence a receiver assembles the full message by getting the payload from the storage. This process is depicted by the blue flow lines.
184
+ The same queue can be consumed by _n_ identical receivers, which is how you scale a service domain horizontally. The sender splits each message into an _envelope_ and a _payload_, then stores the payload in the shared cache and enqueues the envelope in the requests (destination) queue. Receivers can subscribe to that queue to fetch enqueued messages and process their contents. During the fetch sequence a receiver assembles the full message by getting the payload from the storage. This is the blue (request) path in the diagram above.
152
185
 
153
- After the processing is done, the message payload is modified, and the receiver sends the message back to the original sender using the same mechanism. It again splits the message into an envelope and a payload, stores the payload in the storage, and enqueues the envelope in the sender response (source) queue. The sender will then assemble the message back and process the contained results. This process is depicted by the red flow lines.
186
+ After the processing is done, the message payload is modified, and the receiver sends the message back to the original sender using the same mechanism. It again splits the message into an envelope and a payload, stores the payload in the storage, and enqueues the envelope in the sender response (source) queue. The sender will then assemble the message back and process the contained results. This is the red (response) path in the diagram above.
154
187
 
155
188
  In this scenario the framework uses _Redis lists_ as queues for the message envelopes and _Redis hash_ as message payload storage. The splitting between envelope and payload is done to avoid unnecessary transportation of potentially large volumes of operational data between the microservices. Other message brokers might use a slightly different approach, but they should still adhere to the same logical flow.
156
189
 
@@ -162,6 +195,22 @@ The modules associated with this tier are all located in the `components/exchang
162
195
  * Class `MessageExchange`: the exchange is the actual message processing engine. It handles sending and receiving messages via preconfigured message senders and message receivers.
163
196
  * Class `MessageObserver`: an observer is a custom event listener that can be used to react on message `sent` and `received` events.
164
197
 
198
+ The exchange classes form the hierarchy below. `MessageHandler` is the shared abstract base that owns the integrity hash; the concrete `Default*` classes are the Redis implementation you replace to swap brokers:
199
+
200
+ ```mermaid
201
+ classDiagram
202
+ direction LR
203
+ MessageHandler <|-- MessageSender
204
+ MessageHandler <|-- MessageReceiver
205
+ MessageObserver <|-- MessageExchange
206
+ MessageSender <|-- DefaultMessageSender
207
+ MessageReceiver <|-- DefaultMessageReceiver
208
+ MessageExchange <|-- DefaultMessageExchange
209
+ class MessageHandler {
210
+ +createMessageHash()
211
+ }
212
+ ```
213
+
165
214
  ### Tier 2 - Service domains
166
215
 
167
216
  This tier focuses on hosting and executing the _business logic_ of your application. It consists of _business services_ that process input data and return the result of the processing as output data. The business services are grouped in _service domains_, which are in turn hosted inside stateless _microservices_ also named _service instances_. All microservices are based on the `ServiceInstance` class, which establishes the basic framework structure and provides the basic functionality for the microservice lifecycle. It should not be used directly, however. Instead, there are two child types of `ServiceInstance` in **ti-engine** that you should use to implement your solution:
@@ -29,7 +29,15 @@ const envFilePath = ( () => {
29
29
  return envPath;
30
30
  } )();
31
31
 
32
- require( "@dotenvx/dotenvx" ).config( { path: envFilePath, quiet: true } );
32
+ // Load the resolved .env file using the native Node loader (Node >= 20.12). A missing file is not fatal
33
+ // environment variables may be supplied entirely by the OS/container. Existing process.env values are NOT overridden.
34
+ try {
35
+ process.loadEnvFile( envFilePath );
36
+ } catch ( error ) {
37
+ if ( error.code !== "ENOENT" ) {
38
+ throw error;
39
+ }
40
+ }
33
41
 
34
42
  const tools = require( "#tools" );
35
43
  const logger = require( "#logger" );
@@ -123,7 +123,8 @@ class MessageHandler extends ConnectionObserver {
123
123
  * @public
124
124
  */
125
125
  createMessageHash( message ) {
126
- let key = config.getSetting( config.setting.MESSAGE_EXCHANGE_SECURITY_HASH_KEY );
126
+ const rawKey = config.getSetting( config.setting.MESSAGE_EXCHANGE_SECURITY_HASH_KEY );
127
+ let key = rawKey == null ? "" : String( rawKey );
127
128
  if ( keyWarningEmitted === false ) {
128
129
  keyWarningEmitted = true;
129
130
  if ( !key || key === OLD_DEFAULT_HASH_KEY ) {
@@ -131,7 +132,7 @@ class MessageHandler extends ConnectionObserver {
131
132
  }
132
133
  }
133
134
  let transformed = tools.decomposeJSON( tools.decycle( message ) );
134
- let hmac = crypto.createHmac( "sha256", Buffer.from( key ) );
135
+ let hmac = crypto.createHmac( "sha256", Buffer.from( key, "utf8" ) );
135
136
  hmac.update( Buffer.from( transformed ) );
136
137
  return hmac.digest( "hex" );
137
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.6.1",
3
+ "version": "1.7.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",
@@ -59,7 +59,6 @@
59
59
  "#tools": "./utils/tools.js"
60
60
  },
61
61
  "dependencies": {
62
- "@dotenvx/dotenvx": "^1.75.1",
63
62
  "ioredis": "^5.11.1",
64
63
  "lodash": "^4.18.1",
65
64
  "node-schedule": "^2.1.1"
@@ -86,6 +85,6 @@
86
85
  },
87
86
  "homepage": "https://github.com/Belleal/ti-engine/tree/master/packages/core#readme",
88
87
  "engines": {
89
- "node": ">=20.0.0"
88
+ "node": ">=20.12.0"
90
89
  }
91
90
  }