@venizia/ignis-docs 0.0.5 → 0.0.6-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.
Files changed (98) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architecture-decisions.md +0 -8
  3. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  4. package/wiki/best-practices/performance-optimization.md +3 -3
  5. package/wiki/best-practices/security-guidelines.md +2 -2
  6. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  7. package/wiki/guides/core-concepts/components-guide.md +1 -1
  8. package/wiki/guides/core-concepts/components.md +2 -2
  9. package/wiki/guides/core-concepts/dependency-injection.md +1 -1
  10. package/wiki/guides/core-concepts/services.md +1 -1
  11. package/wiki/guides/tutorials/building-a-crud-api.md +1 -1
  12. package/wiki/guides/tutorials/ecommerce-api.md +2 -2
  13. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  14. package/wiki/guides/tutorials/testing.md +1 -1
  15. package/wiki/references/base/bootstrapping.md +0 -2
  16. package/wiki/references/base/components.md +2 -2
  17. package/wiki/references/base/controllers.md +0 -1
  18. package/wiki/references/base/datasources.md +1 -1
  19. package/wiki/references/base/dependency-injection.md +1 -1
  20. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  21. package/wiki/references/base/middlewares.md +0 -8
  22. package/wiki/references/base/providers.md +0 -9
  23. package/wiki/references/base/services.md +0 -1
  24. package/wiki/references/components/authentication/api.md +444 -0
  25. package/wiki/references/components/authentication/errors.md +177 -0
  26. package/wiki/references/components/authentication/index.md +571 -0
  27. package/wiki/references/components/authentication/usage.md +781 -0
  28. package/wiki/references/components/health-check.md +292 -103
  29. package/wiki/references/components/index.md +14 -12
  30. package/wiki/references/components/mail/api.md +505 -0
  31. package/wiki/references/components/mail/errors.md +176 -0
  32. package/wiki/references/components/mail/index.md +535 -0
  33. package/wiki/references/components/mail/usage.md +404 -0
  34. package/wiki/references/components/request-tracker.md +229 -25
  35. package/wiki/references/components/socket-io/api.md +1051 -0
  36. package/wiki/references/components/socket-io/errors.md +119 -0
  37. package/wiki/references/components/socket-io/index.md +410 -0
  38. package/wiki/references/components/socket-io/usage.md +322 -0
  39. package/wiki/references/components/static-asset/api.md +261 -0
  40. package/wiki/references/components/static-asset/errors.md +89 -0
  41. package/wiki/references/components/static-asset/index.md +617 -0
  42. package/wiki/references/components/static-asset/usage.md +364 -0
  43. package/wiki/references/components/swagger.md +390 -110
  44. package/wiki/references/components/template/api-page.md +125 -0
  45. package/wiki/references/components/template/errors-page.md +100 -0
  46. package/wiki/references/components/template/index.md +104 -0
  47. package/wiki/references/components/template/setup-page.md +134 -0
  48. package/wiki/references/components/template/single-page.md +132 -0
  49. package/wiki/references/components/template/usage-page.md +127 -0
  50. package/wiki/references/components/websocket/api.md +508 -0
  51. package/wiki/references/components/websocket/errors.md +123 -0
  52. package/wiki/references/components/websocket/index.md +453 -0
  53. package/wiki/references/components/websocket/usage.md +475 -0
  54. package/wiki/references/helpers/cron/index.md +224 -0
  55. package/wiki/references/helpers/crypto/index.md +537 -0
  56. package/wiki/references/helpers/env/index.md +214 -0
  57. package/wiki/references/helpers/error/index.md +232 -0
  58. package/wiki/references/helpers/index.md +16 -15
  59. package/wiki/references/helpers/inversion/index.md +608 -0
  60. package/wiki/references/helpers/logger/index.md +600 -0
  61. package/wiki/references/helpers/network/api.md +986 -0
  62. package/wiki/references/helpers/network/index.md +620 -0
  63. package/wiki/references/helpers/queue/index.md +589 -0
  64. package/wiki/references/helpers/redis/index.md +495 -0
  65. package/wiki/references/helpers/socket-io/api.md +497 -0
  66. package/wiki/references/helpers/socket-io/index.md +513 -0
  67. package/wiki/references/helpers/storage/api.md +705 -0
  68. package/wiki/references/helpers/storage/index.md +583 -0
  69. package/wiki/references/helpers/template/index.md +66 -0
  70. package/wiki/references/helpers/template/single-page.md +126 -0
  71. package/wiki/references/helpers/testing/index.md +510 -0
  72. package/wiki/references/helpers/types/index.md +512 -0
  73. package/wiki/references/helpers/uid/index.md +272 -0
  74. package/wiki/references/helpers/websocket/api.md +736 -0
  75. package/wiki/references/helpers/websocket/index.md +574 -0
  76. package/wiki/references/helpers/worker-thread/index.md +470 -0
  77. package/wiki/references/quick-reference.md +3 -18
  78. package/wiki/references/utilities/jsx.md +1 -8
  79. package/wiki/references/utilities/statuses.md +0 -7
  80. package/wiki/references/components/authentication.md +0 -476
  81. package/wiki/references/components/mail.md +0 -687
  82. package/wiki/references/components/socket-io.md +0 -562
  83. package/wiki/references/components/static-asset.md +0 -1277
  84. package/wiki/references/helpers/cron.md +0 -108
  85. package/wiki/references/helpers/crypto.md +0 -132
  86. package/wiki/references/helpers/env.md +0 -83
  87. package/wiki/references/helpers/error.md +0 -97
  88. package/wiki/references/helpers/inversion.md +0 -176
  89. package/wiki/references/helpers/logger.md +0 -296
  90. package/wiki/references/helpers/network.md +0 -396
  91. package/wiki/references/helpers/queue.md +0 -150
  92. package/wiki/references/helpers/redis.md +0 -142
  93. package/wiki/references/helpers/socket-io.md +0 -932
  94. package/wiki/references/helpers/storage.md +0 -665
  95. package/wiki/references/helpers/testing.md +0 -133
  96. package/wiki/references/helpers/types.md +0 -167
  97. package/wiki/references/helpers/uid.md +0 -167
  98. package/wiki/references/helpers/worker-thread.md +0 -178
@@ -0,0 +1,404 @@
1
+ # Mail -- Usage & Examples
2
+
3
+ > Practical examples for sending emails, using templates, queue executors, verification generators, and batch operations.
4
+
5
+ ## Sending Emails
6
+
7
+ Inject `IMailService` via the `MailKeys.MAIL_SERVICE` binding key to send emails from any service.
8
+
9
+ **Sending a simple email:**
10
+
11
+ ```typescript
12
+ import { BaseService, inject } from '@venizia/ignis';
13
+ import { MailKeys, type IMailService } from '@venizia/ignis/mail';
14
+
15
+ export class UserService extends BaseService {
16
+ constructor(
17
+ @inject({ key: MailKeys.MAIL_SERVICE })
18
+ private _mailService: IMailService,
19
+ ) {
20
+ super({ scope: UserService.name });
21
+ }
22
+
23
+ async sendWelcomeEmail(opts: { userEmail: string; userName: string }) {
24
+ const result = await this._mailService.send({
25
+ to: opts.userEmail,
26
+ subject: 'Welcome to Our App!',
27
+ html: `<h1>Welcome ${opts.userName}!</h1><p>Thanks for joining us.</p>`,
28
+ text: `Welcome ${opts.userName}! Thanks for joining us.`,
29
+ });
30
+
31
+ if (result.success) {
32
+ this.logger.info('[sendWelcomeEmail] Email sent: %s', result.messageId);
33
+ } else {
34
+ this.logger.error('[sendWelcomeEmail] Failed to send email: %s', result.error);
35
+ }
36
+
37
+ return result;
38
+ }
39
+ }
40
+ ```
41
+
42
+ **Batch email sending:**
43
+
44
+ ```typescript
45
+ async sendBulkNotifications(users: Array<{ email: string; name: string }>) {
46
+ const messages = users.map(user => ({
47
+ to: user.email,
48
+ subject: 'Important Update',
49
+ html: `<p>Hello ${user.name}, we have an important update for you.</p>`,
50
+ }));
51
+
52
+ const results = await this.mailService.sendBatch(messages, {
53
+ concurrency: 5, // Send 5 emails at a time
54
+ });
55
+
56
+ const successCount = results.filter(r => r.success).length;
57
+ this.logger.info(
58
+ '[sendBulkNotifications] Sent %d/%d emails successfully',
59
+ successCount,
60
+ results.length,
61
+ );
62
+
63
+ return results;
64
+ }
65
+ ```
66
+
67
+ **Message validation:**
68
+
69
+ The `MailService` validates every message before sending via its internal `validateMessage()` method. This pre-transport check throws immediately if any of these conditions is met:
70
+
71
+ | Condition | Error Code | Message |
72
+ |-----------|-----------|---------|
73
+ | `to` is missing or empty array | `MailErrorCodes.INVALID_RECIPIENT` | `Recipient email address is required` |
74
+ | `subject` is missing | `MailErrorCodes.INVALID_CONFIGURATION` | `Email subject is required` |
75
+ | Both `text` and `html` are missing | `MailErrorCodes.INVALID_CONFIGURATION` | `Email must have either text or html content` |
76
+
77
+ ```typescript
78
+ // This will throw BEFORE reaching the transport
79
+ await mailService.send({
80
+ to: 'user@example.com',
81
+ subject: '', // Empty subject triggers validation error
82
+ html: '<p>Hello</p>',
83
+ });
84
+ // Error: { statusCode: 400, messageCode: 'MAIL_INVALID_CONFIGURATION', message: 'Email subject is required' }
85
+ ```
86
+
87
+ ## Template Engine
88
+
89
+ ### Using Templates
90
+
91
+ Inject both `IMailTemplateEngine` and `IMailService` to register templates and send template-based emails.
92
+
93
+ ```typescript
94
+ import { BaseService, inject } from '@venizia/ignis';
95
+ import { MailKeys, type IMailTemplateEngine, type IMailService } from '@venizia/ignis/mail';
96
+
97
+ export class NotificationService extends BaseService {
98
+ constructor(
99
+ @inject({ key: MailKeys.MAIL_TEMPLATE_ENGINE })
100
+ private templateEngine: IMailTemplateEngine,
101
+ @inject({ key: MailKeys.MAIL_SERVICE })
102
+ private mailService: IMailService,
103
+ ) {
104
+ super({ scope: NotificationService.name });
105
+ this.registerTemplates();
106
+ }
107
+
108
+ registerTemplates() {
109
+ // Register a welcome email template
110
+ this.templateEngine.registerTemplate({
111
+ name: 'welcome-email',
112
+ content: `
113
+ <html>
114
+ <body>
115
+ <h1>Welcome {{userName}}!</h1>
116
+ <p>Your account has been created successfully.</p>
117
+ <p>Your verification code is: <strong>{{verificationCode}}</strong></p>
118
+ </body>
119
+ </html>
120
+ `,
121
+ options: {
122
+ subject: 'Welcome to {{appName}}',
123
+ description: 'Welcome email for new users',
124
+ },
125
+ });
126
+ }
127
+
128
+ async sendWelcomeEmail(userEmail: string, userName: string, verificationCode: string) {
129
+ const result = await this.mailService.sendTemplate({
130
+ templateName: 'welcome-email',
131
+ data: {
132
+ userName,
133
+ verificationCode,
134
+ appName: 'My Application',
135
+ },
136
+ recipients: userEmail,
137
+ options: {
138
+ // Optional: override template subject or add attachments
139
+ attachments: [
140
+ {
141
+ filename: 'logo.png',
142
+ path: '/path/to/logo.png',
143
+ cid: 'logo',
144
+ },
145
+ ],
146
+ },
147
+ });
148
+
149
+ return result;
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Template Rendering
155
+
156
+ The `TemplateEngineService` provides a simple <code v-pre>{{variable}}</code> substitution engine using an in-memory `Map<string, ITemplate>` as its template store.
157
+
158
+ The `renderSimpleTemplate()` method uses regex `/\{\{(\s*[\w.]+\s*)\}\}/g` to find placeholders. For each match:
159
+
160
+ 1. The key is trimmed of whitespace
161
+ 2. Nested value lookup via dot notation (e.g., `user.profile.name` resolves by splitting on `.` and walking the object)
162
+ 3. If the value is `undefined` or `null`, the **original placeholder is preserved as-is** (e.g., <code v-pre>{{missingKey}}</code> remains literally in the output). A warning is logged
163
+ 4. Otherwise, the value is converted to string via `String(value)`
164
+
165
+ > [!IMPORTANT]
166
+ > Missing template variables are **not** replaced with empty strings. The original <code v-pre>{{placeholder}}</code> text is preserved in the output. This makes debugging easier since you can see which variables were not resolved.
167
+
168
+ **Template Features:**
169
+
170
+ - Simple <code v-pre>{{variable}}</code> syntax (no loops or conditionals)
171
+ - Nested object access via dot notation: <code v-pre>{{user.profile.name}}</code>
172
+ - Subject line templating (subjects are rendered through the same engine)
173
+ - HTML and plain text support
174
+ - Validation before rendering (optional, throws on missing keys)
175
+ - In-memory template registry (`Map<string, ITemplate>`)
176
+ - Template metadata (subject, description via `ITemplate`)
177
+ - Missing placeholders preserved as-is (not replaced with empty strings)
178
+ - `clearTemplates()` to reset the entire registry
179
+
180
+ ### Template Validation
181
+
182
+ `validateTemplateData()` extracts all unique placeholder keys from a template string and checks if each key resolves to a non-null, non-undefined value in the data object. It returns:
183
+
184
+ ```typescript
185
+ {
186
+ isValid: boolean; // true if all placeholders have values
187
+ missingKeys: string[]; // placeholder names missing from data
188
+ allKeys: string[]; // all unique placeholder names found
189
+ }
190
+ ```
191
+
192
+ When `requireValidate: true` is passed to `render()` or `renderSimpleTemplate()`, validation runs first and throws with `MailErrorCodes.INVALID_CONFIGURATION` if any keys are missing.
193
+
194
+ Template validation example:
195
+
196
+ ```typescript
197
+ const template = '<h1>Hello {{userName}}, your code is {{code}}</h1>';
198
+ const data = { userName: 'John' }; // Missing 'code'
199
+
200
+ const validation = this.templateEngine.validateTemplateData({ template, data });
201
+
202
+ if (!validation.isValid) {
203
+ console.error('Missing template variables:', validation.missingKeys);
204
+ // Output: ['code']
205
+ }
206
+
207
+ // Render with validation
208
+ try {
209
+ const html = this.templateEngine.render({
210
+ templateData: template,
211
+ data,
212
+ requireValidate: true, // Throws error if validation fails
213
+ });
214
+ } catch (error) {
215
+ console.error('Template rendering failed:', error.message);
216
+ }
217
+ ```
218
+
219
+ ### Syncing Templates from a Database
220
+
221
+ ```typescript
222
+ async syncTemplatesFromDatabase() {
223
+ const templateEngine = this.application.get<IMailTemplateEngine>({
224
+ key: MailKeys.MAIL_TEMPLATE_ENGINE,
225
+ });
226
+
227
+ const configRepository = this.application.get<ConfigurationRepository>({
228
+ key: 'repositories.ConfigurationRepository',
229
+ });
230
+
231
+ const templateConfigs = await configRepository.find({
232
+ filter: {
233
+ where: {
234
+ code: { inq: ['MAIL_TEMPLATE_WELCOME', 'MAIL_TEMPLATE_VERIFICATION'] },
235
+ },
236
+ },
237
+ });
238
+
239
+ templateConfigs.forEach(config => {
240
+ templateEngine.registerTemplate({
241
+ name: config.code,
242
+ content: config.jValue.content,
243
+ options: {
244
+ subject: config.jValue.subject,
245
+ description: config.jValue.description,
246
+ },
247
+ });
248
+ this.logger.info('[syncTemplates] Registered template: %s', config.code);
249
+ });
250
+ }
251
+ ```
252
+
253
+ ## Queue Executors
254
+
255
+ ### Direct Executor
256
+
257
+ The simplest executor. `DirectMailExecutorHelper` extends `BaseHelper`. Calls the processor function immediately without any queueing. Returns `{ queued: false, ... }` to indicate no queue was used. Throws if `setProcessor()` has not been called. Useful for development environments or when you need guaranteed synchronous email sending.
258
+
259
+ ### Internal Queue Executor
260
+
261
+ `InternalQueueMailExecutorHelper` extends `BaseHelper`. Uses the in-memory `QueueHelper` from `@venizia/ignis-helpers` with `autoDispatch: true`. Key behaviors:
262
+
263
+ - Generates job IDs in the format `job_<counter>_<timestamp>`
264
+ - Supports delayed jobs via `setTimeout` (stored in a `delayedJobs` Map)
265
+ - Retry logic: on failure, retries up to `options.attempts` (default 3) with configurable backoff
266
+ - Backoff calculation: `exponential` uses `delay * 2^(attempt-1)`, `fixed` uses the raw delay, no backoff config defaults to 1000ms
267
+ - Does not persist jobs across restarts
268
+ - Logs queue state changes and individual job lifecycle events
269
+
270
+ ### BullMQ Executor
271
+
272
+ `BullMQMailExecutorHelper` extends `BaseHelper`. Full-featured Redis-backed queue with:
273
+
274
+ - Job persistence across restarts
275
+ - Distributed worker support
276
+ - Configurable retry strategies (exponential by default, with 1000ms base delay)
277
+ - Job prioritization
278
+ - Delayed job execution
279
+ - Job progress tracking via worker callbacks
280
+ - `removeOnComplete: true`, `removeOnFail: false` (failed jobs retained for debugging)
281
+
282
+ **Mode behavior:**
283
+
284
+ | Mode | Queue Initialized | Workers Created | Can Enqueue | Can Process |
285
+ |------|-------------------|-----------------|-------------|-------------|
286
+ | `'queue-only'` | Yes | No (skipped in `setProcessor`) | Yes | No |
287
+ | `'worker-only'` | No | Yes | No (throws) | Yes |
288
+ | `'both'` | Yes | Yes | Yes | Yes |
289
+
290
+ ## Verification Generators
291
+
292
+ Three generators are registered by `MailComponent`:
293
+
294
+ - **`NumericCodeGenerator`** -- Implements `IVerificationCodeGenerator`. Generates numeric verification codes of configurable length (e.g., 6-digit `"482917"`)
295
+ - **`RandomTokenGenerator`** -- Implements `IVerificationTokenGenerator`. Generates cryptographically random **base64url**-encoded tokens of configurable byte length
296
+ - **`DefaultVerificationDataGenerator`** -- Implements `IVerificationDataGenerator`. Composes both generators via `@inject` and produces a full `IVerificationData` object with expiry timestamps
297
+
298
+ **NumericCodeGenerator:**
299
+
300
+ Generates cryptographically random numeric codes. Uses `crypto.randomInt(0, 10^length)` to ensure uniform distribution. The result is zero-padded to the requested length via `padStart()` (e.g., code `42` with length 6 becomes `"000042"`).
301
+
302
+ **RandomTokenGenerator:**
303
+
304
+ Generates URL-safe random tokens using `crypto.randomBytes(bytes).toString('base64url')`. The output is **base64url-encoded** (not hex). For 32 bytes of input, this produces a 43-character base64url string (not 64 hex characters). Base64url encoding uses characters `A-Z`, `a-z`, `0-9`, `-`, `_` with no padding.
305
+
306
+ **DefaultVerificationDataGenerator:**
307
+
308
+ Uses `@inject` to receive both `NumericCodeGenerator` (via `MailKeys.MAIL_VERIFICATION_CODE_GENERATOR`) and `RandomTokenGenerator` (via `MailKeys.MAIL_VERIFICATION_TOKEN_GENERATOR`). Produces a complete verification data object with:
309
+ - A short numeric code for manual entry (SMS, email)
310
+ - A long random base64url token for URL-based verification
311
+ - Separate expiry times: code uses `getExpiryTime(minutes)`, token uses `getExpiryTimeInHours(hours)`
312
+ - Generation timestamps in ISO 8601 format
313
+ - Attempt counter (set to 0 initially)
314
+ - `lastCodeSentAt` set to `now`
315
+
316
+ **Email verification flow example:**
317
+
318
+ ```typescript
319
+ import { BaseService, inject } from '@venizia/ignis';
320
+ import {
321
+ MailKeys,
322
+ type IMailService,
323
+ type IVerificationDataGenerator,
324
+ } from '@venizia/ignis/mail';
325
+
326
+ export class AuthService extends BaseService {
327
+ constructor(
328
+ @inject({ key: MailKeys.MAIL_SERVICE })
329
+ private mailService: IMailService,
330
+ @inject({ key: MailKeys.MAIL_VERIFICATION_DATA_GENERATOR })
331
+ private verificationGenerator: IVerificationDataGenerator,
332
+ ) {
333
+ super({ scope: AuthService.name });
334
+ }
335
+
336
+ async sendVerificationEmail(userEmail: string) {
337
+ // Generate verification code and token
338
+ const verificationData = this.verificationGenerator.generateVerificationData({
339
+ codeLength: 6, // 6-digit code
340
+ tokenBytes: 32, // 32-byte token
341
+ codeExpiryMinutes: 10, // Code expires in 10 minutes
342
+ tokenExpiryHours: 24, // Token expires in 24 hours
343
+ });
344
+
345
+ // Save verification data to database
346
+ // await this.saveVerificationData(userEmail, verificationData);
347
+
348
+ // Send verification email
349
+ const result = await this.mailService.send({
350
+ to: userEmail,
351
+ subject: 'Email Verification',
352
+ html: `
353
+ <h2>Verify Your Email</h2>
354
+ <p>Your verification code is: <strong>${verificationData.verificationCode}</strong></p>
355
+ <p>This code expires at: ${verificationData.codeExpiresAt}</p>
356
+ <p>Or click this link: https://example.com/verify?token=${verificationData.verificationToken}</p>
357
+ `,
358
+ });
359
+
360
+ return { result, verificationData };
361
+ }
362
+ }
363
+ ```
364
+
365
+ **Storing verification data:**
366
+
367
+ ```typescript
368
+ const verificationData = this.verificationGenerator.generateVerificationData({
369
+ codeLength: 6, // 6-digit code
370
+ tokenBytes: 32, // 32-byte token -> 43-char base64url string
371
+ codeExpiryMinutes: 10, // Code expires in 10 minutes
372
+ tokenExpiryHours: 24, // Token expires in 24 hours
373
+ });
374
+
375
+ // Store in database
376
+ await this.userRepo.update({
377
+ where: { id: userId },
378
+ data: {
379
+ verificationCode: verificationData.verificationCode,
380
+ verificationCodeExpiresAt: new Date(verificationData.codeExpiresAt),
381
+ verificationToken: verificationData.verificationToken,
382
+ verificationTokenExpiresAt: new Date(verificationData.tokenExpiresAt),
383
+ },
384
+ });
385
+ ```
386
+
387
+ ## Security Note
388
+
389
+ The `MailComponent.createAndBindInstances()` method logs the full `mailOptions` object at `info` level:
390
+
391
+ ```typescript
392
+ this.logger.for(this.createAndBindInstances.name).info('Mail Options: %j', mailOptions);
393
+ ```
394
+
395
+ This includes sensitive fields such as SMTP passwords, OAuth2 client secrets, refresh tokens, and API keys. Similarly, the queue executor config (which may contain Redis passwords) is logged. In production environments, ensure your logging configuration either:
396
+ - Sets the mail component scope to a level higher than `info`
397
+ - Uses a log pipeline that redacts sensitive fields
398
+ - Strips credential fields before binding the options
399
+
400
+ ## See Also
401
+
402
+ - [Setup & Configuration](./) -- Quick reference, setup steps, configuration options, and binding keys
403
+ - [API Reference](./api) -- Architecture, interfaces, and internals
404
+ - [Error Reference](./errors) -- Error codes and troubleshooting
@@ -1,50 +1,254 @@
1
- # Request Tracker Component
1
+ # Request Tracker
2
2
 
3
- The Request Tracker component logs incoming requests and adds a unique request ID for tracing purposes.
3
+ Automatic request logging middleware with unique request IDs, client IP detection, body parsing, and timing -- auto-registered by the framework.
4
4
 
5
- ## Overview
5
+ > [!IMPORTANT]
6
+ > This component is **auto-registered** by `BaseApplication` during `initialize()`. No manual registration is needed.
6
7
 
7
- - **Feature Name:** Request Tracker
8
- - **Purpose:** To log incoming requests and add a unique request ID for tracing purposes.
9
- - **Background:** In a production environment, it is crucial to have detailed logs for debugging and monitoring. The Request Tracker component provides a way to automatically log every incoming request with a unique ID, making it easier to trace the entire lifecycle of a request.
10
- - **Related Features/Modules:** This component is a default middleware that is registered at the application level and integrates with the core logging feature.
8
+ ## Quick Reference
11
9
 
12
- ## Design and Architecture
10
+ | Item | Value |
11
+ |------|-------|
12
+ | **Package** | `@venizia/ignis` |
13
+ | **Component** | `RequestTrackerComponent` |
14
+ | **Middleware** | `RequestSpyMiddleware` |
15
+ | **Utility** | `getIncomingIp()` |
16
+ | **Runtimes** | Both (Bun and Node.js) |
13
17
 
14
- - **`RequestTrackerComponent`:** This component registers the `RequestSpyMiddleware`.
15
- - **`RequestSpyMiddleware`:** A middleware that intercepts incoming requests, logs them, and adds a request ID to the context. It uses the `requestId` middleware from `hono/request-id` to generate the unique ID.
18
+ #### Import Paths
19
+ ```typescript
20
+ import { RequestTrackerComponent } from '@venizia/ignis';
21
+ ```
22
+
23
+ ## Setup
24
+
25
+ ### Step 1: Bind Configuration
26
+
27
+ No configuration binding is required. The component has no user-facing configuration options.
28
+
29
+ ### Step 2: Register Component
16
30
 
17
- ## Implementation Details
31
+ This happens automatically inside `BaseApplication.initialize()`:
18
32
 
19
- ### Tech Stack
33
+ ```typescript
34
+ // Internal to BaseApplication -- shown for reference only
35
+ this.component(RequestTrackerComponent);
36
+ ```
37
+
38
+ The component registers two Hono middlewares on the application server during its `binding()` phase:
39
+ 1. `requestId()` from `hono/request-id` -- generates a UUID and stores it on the Hono context under the key `'requestId'`
40
+ 2. `RequestSpyMiddleware` -- logs request start/end with IP, method, path, query, body, and timing
41
+
42
+ ### Step 3: Use
20
43
 
21
- - **Hono**
22
- - **`hono/request-id`**
44
+ No injection or manual usage is needed. Once the application starts, every incoming request is automatically logged with a unique request ID.
23
45
 
24
- ### Configuration
46
+ A sample log output in **non-production** mode looks like this:
25
47
 
26
- The `RequestTrackerComponent` is enabled by default in `BaseApplication` and requires no manual registration or configuration. It is automatically registered as part of the application's default middleware stack during the `initialize()` lifecycle step.
48
+ ```
49
+ [SpyMW] [<request-id>][127.0.0.1][=>] GET /hello | query: {} | body: null
50
+ [SpyMW] [<request-id>][127.0.0.1][<=] GET /hello | Took: 1.23 (ms)
51
+ ```
27
52
 
28
- When the component is active, it adds the `requestId` and `RequestSpyMiddleware` to the Hono application instance. A sample log output for a request would look like this:
53
+ In **production** mode (`NODE_ENV=production`), body is excluded but query is still logged:
29
54
 
30
55
  ```
31
- [spy][<request-id>] START | Handling Request | forwardedIp: 127.0.0.1 | path: /hello | method: GET
32
- [spy][<request-id>] DONE | Handling Request | forwardedIp: 127.0.0.1 | path: /hello | method: GET | Took: 1.234 (ms)
56
+ [SpyMW] [<request-id>][127.0.0.1][=>] GET /hello | query: {}
57
+ [SpyMW] [<request-id>][127.0.0.1][<=] GET /hello | Took: 1.23 (ms)
33
58
  ```
34
59
 
35
- This feature is essential for building production-ready applications with proper logging and traceability.
60
+ The log format follows this structure:
61
+
62
+ | Direction | Format |
63
+ |-----------|--------|
64
+ | Incoming (`=>`) | `[requestId][clientIp][=>] METHOD path \| query: {...} \| body: {...}` |
65
+ | Outgoing (`<=`) | `[requestId][clientIp][<=] METHOD path \| Took: X.XX (ms)` |
66
+
67
+ The HTTP method is padded to 8 characters for consistent alignment in log output.
68
+
69
+ > [!TIP]
70
+ > The request ID is also available in error middleware contexts (`NotFoundMiddleware`, `AppErrorMiddleware`), making it easy to correlate error logs with the original request.
71
+
72
+ ## Configuration
73
+
74
+ The Request Tracker component has no user-configurable options. Its behavior is fully automatic.
75
+
76
+ | Behavior | Description |
77
+ |----------|-------------|
78
+ | **Request ID** | Generated automatically via `hono/request-id` middleware (UUID), stored on context as `'requestId'` |
79
+ | **IP Detection** | Priority: (1) connection info via `getIncomingIp()`, (2) `x-real-ip` header, (3) `x-forwarded-for` header |
80
+ | **Body Logging** | Logs request body in non-production environments only. Query is always logged |
81
+ | **Timing** | Measures and logs request duration in milliseconds (2 decimal places) via `performance.now()` |
82
+ | **Scope** | Registered as a singleton provider |
83
+
84
+ > [!NOTE]
85
+ > In **production** (`NODE_ENV=production`), only the request **body** is excluded from log output to prevent sensitive data exposure. Query parameters are still logged in all environments.
86
+
87
+ ## Internals
88
+
89
+ ### RequestTrackerComponent
90
+
91
+ The component class extends `BaseComponent`. It receives `BaseApplication` via `@inject({ key: CoreBindings.APPLICATION_INSTANCE })` in its constructor.
92
+
93
+ During construction, it creates a singleton binding for the middleware:
94
+
95
+ ```typescript
96
+ Binding.bind({ key: RequestTrackerComponent.REQUEST_TRACKER_MW_BINDING_KEY })
97
+ .toProvider(RequestSpyMiddleware)
98
+ .setScope(BindingScopes.SINGLETON)
99
+ ```
100
+
101
+ The binding key is constructed as `BindingNamespaces.MIDDLEWARE + '.' + RequestSpyMiddleware.name`, which resolves to `'middlewares.RequestSpyMiddleware'`.
102
+
103
+ #### Component Lifecycle
104
+
105
+ 1. **`constructor()`** -- Receives `BaseApplication` via DI. Defines the middleware binding as a singleton provider.
106
+ 2. **`binding()`** -- Registers `requestId()` middleware on the server. Resolves the `RequestSpyMiddleware` binding from the DI container. Throws if the middleware cannot be resolved. Registers the resolved middleware on the server.
107
+
108
+ ### RequestSpyMiddleware
109
+
110
+ The middleware class extends `BaseHelper` with scope `'SpyMW'` and implements `IProvider<MiddlewareHandler>`.
111
+
112
+ ```typescript
113
+ class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
114
+ static readonly REQUEST_ID_KEY = 'requestId';
115
+ private isDebugMode: boolean;
116
+ // ...
117
+ }
118
+ ```
119
+
120
+ #### IProvider Pattern
121
+
122
+ `RequestSpyMiddleware` implements the `IProvider<T>` interface from `@venizia/ignis-inversion`. This interface requires a single method:
123
+
124
+ ```typescript
125
+ interface IProvider<T> {
126
+ value(container: Container): T;
127
+ }
128
+ ```
129
+
130
+ When the DI container resolves the binding (via `.toProvider(RequestSpyMiddleware)`), it instantiates the class and calls `value()` to obtain the actual `MiddlewareHandler`. This pattern allows the middleware to hold state (like `isDebugMode`) while producing a clean middleware function.
131
+
132
+ #### Debug Mode Detection
133
+
134
+ The constructor checks `process.env.NODE_ENV`:
135
+
136
+ ```typescript
137
+ constructor() {
138
+ super({ scope: 'SpyMW' });
139
+ const env = process.env.NODE_ENV?.toLowerCase();
140
+ this.isDebugMode = env !== Environment.PRODUCTION;
141
+ }
142
+ ```
143
+
144
+ When `isDebugMode` is `true` (any environment other than `'production'`), the incoming request log includes both `query` and `body`. When `false`, only `query` is logged.
145
+
146
+ #### value() -- Middleware Handler
147
+
148
+ The `value()` method returns a Hono middleware created via `createMiddleware()` from `hono/factory`. The middleware performs the following steps:
149
+
150
+ 1. Starts a performance timer via `performance.now()`
151
+ 2. Extracts the request ID from the Hono context (set by `requestId()` middleware)
152
+ 3. Resolves the client IP using the priority chain (see IP Detection below)
153
+ 4. Throws `'Malformed Connection Info'` (400) if both `incomingIp` and `forwardedIp` are `null`
154
+ 5. Extracts method, path, and query from the request
155
+ 6. Parses the request body via `parseBody()`
156
+ 7. Logs the incoming request with `[=>]` direction marker
157
+ 8. Calls `await next()` to proceed to the next middleware/handler
158
+ 9. Calculates duration and logs the outgoing response with `[<=]` direction marker
159
+
160
+ #### parseBody()
161
+
162
+ A public method that parses the request body based on `Content-Type` and `Content-Length` headers.
163
+
164
+ ```typescript
165
+ async parseBody(opts: { req: TContext['req'] }): Promise<unknown>
166
+ ```
167
+
168
+ **Return conditions:**
169
+
170
+ | Condition | Result |
171
+ |-----------|--------|
172
+ | No `Content-Type` header | Returns `null` |
173
+ | No `Content-Length` header or value is `'0'` | Returns `null` |
174
+ | `Content-Type` includes `application/json` | Calls `req.json()` |
175
+ | `Content-Type` includes `multipart/form-data` | Calls `req.parseBody()` |
176
+ | `Content-Type` includes `application/x-www-form-urlencoded` | Calls `req.parseBody()` |
177
+ | Any other `Content-Type` (text, html, xml, etc.) | Calls `req.text()` |
178
+ | Parsing fails for any content type | Throws `'Malformed Body Payload'` (HTTP 400) |
179
+
180
+ ### getIncomingIp() Utility
181
+
182
+ A utility function that attempts to extract the client IP address from the Hono context using runtime-specific connection info.
183
+
184
+ ```typescript
185
+ const getIncomingIp = (context: Context): string | null
186
+ ```
187
+
188
+ **Runtime detection:**
189
+ - Uses `RuntimeModules.isBun()` from `@venizia/ignis-helpers` to detect the runtime
190
+ - On **Bun**: imports `getConnInfo` from `hono/bun`
191
+ - On **Node.js**: imports `getConnInfo` from `@hono/node-server/conninfo`
192
+ - Returns `connInfo.remote.address` if available, `null` otherwise
193
+ - Returns `null` if `getConnInfo` is unavailable or throws
194
+
195
+ #### IP Detection Priority
196
+
197
+ The middleware resolves the client IP using a three-step fallback chain:
198
+
199
+ | Priority | Source | Description |
200
+ |----------|--------|-------------|
201
+ | 1 | `getIncomingIp(context)` | Direct connection info from the runtime (Bun or Node.js) |
202
+ | 2 | `x-real-ip` header | Set by reverse proxies (e.g., Nginx `proxy_set_header X-Real-IP`) |
203
+ | 3 | `x-forwarded-for` header | Standard proxy header with original client IP |
204
+
205
+ The `clientIp` used in log output is resolved as `incomingIp ?? forwardedIp` -- meaning connection info takes precedence when available.
206
+
207
+ If **all three** sources return `null`, the middleware throws a `'Malformed Connection Info'` error with HTTP 400 status.
208
+
209
+ ## Binding Keys
210
+
211
+ | Key | Constant | Type | Required | Default |
212
+ |-----|----------|------|----------|---------|
213
+ | `middlewares.RequestSpyMiddleware` | `RequestTrackerComponent.REQUEST_TRACKER_MW_BINDING_KEY` | `MiddlewareHandler` | Auto | Provided by component |
214
+
215
+ The key is constructed from `BindingNamespaces.MIDDLEWARE` (`'middlewares'`) + `RequestSpyMiddleware.name` (`'RequestSpyMiddleware'`). The component binds `RequestSpyMiddleware` as a singleton provider at this key during construction.
216
+
217
+ ## Troubleshooting
218
+
219
+ ### "Invalid middleware to init request tracker | Please check again binding value"
220
+
221
+ **Cause:** The `RequestSpyMiddleware` binding could not be resolved from the DI container during the component's `binding()` phase. This typically means the binding was removed or overwritten before `binding()` executed.
222
+
223
+ **Fix:** Ensure no custom code unbinds or replaces the `middlewares.RequestSpyMiddleware` key. If you need to customize request logging, extend the component rather than removing the binding.
224
+
225
+ ### "Malformed Body Payload"
226
+
227
+ **Cause:** The `RequestSpyMiddleware` attempted to parse the request body based on the `Content-Type` header, but the body content was malformed (e.g., invalid JSON with `application/json` content type, or corrupt form data).
228
+
229
+ **Fix:** Ensure clients send valid body content that matches the declared `Content-Type` header. This error returns HTTP 400 Bad Request.
230
+
231
+ ### "Malformed Connection Info"
232
+
233
+ **Cause:** The middleware could not determine the client IP address from any source. All three must have failed: (1) `getIncomingIp()` returned `null` (runtime connection info unavailable), (2) `x-real-ip` header was absent, and (3) `x-forwarded-for` header was absent. This error returns HTTP 400 Bad Request.
234
+
235
+ **Fix:** Ensure your reverse proxy (e.g., Nginx, Caddy) forwards at least one of these headers:
236
+ - `x-real-ip`
237
+ - `x-forwarded-for`
238
+
239
+ If running without a proxy, ensure the runtime provides connection info (Bun does this natively; Node.js requires `@hono/node-server`).
36
240
 
37
241
  ## See Also
38
242
 
39
- - **Related Concepts:**
243
+ - **Guides:**
40
244
  - [Components Overview](/guides/core-concepts/components) - Component system basics
41
245
  - [Middlewares](/references/base/middlewares) - Request middleware system
42
246
 
43
- - **Other Components:**
44
- - [Components Index](./index) - All built-in components
247
+ - **Components:**
248
+ - [All Components](./index) - Built-in components list
45
249
 
46
- - **References:**
47
- - [Logger Helper](/references/helpers/logger) - Logging utilities
250
+ - **Helpers:**
251
+ - [Logger Helper](/references/helpers/logger/) - Logging utilities
48
252
 
49
253
  - **Best Practices:**
50
254
  - [Troubleshooting Tips](/best-practices/troubleshooting-tips) - Debugging with request IDs