@venizia/ignis-docs 0.2.0 → 0.2.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) 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 +24 -13
  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 +27 -3
  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 +8 -4
  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 +247 -153
  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 +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -1,12 +1,12 @@
1
- # Mail -- Usage & Examples
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. It 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` wins if you pass it. Otherwise the template's own `subject` wins, rendered through the same engine. If neither is set, the subject falls back to the literal `'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 still compiles without it - but the first `sendTemplate()` call then fails.
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>`. It substitutes <code v-pre>{{variable}}</code> placeholders using the 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. The engine never replaces it with an empty string.
156
+ - **String coercion.** The engine converts a resolved value 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
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
- ```
159
+ > Missing template variables are **not** replaced with empty strings. This makes debugging easier: the rendered output shows you exactly which variables did not resolve.
191
160
 
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,120 @@ 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
254
223
 
255
- ### Direct Executor
224
+ `IMailQueueExecutor` is a separate subsystem from `MailService`. It only exposes `enqueueVerificationEmail()` and `setProcessor()`, and it 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 that function.
256
225
 
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.
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` |
258
231
 
259
- ### Internal Queue Executor
232
+ ### Direct executor
260
233
 
261
- `InternalQueueMailExecutorHelper` extends `BaseHelper`. Uses the in-memory `QueueHelper` from `@venizia/ignis-helpers` with `autoDispatch: true`. Key behaviors:
234
+ The direct executor calls the processor immediately, with no queueing. It returns `{ queued: false, ... }`. If `enqueueVerificationEmail()` runs before `setProcessor()`, it throws `Processor not set. Call setProcessor() first.` Use it for development, or whenever a caller needs a synchronous result.
262
235
 
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
236
+ ### Internal queue executor
269
237
 
270
- ### BullMQ Executor
238
+ The internal queue executor is in-memory and single-instance, backed by `SequentialQueueHelper` from `@venizia/ignis-helpers` with `autoDispatch: true`.
271
239
 
272
- `BullMQMailExecutorHelper` extends `BaseHelper`. Full-featured Redis-backed queue with:
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`).
243
+ - Does not persist jobs across restarts. `close()` clears every pending delayed/retry timer.
273
244
 
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)
245
+ Retry backoff:
281
246
 
282
- **Mode behavior:**
247
+ | `backoff` config | Delay |
248
+ |---|---|
249
+ | `{ type: 'exponential', delay }` | `delay * 2^(attempt - 1)` |
250
+ | `{ type: 'fixed', delay }` | the raw `delay` |
251
+ | Not set | `1000ms` |
283
252
 
284
- | Mode | Queue Initialized | Workers Created | Can Enqueue | Can Process |
285
- |------|-------------------|-----------------|-------------|-------------|
286
- | `'queue-only'` | Yes | No (skipped in `setProcessor`) | Yes | No |
253
+ ### BullMQ executor
254
+
255
+ 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 }`.
256
+
257
+ **Mode gates what the executor can do:**
258
+
259
+ | Mode | Queue created | Workers created | Can enqueue | Can process |
260
+ |------|----------------|------------------|-------------|-------------|
261
+ | `'queue-only'` | Yes | No (`setProcessor()` skips worker creation) | Yes -- **without** calling `setProcessor()` first | No |
287
262
  | `'worker-only'` | No | Yes | No (throws) | Yes |
288
- | `'both'` | Yes | Yes | Yes | Yes |
263
+ | `'both'` | Yes | Yes | Yes (requires `setProcessor()` first) | Yes |
289
264
 
290
- ## Verification Generators
265
+ > [!IMPORTANT]
266
+ > `'queue-only'` mode is the one exception to "call `setProcessor()` before you enqueue." In that mode, `enqueueVerificationEmail()` does not need a processor. A producer instance can enqueue jobs that a separate `worker-only` instance later processes.
291
267
 
292
- Three generators are registered by `MailComponent`:
268
+ **Dynamic worker management.** Get the bound instance and manage workers at runtime - no restart required:
269
+
270
+ ```typescript
271
+ const executor = this.application.get<BullMQMailExecutorHelper>({
272
+ key: MailKeys.MAIL_QUEUE_EXECUTOR_INSTANCE,
273
+ });
293
274
 
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
275
+ executor.addWorker({ workerIdentifier: 'mail-queue-worker-extra', concurrency: 10, lockDuration: 60000 });
297
276
 
298
- **NumericCodeGenerator:**
277
+ executor.getWorkerCount(); // e.g. 2
278
+ executor.getMode(); // e.g. 'both'
279
+
280
+ await executor.removeWorker(1); // remove by array index
281
+ await executor.clearWorkers(); // close and remove every worker
282
+ ```
283
+
284
+ `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:
285
+
286
+ ```typescript
287
+ await executor.setProcessor(
288
+ async (email: string) => {
289
+ // your processing logic
290
+ return { success: true, message: 'Sent', expiresInMinutes: 10 };
291
+ },
292
+ {
293
+ numberOfWorkers: 3, // default: 1
294
+ concurrencyPerWorker: 10, // default: 5
295
+ lockDuration: 60000, // job lock duration in ms, default: 30000
296
+ },
297
+ );
298
+ ```
299
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"`).
300
+ ## Verification generators
301
301
 
302
- **RandomTokenGenerator:**
302
+ `MailComponent` binds three generators. All are **transient** - a fresh instance per resolution, since none is registered with `.setScope('singleton')`:
303
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.
304
+ | Generator | Implements | Behavior |
305
+ |-----------|-----------|----------|
306
+ | `NumericCodeGenerator` | `IVerificationCodeGenerator` | `crypto.randomInt(0, 10^length)`, zero-padded via `padStart()` -- e.g. code `42` at length `6` becomes `"000042"` |
307
+ | `RandomTokenGenerator` | `IVerificationTokenGenerator` | `crypto.randomBytes(bytes).toString('base64url')` -- 32 bytes produces a 43-character string, URL-safe, no padding |
308
+ | `DefaultVerificationDataGenerator` | `IVerificationDataGenerator` | Composes both generators via `@inject`, producing a full `IVerificationData` with separate expiries |
305
309
 
306
- **DefaultVerificationDataGenerator:**
310
+ `DefaultVerificationDataGenerator.generateVerificationData()` returns:
307
311
 
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`
312
+ - A short numeric code (manual entry: SMS, email)
313
+ - A long base64url token (URL-based verification links)
314
+ - Separate expiries: code via `getExpiryTime(minutes)`, token via `getExpiryTimeInHours(hours)`
315
+ - ISO 8601 generation timestamps, `codeAttempts: 0`, `lastCodeSentAt` set to now
315
316
 
316
- **Email verification flow example:**
317
+ **End-to-end verification flow:**
317
318
 
318
319
  ```typescript
319
320
  import { BaseService, inject } from '@venizia/ignis';
320
- import {
321
- MailKeys,
322
- type IMailService,
323
- type IVerificationDataGenerator,
324
- } from '@venizia/ignis/mail';
321
+ import { MailKeys, type IMailService, type IVerificationDataGenerator } from '@venizia/ignis/mail';
325
322
 
326
323
  export class AuthService extends BaseService {
327
324
  constructor(
@@ -334,18 +331,15 @@ export class AuthService extends BaseService {
334
331
  }
335
332
 
336
333
  async sendVerificationEmail(userEmail: string) {
337
- // Generate verification code and token
338
334
  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
335
+ codeLength: 6,
336
+ tokenBytes: 32,
337
+ codeExpiryMinutes: 10,
338
+ tokenExpiryHours: 24,
343
339
  });
344
340
 
345
- // Save verification data to database
346
- // await this.saveVerificationData(userEmail, verificationData);
341
+ // Persist verificationData to your own user/verification table here.
347
342
 
348
- // Send verification email
349
343
  const result = await this.mailService.send({
350
344
  to: userEmail,
351
345
  subject: 'Email Verification',
@@ -362,43 +356,15 @@ export class AuthService extends BaseService {
362
356
  }
363
357
  ```
364
358
 
365
- **Storing verification data:**
359
+ ## Logging and credentials
366
360
 
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
- ```
361
+ `MailComponent.createAndBindInstances()` logs only `mailOptions.provider` and `queueExecutorConfig.type`, at `info` level. It never logs the full config objects, by design. That keeps SMTP passwords, OAuth2 secrets, API keys, and Redis passwords out of the log sink - at least through the component itself.
394
362
 
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
363
+ > [!WARNING]
364
+ > That guarantee only covers what `MailComponent` logs internally. If your own wrapper component or bootstrap code logs the `TMailOptions` or `IMailQueueExecutorConfig` object directly, you reintroduce the leak yourself. This commonly happens while debugging a binding. Log individual safe fields (`provider`, `type`) instead of the whole object.
399
365
 
400
- ## See Also
366
+ ## See also
401
367
 
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
368
+ - [Setup & Configuration](./) -- quick reference, setup, configuration, and binding keys
369
+ - [API Reference](./api) -- architecture, interfaces, and internals
370
+ - [Error Reference](./errors) -- error codes and troubleshooting