@venizia/ignis-docs 0.2.0 → 0.2.1-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 (142) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +22 -11
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +6 -2
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +182 -93
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -1,12 +1,12 @@
1
1
  # Mail -- Usage & Examples
2
2
 
3
- > Practical examples for sending emails, using templates, queue executors, verification generators, and batch operations.
3
+ > Practical examples for sending emails, using templates, queue executors, and verification generators.
4
4
 
5
- ## Sending Emails
5
+ ## Sending emails
6
6
 
7
- Inject `IMailService` via the `MailKeys.MAIL_SERVICE` binding key to send emails from any service.
7
+ Inject `IMailService` via the `MailKeys.MAIL_SERVICE` binding key from any service.
8
8
 
9
- **Sending a simple email:**
9
+ **Send a single email:**
10
10
 
11
11
  ```typescript
12
12
  import { BaseService, inject } from '@venizia/ignis';
@@ -39,7 +39,10 @@ export class UserService extends BaseService {
39
39
  }
40
40
  ```
41
41
 
42
- **Batch email sending:**
42
+ - **Default `from`.** If `message.from` is omitted, `send()` fills it from `MailKeys.MAIL_OPTIONS`. With `fromName` set, the default renders as `"fromName" <from>`; with neither `from` nor `fromName`, it falls back to `MailDefaults.FALLBACK_FROM` (`noreply@example.com`).
43
+ - **Transport errors never throw here.** The built-in Nodemailer and Mailgun transports catch their own errors and return `{ success: false, error }`. `send()` only throws `SEND_FAILED` for a custom transport that throws instead of returning a failed result.
44
+
45
+ **Send a batch of emails:**
43
46
 
44
47
  ```typescript
45
48
  async sendBulkNotifications(users: Array<{ email: string; name: string }>) {
@@ -64,31 +67,35 @@ async sendBulkNotifications(users: Array<{ email: string; name: string }>) {
64
67
  }
65
68
  ```
66
69
 
67
- **Message validation:**
70
+ - **Concurrency defaults to `MailDefaults.BATCH_CONCURRENCY` (`5`).** `sendBatch()` runs every message through `send()` with `executePromiseWithLimit()`.
71
+ - **One bad message never aborts the batch.** A `send()` that throws is caught per-message and converted to `{ success: false, error }`; only a failure of the batch operation itself throws `BATCH_SEND_FAILED`.
72
+
73
+ ### Message validation
68
74
 
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:
75
+ `MailService.validateMessage()` runs before every send and throws immediately -- before the transport is ever called -- if any of these hold:
70
76
 
71
- | Condition | Error Code | Message |
77
+ | Condition | Error code | Message |
72
78
  |-----------|-----------|---------|
73
- | `to` is missing or empty array | `MailErrorCodes.INVALID_RECIPIENT` | `Recipient email address is required` |
79
+ | `to` is missing or an empty array | `MailErrorCodes.INVALID_RECIPIENT` | `Recipient email address is required` |
74
80
  | `subject` is missing | `MailErrorCodes.INVALID_CONFIGURATION` | `Email subject is required` |
75
81
  | Both `text` and `html` are missing | `MailErrorCodes.INVALID_CONFIGURATION` | `Email must have either text or html content` |
76
82
 
77
83
  ```typescript
78
- // This will throw BEFORE reaching the transport
84
+ // Throws before reaching the transport
79
85
  await mailService.send({
80
86
  to: 'user@example.com',
81
87
  subject: '', // Empty subject triggers validation error
82
88
  html: '<p>Hello</p>',
83
89
  });
84
- // Error: { statusCode: 400, messageCode: 'core.mail.invalid_configuration', message: 'Email subject is required' }
90
+ // Error: { statusCode: 400, message: 'Email subject is required',
91
+ // normalized: { code: 'core.mail.invalid_configuration', args: {}, text: 'Email subject is required' } }
85
92
  ```
86
93
 
87
- ## Template Engine
94
+ ## Template engine
88
95
 
89
- ### Using Templates
96
+ ### Register and send a template
90
97
 
91
- Inject both `IMailTemplateEngine` and `IMailService` to register templates and send template-based emails.
98
+ Inject both `IMailTemplateEngine` and `IMailService`. `sendTemplate()` renders through the engine and sends via `IMailService` -- it never bypasses `send()`.
92
99
 
93
100
  ```typescript
94
101
  import { BaseService, inject } from '@venizia/ignis';
@@ -106,7 +113,6 @@ export class NotificationService extends BaseService {
106
113
  }
107
114
 
108
115
  registerTemplates() {
109
- // Register a welcome email template
110
116
  this.templateEngine.registerTemplate({
111
117
  name: 'welcome-email',
112
118
  content: `
@@ -126,72 +132,33 @@ export class NotificationService extends BaseService {
126
132
  }
127
133
 
128
134
  async sendWelcomeEmail(userEmail: string, userName: string, verificationCode: string) {
129
- const result = await this.mailService.sendTemplate({
135
+ return this.mailService.sendTemplate({
130
136
  templateName: 'welcome-email',
131
- data: {
132
- userName,
133
- verificationCode,
134
- appName: 'My Application',
135
- },
137
+ data: { userName, verificationCode, appName: 'My Application' },
136
138
  recipients: userEmail,
137
139
  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
- ],
140
+ attachments: [{ filename: 'logo.png', path: '/path/to/logo.png', cid: 'logo' }],
146
141
  },
147
142
  });
148
-
149
- return result;
150
143
  }
151
144
  }
152
145
  ```
153
146
 
154
- ### Template Rendering
147
+ - **Subject resolution order.** `options.subject` (explicit override) beats the template's own `subject` (rendered through the same engine) beats the literal fallback `'No Subject'`.
148
+ - **`sendTemplate()` requires the template engine binding.** It throws `INVALID_CONFIGURATION` ("Template engine not configured") if `MailKeys.MAIL_TEMPLATE_ENGINE` was never injected -- the constructor parameter is `isOptional: true`, so a service that skips it degrades silently until the first `sendTemplate()` call.
155
149
 
156
- The `TemplateEngineService` provides a simple <code v-pre>{{variable}}</code> substitution engine using an in-memory `Map<string, ITemplate>` as its template store.
150
+ ### How rendering works
157
151
 
158
- The `renderSimpleTemplate()` method uses regex `/\{\{(\s*[\w.]+\s*)\}\}/g` to find placeholders. For each match:
152
+ `TemplateEngineService` keeps templates in an in-memory `Map<string, ITemplate>` and substitutes <code v-pre>{{variable}}</code> placeholders with regex `/\{\{(\s*[\w.]+\s*)\}\}/g`.
159
153
 
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)`
154
+ - **Nested lookup.** A key is trimmed, then resolved by splitting on `.` and walking the data object (`user.profile.name`).
155
+ - **Missing values are preserved, not blanked.** If a resolved value is `undefined` or `null`, the original <code v-pre>{{placeholder}}</code> text stays in the output and a warning is logged -- it is never replaced with an empty string.
156
+ - **String coercion.** A resolved value is converted with `String(value)`.
164
157
 
165
158
  > [!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
159
+ > Missing template variables are **not** replaced with empty strings. This makes debugging easier -- you can see which variables were not resolved directly in the rendered output.
179
160
 
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:
161
+ ### Validate template data before sending
195
162
 
196
163
  ```typescript
197
164
  const template = '<h1>Hello {{userName}}, your code is {{code}}</h1>';
@@ -200,23 +167,32 @@ const data = { userName: 'John' }; // Missing 'code'
200
167
  const validation = this.templateEngine.validateTemplateData({ template, data });
201
168
 
202
169
  if (!validation.isValid) {
203
- console.error('Missing template variables:', validation.missingKeys);
204
- // Output: ['code']
170
+ console.error('Missing template variables:', validation.missingKeys); // ['code']
205
171
  }
206
172
 
207
- // Render with validation
173
+ // Or throw at render time instead of checking manually
208
174
  try {
209
175
  const html = this.templateEngine.render({
210
176
  templateData: template,
211
177
  data,
212
- requireValidate: true, // Throws error if validation fails
178
+ requireValidate: true, // Throws INVALID_CONFIGURATION if any placeholder is missing
213
179
  });
214
180
  } catch (error) {
215
181
  console.error('Template rendering failed:', error.message);
216
182
  }
217
183
  ```
218
184
 
219
- ### Syncing Templates from a Database
185
+ `validateTemplateData()` extracts every unique placeholder key from the template and reports:
186
+
187
+ ```typescript
188
+ {
189
+ isValid: boolean; // true if all placeholders resolve to a non-null value
190
+ missingKeys: string[]; // placeholder names missing from data
191
+ allKeys: string[]; // every unique placeholder name found
192
+ }
193
+ ```
194
+
195
+ ### Sync templates from a database
220
196
 
221
197
  ```typescript
222
198
  async syncTemplatesFromDatabase() {
@@ -229,99 +205,112 @@ async syncTemplatesFromDatabase() {
229
205
  });
230
206
 
231
207
  const templateConfigs = await configRepository.find({
232
- filter: {
233
- where: {
234
- code: { inq: ['MAIL_TEMPLATE_WELCOME', 'MAIL_TEMPLATE_VERIFICATION'] },
235
- },
236
- },
208
+ filter: { where: { code: { inq: ['MAIL_TEMPLATE_WELCOME', 'MAIL_TEMPLATE_VERIFICATION'] } } },
237
209
  });
238
210
 
239
211
  templateConfigs.forEach(config => {
240
212
  templateEngine.registerTemplate({
241
213
  name: config.code,
242
214
  content: config.jValue.content,
243
- options: {
244
- subject: config.jValue.subject,
245
- description: config.jValue.description,
246
- },
215
+ options: { subject: config.jValue.subject, description: config.jValue.description },
247
216
  });
248
217
  this.logger.info('[syncTemplates] Registered template: %s', config.code);
249
218
  });
250
219
  }
251
220
  ```
252
221
 
253
- ## Queue Executors
222
+ ## Queue executors
223
+
224
+ `IMailQueueExecutor` is a separate subsystem from `MailService` -- it only exposes `enqueueVerificationEmail()` and `setProcessor()`, and never calls `send()` on its own. You provide the processor function (typically one that wraps `mailService.send()`); the executor's job is timing, retry, and delivery guarantees around calling it.
254
225
 
255
- ### Direct Executor
226
+ | Executor | Class | Backing |
227
+ |----------|-------|---------|
228
+ | `direct` | `DirectMailExecutorHelper` | None -- calls the processor immediately |
229
+ | `internal-queue` | `InternalQueueMailExecutorHelper` | In-memory `SequentialQueueHelper` |
230
+ | `bullmq` | `BullMQMailExecutorHelper` | Redis, via `BullMQHelper` |
256
231
 
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.
232
+ ### Direct executor
258
233
 
259
- ### Internal Queue Executor
234
+ Calls the processor immediately, with no queueing. Returns `{ queued: false, ... }`. Throws `Processor not set. Call setProcessor() first.` if `enqueueVerificationEmail()` runs before `setProcessor()`. Use it for development or when a caller needs a synchronous result.
260
235
 
261
- `InternalQueueMailExecutorHelper` extends `BaseHelper`. Uses the in-memory `QueueHelper` from `@venizia/ignis-helpers` with `autoDispatch: true`. Key behaviors:
236
+ ### Internal queue executor
262
237
 
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
238
+ In-memory, single-instance, backed by `SequentialQueueHelper` from `@venizia/ignis-helpers` with `autoDispatch: true`.
269
239
 
270
- ### BullMQ Executor
240
+ - Job IDs follow `job_<counter>_<timestamp>`.
241
+ - A `delay` option schedules the enqueue itself via `setTimeout`, tracked in a `delayedJobs` map.
242
+ - On failure (a thrown error, or the processor returning `{ success: false }`), it retries up to `options.attempts` (default `3`) with backoff: `exponential` is `delay * 2^(attempt - 1)`, `fixed` is the raw delay, and no `backoff` config at all defaults to `1000ms`.
243
+ - Does not persist jobs across restarts. `close()` clears every pending delayed/retry timer.
271
244
 
272
- `BullMQMailExecutorHelper` extends `BaseHelper`. Full-featured Redis-backed queue with:
245
+ ### BullMQ executor
273
246
 
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)
247
+ Redis-backed, distributed, backed by `BullMQHelper`. Job persistence, worker concurrency, prioritization, and delayed execution come from BullMQ itself. `removeOnComplete: true`, `removeOnFail: false` (failed jobs stay for debugging). Default enqueue options: `attempts: 3`, `backoff: { type: 'exponential', delay: 1000 }`.
281
248
 
282
- **Mode behavior:**
249
+ **Mode gates what the executor can do:**
283
250
 
284
- | Mode | Queue Initialized | Workers Created | Can Enqueue | Can Process |
285
- |------|-------------------|-----------------|-------------|-------------|
286
- | `'queue-only'` | Yes | No (skipped in `setProcessor`) | Yes | No |
251
+ | Mode | Queue created | Workers created | Can enqueue | Can process |
252
+ |------|----------------|------------------|-------------|-------------|
253
+ | `'queue-only'` | Yes | No (`setProcessor()` skips worker creation) | Yes -- **without** calling `setProcessor()` first | No |
287
254
  | `'worker-only'` | No | Yes | No (throws) | Yes |
288
- | `'both'` | Yes | Yes | Yes | Yes |
255
+ | `'both'` | Yes | Yes | Yes (requires `setProcessor()` first) | Yes |
289
256
 
290
- ## Verification Generators
257
+ > [!IMPORTANT]
258
+ > `'queue-only'` mode is the one exception to "call `setProcessor()` before you enqueue" -- `enqueueVerificationEmail()` only requires a processor when the mode is *not* `queue-only`. A producer instance can enqueue jobs a separate `worker-only` instance later processes.
291
259
 
292
- Three generators are registered by `MailComponent`:
260
+ **Dynamic worker management** -- get the bound instance and manage workers at runtime, no restart required:
293
261
 
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
262
+ ```typescript
263
+ const executor = this.application.get<BullMQMailExecutorHelper>({
264
+ key: MailKeys.MAIL_QUEUE_EXECUTOR_INSTANCE,
265
+ });
266
+
267
+ executor.addWorker({ workerIdentifier: 'mail-queue-worker-extra', concurrency: 10, lockDuration: 60000 });
297
268
 
298
- **NumericCodeGenerator:**
269
+ executor.getWorkerCount(); // e.g. 2
270
+ executor.getMode(); // e.g. 'both'
299
271
 
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"`).
272
+ await executor.removeWorker(1); // remove by array index
273
+ await executor.clearWorkers(); // close and remove every worker
274
+ ```
275
+
276
+ `setProcessor()` on the BullMQ executor is `async` and takes an optional second argument for worker configuration -- it clears all existing workers before creating new ones:
277
+
278
+ ```typescript
279
+ await executor.setProcessor(
280
+ async (email: string) => {
281
+ // your processing logic
282
+ return { success: true, message: 'Sent', expiresInMinutes: 10 };
283
+ },
284
+ {
285
+ numberOfWorkers: 3, // default: 1
286
+ concurrencyPerWorker: 10, // default: 5
287
+ lockDuration: 60000, // job lock duration in ms, default: 30000
288
+ },
289
+ );
290
+ ```
301
291
 
302
- **RandomTokenGenerator:**
292
+ ## Verification generators
303
293
 
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.
294
+ `MailComponent` binds three generators, all **transient** (a fresh instance per resolution, since none is registered with `.setScope('singleton')`):
305
295
 
306
- **DefaultVerificationDataGenerator:**
296
+ | Generator | Implements | Behavior |
297
+ |-----------|-----------|----------|
298
+ | `NumericCodeGenerator` | `IVerificationCodeGenerator` | `crypto.randomInt(0, 10^length)`, zero-padded via `padStart()` -- e.g. code `42` at length `6` becomes `"000042"` |
299
+ | `RandomTokenGenerator` | `IVerificationTokenGenerator` | `crypto.randomBytes(bytes).toString('base64url')` -- 32 bytes produces a 43-character string, URL-safe, no padding |
300
+ | `DefaultVerificationDataGenerator` | `IVerificationDataGenerator` | Composes both generators via `@inject`, producing a full `IVerificationData` with separate expiries |
307
301
 
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`
302
+ `DefaultVerificationDataGenerator.generateVerificationData()` returns:
315
303
 
316
- **Email verification flow example:**
304
+ - A short numeric code (manual entry: SMS, email)
305
+ - A long base64url token (URL-based verification links)
306
+ - Separate expiries: code via `getExpiryTime(minutes)`, token via `getExpiryTimeInHours(hours)`
307
+ - ISO 8601 generation timestamps, `codeAttempts: 0`, `lastCodeSentAt` set to now
308
+
309
+ **End-to-end verification flow:**
317
310
 
318
311
  ```typescript
319
312
  import { BaseService, inject } from '@venizia/ignis';
320
- import {
321
- MailKeys,
322
- type IMailService,
323
- type IVerificationDataGenerator,
324
- } from '@venizia/ignis/mail';
313
+ import { MailKeys, type IMailService, type IVerificationDataGenerator } from '@venizia/ignis/mail';
325
314
 
326
315
  export class AuthService extends BaseService {
327
316
  constructor(
@@ -334,18 +323,15 @@ export class AuthService extends BaseService {
334
323
  }
335
324
 
336
325
  async sendVerificationEmail(userEmail: string) {
337
- // Generate verification code and token
338
326
  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
327
+ codeLength: 6,
328
+ tokenBytes: 32,
329
+ codeExpiryMinutes: 10,
330
+ tokenExpiryHours: 24,
343
331
  });
344
332
 
345
- // Save verification data to database
346
- // await this.saveVerificationData(userEmail, verificationData);
333
+ // Persist verificationData to your own user/verification table here.
347
334
 
348
- // Send verification email
349
335
  const result = await this.mailService.send({
350
336
  to: userEmail,
351
337
  subject: 'Email Verification',
@@ -362,43 +348,15 @@ export class AuthService extends BaseService {
362
348
  }
363
349
  ```
364
350
 
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
351
+ ## Logging and credentials
388
352
 
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
- ```
353
+ `MailComponent.createAndBindInstances()` logs only `mailOptions.provider` and `queueExecutorConfig.type` at `info` level -- by design, never the full config objects, so SMTP passwords, OAuth2 secrets, API keys, and Redis passwords never reach a log sink through the component itself.
394
354
 
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
355
+ > [!WARNING]
356
+ > That guarantee only covers what `MailComponent` logs internally. If your own wrapper component or bootstrap code logs the `TMailOptions` or `IMailQueueExecutorConfig` object directly (for example, while debugging a binding), you reintroduce the leak yourself -- log individual safe fields (`provider`, `type`) instead of the whole object.
399
357
 
400
- ## See Also
358
+ ## See also
401
359
 
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
360
+ - [Setup & Configuration](./) -- quick reference, setup, configuration, and binding keys
361
+ - [API Reference](./api) -- architecture, interfaces, and internals
362
+ - [Error Reference](./errors) -- error codes and troubleshooting