@ti-engine/core 1.6.0 → 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 +18 -0
- package/README.md +55 -6
- package/bin/start-instance.js +9 -1
- package/components/exchange/message-handler.js +3 -2
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
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
|
+
|
|
18
|
+
## Version 1.6.1
|
|
19
|
+
|
|
20
|
+
* build(deps): update `@dotenvx/dotenvx` from ^1.73.1 to ^1.75.1
|
|
21
|
+
* build(deps): remove `zeromq` as optional dependency
|
|
22
|
+
|
|
5
23
|
## Version 1.6.0
|
|
6
24
|
|
|
7
25
|
* refactor(exchange)!: replace the blake2 keyed hash with native `node:crypto` HMAC-SHA256 in the message integrity check
|
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 **
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
package/bin/start-instance.js
CHANGED
|
@@ -29,7 +29,15 @@ const envFilePath = ( () => {
|
|
|
29
29
|
return envPath;
|
|
30
30
|
} )();
|
|
31
31
|
|
|
32
|
-
|
|
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
|
-
|
|
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.
|
|
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.73.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.
|
|
88
|
+
"node": ">=20.12.0"
|
|
90
89
|
}
|
|
91
90
|
}
|