@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.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Mail
|
|
1
|
+
# Mail - Usage & Examples
|
|
2
2
|
|
|
3
|
-
> Practical examples for sending emails, using templates, queue executors, verification generators
|
|
3
|
+
> Practical examples for sending emails, using templates, queue executors, and verification generators.
|
|
4
4
|
|
|
5
|
-
## Sending
|
|
5
|
+
## Sending emails
|
|
6
6
|
|
|
7
|
-
Inject `IMailService` via the `MailKeys.MAIL_SERVICE` binding key
|
|
7
|
+
Inject `IMailService` via the `MailKeys.MAIL_SERVICE` binding key from any service.
|
|
8
8
|
|
|
9
|
-
**
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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,
|
|
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
|
|
94
|
+
## Template engine
|
|
88
95
|
|
|
89
|
-
###
|
|
96
|
+
### Register and send a template
|
|
90
97
|
|
|
91
|
-
Inject both `IMailTemplateEngine` and `IMailService`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
### How rendering works
|
|
157
151
|
|
|
158
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
222
|
+
## Queue executors
|
|
254
223
|
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
232
|
+
### Direct executor
|
|
260
233
|
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
238
|
+
The internal queue executor is in-memory and single-instance, backed by `SequentialQueueHelper` from `@venizia/ignis-helpers` with `autoDispatch: true`.
|
|
271
239
|
|
|
272
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
+
## Verification generators
|
|
301
301
|
|
|
302
|
-
**
|
|
302
|
+
`MailComponent` binds three generators. All are **transient** - a fresh instance per resolution, since none is registered with `.setScope('singleton')`:
|
|
303
303
|
|
|
304
|
-
|
|
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
|
-
|
|
310
|
+
`DefaultVerificationDataGenerator.generateVerificationData()` returns:
|
|
307
311
|
|
|
308
|
-
|
|
309
|
-
- A
|
|
310
|
-
-
|
|
311
|
-
-
|
|
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
|
-
**
|
|
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,
|
|
340
|
-
tokenBytes: 32,
|
|
341
|
-
codeExpiryMinutes: 10,
|
|
342
|
-
tokenExpiryHours: 24,
|
|
335
|
+
codeLength: 6,
|
|
336
|
+
tokenBytes: 32,
|
|
337
|
+
codeExpiryMinutes: 10,
|
|
338
|
+
tokenExpiryHours: 24,
|
|
343
339
|
});
|
|
344
340
|
|
|
345
|
-
//
|
|
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
|
-
|
|
359
|
+
## Logging and credentials
|
|
366
360
|
|
|
367
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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
|
|
366
|
+
## See also
|
|
401
367
|
|
|
402
|
-
- [Setup & Configuration](./) --
|
|
403
|
-
- [API Reference](./api) --
|
|
404
|
-
- [Error Reference](./errors) --
|
|
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
|