@hiliosai/sdk 0.1.0 → 0.1.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/INTEGRATION_SERVICE.md +394 -0
- package/package.json +5 -3
- package/src/configs/constants.ts +122 -0
- package/src/configs/index.ts +1 -0
- package/src/configs/moleculer/bulkhead.ts +8 -0
- package/src/configs/moleculer/channels.ts +102 -0
- package/src/configs/moleculer/circuit-breaker.ts +17 -0
- package/src/configs/moleculer/index.ts +85 -0
- package/src/configs/moleculer/logger.ts +17 -0
- package/src/configs/moleculer/metrics.ts +20 -0
- package/src/configs/moleculer/registry.ts +7 -0
- package/src/configs/moleculer/retry-policy.ts +17 -0
- package/src/configs/moleculer/tracing.ts +6 -0
- package/src/configs/moleculer/tracking.ts +6 -0
- package/src/datasources/base.datasource.ts +79 -0
- package/src/datasources/index.ts +1 -0
- package/src/middlewares/context-helpers.middleware.ts +4 -1
- package/src/middlewares/datasource.middleware.ts +3 -1
- package/src/middlewares/index.ts +0 -1
- package/src/mixins/datasource.mixin.ts +118 -0
- package/src/mixins/index.ts +1 -0
- package/src/service/define-integration.ts +177 -61
- package/src/service/define-service.ts +10 -30
- package/src/service/example-user/user.service.ts +2 -10
- package/src/types/context.ts +7 -4
- package/src/types/message.ts +1 -0
- package/src/types/service.ts +3 -4
- package/src/examples/cache-usage.example.ts +0 -293
- package/src/middlewares/cache.middleware.ts +0 -278
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Integration Service Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Integration Services are specialized Moleculer services that provide a standardized way to connect external messaging platforms (WhatsApp, Telegram, Discord, etc.) with the application. They handle webhook reception, message normalization, and platform-specific communication patterns.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
The Integration Service pattern solves several key challenges:
|
|
10
|
+
|
|
11
|
+
1. **Platform Abstraction**: Provides unified interface for different messaging platforms
|
|
12
|
+
2. **Webhook Security**: Implements signature validation and replay attack prevention
|
|
13
|
+
3. **Message Normalization**: Converts platform-specific messages to unified format
|
|
14
|
+
4. **Retry Logic**: Handles transient failures with exponential backoff
|
|
15
|
+
5. **Event Broadcasting**: Emits standardized events for message processing
|
|
16
|
+
6. **Health Monitoring**: Provides health checks and status reporting
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
graph TB
|
|
22
|
+
subgraph "External Platforms"
|
|
23
|
+
WA[WhatsApp]
|
|
24
|
+
TG[Telegram]
|
|
25
|
+
DC[Discord]
|
|
26
|
+
SL[Slack]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph "Integration Layer"
|
|
30
|
+
subgraph "Integration Services"
|
|
31
|
+
WAS[WhatsApp Service]
|
|
32
|
+
TGS[Telegram Service]
|
|
33
|
+
DCS[Discord Service]
|
|
34
|
+
SLS[Slack Service]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
subgraph "Service Actions"
|
|
38
|
+
RW[i_receiveWebhook]
|
|
39
|
+
SM[i_sendMessage]
|
|
40
|
+
HC[i_healthCheck]
|
|
41
|
+
VW[i_verifyWebhook]
|
|
42
|
+
ST[i_status]
|
|
43
|
+
VC[i_validateCredentials]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph "Core Application"
|
|
48
|
+
subgraph "Message Processing"
|
|
49
|
+
MP[Message Processor]
|
|
50
|
+
MR[Message Router]
|
|
51
|
+
MS[Message Store]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
subgraph "Event System"
|
|
55
|
+
EB[Event Bus]
|
|
56
|
+
EH[Event Handlers]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
subgraph "Infrastructure"
|
|
61
|
+
DS[Datasources]
|
|
62
|
+
CA[Cache]
|
|
63
|
+
PE[Permissions]
|
|
64
|
+
CH[Context Helpers]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
%% External webhook flows
|
|
68
|
+
WA -->|Webhook| WAS
|
|
69
|
+
TG -->|Webhook| TGS
|
|
70
|
+
DC -->|Webhook| DCS
|
|
71
|
+
SL -->|Webhook| SLS
|
|
72
|
+
|
|
73
|
+
%% Integration service actions
|
|
74
|
+
WAS --> RW
|
|
75
|
+
WAS --> SM
|
|
76
|
+
WAS --> HC
|
|
77
|
+
|
|
78
|
+
%% Service infrastructure
|
|
79
|
+
WAS --> DS
|
|
80
|
+
TGS --> CA
|
|
81
|
+
DCS --> PE
|
|
82
|
+
SLS --> CH
|
|
83
|
+
|
|
84
|
+
%% Event flows
|
|
85
|
+
RW -->|integration.message.received| EB
|
|
86
|
+
SM -->|integration.message.sent| EB
|
|
87
|
+
SM -->|integration.message.failed| EB
|
|
88
|
+
|
|
89
|
+
%% Processing flows
|
|
90
|
+
EB --> EH
|
|
91
|
+
EH --> MP
|
|
92
|
+
MP --> MR
|
|
93
|
+
MR --> MS
|
|
94
|
+
|
|
95
|
+
%% Response flows
|
|
96
|
+
MP -->|Send Response| SM
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Integration Service Structure
|
|
100
|
+
|
|
101
|
+
### Core Components
|
|
102
|
+
|
|
103
|
+
1. **defineIntegration()** - Factory function that creates integration services
|
|
104
|
+
2. **Security Helpers** - Webhook validation, timestamp checking, correlation IDs
|
|
105
|
+
3. **Retry Mechanism** - Exponential backoff for failed operations
|
|
106
|
+
4. **Standard Actions** - Predefined actions for common integration operations
|
|
107
|
+
|
|
108
|
+
### Standard Actions
|
|
109
|
+
|
|
110
|
+
All integration services automatically get these actions:
|
|
111
|
+
|
|
112
|
+
| Action | REST Endpoint | Purpose |
|
|
113
|
+
|--------|---------------|---------|
|
|
114
|
+
| `i_receiveWebhook` | `POST /webhook` | Receive and process incoming webhooks |
|
|
115
|
+
| `i_sendMessage` | `POST /send` | Send messages to external platform |
|
|
116
|
+
| `i_healthCheck` | `GET /health` | Check integration health status |
|
|
117
|
+
| `i_verifyWebhook` | `GET /webhook` | Verify webhook subscriptions |
|
|
118
|
+
| `i_status` | `GET /status` | Get integration status information |
|
|
119
|
+
| `i_validateCredentials` | `POST /validate` | Validate platform credentials |
|
|
120
|
+
|
|
121
|
+
### Service Methods
|
|
122
|
+
|
|
123
|
+
Integration-specific logic is implemented as service methods:
|
|
124
|
+
|
|
125
|
+
| Method | Purpose | Required |
|
|
126
|
+
|--------|---------|----------|
|
|
127
|
+
| `normalize()` | Convert platform message to unified format | ✅ |
|
|
128
|
+
| `sendMessage()` | Send message to platform | ✅ |
|
|
129
|
+
| `transform()` | Transform outbound message to platform format | ❌ |
|
|
130
|
+
| `validateWebhook()` | Validate webhook payload structure | ❌ |
|
|
131
|
+
| `validateSignature()` | Validate webhook signature | ❌ |
|
|
132
|
+
| `verifyWebhook()` | Handle webhook verification challenge | ❌ |
|
|
133
|
+
| `checkHealth()` | Custom health check logic | ❌ |
|
|
134
|
+
| `validateCredentials()` | Validate platform credentials | ❌ |
|
|
135
|
+
|
|
136
|
+
## Usage Example
|
|
137
|
+
|
|
138
|
+
### Basic WhatsApp Integration
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import {defineIntegration} from '@pkg/sdk';
|
|
142
|
+
import type {BaseIntegration, NormalizedMessage, WebhookEvent} from '@pkg/sdk';
|
|
143
|
+
|
|
144
|
+
const whatsappIntegration: BaseIntegration = {
|
|
145
|
+
id: 'whatsapp-business',
|
|
146
|
+
name: 'WhatsApp Business',
|
|
147
|
+
platform: 'whatsapp',
|
|
148
|
+
version: '1.0.0',
|
|
149
|
+
status: 'ACTIVE',
|
|
150
|
+
capabilities: ['SEND_MESSAGE', 'RECEIVE_MESSAGE', 'WEBHOOK_VALIDATION']
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default defineIntegration({
|
|
154
|
+
name: 'whatsapp',
|
|
155
|
+
integration: whatsappIntegration,
|
|
156
|
+
|
|
157
|
+
// Required: Convert WhatsApp webhook to normalized message
|
|
158
|
+
async normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]> {
|
|
159
|
+
const messages: NormalizedMessage[] = [];
|
|
160
|
+
|
|
161
|
+
for (const entry of webhook.rawPayload.entry || []) {
|
|
162
|
+
for (const change of entry.changes || []) {
|
|
163
|
+
if (change.field === 'messages') {
|
|
164
|
+
for (const message of change.value.messages || []) {
|
|
165
|
+
messages.push({
|
|
166
|
+
id: message.id,
|
|
167
|
+
conversationId: message.from,
|
|
168
|
+
from: {
|
|
169
|
+
id: message.from,
|
|
170
|
+
name: change.value.contacts?.[0]?.profile?.name,
|
|
171
|
+
},
|
|
172
|
+
to: {
|
|
173
|
+
id: webhook.tenantId,
|
|
174
|
+
},
|
|
175
|
+
content: {
|
|
176
|
+
type: message.type === 'text' ? 'TEXT' : 'FILE',
|
|
177
|
+
text: message.text?.body,
|
|
178
|
+
media: message.image ? {
|
|
179
|
+
url: message.image.id,
|
|
180
|
+
mimeType: message.image.mime_type
|
|
181
|
+
} : undefined
|
|
182
|
+
},
|
|
183
|
+
timestamp: parseInt(message.timestamp) * 1000,
|
|
184
|
+
platform: 'whatsapp',
|
|
185
|
+
metadata: {
|
|
186
|
+
phoneNumberId: change.value.metadata.phone_number_id
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return messages;
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// Required: Send message to WhatsApp
|
|
198
|
+
async sendMessage(ctx, message, config) {
|
|
199
|
+
const phoneNumberId = config.credentials.phoneNumberId;
|
|
200
|
+
const accessToken = config.credentials.accessToken;
|
|
201
|
+
|
|
202
|
+
const response = await fetch(
|
|
203
|
+
`https://graph.facebook.com/v18.0/${phoneNumberId}/messages`,
|
|
204
|
+
{
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: {
|
|
207
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
208
|
+
'Content-Type': 'application/json'
|
|
209
|
+
},
|
|
210
|
+
body: JSON.stringify({
|
|
211
|
+
messaging_product: 'whatsapp',
|
|
212
|
+
to: message.to.id,
|
|
213
|
+
text: { body: message.content.text }
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
const error = await response.text();
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: new Error(`WhatsApp API error: ${error}`)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = await response.json();
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
messageId: result.messages[0].id,
|
|
230
|
+
metadata: { phoneNumberId }
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
// Optional: Validate webhook signature
|
|
235
|
+
validateSignature(webhook) {
|
|
236
|
+
const signature = webhook.headers['x-hub-signature-256'];
|
|
237
|
+
const secret = process.env.WHATSAPP_WEBHOOK_SECRET;
|
|
238
|
+
|
|
239
|
+
if (!signature || !secret) return false;
|
|
240
|
+
|
|
241
|
+
const expectedSignature = 'sha256=' +
|
|
242
|
+
crypto.createHmac('sha256', secret)
|
|
243
|
+
.update(webhook.rawBody || '')
|
|
244
|
+
.digest('hex');
|
|
245
|
+
|
|
246
|
+
return SecurityHelpers.secureCompare(signature, expectedSignature);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
// Optional: Handle webhook verification
|
|
250
|
+
verifyWebhook(params) {
|
|
251
|
+
const { mode, token, challenge } = params;
|
|
252
|
+
const verifyToken = process.env.WHATSAPP_VERIFY_TOKEN;
|
|
253
|
+
|
|
254
|
+
if (mode === 'subscribe' && token === verifyToken) {
|
|
255
|
+
return challenge;
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// Optional: Custom health check
|
|
261
|
+
async checkHealth(ctx, config) {
|
|
262
|
+
try {
|
|
263
|
+
const response = await fetch(
|
|
264
|
+
`https://graph.facebook.com/v18.0/${config.credentials.phoneNumberId}`,
|
|
265
|
+
{
|
|
266
|
+
headers: {
|
|
267
|
+
'Authorization': `Bearer ${config.credentials.accessToken}`
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
status: response.ok ? 'healthy' : 'unhealthy',
|
|
274
|
+
message: response.ok ? 'WhatsApp API accessible' : 'WhatsApp API error',
|
|
275
|
+
details: {
|
|
276
|
+
statusCode: response.status,
|
|
277
|
+
timestamp: new Date().toISOString()
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return {
|
|
282
|
+
status: 'unhealthy',
|
|
283
|
+
message: error.message,
|
|
284
|
+
details: { timestamp: new Date().toISOString() }
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Advanced Integration with Datasources and Cache
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import {defineIntegration} from '@pkg/sdk';
|
|
295
|
+
import {WhatsAppDatasource} from './datasources';
|
|
296
|
+
|
|
297
|
+
export default defineIntegration({
|
|
298
|
+
name: 'whatsapp',
|
|
299
|
+
integration: whatsappIntegration,
|
|
300
|
+
|
|
301
|
+
// Configure per-service datasources
|
|
302
|
+
datasources: {
|
|
303
|
+
whatsapp: WhatsAppDatasource
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// Configure per-service cache
|
|
307
|
+
cache: {
|
|
308
|
+
redisUrl: process.env.WHATSAPP_REDIS_URL,
|
|
309
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
310
|
+
namespace: 'whatsapp'
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
async normalize(webhook) {
|
|
314
|
+
// Access datasource via context in actions
|
|
315
|
+
// this.datasources is not available in methods
|
|
316
|
+
// Use dependency injection pattern instead
|
|
317
|
+
return await this.processWebhookMessages(webhook);
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
async sendMessage(ctx, message, config) {
|
|
321
|
+
// Use cache for rate limiting
|
|
322
|
+
const rateLimitKey = `rate_limit:${config.credentials.phoneNumberId}`;
|
|
323
|
+
const currentCount = await ctx.cache.get(rateLimitKey) || 0;
|
|
324
|
+
|
|
325
|
+
if (currentCount >= 1000) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: new Error('Rate limit exceeded')
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Send message...
|
|
333
|
+
const result = await this.sendToWhatsApp(message, config);
|
|
334
|
+
|
|
335
|
+
// Update rate limit counter
|
|
336
|
+
await ctx.cache.set(rateLimitKey, currentCount + 1, 60 * 60 * 1000);
|
|
337
|
+
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Event Flow
|
|
344
|
+
|
|
345
|
+
### Inbound Message Flow
|
|
346
|
+
|
|
347
|
+
1. **Webhook Received** → `i_receiveWebhook` action
|
|
348
|
+
2. **Security Validation** → Timestamp, signature, payload checks
|
|
349
|
+
3. **Message Normalization** → Convert to unified format
|
|
350
|
+
4. **Event Emission** → `integration.message.received` event
|
|
351
|
+
5. **Message Processing** → Application-specific logic
|
|
352
|
+
6. **Response Generation** → Optional automated responses
|
|
353
|
+
|
|
354
|
+
### Outbound Message Flow
|
|
355
|
+
|
|
356
|
+
1. **Send Request** → `i_sendMessage` action
|
|
357
|
+
2. **Message Transformation** → Convert to platform format
|
|
358
|
+
3. **Retry Logic** → Exponential backoff for failures
|
|
359
|
+
4. **Platform API Call** → Send via platform API
|
|
360
|
+
5. **Event Emission** → `integration.message.sent` or `integration.message.failed`
|
|
361
|
+
6. **Status Tracking** → Update message delivery status
|
|
362
|
+
|
|
363
|
+
## Security Features
|
|
364
|
+
|
|
365
|
+
### Webhook Validation
|
|
366
|
+
|
|
367
|
+
- **Signature Verification**: HMAC-SHA256 validation using platform secrets
|
|
368
|
+
- **Replay Attack Prevention**: Timestamp validation (5-minute window)
|
|
369
|
+
- **Timing Attack Protection**: Constant-time string comparison
|
|
370
|
+
- **Correlation IDs**: Request tracking for debugging and audit trails
|
|
371
|
+
|
|
372
|
+
### Best Practices
|
|
373
|
+
|
|
374
|
+
1. **Always validate webhook signatures** in production
|
|
375
|
+
2. **Use environment variables** for secrets and tokens
|
|
376
|
+
3. **Implement proper error handling** with structured errors
|
|
377
|
+
4. **Add correlation IDs** for request tracking
|
|
378
|
+
5. **Use retry logic** for transient failures
|
|
379
|
+
6. **Monitor health endpoints** for platform availability
|
|
380
|
+
7. **Cache frequently accessed data** to reduce API calls
|
|
381
|
+
|
|
382
|
+
## Infrastructure Integration
|
|
383
|
+
|
|
384
|
+
Integration services automatically inherit:
|
|
385
|
+
|
|
386
|
+
- **Datasource Access**: Database connections, external APIs
|
|
387
|
+
- **Cache Layer**: Redis/in-memory caching with TTL
|
|
388
|
+
- **Permission System**: Role-based access control
|
|
389
|
+
- **Context Helpers**: Common utilities and operations
|
|
390
|
+
- **Memoization**: Action result caching
|
|
391
|
+
- **Health Monitoring**: Automated health checks
|
|
392
|
+
- **Event System**: Pub/sub event broadcasting
|
|
393
|
+
|
|
394
|
+
This architecture provides a robust, scalable foundation for building messaging platform integrations while maintaining consistency, security, and observability across all platforms.
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiliosai/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsup",
|
|
10
|
-
"format": "bunx prettier --write ./**/*.{ts,json,md,yaml}"
|
|
10
|
+
"format": "bunx prettier --write ./**/*.{ts,json,md,yaml}",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
14
|
"@keyv/redis": "5.1.5",
|
|
14
15
|
"@ltv/env": "4.0.3",
|
|
16
|
+
"@moleculer/channels": "0.2.0",
|
|
15
17
|
"keyv": "5.5.5",
|
|
16
18
|
"lodash": "4.17.21",
|
|
17
19
|
"moleculer": "0.14.35"
|
|
@@ -24,4 +26,4 @@
|
|
|
24
26
|
"bun-types": "latest"
|
|
25
27
|
},
|
|
26
28
|
"prettier": "@hiliosai/prettier"
|
|
27
|
-
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import env from '@ltv/env';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base namespace for all channels
|
|
5
|
+
*/
|
|
6
|
+
export const NAMESPACE = env.string('NAMESPACE', 'hios').toLowerCase();
|
|
7
|
+
|
|
8
|
+
export const CHANNELS = {
|
|
9
|
+
// Webhook processing channels
|
|
10
|
+
WEBHOOK: {
|
|
11
|
+
// Pattern: hios.webhook.{tenantId}.{platform}
|
|
12
|
+
PATTERN: `${NAMESPACE}.webhook.*.*`,
|
|
13
|
+
PREFIX: `${NAMESPACE}.webhook`,
|
|
14
|
+
build: (tenantId: string, platform: string) =>
|
|
15
|
+
`${NAMESPACE}.webhook.${tenantId}.${platform}`,
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// Message processing channels
|
|
19
|
+
PROCESSING: {
|
|
20
|
+
// Pattern: hios.processing.{tenantId}.{messageType}
|
|
21
|
+
PATTERN: `${NAMESPACE}.processing.*.*`,
|
|
22
|
+
PREFIX: `${NAMESPACE}.processing`,
|
|
23
|
+
build: (tenantId: string, messageType: string) =>
|
|
24
|
+
`${NAMESPACE}.processing.${tenantId}.${messageType}`,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Response/outbound message channels
|
|
28
|
+
RESPONSE: {
|
|
29
|
+
// Pattern: hios.response.{tenantId}.{platform}
|
|
30
|
+
PATTERN: `${NAMESPACE}.response.*.*`,
|
|
31
|
+
PREFIX: `${NAMESPACE}.response`,
|
|
32
|
+
build: (tenantId: string, platform: string) =>
|
|
33
|
+
`${NAMESPACE}.response.${tenantId}.${platform}`,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// System channels
|
|
37
|
+
SYSTEM: {
|
|
38
|
+
// Error handling
|
|
39
|
+
ERRORS: `${NAMESPACE}.system.errors`,
|
|
40
|
+
|
|
41
|
+
// Metrics and monitoring
|
|
42
|
+
METRICS: `${NAMESPACE}.system.metrics`,
|
|
43
|
+
|
|
44
|
+
// Health checks
|
|
45
|
+
HEALTH: `${NAMESPACE}.system.health`,
|
|
46
|
+
|
|
47
|
+
// Integration lifecycle events
|
|
48
|
+
INTEGRATION_REGISTERED: `${NAMESPACE}.system.integration.registered`,
|
|
49
|
+
INTEGRATION_UNREGISTERED: `${NAMESPACE}.system.integration.unregistered`,
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Dead letter queues
|
|
53
|
+
DLQ: {
|
|
54
|
+
// Failed webhook processing
|
|
55
|
+
WEBHOOK_FAILED: `${NAMESPACE}.dlq.webhook.failed`,
|
|
56
|
+
|
|
57
|
+
// Failed message sends
|
|
58
|
+
SEND_FAILED: `${NAMESPACE}.dlq.send.failed`,
|
|
59
|
+
|
|
60
|
+
// Failed processing
|
|
61
|
+
PROCESSING_FAILED: `${NAMESPACE}.dlq.processing.failed`,
|
|
62
|
+
|
|
63
|
+
// Build DLQ name for specific integration
|
|
64
|
+
buildSendFailed: (platform: string) =>
|
|
65
|
+
`${NAMESPACE}.dlq.send.${platform}.failed`,
|
|
66
|
+
},
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Channel Configuration Constants
|
|
71
|
+
*/
|
|
72
|
+
export const CHANNEL_CONFIG = {
|
|
73
|
+
// Default settings for message channels
|
|
74
|
+
DEFAULTS: {
|
|
75
|
+
maxInFlight: 10,
|
|
76
|
+
maxRetries: 3,
|
|
77
|
+
deadLettering: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// High-priority channels (webhooks)
|
|
83
|
+
HIGH_PRIORITY: {
|
|
84
|
+
maxInFlight: 50,
|
|
85
|
+
maxRetries: 5,
|
|
86
|
+
deadLettering: {
|
|
87
|
+
enabled: true,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Low-priority channels (metrics, logs)
|
|
92
|
+
LOW_PRIORITY: {
|
|
93
|
+
maxInFlight: 5,
|
|
94
|
+
maxRetries: 1,
|
|
95
|
+
deadLettering: {
|
|
96
|
+
enabled: false,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
} as const;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Subject patterns for NATS JetStream
|
|
103
|
+
*/
|
|
104
|
+
export const SUBJECTS = {
|
|
105
|
+
// All webhook subjects
|
|
106
|
+
WEBHOOK_ALL: `${NAMESPACE}.webhook.>`,
|
|
107
|
+
|
|
108
|
+
// All processing subjects
|
|
109
|
+
PROCESSING_ALL: `${NAMESPACE}.processing.>`,
|
|
110
|
+
|
|
111
|
+
// All response subjects
|
|
112
|
+
RESPONSE_ALL: `${NAMESPACE}.response.>`,
|
|
113
|
+
|
|
114
|
+
// All system subjects
|
|
115
|
+
SYSTEM_ALL: `${NAMESPACE}.system.>`,
|
|
116
|
+
|
|
117
|
+
// All DLQ subjects
|
|
118
|
+
DLQ_ALL: `${NAMESPACE}.dlq.>`,
|
|
119
|
+
|
|
120
|
+
// Wildcard for all HIOS subjects
|
|
121
|
+
ALL: `${NAMESPACE}.>`,
|
|
122
|
+
} as const;
|
package/src/configs/index.ts
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import env from '@ltv/env';
|
|
2
|
+
import {Middleware as createChannelsMiddleware} from '@moleculer/channels';
|
|
3
|
+
import {type Middleware} from 'moleculer';
|
|
4
|
+
|
|
5
|
+
import {SUBJECTS, NAMESPACE as cfgNamespace} from '../constants';
|
|
6
|
+
|
|
7
|
+
const NAMESPACE = cfgNamespace.toUpperCase();
|
|
8
|
+
|
|
9
|
+
const middleware = createChannelsMiddleware({
|
|
10
|
+
adapter: {
|
|
11
|
+
type: 'NATS',
|
|
12
|
+
options: {
|
|
13
|
+
nats: {
|
|
14
|
+
url: env.string('NATS_URL', 'nats://localhost:4222'),
|
|
15
|
+
|
|
16
|
+
/** Connection options for reliability */
|
|
17
|
+
connectionOptions: {
|
|
18
|
+
name: 'hios',
|
|
19
|
+
timeout: 10000,
|
|
20
|
+
reconnect: true,
|
|
21
|
+
maxReconnectAttempts: 10,
|
|
22
|
+
reconnectTimeWait: 2000,
|
|
23
|
+
maxReconnectTimeWait: 30000,
|
|
24
|
+
pingInterval: 20000,
|
|
25
|
+
maxPingOut: 2,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Stream configuration for multi-tenant messaging
|
|
30
|
+
*
|
|
31
|
+
* Environment variables for production:
|
|
32
|
+
* - NATS_MAX_MESSAGES: Default 100K (dev) -> 10M+ (prod)
|
|
33
|
+
* - NATS_MAX_BYTES_GB: Default 1GB (dev) -> 100GB+ (prod)
|
|
34
|
+
* - NATS_MAX_AGE_DAYS: Default 7 (dev) -> 30+ (prod)
|
|
35
|
+
* - NATS_MAX_MSG_SIZE_MB: Default 1MB (dev) -> 5MB (prod)
|
|
36
|
+
* - NATS_REPLICAS: Default 1 (dev) -> 3 (prod)
|
|
37
|
+
*/
|
|
38
|
+
streamConfig: {
|
|
39
|
+
name: `${NAMESPACE}_MESSAGES`,
|
|
40
|
+
subjects: [
|
|
41
|
+
SUBJECTS.WEBHOOK_ALL,
|
|
42
|
+
SUBJECTS.PROCESSING_ALL,
|
|
43
|
+
SUBJECTS.RESPONSE_ALL,
|
|
44
|
+
SUBJECTS.SYSTEM_ALL,
|
|
45
|
+
SUBJECTS.DLQ_ALL,
|
|
46
|
+
],
|
|
47
|
+
retention: 'limits',
|
|
48
|
+
max_msgs: env.int('NATS_MAX_MESSAGES', 100_000), // 100K for dev, 10M+ for prod
|
|
49
|
+
max_bytes: env.int('NATS_MAX_BYTES_GB', 1) * 1024 * 1024 * 1024, // 1GB for dev, 100GB+ for prod
|
|
50
|
+
max_age: env.int('NATS_MAX_AGE_DAYS', 7) * 24 * 60 * 60 * 1000000000, // 7 days dev, 30+ days prod
|
|
51
|
+
max_msg_size: env.int('NATS_MAX_MSG_SIZE_MB', 1) * 1024 * 1024, // 1MB dev, 5MB prod
|
|
52
|
+
storage: 'file', // Persistent storage
|
|
53
|
+
num_replicas: env.int('NATS_REPLICAS', 1), // 1 for dev, 3 for prod
|
|
54
|
+
discard: 'old', // Remove old messages when limits hit
|
|
55
|
+
duplicate_window: 2 * 60 * 1000000000, // 2 minutes dedup window
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/** Consumer options optimized for LLM processing */
|
|
59
|
+
consumerOptions: {
|
|
60
|
+
config: {
|
|
61
|
+
// Start with new messages (don't replay old ones on restart)
|
|
62
|
+
deliver_policy: 'new',
|
|
63
|
+
|
|
64
|
+
// Explicit acknowledgment required (critical for LLM processing)
|
|
65
|
+
ack_policy: 'explicit',
|
|
66
|
+
|
|
67
|
+
// Allow 5 unacknowledged messages per consumer (rate limiting)
|
|
68
|
+
max_ack_pending: 5,
|
|
69
|
+
|
|
70
|
+
// Acknowledgment timeout for LLM processing (2 minutes)
|
|
71
|
+
ack_wait: 120 * 1000000000, // 2 minutes in nanoseconds
|
|
72
|
+
|
|
73
|
+
// Maximum delivery attempts before dead letter
|
|
74
|
+
max_deliver: 3,
|
|
75
|
+
|
|
76
|
+
// Backoff for failed message retries
|
|
77
|
+
backoff: [
|
|
78
|
+
1000000000, // 1 second
|
|
79
|
+
5000000000, // 5 seconds
|
|
80
|
+
30000000000, // 30 seconds
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/** Application-level flow control */
|
|
87
|
+
maxInFlight: 5, // Limit concurrent LLM requests per service
|
|
88
|
+
maxRetries: 2, // App-level retries (NATS handles delivery retries)
|
|
89
|
+
|
|
90
|
+
/** Dead letter queue for failed messages */
|
|
91
|
+
deadLettering: {
|
|
92
|
+
enabled: true,
|
|
93
|
+
queueName: 'FAILED_MESSAGES',
|
|
94
|
+
// Send to dead letter after NATS max_deliver attempts
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}) as unknown as Middleware;
|
|
99
|
+
|
|
100
|
+
export const ChannelsMiddleware: Middleware = {
|
|
101
|
+
...middleware,
|
|
102
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {Errors} from 'moleculer';
|
|
2
|
+
|
|
3
|
+
export const circuitBreakerConfig = {
|
|
4
|
+
// Enable feature
|
|
5
|
+
enabled: false,
|
|
6
|
+
// Threshold value. 0.5 means that 50% should be failed for tripping.
|
|
7
|
+
threshold: 0.5,
|
|
8
|
+
// Minimum request count. Below it, CB does not trip.
|
|
9
|
+
minRequestCount: 20,
|
|
10
|
+
// Number of seconds for time window.
|
|
11
|
+
windowTime: 60,
|
|
12
|
+
// Number of milliseconds to switch from open to half-open state
|
|
13
|
+
halfOpenTime: 10 * 1000,
|
|
14
|
+
// A function to check failed requests.
|
|
15
|
+
check: (err: Errors.MoleculerError | Error) =>
|
|
16
|
+
(err as Errors.MoleculerError).code >= 500,
|
|
17
|
+
};
|