@venizia/ignis-docs 0.0.5 → 0.0.6-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.
Files changed (123) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architectural-patterns.md +0 -2
  3. package/wiki/best-practices/architecture-decisions.md +0 -8
  4. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  5. package/wiki/best-practices/code-style-standards/index.md +0 -1
  6. package/wiki/best-practices/code-style-standards/tooling.md +0 -3
  7. package/wiki/best-practices/contribution-workflow.md +12 -12
  8. package/wiki/best-practices/index.md +4 -14
  9. package/wiki/best-practices/performance-optimization.md +3 -3
  10. package/wiki/best-practices/security-guidelines.md +2 -2
  11. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  12. package/wiki/guides/core-concepts/application/bootstrapping.md +6 -7
  13. package/wiki/guides/core-concepts/components-guide.md +1 -1
  14. package/wiki/guides/core-concepts/components.md +2 -2
  15. package/wiki/guides/core-concepts/dependency-injection.md +4 -5
  16. package/wiki/guides/core-concepts/persistent/datasources.md +4 -5
  17. package/wiki/guides/core-concepts/services.md +1 -1
  18. package/wiki/guides/get-started/5-minute-quickstart.md +4 -5
  19. package/wiki/guides/get-started/philosophy.md +12 -24
  20. package/wiki/guides/index.md +2 -9
  21. package/wiki/guides/reference/mcp-docs-server.md +13 -13
  22. package/wiki/guides/tutorials/building-a-crud-api.md +10 -10
  23. package/wiki/guides/tutorials/complete-installation.md +11 -12
  24. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  25. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  26. package/wiki/guides/tutorials/testing.md +4 -5
  27. package/wiki/index.md +8 -14
  28. package/wiki/references/base/bootstrapping.md +0 -3
  29. package/wiki/references/base/components.md +2 -2
  30. package/wiki/references/base/controllers.md +0 -1
  31. package/wiki/references/base/datasources.md +1 -1
  32. package/wiki/references/base/dependency-injection.md +2 -2
  33. package/wiki/references/base/filter-system/default-filter.md +2 -3
  34. package/wiki/references/base/filter-system/index.md +1 -1
  35. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  36. package/wiki/references/base/middlewares.md +0 -8
  37. package/wiki/references/base/providers.md +0 -9
  38. package/wiki/references/base/repositories/advanced.md +1 -1
  39. package/wiki/references/base/repositories/mixins.md +2 -3
  40. package/wiki/references/base/services.md +0 -1
  41. package/wiki/references/components/authentication/api.md +444 -0
  42. package/wiki/references/components/authentication/errors.md +177 -0
  43. package/wiki/references/components/authentication/index.md +571 -0
  44. package/wiki/references/components/authentication/usage.md +781 -0
  45. package/wiki/references/components/health-check.md +292 -103
  46. package/wiki/references/components/index.md +14 -12
  47. package/wiki/references/components/mail/api.md +505 -0
  48. package/wiki/references/components/mail/errors.md +176 -0
  49. package/wiki/references/components/mail/index.md +535 -0
  50. package/wiki/references/components/mail/usage.md +404 -0
  51. package/wiki/references/components/request-tracker.md +229 -25
  52. package/wiki/references/components/socket-io/api.md +1051 -0
  53. package/wiki/references/components/socket-io/errors.md +119 -0
  54. package/wiki/references/components/socket-io/index.md +410 -0
  55. package/wiki/references/components/socket-io/usage.md +322 -0
  56. package/wiki/references/components/static-asset/api.md +261 -0
  57. package/wiki/references/components/static-asset/errors.md +89 -0
  58. package/wiki/references/components/static-asset/index.md +617 -0
  59. package/wiki/references/components/static-asset/usage.md +364 -0
  60. package/wiki/references/components/swagger.md +390 -110
  61. package/wiki/references/components/template/api-page.md +125 -0
  62. package/wiki/references/components/template/errors-page.md +100 -0
  63. package/wiki/references/components/template/index.md +104 -0
  64. package/wiki/references/components/template/setup-page.md +134 -0
  65. package/wiki/references/components/template/single-page.md +132 -0
  66. package/wiki/references/components/template/usage-page.md +127 -0
  67. package/wiki/references/components/websocket/api.md +508 -0
  68. package/wiki/references/components/websocket/errors.md +123 -0
  69. package/wiki/references/components/websocket/index.md +453 -0
  70. package/wiki/references/components/websocket/usage.md +475 -0
  71. package/wiki/references/helpers/cron/index.md +224 -0
  72. package/wiki/references/helpers/crypto/index.md +537 -0
  73. package/wiki/references/helpers/env/index.md +214 -0
  74. package/wiki/references/helpers/error/index.md +232 -0
  75. package/wiki/references/helpers/index.md +16 -15
  76. package/wiki/references/helpers/inversion/index.md +608 -0
  77. package/wiki/references/helpers/logger/index.md +600 -0
  78. package/wiki/references/helpers/network/api.md +986 -0
  79. package/wiki/references/helpers/network/index.md +620 -0
  80. package/wiki/references/helpers/queue/index.md +589 -0
  81. package/wiki/references/helpers/redis/index.md +495 -0
  82. package/wiki/references/helpers/socket-io/api.md +497 -0
  83. package/wiki/references/helpers/socket-io/index.md +513 -0
  84. package/wiki/references/helpers/storage/api.md +705 -0
  85. package/wiki/references/helpers/storage/index.md +583 -0
  86. package/wiki/references/helpers/template/index.md +66 -0
  87. package/wiki/references/helpers/template/single-page.md +126 -0
  88. package/wiki/references/helpers/testing/index.md +510 -0
  89. package/wiki/references/helpers/types/index.md +512 -0
  90. package/wiki/references/helpers/uid/index.md +272 -0
  91. package/wiki/references/helpers/websocket/api.md +736 -0
  92. package/wiki/references/helpers/websocket/index.md +574 -0
  93. package/wiki/references/helpers/worker-thread/index.md +470 -0
  94. package/wiki/references/index.md +2 -9
  95. package/wiki/references/quick-reference.md +3 -18
  96. package/wiki/references/utilities/jsx.md +1 -8
  97. package/wiki/references/utilities/statuses.md +0 -7
  98. package/wiki/references/components/authentication.md +0 -476
  99. package/wiki/references/components/mail.md +0 -687
  100. package/wiki/references/components/socket-io.md +0 -562
  101. package/wiki/references/components/static-asset.md +0 -1277
  102. package/wiki/references/helpers/cron.md +0 -108
  103. package/wiki/references/helpers/crypto.md +0 -132
  104. package/wiki/references/helpers/env.md +0 -83
  105. package/wiki/references/helpers/error.md +0 -97
  106. package/wiki/references/helpers/inversion.md +0 -176
  107. package/wiki/references/helpers/logger.md +0 -296
  108. package/wiki/references/helpers/network.md +0 -396
  109. package/wiki/references/helpers/queue.md +0 -150
  110. package/wiki/references/helpers/redis.md +0 -142
  111. package/wiki/references/helpers/socket-io.md +0 -932
  112. package/wiki/references/helpers/storage.md +0 -665
  113. package/wiki/references/helpers/testing.md +0 -133
  114. package/wiki/references/helpers/types.md +0 -167
  115. package/wiki/references/helpers/uid.md +0 -167
  116. package/wiki/references/helpers/worker-thread.md +0 -178
  117. package/wiki/references/src-details/boot.md +0 -379
  118. package/wiki/references/src-details/core.md +0 -263
  119. package/wiki/references/src-details/dev-configs.md +0 -298
  120. package/wiki/references/src-details/docs.md +0 -71
  121. package/wiki/references/src-details/helpers.md +0 -211
  122. package/wiki/references/src-details/index.md +0 -86
  123. package/wiki/references/src-details/inversion.md +0 -340
@@ -0,0 +1,505 @@
1
+ # Mail -- API Reference
2
+
3
+ > Architecture, interfaces, and internal implementation details of the Mail component.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────┐
9
+ │ Your Application │
10
+ │ │
11
+ │ NodemailerComponent (wrapper) │
12
+ │ ├── binds MailKeys.MAIL_OPTIONS │
13
+ │ ├── binds MailKeys.MAIL_QUEUE_EXECUTOR_CONFIG │
14
+ │ └── registers MailComponent │
15
+ └───────────────────────┬─────────────────────────┘
16
+
17
+
18
+ ┌─────────────────────────────────────────────────┐
19
+ │ MailComponent │
20
+ │ │
21
+ │ binding() │
22
+ │ ├── initGenerators() │
23
+ │ │ ├── NumericCodeGenerator │
24
+ │ │ ├── RandomTokenGenerator │
25
+ │ │ └── DefaultVerificationDataGenerator │
26
+ │ │ │
27
+ │ ├── initProviders() │
28
+ │ │ ├── MailTransportProvider (singleton) │
29
+ │ │ └── MailQueueExecutorProvider (singleton) │
30
+ │ │ │
31
+ │ ├── initServices() │
32
+ │ │ ├── MailService (singleton) │
33
+ │ │ └── TemplateEngineService (singleton) │
34
+ │ │ │
35
+ │ └── createAndBindInstances() │
36
+ │ ├── Transport Instance ◄── MAIL_OPTIONS │
37
+ │ └── Queue Executor ◄── QUEUE_CONFIG │
38
+ └─────────────────────────────────────────────────┘
39
+ ```
40
+
41
+ **Architecture Components:**
42
+
43
+ - **`MailComponent`** -- Initializes and registers all mail services, transporters, and queue executors. Extends `BaseComponent`. Validates that `MailKeys.MAIL_OPTIONS` is bound before proceeding
44
+ - **`MailService`** -- Extends `BaseService`. Provides `send()`, `sendBatch()`, `sendTemplate()`, and `verify()`. Injects transport instance and template engine. Validates messages before sending
45
+ - **`TemplateEngineService`** -- Extends `BaseService`. Manages email templates with simple <code v-pre>{{variable}}</code> substitution. Stores templates in an in-memory `Map`
46
+ - **`MailTransportProvider`** -- Extends `BaseProvider`. Factory that creates transport instances with type-guard methods (`isNodemailerOptions`, `isMailgunOptions`, `isCustomOptions`) and throws `MailErrorCodes` on invalid configs
47
+ - **`MailQueueExecutorProvider`** -- Extends `BaseProvider`. Factory that creates queue executor instances. Throws for missing sub-configs (e.g., `config.internalQueue` or `config.bullmq`)
48
+ - **Verification Generators** -- `NumericCodeGenerator`, `RandomTokenGenerator`, `DefaultVerificationDataGenerator`. Generate verification codes, tokens, and data for email verification flows
49
+
50
+ **Tech Stack:**
51
+
52
+ - **Nodemailer** -- SMTP-based email sending (peer dependency: `nodemailer`)
53
+ - **Mailgun** -- Mailgun API client (peer dependency: `mailgun.js`)
54
+ - **BullMQ** (optional) -- Redis-backed queue for distributed processing (peer dependency: `bullmq`)
55
+ - **Handlebars-style Templates** -- Simple <code v-pre>{{variable}}</code> syntax for email templates (no external dependency)
56
+
57
+ ## Transport Layer
58
+
59
+ ### Transport Layer Implementation
60
+
61
+ The `MailTransportProvider` extends `BaseProvider` and returns a factory function from its `value()` method. It creates the appropriate transport based on the `provider` field in `MailKeys.MAIL_OPTIONS`:
62
+
63
+ - **`'nodemailer'`** -- Creates a `NodemailerTransportHelper` backed by `nodemailer` with SMTP or OAuth2 auth
64
+ - **`'mailgun'`** -- Creates a `MailgunTransportHelper` using the Mailgun HTTP API
65
+ - **`'custom'`** -- Expects the `config` value to implement `IMailTransport` directly (must have `send()` and `verify()`)
66
+ - **Any other string** -- Falls through to `default` and throws `Unsupported mail provider: <provider>` with `MailErrorCodes.INVALID_CONFIGURATION`
67
+
68
+ The provider uses three private type-guard methods to narrow the union type before creating transports:
69
+
70
+ ```typescript
71
+ private isNodemailerOptions(options: TMailOptions): options is INodemailerMailOptions
72
+ private isMailgunOptions(options: TMailOptions): options is IMailgunMailOptions
73
+ private isCustomOptions(options: TMailOptions): options is ICustomMailOptions
74
+ ```
75
+
76
+ For custom transports, an additional `isMailTransport()` utility validates that the config object has `send()` and `verify()` methods, reporting specific missing methods in the error message.
77
+
78
+ Both built-in transports implement `IMailTransport`:
79
+
80
+ ```typescript
81
+ interface IMailTransport {
82
+ send(message: IMailMessage): Promise<IMailSendResult>;
83
+ verify(): Promise<boolean>;
84
+ close?(): Promise<void>;
85
+ }
86
+ ```
87
+
88
+ ### Module Validation
89
+
90
+ Both `NodemailerTransportHelper` and `MailgunTransportHelper` call `validateModule()` in their `configure()` method before importing the peer dependency. This checks that the required npm module is installed:
91
+
92
+ - `NodemailerTransportHelper` requires `nodemailer`
93
+ - `MailgunTransportHelper` requires `mailgun.js`
94
+
95
+ If the module is not installed, `validateModule()` throws an error identifying the missing peer dependency.
96
+
97
+ **Nodemailer Transport:**
98
+
99
+ The `NodemailerTransportHelper` extends `BaseHelper` and wraps Nodemailer's SMTP transport. In `configure()`, it calls `require('nodemailer')` and creates a transporter. Key behaviors:
100
+ - `send()` maps `IMailMessage` fields to Nodemailer's mail options, joining array recipients with `, `
101
+ - `send()` catches transport errors and returns `{ success: false, error: ... }` instead of throwing
102
+ - `verify()` delegates to Nodemailer's built-in `transporter.verify()` SMTP handshake
103
+ - `close()` calls `transporter.close()` to release the connection
104
+
105
+ **Mailgun Transport:**
106
+
107
+ The `MailgunTransportHelper` extends `BaseHelper` and uses the Mailgun REST API via `mailgun.js`. In `configure()`, it creates a `Mailgun` client using `FormData`. Key behaviors:
108
+ - `send()` converts `IMailMessage` to Mailgun's format: `to` becomes an array, `replyTo` becomes `h:Reply-To`, all custom headers are prefixed with `h:`
109
+ - Attachments are mapped to `{ filename, data }` objects where `data` is `path ?? content ?? Buffer.from('')`
110
+ - `verify()` sends a test email to `verify@<domain>` with `o:testmode: 'yes'` flag to check API credentials without actually sending
111
+ - No `close()` method (HTTP API is stateless)
112
+
113
+ **Custom Transport:**
114
+
115
+ You can provide your own transport implementation by setting `provider: MailProviders.CUSTOM` and passing an object that implements `IMailTransport` as the `config` value. The provider validates that `send()` and `verify()` are functions, reporting specific missing methods. This is useful for integrating with services like SendGrid, AWS SES, or custom SMTP relays.
116
+
117
+ ## IMailService Interface
118
+
119
+ ```typescript
120
+ interface IMailService {
121
+ // Send a single email
122
+ send(message: IMailMessage): Promise<IMailSendResult>;
123
+
124
+ // Send multiple emails with controlled concurrency
125
+ sendBatch(
126
+ messages: IMailMessage[],
127
+ options?: { concurrency?: number },
128
+ ): Promise<IMailSendResult[]>;
129
+
130
+ // Send email using a registered template
131
+ sendTemplate(opts: {
132
+ templateName: string;
133
+ data: Record<string, any>;
134
+ recipients: string | string[];
135
+ options?: Partial<IMailMessage>;
136
+ }): Promise<IMailSendResult>;
137
+
138
+ // Verify transport connection
139
+ verify(): Promise<boolean>;
140
+ }
141
+ ```
142
+
143
+ **Method Details:**
144
+
145
+ **`send(message: IMailMessage)`**
146
+
147
+ Sends a single email using the configured transport. Internally:
148
+ 1. Calls `validateMessage()` which throws for missing `to`, `subject`, or both `text`/`html` (see error reference below)
149
+ 2. Merges `message.from` with the default from address (via `getDefaultFrom()`)
150
+ 3. Delegates to `transport.send()`
151
+ 4. Returns a result object with `success`, `messageId`, and optional `error` fields
152
+ 5. If the transport throws, catches the error and re-throws with `MailErrorCodes.SEND_FAILED`
153
+
154
+ **`sendBatch(messages: IMailMessage[], options?: { concurrency?: number })`**
155
+
156
+ Sends multiple emails with controlled concurrency using `executePromiseWithLimit()`. Default concurrency is `MailDefaults.BATCH_CONCURRENCY` (5). Each message is sent via `send()` individually. If an individual `send()` throws, it is caught and converted to `{ success: false, error: '...' }` so the batch continues. If the entire batch operation fails, throws with `MailErrorCodes.BATCH_SEND_FAILED`.
157
+
158
+ **`sendTemplate(opts: { templateName, data, recipients, options? })`**
159
+
160
+ Renders a registered template with the provided data and sends the email. The subject is resolved through a priority chain:
161
+
162
+ 1. `options.subject` -- explicit override from the caller
163
+ 2. Template subject rendered through the template engine (if `templateData.subject` is defined)
164
+ 3. `'No Subject'` -- fallback if neither is provided
165
+
166
+ Throws `MailErrorCodes.INVALID_CONFIGURATION` if the template engine is not configured. Re-throws any other errors (including `TEMPLATE_NOT_FOUND` from the template engine).
167
+
168
+ **`verify()`**
169
+
170
+ Verifies the transport connection without sending an email. Delegates to `transport.verify()`. If the transport throws, catches and re-throws with `MailErrorCodes.VERIFICATION_FAILED`.
171
+
172
+ ### Protected Methods (MailService)
173
+
174
+ **`validateMessage(message: IMailMessage)`**
175
+
176
+ Pre-transport validation that throws immediately for invalid messages:
177
+
178
+ | Check | Error Code | Status | Message |
179
+ |-------|-----------|--------|---------|
180
+ | `to` is falsy or empty array | `INVALID_RECIPIENT` | 400 | `Recipient email address is required` |
181
+ | `subject` is falsy | `INVALID_CONFIGURATION` | 400 | `Email subject is required` |
182
+ | Both `text` and `html` are falsy | `INVALID_CONFIGURATION` | 400 | `Email must have either text or html content` |
183
+
184
+ **`getDefaultFrom()`**
185
+
186
+ Constructs the default "from" address. If `options.fromName` is set, returns `"fromName" <from>`. Otherwise returns `options.from ?? 'noreply@example.com'`.
187
+
188
+ ## IMailMessage Interface
189
+
190
+ ```typescript
191
+ interface IMailMessage {
192
+ from?: string; // Sender email (uses default if not provided)
193
+ to: string | string[]; // Recipient(s)
194
+ cc?: string | string[]; // CC recipient(s)
195
+ bcc?: string | string[]; // BCC recipient(s)
196
+ replyTo?: string; // Reply-to address
197
+ subject: string; // Email subject
198
+ text?: string; // Plain text content
199
+ html?: string; // HTML content
200
+ attachments?: IMailAttachment[]; // File attachments
201
+ headers?: Record<string, string>; // Custom headers
202
+ requireValidate?: boolean; // Validate template data
203
+ [key: string]: any; // Additional arbitrary fields
204
+ }
205
+ ```
206
+
207
+ **Field Details:**
208
+
209
+ **`from`**
210
+
211
+ Sender email address. If not provided, uses the default `from` value from `MailKeys.MAIL_OPTIONS`. When `fromName` is configured, the default from is formatted as `"Display Name" <email@example.com>`. Can be overridden per-message for multi-tenant scenarios.
212
+
213
+ **`to`, `cc`, `bcc`**
214
+
215
+ Recipient addresses. Can be a single string or an array of strings. Format can be either plain email (`user@example.com`) or display name + email (`John Doe <john@example.com>`). For Nodemailer, arrays are joined with `, `. For Mailgun, arrays are passed as-is.
216
+
217
+ **`replyTo`**
218
+
219
+ Reply-to address if different from the sender. Useful for no-reply addresses that route replies to a support inbox. Mailgun maps this to `h:Reply-To`.
220
+
221
+ **`subject`**
222
+
223
+ Email subject line. Supports template variables when used with `sendTemplate()` (the subject is rendered through the same template engine).
224
+
225
+ **`text`, `html`**
226
+
227
+ Plain text and HTML versions of the email body. At least one must be provided (validated by `validateMessage()`). Most email clients prefer HTML but fall back to text if HTML is not available. Best practice is to provide both.
228
+
229
+ **`attachments`**
230
+
231
+ Array of `IMailAttachment` objects:
232
+
233
+ ```typescript
234
+ interface IMailAttachment {
235
+ filename?: string;
236
+ contentType?: string;
237
+ path?: string;
238
+ content?: string | Buffer | Readable;
239
+ cid?: string;
240
+ [key: string]: any;
241
+ }
242
+ ```
243
+
244
+ Examples:
245
+ - A file path: `{ filename: 'doc.pdf', path: '/path/to/doc.pdf' }`
246
+ - A buffer: `{ filename: 'data.txt', content: Buffer.from('...') }`
247
+ - An inline image: `{ filename: 'logo.png', path: '...', cid: 'logo' }`
248
+
249
+ **`headers`**
250
+
251
+ Custom SMTP headers. For Nodemailer, passed directly. For Mailgun, each key is auto-prefixed with `h:`.
252
+
253
+ **`requireValidate`**
254
+
255
+ When `true`, template rendering will throw an error if any <code v-pre>{{variable}}</code> placeholders are missing from the data object. Defaults to `false` (missing variables are preserved as their original placeholder text, not replaced with empty strings).
256
+
257
+ **`[key: string]: any`**
258
+
259
+ The interface is open-ended -- additional fields are accepted for provider-specific options.
260
+
261
+ ## IMailTemplateEngine Interface
262
+
263
+ ```typescript
264
+ interface IMailTemplateEngine {
265
+ // Render a template with data
266
+ render(opts: {
267
+ templateData?: string;
268
+ templateName?: string;
269
+ data: Record<string, any>;
270
+ requireValidate?: boolean;
271
+ }): string;
272
+
273
+ // Register a new template
274
+ registerTemplate(opts: { name: string; content: string }): void;
275
+
276
+ // Validate template data
277
+ validateTemplateData(opts: { template: string; data: Record<string, any> }): {
278
+ isValid: boolean;
279
+ missingKeys: string[];
280
+ allKeys: string[];
281
+ };
282
+
283
+ // Get a registered template
284
+ getTemplate(name: string): ITemplate | undefined;
285
+
286
+ // List all registered templates
287
+ listTemplates(): ITemplate[];
288
+
289
+ // Check if template exists
290
+ hasTemplate(name: string): boolean;
291
+
292
+ // Remove a template
293
+ removeTemplate(name: string): boolean;
294
+ }
295
+ ```
296
+
297
+ **Method Details:**
298
+
299
+ **`render(opts: { templateData?, templateName?, data, requireValidate? })`**
300
+
301
+ Renders a template by name (from registry) or by raw template string (`templateData`). At least one of `templateData` or `templateName` must be provided -- throws if neither is given. If `templateName` is used, looks up the template and throws `TEMPLATE_NOT_FOUND` if not registered. Delegates to `renderSimpleTemplate()` which replaces all <code v-pre>{{variable}}</code> placeholders with values from the `data` object. If `requireValidate` is `true`, throws `INVALID_CONFIGURATION` if any placeholders are missing from the data.
302
+
303
+ **`registerTemplate(opts: { name, content, options? })`**
304
+
305
+ Registers a new template in the in-memory registry. The `options` parameter (on the class implementation) can include `subject` and `description` via `Partial<ITemplate>`. Overwrites any existing template with the same name.
306
+
307
+ **`validateTemplateData(opts: { template, data })`**
308
+
309
+ Extracts all <code v-pre>{{variable}}</code> placeholders from the template string using the regex `/\{\{(\s*[\w.]+\s*)\}\}/g`. Deduplicates keys. For each unique key, resolves nested values via dot notation. Returns:
310
+ - `isValid` -- `true` if all placeholders have non-null, non-undefined values
311
+ - `missingKeys` -- Array of placeholder names that are missing or null/undefined in the data
312
+ - `allKeys` -- Array of all unique placeholder names found in the template
313
+
314
+ **`getTemplate(name: string)`**
315
+
316
+ Retrieves a registered template by name. Returns `undefined` if the template does not exist.
317
+
318
+ **`listTemplates()`**
319
+
320
+ Returns an array of all registered templates (values from the internal `Map`).
321
+
322
+ **`hasTemplate(name: string)`**
323
+
324
+ Checks if a template with the given name exists in the registry.
325
+
326
+ **`removeTemplate(name: string)`**
327
+
328
+ Removes a template from the registry. Logs the removal. Returns `true` if the template was found and removed, `false` otherwise.
329
+
330
+ ### ITemplate Interface
331
+
332
+ ```typescript
333
+ interface ITemplate {
334
+ name: string;
335
+ content?: string;
336
+ render?: (data: Record<string, AnyType>) => string;
337
+ subject?: string;
338
+ description?: string;
339
+ }
340
+ ```
341
+
342
+ The `render` function on the interface supports custom render implementations, though the built-in `TemplateEngineService` uses `content` + `renderSimpleTemplate()` instead.
343
+
344
+ ## Additional Interfaces
345
+
346
+ ### IMailSendResult
347
+
348
+ ```typescript
349
+ interface IMailSendResult {
350
+ success: boolean;
351
+ messageId?: string;
352
+ response?: any;
353
+ error?: string;
354
+ }
355
+ ```
356
+
357
+ Returned by `send()` and individual entries in the `sendBatch()` result array. Transport-level errors populate `error` with the message string.
358
+
359
+ ### IMailQueueExecutor
360
+
361
+ ```typescript
362
+ interface IMailQueueExecutor {
363
+ enqueueVerificationEmail(email: string, options?: IMailQueueOptions): Promise<IMailQueueResult>;
364
+ setProcessor(processor: (email: string) => Promise<IMailProcessorResult>): void;
365
+ }
366
+ ```
367
+
368
+ ### IMailQueueOptions
369
+
370
+ ```typescript
371
+ interface IMailQueueOptions {
372
+ priority?: number;
373
+ delay?: number;
374
+ attempts?: number;
375
+ backoff?: {
376
+ type: 'fixed' | 'exponential';
377
+ delay: number;
378
+ };
379
+ }
380
+ ```
381
+
382
+ ### IMailQueueResult
383
+
384
+ ```typescript
385
+ interface IMailQueueResult {
386
+ jobId?: string;
387
+ queued: boolean;
388
+ message: string;
389
+ result?: IMailProcessorResult;
390
+ }
391
+ ```
392
+
393
+ Returned by `enqueueVerificationEmail()`. The `queued` field is `false` for direct execution, `true` for internal queue and BullMQ. The `result` field is populated only for direct execution (since the processor runs synchronously).
394
+
395
+ ### IMailProcessorResult
396
+
397
+ ```typescript
398
+ interface IMailProcessorResult {
399
+ success: boolean;
400
+ message: string;
401
+ expiresInMinutes: number;
402
+ nextResendAt?: string;
403
+ }
404
+ ```
405
+
406
+ The return type of the processor function registered via `setProcessor()`.
407
+
408
+ ### IVerificationData
409
+
410
+ ```typescript
411
+ interface IVerificationData {
412
+ verificationCode: string;
413
+ codeGeneratedAt: string;
414
+ codeExpiresAt: string;
415
+ codeAttempts: number;
416
+ verificationToken: string;
417
+ tokenGeneratedAt: string;
418
+ tokenExpiresAt: string;
419
+ lastCodeSentAt: string;
420
+ }
421
+ ```
422
+
423
+ ### IVerificationGenerationOptions
424
+
425
+ ```typescript
426
+ interface IVerificationGenerationOptions {
427
+ codeLength: number;
428
+ tokenBytes: number;
429
+ codeExpiryMinutes: number;
430
+ tokenExpiryHours: number;
431
+ }
432
+ ```
433
+
434
+ All fields are required. Passed to `DefaultVerificationDataGenerator.generateVerificationData()`.
435
+
436
+ ### IBullMQMailExecutorOpts
437
+
438
+ ```typescript
439
+ interface IBullMQMailExecutorOpts {
440
+ redis: IRedisHelperOptions;
441
+ queue: { identifier: string; name: string };
442
+ mode: TConstValue<typeof BullMQExecutorModes>; // REQUIRED
443
+ }
444
+ ```
445
+
446
+ The `mode` field is **required** -- there is no default value. This is the config object for `IMailQueueExecutorConfig.bullmq`.
447
+
448
+ ### BullMQ Dynamic Worker Management Methods
449
+
450
+ Dynamic worker management methods (on `BullMQMailExecutorHelper`, not on the interface):
451
+
452
+ - **`addWorker(opts)`** -- Adds a new BullMQ worker with configurable concurrency (default 5) and lock duration (default 30000ms). Requires `setProcessor()` to have been called first. Each worker gets a unique identifier
453
+ - **`removeWorker(index)`** -- Removes a worker by its array index. Calls `worker.close()` before removal. Returns `false` if the index is out of range
454
+ - **`clearWorkers()`** -- Closes all workers and empties the worker array. Called internally by `setProcessor()` before creating new workers
455
+ - **`getWorkerCount()`** -- Returns the current number of active workers
456
+ - **`getMode()`** -- Returns the current executor mode
457
+
458
+ **Extended `setProcessor()` signature (BullMQ only):**
459
+
460
+ Unlike the interface's synchronous `setProcessor()`, the BullMQ executor's version is `async` and accepts an optional second argument:
461
+
462
+ ```typescript
463
+ async setProcessor(
464
+ processor: (email: string) => Promise<IMailProcessorResult>,
465
+ opts?: {
466
+ numberOfWorkers?: number; // default: 1
467
+ concurrencyPerWorker?: number; // default: 5
468
+ lockDuration?: number; // default: 30000 (ms)
469
+ },
470
+ ): Promise<void>
471
+ ```
472
+
473
+ It clears all existing workers before creating new ones. In `queue-only` mode, it stores the processor but skips worker creation entirely (logs a warning).
474
+
475
+ ## Utility Functions
476
+
477
+ ### Type Utilities
478
+
479
+ ```typescript
480
+ // Check if a value implements IMailTransport
481
+ function isMailTransport(value: AnyType): value is IMailTransport;
482
+
483
+ // Check if a value is valid TMailOptions
484
+ function isValidMailOptions(options: AnyType): options is TMailOptions;
485
+ ```
486
+
487
+ `isMailTransport()` checks for `send` and `verify` as functions, and `close` as either a function or undefined.
488
+
489
+ `isValidMailOptions()` checks for a string `provider` field and a truthy `config` field.
490
+
491
+ ### Verification Utilities
492
+
493
+ ```typescript
494
+ // Get a Date object `minutes` minutes in the future
495
+ function getExpiryTime(minutes: number): Date;
496
+
497
+ // Get a Date object `hours` hours in the future
498
+ function getExpiryTimeInHours(hours: number): Date;
499
+ ```
500
+
501
+ ## See Also
502
+
503
+ - [Setup & Configuration](./) -- Quick reference, setup steps, configuration options, and binding keys
504
+ - [Usage & Examples](./usage) -- Sending emails, templates, queue executors, and verification
505
+ - [Error Reference](./errors) -- Error codes and troubleshooting
@@ -0,0 +1,176 @@
1
+ # Mail -- Error Reference & Troubleshooting
2
+
3
+ > Complete error code reference and troubleshooting guide for the Mail component.
4
+
5
+ ## Error Reference
6
+
7
+ All errors are created via `getError()` and include `statusCode`, `messageCode`, and `message` fields.
8
+
9
+ ### MailService Errors
10
+
11
+ | Condition | Status | Error Code | Message |
12
+ |-----------|--------|-----------|---------|
13
+ | `to` is missing or empty array | 400 | `MAIL_INVALID_RECIPIENT` | `Recipient email address is required` |
14
+ | `subject` is falsy | 400 | `MAIL_INVALID_CONFIGURATION` | `Email subject is required` |
15
+ | Both `text` and `html` are falsy | 400 | `MAIL_INVALID_CONFIGURATION` | `Email must have either text or html content` |
16
+ | Transport throws during `send()` | 500 | `MAIL_SEND_FAILED` | `Failed to send email: <error>` |
17
+ | Batch operation fails | 500 | `MAIL_BATCH_SEND_FAILED` | `Failed to send batch emails: <error>` |
18
+ | Template engine not configured for `sendTemplate()` | 500 | `MAIL_INVALID_CONFIGURATION` | `Template engine not configured` |
19
+ | Transport throws during `verify()` | 500 | `MAIL_VERIFICATION_FAILED` | `Mail transport verification failed: <error>` |
20
+
21
+ ### MailComponent Errors
22
+
23
+ | Condition | Status | Error Code | Message |
24
+ |-----------|--------|-----------|---------|
25
+ | `MAIL_OPTIONS` not bound | -- | -- | `Mail options not configured` |
26
+
27
+ ### MailTransportProvider Errors
28
+
29
+ | Condition | Status | Error Code | Message |
30
+ |-----------|--------|-----------|---------|
31
+ | Unsupported provider string | 500 | `MAIL_INVALID_CONFIGURATION` | `Unsupported mail provider: <provider>` |
32
+ | Nodemailer options fail type guard | 500 | `MAIL_INVALID_CONFIGURATION` | `Invalid Nodemailer configuration` |
33
+ | Mailgun options fail type guard | 500 | `MAIL_INVALID_CONFIGURATION` | `Invalid Mailgun configuration` |
34
+ | Custom options fail type guard | 500 | `MAIL_INVALID_CONFIGURATION` | `Invalid custom mail provider configuration` |
35
+ | Custom config missing `send`/`verify` | 500 | `MAIL_INVALID_CONFIGURATION` | `Custom mail provider must implement IMailTransport interface. Missing methods: <methods>` |
36
+
37
+ ### MailQueueExecutorProvider Errors
38
+
39
+ | Condition | Status | Error Code | Message |
40
+ |-----------|--------|-----------|---------|
41
+ | `config.internalQueue` missing for `internal-queue` type | -- | -- | `Internal queue configuration is missing` |
42
+ | `config.bullmq` missing for `bullmq` type | -- | -- | `BullMQ configuration is missing` |
43
+ | Unknown executor type | -- | -- | `Unknown mail queue executor type: <type>` |
44
+
45
+ ### TemplateEngineService Errors
46
+
47
+ | Condition | Status | Error Code | Message |
48
+ |-----------|--------|-----------|---------|
49
+ | Neither `templateName` nor `templateData` provided | -- | -- | `Either templateName or templateData must be provided` |
50
+ | Template name not found in registry | 404 | `TEMPLATE_NOT_FOUND` | `Template not found: <name>` |
51
+ | Missing template data keys (with `requireValidate: true`) | 400 | `MAIL_INVALID_CONFIGURATION` | `Missing template data for keys: <keys>` |
52
+
53
+ ### Queue Executor Errors
54
+
55
+ | Condition | Executor | Message |
56
+ |-----------|----------|---------|
57
+ | Processor not set before enqueue | Direct, Internal Queue, BullMQ | `Processor not set. Call setProcessor() first.` |
58
+ | Processor not set before adding worker | BullMQ | `Processor not set. Call setProcessor() first.` |
59
+ | Enqueue in worker-only mode | BullMQ | `Cannot enqueue jobs in worker-only mode. Set mode to "queue-only" or "both".` |
60
+ | Queue helper unexpectedly null | BullMQ | `Queue helper not initialized. This should not happen in queue-enabled mode.` |
61
+
62
+ ## Troubleshooting
63
+
64
+ ### "Mail options not configured"
65
+
66
+ **Cause:** `MailKeys.MAIL_OPTIONS` was not bound in the DI container before `MailComponent` was registered. The component checks `isBound()` in its `binding()` phase and throws immediately.
67
+
68
+ **Fix:** Ensure the options binding exists before calling `this.application.component(MailComponent)`:
69
+
70
+ ```typescript
71
+ this.application.bind({ key: MailKeys.MAIL_OPTIONS }).toValue({
72
+ provider: MailProviders.NODEMAILER,
73
+ from: 'noreply@example.com',
74
+ config: { host: 'smtp.example.com', port: 587, secure: false, auth: { user: '...', pass: '...' } },
75
+ });
76
+
77
+ // Then register the component
78
+ this.application.component(MailComponent);
79
+ ```
80
+
81
+ ### "TEMPLATE_NOT_FOUND" when calling `sendTemplate()`
82
+
83
+ **Cause:** The template name passed to `sendTemplate()` was never registered via `templateEngine.registerTemplate()`.
84
+
85
+ **Fix:** Register the template before sending. If templates are loaded from a database, ensure the sync runs before the first `sendTemplate()` call:
86
+
87
+ ```typescript
88
+ this.templateEngine.registerTemplate({
89
+ name: 'welcome-email',
90
+ content: '<h1>Welcome {{userName}}</h1>',
91
+ });
92
+ ```
93
+
94
+ ### Emails silently fail with `success: false`
95
+
96
+ **Cause:** The transport connection is misconfigured (wrong credentials, blocked port, expired OAuth2 token). The `MailService.send()` method catches transport errors and returns `{ success: false, error: '...' }` rather than throwing.
97
+
98
+ **Fix:** Check `result.error` for the specific transport error. Verify transport on startup:
99
+
100
+ ```typescript
101
+ const isConnected = await this.mailService.verify();
102
+ if (!isConnected) {
103
+ this.logger.error('Mail transport verification failed');
104
+ }
105
+ ```
106
+
107
+ ### BullMQ executor not processing jobs
108
+
109
+ **Cause:** The `mode` is set to `'queue-only'` which only enqueues jobs without starting a worker, or the Redis connection is unreachable.
110
+
111
+ **Fix:** Ensure `mode` is `'both'` or `'worker-only'` on the instance that should process jobs. Verify Redis connectivity:
112
+
113
+ ```typescript
114
+ {
115
+ type: 'bullmq',
116
+ bullmq: {
117
+ redis: { host: 'localhost', port: 6379, /* ... */ },
118
+ queue: { identifier: 'mail-queue', name: 'mail-queue' },
119
+ mode: 'both', // Must be 'both' or 'worker-only' to process
120
+ },
121
+ }
122
+ ```
123
+
124
+ ### Template variables not replaced (placeholders preserved)
125
+
126
+ **Cause:** The `data` object passed to `render()` or `sendTemplate()` does not contain all <code v-pre>{{key}}</code> placeholders found in the template. When `requireValidate` is **not** set (or set to `false`), the template engine preserves the original placeholder text as-is (e.g., <code v-pre>{{missingKey}}</code> remains literally in the output). It does **not** replace missing variables with empty strings.
127
+
128
+ **Fix:** Use `validateTemplateData()` to check which keys are missing before rendering:
129
+
130
+ ```typescript
131
+ const validation = this.templateEngine.validateTemplateData({ template, data });
132
+ if (!validation.isValid) {
133
+ console.error('Missing keys:', validation.missingKeys);
134
+ }
135
+ ```
136
+
137
+ Or set `requireValidate: true` to throw an error when keys are missing:
138
+
139
+ ```typescript
140
+ const html = this.templateEngine.render({
141
+ templateName: 'welcome-email',
142
+ data,
143
+ requireValidate: true, // Throws if any placeholders are missing
144
+ });
145
+ ```
146
+
147
+ ### "Processor not set. Call setProcessor() first."
148
+
149
+ **Cause:** The queue executor's `enqueueVerificationEmail()` was called before `setProcessor()`. All three executor types (Direct, Internal Queue, BullMQ) require a processor function to be registered first.
150
+
151
+ **Fix:** Call `setProcessor()` before enqueuing any jobs:
152
+
153
+ ```typescript
154
+ executor.setProcessor(async (email: string) => {
155
+ // Your email processing logic here
156
+ return { success: true, message: 'Verification email sent', expiresInMinutes: 10 };
157
+ });
158
+ ```
159
+
160
+ ### "Cannot enqueue jobs in worker-only mode"
161
+
162
+ **Cause:** The BullMQ executor is configured with `mode: 'worker-only'`, which does not initialize a queue and therefore cannot accept new jobs.
163
+
164
+ **Fix:** Use `mode: 'both'` or `mode: 'queue-only'` on instances that need to enqueue jobs. Use `mode: 'worker-only'` only on dedicated worker processes.
165
+
166
+ ### Credential logging at startup
167
+
168
+ **Cause:** The `MailComponent.createAndBindInstances()` method logs the full `mailOptions` object (including credentials) at `info` level during initialization. This is by design for debugging, but it means sensitive values like API keys, passwords, and OAuth tokens will appear in logs.
169
+
170
+ **Fix:** In production, either use a log level higher than `info` for the mail component scope, or ensure your log pipeline redacts sensitive fields.
171
+
172
+ ## See Also
173
+
174
+ - [Setup & Configuration](./) -- Quick reference, setup steps, configuration options, and binding keys
175
+ - [Usage & Examples](./usage) -- Sending emails, templates, queue executors, and verification
176
+ - [API Reference](./api) -- Architecture, interfaces, and internals