@venizia/ignis-docs 0.0.8 → 0.2.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.
- package/README.md +7 -7
- package/content/best-practices/api-usage-examples.md +15 -12
- package/content/best-practices/architectural-patterns.md +70 -78
- package/content/best-practices/architecture-decisions.md +91 -60
- package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
- package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
- package/content/best-practices/code-style-standards/control-flow.md +5 -2
- package/content/best-practices/code-style-standards/documentation.md +13 -13
- package/content/best-practices/code-style-standards/function-patterns.md +9 -10
- package/content/best-practices/code-style-standards/index.md +1 -1
- package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
- package/content/best-practices/code-style-standards/route-definitions.md +30 -12
- package/content/best-practices/code-style-standards/tooling.md +8 -5
- package/content/best-practices/code-style-standards/type-safety.md +13 -12
- package/content/best-practices/common-pitfalls.md +56 -37
- package/content/best-practices/contribution-workflow.md +13 -14
- package/content/best-practices/data-modeling.md +46 -22
- package/content/best-practices/deployment-strategies.md +28 -27
- package/content/best-practices/error-handling.md +48 -24
- package/content/best-practices/index.md +5 -5
- package/content/best-practices/performance-optimization.md +40 -31
- package/content/best-practices/security-guidelines.md +52 -23
- package/content/best-practices/testing-strategies.md +65 -51
- package/content/best-practices/troubleshooting-tips.md +24 -24
- package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
- package/content/extensions/components/authentication/api.md +19 -19
- package/content/extensions/components/authentication/errors.md +7 -7
- package/content/extensions/components/authentication/index.md +10 -8
- package/content/extensions/components/authentication/usage.md +101 -6
- package/content/extensions/components/authorization/api.md +45 -25
- package/content/extensions/components/authorization/errors.md +6 -6
- package/content/extensions/components/authorization/index.md +11 -10
- package/content/extensions/components/authorization/usage.md +21 -21
- package/content/extensions/components/health-check.md +1 -1
- package/content/extensions/components/index.md +5 -5
- package/content/extensions/components/mail/errors.md +15 -15
- package/content/extensions/components/mail/index.md +1 -2
- package/content/extensions/components/mail/usage.md +1 -1
- package/content/extensions/components/request-tracker.md +1 -1
- package/content/extensions/components/socket-io/api.md +9 -9
- package/content/extensions/components/socket-io/errors.md +5 -5
- package/content/extensions/components/socket-io/index.md +8 -8
- package/content/extensions/components/socket-io/usage.md +1 -1
- package/content/extensions/components/static-asset/api.md +17 -4
- package/content/extensions/components/static-asset/errors.md +4 -4
- package/content/extensions/components/static-asset/index.md +26 -28
- package/content/extensions/components/static-asset/usage.md +13 -12
- package/content/extensions/components/template/index.md +2 -2
- package/content/extensions/components/template/setup-page.md +1 -1
- package/content/extensions/components/websocket/api.md +3 -3
- package/content/extensions/components/websocket/errors.md +5 -5
- package/content/extensions/components/websocket/index.md +5 -5
- package/content/extensions/components/websocket/usage.md +3 -3
- package/content/extensions/helpers/cron/index.md +2 -2
- package/content/extensions/helpers/crypto/index.md +1 -1
- package/content/extensions/helpers/env/index.md +27 -12
- package/content/extensions/helpers/error/index.md +81 -25
- package/content/extensions/helpers/index.md +2 -3
- package/content/extensions/helpers/inversion/index.md +15 -7
- package/content/extensions/helpers/kafka/compile-binary.md +92 -0
- package/content/extensions/helpers/kafka/examples.md +1 -1
- package/content/extensions/helpers/kafka/index.md +3 -0
- package/content/extensions/helpers/logger/index.md +32 -2
- package/content/extensions/helpers/network/index.md +6 -0
- package/content/extensions/helpers/queue/index.md +14 -17
- package/content/extensions/helpers/redis/index.md +548 -323
- package/content/extensions/helpers/socket-io/index.md +14 -10
- package/content/extensions/helpers/storage/api.md +44 -8
- package/content/extensions/helpers/storage/index.md +43 -7
- package/content/extensions/helpers/template/index.md +6 -3
- package/content/extensions/helpers/types/index.md +11 -8
- package/content/extensions/helpers/websocket/api.md +9 -9
- package/content/extensions/helpers/websocket/index.md +7 -7
- package/content/extensions/helpers/worker-thread/index.md +2 -2
- package/content/extensions/index.md +3 -4
- package/content/extensions/src-details/mcp-server.md +18 -24
- package/content/guides/core-concepts/application/bootstrapping.md +11 -14
- package/content/guides/core-concepts/application/index.md +3 -3
- package/content/guides/core-concepts/components.md +19 -10
- package/content/guides/core-concepts/dependency-injection.md +6 -3
- package/content/guides/core-concepts/grpc-controllers.md +6 -5
- package/content/guides/core-concepts/persistent/datasources.md +42 -43
- package/content/guides/core-concepts/persistent/index.md +16 -7
- package/content/guides/core-concepts/persistent/models.md +24 -20
- package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
- package/content/guides/core-concepts/persistent/repositories.md +40 -23
- package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
- package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
- package/content/guides/core-concepts/persistent/transactions.md +61 -25
- package/content/guides/core-concepts/rest-controllers.md +12 -9
- package/content/guides/core-concepts/services.md +330 -60
- package/content/guides/get-started/5-minute-quickstart.md +15 -15
- package/content/guides/get-started/philosophy.md +36 -36
- package/content/guides/get-started/setup.md +3 -3
- package/content/guides/index.md +3 -3
- package/content/guides/migrations/redis-helpers-migration.md +177 -0
- package/content/guides/migrations/scoped-rbac-migration.md +17 -17
- package/content/guides/migrations/unified-connectors-migration.md +113 -0
- package/content/guides/reference/glossary.md +19 -12
- package/content/guides/reference/mcp-docs-server.md +22 -18
- package/content/guides/tutorials/building-a-crud-api.md +37 -44
- package/content/guides/tutorials/complete-installation.md +17 -17
- package/content/guides/tutorials/ecommerce-api.md +163 -124
- package/content/guides/tutorials/realtime-chat.md +181 -135
- package/content/guides/tutorials/testing.md +65 -523
- package/content/index.md +2 -180
- package/content/public/apple-touch-icon.png +0 -0
- package/content/public/og-image.png +0 -0
- package/content/public/site.webmanifest +11 -0
- package/content/references/base/application.md +4 -5
- package/content/references/base/bootstrapping.md +18 -5
- package/content/references/base/components.md +149 -120
- package/content/references/base/connectors.md +178 -0
- package/content/references/base/controllers.md +41 -30
- package/content/references/base/datasources.md +163 -92
- package/content/references/base/dependency-injection.md +34 -22
- package/content/references/base/filter-system/application-usage.md +17 -14
- package/content/references/base/filter-system/array-operators.md +7 -2
- package/content/references/base/filter-system/comparison-operators.md +3 -0
- package/content/references/base/filter-system/default-filter.md +89 -71
- package/content/references/base/filter-system/fields-order-pagination.md +22 -22
- package/content/references/base/filter-system/index.md +6 -3
- package/content/references/base/filter-system/json-filtering.md +20 -1
- package/content/references/base/filter-system/list-operators.md +1 -1
- package/content/references/base/filter-system/logical-operators.md +33 -1
- package/content/references/base/filter-system/null-operators.md +30 -1
- package/content/references/base/filter-system/quick-reference.md +23 -4
- package/content/references/base/filter-system/tips.md +5 -5
- package/content/references/base/filter-system/use-cases.md +12 -12
- package/content/references/base/grpc-controllers.md +13 -13
- package/content/references/base/index.md +24 -12
- package/content/references/base/middlewares.md +265 -327
- package/content/references/base/models.md +63 -49
- package/content/references/base/providers.md +136 -130
- package/content/references/base/repositories/advanced.md +59 -58
- package/content/references/base/repositories/index.md +115 -91
- package/content/references/base/repositories/mixins.md +55 -291
- package/content/references/base/repositories/relations.md +54 -64
- package/content/references/base/repositories/soft-deletable.md +31 -30
- package/content/references/base/services.md +296 -93
- package/content/references/configuration/environment-variables.md +49 -31
- package/content/references/configuration/index.md +6 -6
- package/content/references/index.md +17 -12
- package/content/references/quick-reference.md +65 -106
- package/content/references/utilities/crypto.md +65 -23
- package/content/references/utilities/index.md +3 -3
- package/content/references/utilities/jsx.md +6 -4
- package/content/references/utilities/module.md +68 -20
- package/content/references/utilities/parse.md +4 -14
- package/content/references/utilities/promise.md +9 -7
- package/content/references/utilities/schema.md +5 -3
- package/dist/mcp-server/common/guards.d.ts +8 -0
- package/dist/mcp-server/common/guards.d.ts.map +1 -0
- package/dist/mcp-server/common/guards.js +14 -0
- package/dist/mcp-server/common/guards.js.map +1 -0
- package/dist/mcp-server/common/index.d.ts +1 -0
- package/dist/mcp-server/common/index.d.ts.map +1 -1
- package/dist/mcp-server/common/index.js +1 -0
- package/dist/mcp-server/common/index.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +4 -2
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/helpers/github.helper.js +1 -1
- package/dist/mcp-server/index.js +7 -2
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +6 -2
- package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/base.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
- package/package.json +9 -9
- package/content/extensions/helpers/testing/index.md +0 -510
- package/content/references/base/middleware.md +0 -347
|
@@ -136,10 +136,10 @@ class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
|
136
136
|
// ✅ Configuration-based instance creation
|
|
137
137
|
class DatabaseProvider extends BaseProvider<Database> {
|
|
138
138
|
value(container: Container): Database {
|
|
139
|
-
const config = container.get(
|
|
139
|
+
const config = container.get<IDatabaseConfig>({ key: 'configs.database' });
|
|
140
140
|
return new Database({
|
|
141
|
-
host: config.
|
|
142
|
-
port: config.
|
|
141
|
+
host: config.host,
|
|
142
|
+
port: config.port,
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -191,7 +191,6 @@ class OrderService extends BaseService {
|
|
|
191
191
|
```typescript
|
|
192
192
|
import { BaseProvider } from '@venizia/ignis';
|
|
193
193
|
import { Container } from '@venizia/ignis-inversion';
|
|
194
|
-
import { injectable } from '@venizia/ignis-inversion';
|
|
195
194
|
|
|
196
195
|
interface ILogger {
|
|
197
196
|
log(message: string): void;
|
|
@@ -211,7 +210,6 @@ class FileLogger implements ILogger {
|
|
|
211
210
|
}
|
|
212
211
|
}
|
|
213
212
|
|
|
214
|
-
@injectable()
|
|
215
213
|
export class LoggerProvider extends BaseProvider<ILogger> {
|
|
216
214
|
constructor() {
|
|
217
215
|
super({ scope: LoggerProvider.name });
|
|
@@ -231,14 +229,25 @@ export class LoggerProvider extends BaseProvider<ILogger> {
|
|
|
231
229
|
}
|
|
232
230
|
```
|
|
233
231
|
|
|
232
|
+
Register the provider with `.toProvider()` - consumers then `get()` the **produced value**, not the provider instance (the container instantiates the provider and calls `value(container)` for you):
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// In your application (e.g. preConfigure)
|
|
236
|
+
this.bind<ILogger>({ key: 'providers.Logger' }).toProvider(LoggerProvider);
|
|
237
|
+
|
|
238
|
+
// Consumers receive the produced ILogger directly
|
|
239
|
+
const logger = this.get<ILogger>({ key: 'providers.Logger' });
|
|
240
|
+
```
|
|
241
|
+
|
|
234
242
|
### Factory Function Provider
|
|
235
243
|
|
|
236
244
|
Providers can return factory functions for deferred instantiation:
|
|
237
245
|
|
|
238
246
|
```typescript
|
|
247
|
+
import { getError } from '@venizia/ignis-helpers';
|
|
248
|
+
|
|
239
249
|
type TGetMailTransportFn = (options: MailOptions) => IMailTransport;
|
|
240
250
|
|
|
241
|
-
@injectable()
|
|
242
251
|
export class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
243
252
|
constructor() {
|
|
244
253
|
super({ scope: MailTransportProvider.name });
|
|
@@ -255,14 +264,17 @@ export class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
|
255
264
|
case 'mailgun':
|
|
256
265
|
return new MailgunTransport(options.config);
|
|
257
266
|
default:
|
|
258
|
-
throw
|
|
267
|
+
throw getError({ message: `Unknown provider: ${options.provider}` });
|
|
259
268
|
}
|
|
260
269
|
};
|
|
261
270
|
}
|
|
262
271
|
}
|
|
263
272
|
|
|
264
|
-
//
|
|
265
|
-
|
|
273
|
+
// Registration
|
|
274
|
+
app.bind({ key: 'providers.MailTransport' }).toProvider(MailTransportProvider);
|
|
275
|
+
|
|
276
|
+
// Usage - get() returns the factory function produced by value()
|
|
277
|
+
const getTransport = app.get<TGetMailTransportFn>({ key: 'providers.MailTransport' });
|
|
266
278
|
const transport = getTransport({ provider: 'nodemailer', config: {...} });
|
|
267
279
|
```
|
|
268
280
|
|
|
@@ -271,7 +283,6 @@ const transport = getTransport({ provider: 'nodemailer', config: {...} });
|
|
|
271
283
|
Access other dependencies through the container:
|
|
272
284
|
|
|
273
285
|
```typescript
|
|
274
|
-
@injectable()
|
|
275
286
|
export class DatabaseProvider extends BaseProvider<Database> {
|
|
276
287
|
constructor() {
|
|
277
288
|
super({ scope: DatabaseProvider.name });
|
|
@@ -279,13 +290,11 @@ export class DatabaseProvider extends BaseProvider<Database> {
|
|
|
279
290
|
|
|
280
291
|
value(container: Container): Database {
|
|
281
292
|
// Resolve dependencies from container
|
|
282
|
-
const config = container.get(
|
|
283
|
-
const logger = container.get(LoggerService);
|
|
293
|
+
const config = container.get<IDatabaseConfig>({ key: 'configs.database' });
|
|
284
294
|
|
|
285
295
|
const database = new Database({
|
|
286
|
-
host: config.
|
|
287
|
-
port: config.
|
|
288
|
-
logger: logger,
|
|
296
|
+
host: config.host,
|
|
297
|
+
port: config.port,
|
|
289
298
|
});
|
|
290
299
|
|
|
291
300
|
this.logger.info('[value] Database instance created');
|
|
@@ -303,51 +312,37 @@ Understanding the provider lifecycle helps you use them effectively.
|
|
|
303
312
|
|
|
304
313
|
```mermaid
|
|
305
314
|
graph TD
|
|
306
|
-
A[Application Start] --> B[
|
|
307
|
-
B --> C[
|
|
308
|
-
C --> D[
|
|
309
|
-
D --> E[
|
|
315
|
+
A[Application Start] --> B[Binding Registered via toProvider]
|
|
316
|
+
B --> C[Consumer Calls container.get with key]
|
|
317
|
+
C --> D[Container Instantiates Provider Class]
|
|
318
|
+
D --> E[Container Calls provider.value container]
|
|
310
319
|
E --> F[value Returns Factory/Instance]
|
|
311
320
|
F --> G[Consumer Uses Returned Value]
|
|
312
|
-
G --> H{Need
|
|
313
|
-
H -->|Yes|
|
|
321
|
+
G --> H{Need the Value Again?}
|
|
322
|
+
H -->|Yes| C
|
|
314
323
|
H -->|No| I[End]
|
|
315
324
|
```
|
|
316
325
|
|
|
317
326
|
### Key Points
|
|
318
327
|
|
|
319
|
-
1. **
|
|
320
|
-
2. **`value()` Called
|
|
321
|
-
3. **
|
|
328
|
+
1. **Registered via `.toProvider()`**: Providers are bound explicitly (`bind({ key }).toProvider(MyProvider)`), not auto-scanned
|
|
329
|
+
2. **`value()` Called by the Container**: `container.get({ key })` instantiates the provider and calls `value(container)` - consumers receive the produced value, never the provider instance
|
|
330
|
+
3. **Singleton Scope Caches the Produced Value**: With `.setScope(BindingScopes.SINGLETON)`, the container caches the result of `value()` and returns it on subsequent `get()` calls; with the default transient scope, `value()` runs on every `get()`
|
|
331
|
+
4. **Factory vs Instance**: Providers can return:
|
|
322
332
|
- Direct instances (created each time `value()` is called)
|
|
323
333
|
- Factory functions (deferred creation)
|
|
324
|
-
- Singleton instances (same instance each time)
|
|
325
334
|
|
|
326
335
|
### Example: Singleton vs Factory
|
|
327
336
|
|
|
328
337
|
```typescript
|
|
329
|
-
// Singleton:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
this.logger.info('[value] Database singleton created');
|
|
338
|
-
}
|
|
339
|
-
return this.instance;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Factory: New instance every time
|
|
344
|
-
@injectable()
|
|
345
|
-
export class FactoryDatabaseProvider extends BaseProvider<Database> {
|
|
346
|
-
value(container: Container): Database {
|
|
347
|
-
this.logger.info('[value] Creating new Database instance');
|
|
348
|
-
return new Database({...});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
338
|
+
// Singleton: value() runs once, produced instance is cached by the container
|
|
339
|
+
app
|
|
340
|
+
.bind({ key: 'providers.Database' })
|
|
341
|
+
.toProvider(DatabaseProvider)
|
|
342
|
+
.setScope(BindingScopes.SINGLETON);
|
|
343
|
+
|
|
344
|
+
// Transient (default): value() runs on every get()
|
|
345
|
+
app.bind({ key: 'providers.Database' }).toProvider(DatabaseProvider);
|
|
351
346
|
```
|
|
352
347
|
|
|
353
348
|
|
|
@@ -358,9 +353,8 @@ export class FactoryDatabaseProvider extends BaseProvider<Database> {
|
|
|
358
353
|
From `packages/core/src/components/mail/providers/mail-transporter.provider.ts`:
|
|
359
354
|
|
|
360
355
|
```typescript
|
|
361
|
-
type TGetMailTransportFn = (options: TMailOptions) => IMailTransport;
|
|
356
|
+
export type TGetMailTransportFn = (options: TMailOptions) => IMailTransport;
|
|
362
357
|
|
|
363
|
-
@injectable()
|
|
364
358
|
export class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
365
359
|
constructor() {
|
|
366
360
|
super({ scope: MailTransportProvider.name });
|
|
@@ -368,58 +362,59 @@ export class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
|
368
362
|
|
|
369
363
|
value(_container: Container): TGetMailTransportFn {
|
|
370
364
|
return (options: TMailOptions) => {
|
|
371
|
-
this.logger
|
|
365
|
+
this.logger
|
|
366
|
+
.for(this.value.name)
|
|
367
|
+
.info('Creating mail transport for provider: %s', options.provider);
|
|
372
368
|
|
|
373
369
|
switch (options.provider) {
|
|
374
|
-
case MailProviders.NODEMAILER:
|
|
370
|
+
case MailProviders.NODEMAILER: {
|
|
375
371
|
return this.createNodemailerTransport(options);
|
|
372
|
+
}
|
|
376
373
|
|
|
377
|
-
case MailProviders.MAILGUN:
|
|
374
|
+
case MailProviders.MAILGUN: {
|
|
378
375
|
return this.createMailgunTransport(options);
|
|
376
|
+
}
|
|
379
377
|
|
|
380
|
-
case MailProviders.CUSTOM:
|
|
378
|
+
case MailProviders.CUSTOM: {
|
|
381
379
|
return this.createCustomTransport(options);
|
|
380
|
+
}
|
|
382
381
|
|
|
383
|
-
default:
|
|
384
|
-
throw
|
|
382
|
+
default: {
|
|
383
|
+
throw getError({
|
|
384
|
+
statusCode: 500,
|
|
385
|
+
messageCode: MailErrorCodes.INVALID_CONFIGURATION,
|
|
386
|
+
message: `Unsupported mail provider: ${options.provider}`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
385
389
|
}
|
|
386
390
|
};
|
|
387
391
|
}
|
|
388
392
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
private createMailgunTransport(options: IMailgunMailOptions) {
|
|
395
|
-
this.logger.info('[createMailgunTransport] Initializing');
|
|
396
|
-
return new MailgunTransportHelper(options.config);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private createCustomTransport(options: ICustomMailOptions) {
|
|
400
|
-
this.logger.info('[createCustomTransport] Using custom transport');
|
|
401
|
-
return options.config; // Already implements IMailTransport
|
|
402
|
-
}
|
|
393
|
+
// Each create* method validates the options shape (type guard) before constructing
|
|
394
|
+
// the transport helper, throwing MailErrorCodes.INVALID_CONFIGURATION on mismatch.
|
|
395
|
+
private createNodemailerTransport(options: TMailOptions): NodemailerTransportHelper { /* ... */ }
|
|
396
|
+
private createMailgunTransport(options: TMailOptions): MailgunTransportHelper { /* ... */ }
|
|
397
|
+
private createCustomTransport(options: TMailOptions): IMailTransport { /* ... */ }
|
|
403
398
|
}
|
|
404
399
|
```
|
|
405
400
|
|
|
406
|
-
**Usage
|
|
401
|
+
**Usage** (how `MailComponent` registers and consumes it):
|
|
407
402
|
|
|
408
403
|
```typescript
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
404
|
+
// Registration - MailComponent.initProviders()
|
|
405
|
+
this.application
|
|
406
|
+
.bind({ key: MailKeys.MAIL_TRANSPORT_PROVIDER })
|
|
407
|
+
.toProvider(MailTransportProvider)
|
|
408
|
+
.setScope('singleton');
|
|
409
|
+
|
|
410
|
+
// Consumption - MailComponent.createAndBindInstances()
|
|
411
|
+
const transportGetter = this.application.get<TGetMailTransportFn>({
|
|
412
|
+
key: MailKeys.MAIL_TRANSPORT_PROVIDER,
|
|
416
413
|
});
|
|
414
|
+
const mailOptions = this.application.get<TMailOptions>({ key: MailKeys.MAIL_OPTIONS });
|
|
417
415
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
provider: MailProviders.MAILGUN,
|
|
421
|
-
config: { /* mailgun config */ }
|
|
422
|
-
});
|
|
416
|
+
const mailTransportInstance = transportGetter(mailOptions);
|
|
417
|
+
this.application.bind({ key: MailKeys.MAIL_TRANSPORT_INSTANCE }).toValue(mailTransportInstance);
|
|
423
418
|
```
|
|
424
419
|
|
|
425
420
|
### Example 2: Queue Executor Provider
|
|
@@ -427,9 +422,8 @@ const mailgunTransport = getMailTransport({
|
|
|
427
422
|
From `packages/core/src/components/mail/providers/mail-queue-executor.provider.ts`:
|
|
428
423
|
|
|
429
424
|
```typescript
|
|
430
|
-
type TGetMailQueueExecutorFn = (config: IMailQueueExecutorConfig) => IMailQueueExecutor;
|
|
425
|
+
export type TGetMailQueueExecutorFn = (config: IMailQueueExecutorConfig) => IMailQueueExecutor;
|
|
431
426
|
|
|
432
|
-
@injectable()
|
|
433
427
|
export class MailQueueExecutorProvider extends BaseProvider<TGetMailQueueExecutorFn> {
|
|
434
428
|
constructor() {
|
|
435
429
|
super({ scope: MailQueueExecutorProvider.name });
|
|
@@ -437,22 +431,36 @@ export class MailQueueExecutorProvider extends BaseProvider<TGetMailQueueExecuto
|
|
|
437
431
|
|
|
438
432
|
value(_container: Container): TGetMailQueueExecutorFn {
|
|
439
433
|
return (config: IMailQueueExecutorConfig) => {
|
|
440
|
-
this.logger
|
|
434
|
+
this.logger
|
|
435
|
+
.for(this.value.name)
|
|
436
|
+
.info('Creating mail queue executor of type: %s', config.type);
|
|
441
437
|
|
|
442
438
|
switch (config.type) {
|
|
443
|
-
case MailQueueExecutorTypes.DIRECT:
|
|
439
|
+
case MailQueueExecutorTypes.DIRECT: {
|
|
444
440
|
return new DirectMailExecutorHelper();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
case MailQueueExecutorTypes.INTERNAL_QUEUE: {
|
|
444
|
+
if (!config.internalQueue) {
|
|
445
|
+
throw getError({ message: 'Internal queue configuration is missing' });
|
|
446
|
+
}
|
|
445
447
|
|
|
446
|
-
case MailQueueExecutorTypes.INTERNAL_QUEUE:
|
|
447
448
|
return new InternalQueueMailExecutorHelper({
|
|
448
449
|
identifier: config.internalQueue.identifier,
|
|
449
450
|
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
case MailQueueExecutorTypes.BULLMQ: {
|
|
454
|
+
if (!config.bullmq) {
|
|
455
|
+
throw getError({ message: 'BullMQ configuration is missing' });
|
|
456
|
+
}
|
|
450
457
|
|
|
451
|
-
case MailQueueExecutorTypes.BULLMQ:
|
|
452
458
|
return new BullMQMailExecutorHelper(config.bullmq);
|
|
459
|
+
}
|
|
453
460
|
|
|
454
|
-
default:
|
|
455
|
-
throw
|
|
461
|
+
default: {
|
|
462
|
+
throw getError({ message: `Unknown mail queue executor type: ${config.type}` });
|
|
463
|
+
}
|
|
456
464
|
}
|
|
457
465
|
};
|
|
458
466
|
}
|
|
@@ -464,33 +472,43 @@ export class MailQueueExecutorProvider extends BaseProvider<TGetMailQueueExecuto
|
|
|
464
472
|
Providers can also produce middleware. `RequestSpyMiddleware` is a real-world example that implements `IProvider<MiddlewareHandler>` directly (extending `BaseHelper`, not `BaseProvider`):
|
|
465
473
|
|
|
466
474
|
```typescript
|
|
467
|
-
// From packages/core/src/base/middlewares/request-spy.middleware.ts
|
|
475
|
+
// From packages/core/src/base/middlewares/request-spy/request-spy.middleware.ts
|
|
468
476
|
export class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
|
|
469
477
|
static readonly REQUEST_ID_KEY = 'requestId';
|
|
470
478
|
|
|
479
|
+
private isDebugMode: boolean;
|
|
480
|
+
|
|
471
481
|
constructor() {
|
|
472
482
|
super({ scope: 'SpyMW' });
|
|
483
|
+
this.isDebugMode = process.env.NODE_ENV?.toLowerCase() !== Environment.PRODUCTION;
|
|
473
484
|
}
|
|
474
485
|
|
|
486
|
+
/** Parses request body based on Content-Type header. */
|
|
487
|
+
async parseBody(opts: { req: TContext['req'] }): Promise<unknown> { /* ... */ }
|
|
488
|
+
|
|
475
489
|
/** Returns a Hono middleware that logs request details and duration. */
|
|
476
490
|
value() {
|
|
477
491
|
return createMiddleware(async (context, next) => {
|
|
478
492
|
const t = performance.now();
|
|
479
493
|
const requestId = context.get(RequestSpyMiddleware.REQUEST_ID_KEY);
|
|
494
|
+
const clientIp = /* resolved from connection info or x-real-ip/x-forwarded-for */ '';
|
|
480
495
|
const method = context.req.method;
|
|
481
496
|
const path = context.req.path ?? '/';
|
|
497
|
+
const body = await this.parseBody(context);
|
|
482
498
|
|
|
483
|
-
this.logger.info('[%s][=>] %s %s', requestId, method, path);
|
|
499
|
+
this.logger.info('[%s][%s][=>] %s %s | query: %j | body: %j', requestId, clientIp, method, path, context.req.query(), body);
|
|
484
500
|
|
|
485
501
|
await next();
|
|
486
502
|
|
|
487
503
|
const duration = (performance.now() - t).toFixed(2);
|
|
488
|
-
this.logger.info('[%s][<=] %s %s | Took: %s (ms)', requestId, method, path, duration);
|
|
504
|
+
this.logger.info('[%s][%s][<=] %s %s | Took: %s (ms)', requestId, clientIp, method, path, duration);
|
|
489
505
|
});
|
|
490
506
|
}
|
|
491
507
|
}
|
|
492
508
|
```
|
|
493
509
|
|
|
510
|
+
See [Middlewares](./middlewares.md) for the full implementation (IP resolution, body-parsing rules, and production log redaction).
|
|
511
|
+
|
|
494
512
|
Note that `RequestSpyMiddleware.value()` does not accept a `container` parameter -- the `IProvider<T>` interface defines `value(container: Container): T`, but implementations may ignore the parameter when they don't need container access. In practice, `RequestSpyMiddleware` is registered via `RequestTrackerComponent`, which binds it as a provider in the DI container and resolves it automatically.
|
|
495
513
|
|
|
496
514
|
|
|
@@ -501,30 +519,25 @@ Note that `RequestSpyMiddleware.value()` does not accept a `container` parameter
|
|
|
501
519
|
Validate configuration before creating instances:
|
|
502
520
|
|
|
503
521
|
```typescript
|
|
504
|
-
@injectable()
|
|
505
522
|
export class S3StorageProvider extends BaseProvider<S3Storage> {
|
|
506
523
|
constructor() {
|
|
507
524
|
super({ scope: S3StorageProvider.name });
|
|
508
525
|
}
|
|
509
526
|
|
|
510
527
|
value(container: Container): S3Storage {
|
|
511
|
-
const config = container.get(
|
|
512
|
-
|
|
513
|
-
const accessKey = config.get('AWS_ACCESS_KEY');
|
|
514
|
-
const secretKey = config.get('AWS_SECRET_KEY');
|
|
515
|
-
const bucket = config.get('AWS_S3_BUCKET');
|
|
528
|
+
const config = container.get<IS3Config>({ key: 'configs.s3' });
|
|
516
529
|
|
|
517
530
|
// Validate configuration
|
|
518
|
-
if (!accessKey || !secretKey || !bucket) {
|
|
519
|
-
throw
|
|
531
|
+
if (!config?.accessKey || !config?.secretKey || !config?.bucket) {
|
|
532
|
+
throw getError({ message: 'S3 configuration incomplete' });
|
|
520
533
|
}
|
|
521
534
|
|
|
522
|
-
this.logger.info('[value] Creating S3 storage for bucket: %s', bucket);
|
|
535
|
+
this.logger.info('[value] Creating S3 storage for bucket: %s', config.bucket);
|
|
523
536
|
|
|
524
537
|
return new S3Storage({
|
|
525
|
-
accessKeyId: accessKey,
|
|
526
|
-
secretAccessKey: secretKey,
|
|
527
|
-
bucket: bucket,
|
|
538
|
+
accessKeyId: config.accessKey,
|
|
539
|
+
secretAccessKey: config.secretKey,
|
|
540
|
+
bucket: config.bucket,
|
|
528
541
|
});
|
|
529
542
|
}
|
|
530
543
|
}
|
|
@@ -532,18 +545,17 @@ export class S3StorageProvider extends BaseProvider<S3Storage> {
|
|
|
532
545
|
|
|
533
546
|
### Pattern 2: Lazy Singleton
|
|
534
547
|
|
|
535
|
-
Create instance only once, lazily:
|
|
548
|
+
Create instance only once, lazily (or simply bind with `.setScope(BindingScopes.SINGLETON)` and let the container cache the produced value):
|
|
536
549
|
|
|
537
550
|
```typescript
|
|
538
|
-
@injectable()
|
|
539
551
|
export class DatabaseConnectionProvider extends BaseProvider<DatabaseConnection> {
|
|
540
552
|
private connection?: DatabaseConnection;
|
|
541
553
|
|
|
542
554
|
value(container: Container): DatabaseConnection {
|
|
543
555
|
if (!this.connection) {
|
|
544
556
|
this.logger.info('[value] Creating database connection');
|
|
545
|
-
const config = container.get(
|
|
546
|
-
this.connection = new DatabaseConnection(config.
|
|
557
|
+
const config = container.get<IDatabaseConfig>({ key: 'configs.database' });
|
|
558
|
+
this.connection = new DatabaseConnection(config.url);
|
|
547
559
|
} else {
|
|
548
560
|
this.logger.debug('[value] Reusing existing connection');
|
|
549
561
|
}
|
|
@@ -558,7 +570,6 @@ export class DatabaseConnectionProvider extends BaseProvider<DatabaseConnection>
|
|
|
558
570
|
Select implementation based on environment:
|
|
559
571
|
|
|
560
572
|
```typescript
|
|
561
|
-
@injectable()
|
|
562
573
|
export class CacheProvider extends BaseProvider<ICache> {
|
|
563
574
|
value(container: Container): ICache {
|
|
564
575
|
const env = process.env.NODE_ENV;
|
|
@@ -570,10 +581,10 @@ export class CacheProvider extends BaseProvider<ICache> {
|
|
|
570
581
|
|
|
571
582
|
if (env === 'production') {
|
|
572
583
|
this.logger.info('[value] Using RedisCache for production');
|
|
573
|
-
const config = container.get(
|
|
584
|
+
const config = container.get<IRedisConfig>({ key: 'configs.redis' });
|
|
574
585
|
return new RedisCache({
|
|
575
|
-
host: config.
|
|
576
|
-
port: config.
|
|
586
|
+
host: config.host,
|
|
587
|
+
port: config.port,
|
|
577
588
|
});
|
|
578
589
|
}
|
|
579
590
|
|
|
@@ -586,16 +597,15 @@ export class CacheProvider extends BaseProvider<ICache> {
|
|
|
586
597
|
|
|
587
598
|
## Common Pitfalls
|
|
588
599
|
|
|
589
|
-
### Pitfall 1:
|
|
600
|
+
### Pitfall 1: Expecting `get()` to Return the Provider Instance
|
|
590
601
|
|
|
591
602
|
```typescript
|
|
592
|
-
// ❌ Wrong:
|
|
593
|
-
const provider = app.get(
|
|
594
|
-
const
|
|
603
|
+
// ❌ Wrong: Expecting the provider instance and calling value() yourself
|
|
604
|
+
const provider = app.get<MailTransportProvider>({ key: 'providers.MailTransport' });
|
|
605
|
+
const getTransport = provider.value(container); // provider is NOT the instance - this fails
|
|
595
606
|
|
|
596
|
-
// ✅ Correct:
|
|
597
|
-
const
|
|
598
|
-
const getTransport = provider.value(container);
|
|
607
|
+
// ✅ Correct: get() already returns the produced value (the container calls value() for you)
|
|
608
|
+
const getTransport = app.get<TGetMailTransportFn>({ key: 'providers.MailTransport' });
|
|
599
609
|
const transport = getTransport({ provider: 'nodemailer' });
|
|
600
610
|
```
|
|
601
611
|
|
|
@@ -603,7 +613,6 @@ const transport = getTransport({ provider: 'nodemailer' });
|
|
|
603
613
|
|
|
604
614
|
```typescript
|
|
605
615
|
// ❌ Wrong: Creating instances in constructor
|
|
606
|
-
@injectable()
|
|
607
616
|
export class BadProvider extends BaseProvider<Database> {
|
|
608
617
|
private db: Database;
|
|
609
618
|
|
|
@@ -618,15 +627,14 @@ export class BadProvider extends BaseProvider<Database> {
|
|
|
618
627
|
}
|
|
619
628
|
|
|
620
629
|
// ✅ Correct: Create in value() method
|
|
621
|
-
@injectable()
|
|
622
630
|
export class GoodProvider extends BaseProvider<Database> {
|
|
623
631
|
constructor() {
|
|
624
632
|
super({ scope: GoodProvider.name });
|
|
625
633
|
}
|
|
626
634
|
|
|
627
635
|
value(container: Container): Database {
|
|
628
|
-
const config = container.get(
|
|
629
|
-
return new Database(config.
|
|
636
|
+
const config = container.get<IDatabaseConfig>({ key: 'configs.database' });
|
|
637
|
+
return new Database(config.url);
|
|
630
638
|
}
|
|
631
639
|
}
|
|
632
640
|
```
|
|
@@ -636,16 +644,15 @@ export class GoodProvider extends BaseProvider<Database> {
|
|
|
636
644
|
```typescript
|
|
637
645
|
// ❌ Wrong: No error handling
|
|
638
646
|
value(container: Container): IMailTransport {
|
|
639
|
-
return new MailTransport(
|
|
647
|
+
return new MailTransport(container.get({ key: 'configs.mail' })); // Might throw
|
|
640
648
|
}
|
|
641
649
|
|
|
642
650
|
// ✅ Correct: Validate and handle errors
|
|
643
651
|
value(container: Container): IMailTransport {
|
|
644
|
-
const
|
|
645
|
-
const mailConfig = config.get('MAIL_CONFIG');
|
|
652
|
+
const mailConfig = container.get<IMailConfig>({ key: 'configs.mail', isOptional: true });
|
|
646
653
|
|
|
647
654
|
if (!mailConfig) {
|
|
648
|
-
throw
|
|
655
|
+
throw getError({ message: 'Mail configuration is missing' });
|
|
649
656
|
}
|
|
650
657
|
|
|
651
658
|
try {
|
|
@@ -682,7 +689,6 @@ value(container: Container): Service {
|
|
|
682
689
|
|
|
683
690
|
```typescript
|
|
684
691
|
// Cache expensive operations
|
|
685
|
-
@injectable()
|
|
686
692
|
export class ConfigProvider extends BaseProvider<Config> {
|
|
687
693
|
private cachedConfig?: Config;
|
|
688
694
|
|
|
@@ -708,7 +714,7 @@ export class ConfigProvider extends BaseProvider<Config> {
|
|
|
708
714
|
- **Related References:**
|
|
709
715
|
- [Services](./services.md) - Business logic layer
|
|
710
716
|
- [Dependency Injection](./dependency-injection.md) - DI container and injection
|
|
711
|
-
- [
|
|
717
|
+
- [Middlewares](./middlewares.md) - Built-in middlewares (includes `RequestSpyMiddleware` provider)
|
|
712
718
|
|
|
713
719
|
- **Guides:**
|
|
714
720
|
- [Dependency Injection Guide](/guides/core-concepts/dependency-injection.md)
|