@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.
Files changed (180) hide show
  1. package/README.md +7 -7
  2. package/content/best-practices/api-usage-examples.md +15 -12
  3. package/content/best-practices/architectural-patterns.md +70 -78
  4. package/content/best-practices/architecture-decisions.md +91 -60
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/content/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/content/best-practices/code-style-standards/documentation.md +13 -13
  9. package/content/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/content/best-practices/code-style-standards/index.md +1 -1
  11. package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/content/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/content/best-practices/code-style-standards/tooling.md +8 -5
  14. package/content/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/content/best-practices/common-pitfalls.md +56 -37
  16. package/content/best-practices/contribution-workflow.md +13 -14
  17. package/content/best-practices/data-modeling.md +46 -22
  18. package/content/best-practices/deployment-strategies.md +28 -27
  19. package/content/best-practices/error-handling.md +48 -24
  20. package/content/best-practices/index.md +5 -5
  21. package/content/best-practices/performance-optimization.md +40 -31
  22. package/content/best-practices/security-guidelines.md +52 -23
  23. package/content/best-practices/testing-strategies.md +65 -51
  24. package/content/best-practices/troubleshooting-tips.md +24 -24
  25. package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
  26. package/content/extensions/components/authentication/api.md +19 -19
  27. package/content/extensions/components/authentication/errors.md +7 -7
  28. package/content/extensions/components/authentication/index.md +10 -8
  29. package/content/extensions/components/authentication/usage.md +101 -6
  30. package/content/extensions/components/authorization/api.md +45 -25
  31. package/content/extensions/components/authorization/errors.md +6 -6
  32. package/content/extensions/components/authorization/index.md +11 -10
  33. package/content/extensions/components/authorization/usage.md +21 -21
  34. package/content/extensions/components/health-check.md +1 -1
  35. package/content/extensions/components/index.md +5 -5
  36. package/content/extensions/components/mail/errors.md +15 -15
  37. package/content/extensions/components/mail/index.md +1 -2
  38. package/content/extensions/components/mail/usage.md +1 -1
  39. package/content/extensions/components/request-tracker.md +1 -1
  40. package/content/extensions/components/socket-io/api.md +9 -9
  41. package/content/extensions/components/socket-io/errors.md +5 -5
  42. package/content/extensions/components/socket-io/index.md +8 -8
  43. package/content/extensions/components/socket-io/usage.md +1 -1
  44. package/content/extensions/components/static-asset/api.md +17 -4
  45. package/content/extensions/components/static-asset/errors.md +4 -4
  46. package/content/extensions/components/static-asset/index.md +26 -28
  47. package/content/extensions/components/static-asset/usage.md +13 -12
  48. package/content/extensions/components/template/index.md +2 -2
  49. package/content/extensions/components/template/setup-page.md +1 -1
  50. package/content/extensions/components/websocket/api.md +3 -3
  51. package/content/extensions/components/websocket/errors.md +5 -5
  52. package/content/extensions/components/websocket/index.md +5 -5
  53. package/content/extensions/components/websocket/usage.md +3 -3
  54. package/content/extensions/helpers/cron/index.md +2 -2
  55. package/content/extensions/helpers/crypto/index.md +1 -1
  56. package/content/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +81 -25
  58. package/content/extensions/helpers/index.md +2 -3
  59. package/content/extensions/helpers/inversion/index.md +15 -7
  60. package/content/extensions/helpers/kafka/compile-binary.md +92 -0
  61. package/content/extensions/helpers/kafka/examples.md +1 -1
  62. package/content/extensions/helpers/kafka/index.md +3 -0
  63. package/content/extensions/helpers/logger/index.md +32 -2
  64. package/content/extensions/helpers/network/index.md +6 -0
  65. package/content/extensions/helpers/queue/index.md +14 -17
  66. package/content/extensions/helpers/redis/index.md +548 -323
  67. package/content/extensions/helpers/socket-io/index.md +14 -10
  68. package/content/extensions/helpers/storage/api.md +44 -8
  69. package/content/extensions/helpers/storage/index.md +43 -7
  70. package/content/extensions/helpers/template/index.md +6 -3
  71. package/content/extensions/helpers/types/index.md +11 -8
  72. package/content/extensions/helpers/websocket/api.md +9 -9
  73. package/content/extensions/helpers/websocket/index.md +7 -7
  74. package/content/extensions/helpers/worker-thread/index.md +2 -2
  75. package/content/extensions/index.md +3 -4
  76. package/content/extensions/src-details/mcp-server.md +18 -24
  77. package/content/guides/core-concepts/application/bootstrapping.md +11 -14
  78. package/content/guides/core-concepts/application/index.md +3 -3
  79. package/content/guides/core-concepts/components.md +19 -10
  80. package/content/guides/core-concepts/dependency-injection.md +6 -3
  81. package/content/guides/core-concepts/grpc-controllers.md +6 -5
  82. package/content/guides/core-concepts/persistent/datasources.md +42 -43
  83. package/content/guides/core-concepts/persistent/index.md +16 -7
  84. package/content/guides/core-concepts/persistent/models.md +24 -20
  85. package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
  86. package/content/guides/core-concepts/persistent/repositories.md +40 -23
  87. package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
  88. package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
  89. package/content/guides/core-concepts/persistent/transactions.md +61 -25
  90. package/content/guides/core-concepts/rest-controllers.md +12 -9
  91. package/content/guides/core-concepts/services.md +330 -60
  92. package/content/guides/get-started/5-minute-quickstart.md +15 -15
  93. package/content/guides/get-started/philosophy.md +36 -36
  94. package/content/guides/get-started/setup.md +3 -3
  95. package/content/guides/index.md +3 -3
  96. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  97. package/content/guides/migrations/scoped-rbac-migration.md +17 -17
  98. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  99. package/content/guides/reference/glossary.md +19 -12
  100. package/content/guides/reference/mcp-docs-server.md +22 -18
  101. package/content/guides/tutorials/building-a-crud-api.md +37 -44
  102. package/content/guides/tutorials/complete-installation.md +17 -17
  103. package/content/guides/tutorials/ecommerce-api.md +163 -124
  104. package/content/guides/tutorials/realtime-chat.md +181 -135
  105. package/content/guides/tutorials/testing.md +65 -523
  106. package/content/index.md +2 -180
  107. package/content/public/apple-touch-icon.png +0 -0
  108. package/content/public/og-image.png +0 -0
  109. package/content/public/site.webmanifest +11 -0
  110. package/content/references/base/application.md +4 -5
  111. package/content/references/base/bootstrapping.md +18 -5
  112. package/content/references/base/components.md +149 -120
  113. package/content/references/base/connectors.md +178 -0
  114. package/content/references/base/controllers.md +41 -30
  115. package/content/references/base/datasources.md +163 -92
  116. package/content/references/base/dependency-injection.md +34 -22
  117. package/content/references/base/filter-system/application-usage.md +17 -14
  118. package/content/references/base/filter-system/array-operators.md +7 -2
  119. package/content/references/base/filter-system/comparison-operators.md +3 -0
  120. package/content/references/base/filter-system/default-filter.md +89 -71
  121. package/content/references/base/filter-system/fields-order-pagination.md +22 -22
  122. package/content/references/base/filter-system/index.md +6 -3
  123. package/content/references/base/filter-system/json-filtering.md +20 -1
  124. package/content/references/base/filter-system/list-operators.md +1 -1
  125. package/content/references/base/filter-system/logical-operators.md +33 -1
  126. package/content/references/base/filter-system/null-operators.md +30 -1
  127. package/content/references/base/filter-system/quick-reference.md +23 -4
  128. package/content/references/base/filter-system/tips.md +5 -5
  129. package/content/references/base/filter-system/use-cases.md +12 -12
  130. package/content/references/base/grpc-controllers.md +13 -13
  131. package/content/references/base/index.md +24 -12
  132. package/content/references/base/middlewares.md +265 -327
  133. package/content/references/base/models.md +63 -49
  134. package/content/references/base/providers.md +136 -130
  135. package/content/references/base/repositories/advanced.md +59 -58
  136. package/content/references/base/repositories/index.md +115 -91
  137. package/content/references/base/repositories/mixins.md +55 -291
  138. package/content/references/base/repositories/relations.md +54 -64
  139. package/content/references/base/repositories/soft-deletable.md +31 -30
  140. package/content/references/base/services.md +296 -93
  141. package/content/references/configuration/environment-variables.md +49 -31
  142. package/content/references/configuration/index.md +6 -6
  143. package/content/references/index.md +17 -12
  144. package/content/references/quick-reference.md +65 -106
  145. package/content/references/utilities/crypto.md +65 -23
  146. package/content/references/utilities/index.md +3 -3
  147. package/content/references/utilities/jsx.md +6 -4
  148. package/content/references/utilities/module.md +68 -20
  149. package/content/references/utilities/parse.md +4 -14
  150. package/content/references/utilities/promise.md +9 -7
  151. package/content/references/utilities/schema.md +5 -3
  152. package/dist/mcp-server/common/guards.d.ts +8 -0
  153. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  154. package/dist/mcp-server/common/guards.js +14 -0
  155. package/dist/mcp-server/common/guards.js.map +1 -0
  156. package/dist/mcp-server/common/index.d.ts +1 -0
  157. package/dist/mcp-server/common/index.d.ts.map +1 -1
  158. package/dist/mcp-server/common/index.js +1 -0
  159. package/dist/mcp-server/common/index.js.map +1 -1
  160. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  162. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  163. package/dist/mcp-server/helpers/github.helper.js +1 -1
  164. package/dist/mcp-server/index.js +7 -2
  165. package/dist/mcp-server/index.js.map +1 -1
  166. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  167. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  168. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  169. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  175. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  178. package/package.json +9 -9
  179. package/content/extensions/helpers/testing/index.md +0 -510
  180. 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(ConfigService);
139
+ const config = container.get<IDatabaseConfig>({ key: 'configs.database' });
140
140
  return new Database({
141
- host: config.get('DB_HOST'),
142
- port: config.get('DB_PORT'),
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 new Error(`Unknown provider: ${options.provider}`);
267
+ throw getError({ message: `Unknown provider: ${options.provider}` });
259
268
  }
260
269
  };
261
270
  }
262
271
  }
263
272
 
264
- // Usage
265
- const getTransport = app.get(MailTransportProvider).value(container);
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(ConfigService);
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.get('DB_HOST'),
287
- port: config.get('DB_PORT'),
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[DI Container Scans Providers]
307
- B --> C[Provider Instance Created]
308
- C --> D[Provider Registered in Container]
309
- D --> E[Application Calls provider.value]
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 Another Instance?}
313
- H -->|Yes| E
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. **Provider Instance Created Once**: The provider class itself is instantiated once by the DI container
320
- 2. **`value()` Called When Needed**: The `value(container)` method is called when the application needs the produced value
321
- 3. **Factory vs Instance**: Providers can return:
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: Same instance every time
330
- @injectable()
331
- export class SingletonDatabaseProvider extends BaseProvider<Database> {
332
- private instance?: Database;
333
-
334
- value(container: Container): Database {
335
- if (!this.instance) {
336
- this.instance = new Database({...});
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.info('[value] Creating mail transport: %s', options.provider);
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 new Error(`Unsupported provider: ${options.provider}`);
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
- private createNodemailerTransport(options: INodemailerMailOptions) {
390
- this.logger.info('[createNodemailerTransport] Initializing');
391
- return new NodemailerTransportHelper(options.config);
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
- // In your service or application setup
410
- const getMailTransport = app.get(MailTransportProvider).value(container);
411
-
412
- // Create Nodemailer transport
413
- const nodemailerTransport = getMailTransport({
414
- provider: MailProviders.NODEMAILER,
415
- config: { /* nodemailer config */ }
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
- // Create Mailgun transport
419
- const mailgunTransport = getMailTransport({
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.info('[value] Creating executor: %s', config.type);
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 new Error(`Unknown type: ${config.type}`);
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(ConfigService);
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 new Error('S3 configuration incomplete');
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(ConfigService);
546
- this.connection = new DatabaseConnection(config.get('DATABASE_URL'));
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(ConfigService);
584
+ const config = container.get<IRedisConfig>({ key: 'configs.redis' });
574
585
  return new RedisCache({
575
- host: config.get('REDIS_HOST'),
576
- port: config.get('REDIS_PORT'),
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: Forgetting to Call `value()`
600
+ ### Pitfall 1: Expecting `get()` to Return the Provider Instance
590
601
 
591
602
  ```typescript
592
- // ❌ Wrong: Getting the provider instance
593
- const provider = app.get(MailTransportProvider);
594
- const transport = provider({ provider: 'nodemailer' }); // Error!
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: Call value() first
597
- const provider = app.get(MailTransportProvider);
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(ConfigService);
629
- return new Database(config.get('DATABASE_URL'));
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(config.get('MAIL_CONFIG')); // Might throw
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 config = container.get(ConfigService);
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 new Error('Mail configuration is missing');
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
- - [Middleware](./middleware.md) - Built-in middlewares (includes `RequestSpyMiddleware` provider)
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)