@venizia/ignis-docs 0.0.3 → 0.0.4-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 +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +31 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +50 -20
- package/wiki/references/base/datasources.md +30 -0
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +604 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +731 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -635
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +14 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +16 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Providers Reference
|
|
3
|
+
description: Technical reference for the Provider pattern in IGNIS
|
|
4
|
+
difficulty: advanced
|
|
5
|
+
lastUpdated: 2026-01-03
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Providers Reference
|
|
9
|
+
|
|
10
|
+
Providers implement the Factory pattern in IGNIS, allowing you to create and configure instances dynamically at runtime based on configuration or context. Unlike services that contain business logic, providers are factories that produce values, instances, or functions.
|
|
11
|
+
|
|
12
|
+
**Files:**
|
|
13
|
+
- `packages/core/src/base/providers/base.ts`
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
Before reading this document, you should understand:
|
|
18
|
+
- [TypeScript basics](https://www.typescriptlang.org/docs/)
|
|
19
|
+
- [Dependency Injection in IGNIS](./dependency-injection.md)
|
|
20
|
+
- [Services](./services.md) - To understand the difference
|
|
21
|
+
|
|
22
|
+
## Quick Reference
|
|
23
|
+
|
|
24
|
+
| Feature | Description |
|
|
25
|
+
|---------|-------------|
|
|
26
|
+
| **Purpose** | Factory pattern for runtime instance creation |
|
|
27
|
+
| **Base Class** | `BaseProvider<T>` |
|
|
28
|
+
| **Key Method** | `value(container: Container): T` |
|
|
29
|
+
| **Use Case** | Dynamic configuration, multi-strategy patterns, plugin systems |
|
|
30
|
+
| **Extends** | `BaseHelper` (provides logging) |
|
|
31
|
+
| **Implements** | `IProvider<T>` from `@venizia/ignis-inversion` |
|
|
32
|
+
|
|
33
|
+
## What is a Provider?
|
|
34
|
+
|
|
35
|
+
A **Provider** is a class that implements the Factory pattern, responsible for creating and configuring instances based on runtime conditions, configuration, or context.
|
|
36
|
+
|
|
37
|
+
### Core Characteristics
|
|
38
|
+
|
|
39
|
+
1. **Factory Pattern**: Produces values, instances, or functions
|
|
40
|
+
2. **Configuration-Driven**: Creates different implementations based on config
|
|
41
|
+
3. **Deferred Creation**: Instances are created when needed, not at startup
|
|
42
|
+
4. **Type-Safe**: Generic type `T` ensures type safety of produced values
|
|
43
|
+
|
|
44
|
+
### Common Use Cases
|
|
45
|
+
|
|
46
|
+
- **Strategy Selection**: Choose between multiple implementations (e.g., email providers: Nodemailer, Mailgun, SendGrid)
|
|
47
|
+
- **Configuration-Based Instantiation**: Create instances with different configurations
|
|
48
|
+
- **Plugin Systems**: Load and configure plugins dynamically
|
|
49
|
+
- **Multi-Tenant**: Provide tenant-specific instances
|
|
50
|
+
- **Feature Flags**: Enable/disable features at runtime
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## BaseProvider Class
|
|
55
|
+
|
|
56
|
+
Abstract base class for all providers in IGNIS.
|
|
57
|
+
|
|
58
|
+
### Class Definition
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { Container } from '@/helpers/inversion';
|
|
62
|
+
import { BaseHelper } from '@venizia/ignis-helpers';
|
|
63
|
+
import { IProvider } from '@venizia/ignis-inversion';
|
|
64
|
+
|
|
65
|
+
export abstract class BaseProvider<T> extends BaseHelper implements IProvider<T> {
|
|
66
|
+
abstract value(container: Container): T;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Generic Type Parameter
|
|
71
|
+
|
|
72
|
+
| Parameter | Description |
|
|
73
|
+
|-----------|-------------|
|
|
74
|
+
| `T` | The type of value this provider produces |
|
|
75
|
+
|
|
76
|
+
**Examples:**
|
|
77
|
+
- `BaseProvider<IMailTransport>` - Produces mail transport instances
|
|
78
|
+
- `BaseProvider<MiddlewareHandler>` - Produces Hono middleware
|
|
79
|
+
- `BaseProvider<(config: Config) => Service>` - Produces factory functions
|
|
80
|
+
|
|
81
|
+
### Inheritance
|
|
82
|
+
|
|
83
|
+
- **Extends `BaseHelper`**: Provides scoped logging via `this.logger`
|
|
84
|
+
- **Implements `IProvider<T>`**: Enforces the `value(container: Container): T` contract
|
|
85
|
+
|
|
86
|
+
### Abstract Method: `value(container: Container): T`
|
|
87
|
+
|
|
88
|
+
The `value` method is where you implement your factory logic.
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
| Parameter | Type | Description |
|
|
92
|
+
|-----------|------|-------------|
|
|
93
|
+
| `container` | `Container` | DI container instance for resolving dependencies |
|
|
94
|
+
|
|
95
|
+
**Returns:** `T` - The produced value, instance, or factory function
|
|
96
|
+
|
|
97
|
+
**Purpose:**
|
|
98
|
+
- Access to DI container allows resolving other dependencies
|
|
99
|
+
- Create and configure instances based on logic
|
|
100
|
+
- Return factories for deferred instantiation
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Provider vs Service
|
|
105
|
+
|
|
106
|
+
Understanding when to use Providers vs Services is crucial for proper architecture.
|
|
107
|
+
|
|
108
|
+
### Comparison Table
|
|
109
|
+
|
|
110
|
+
| Aspect | Provider | Service |
|
|
111
|
+
|--------|----------|---------|
|
|
112
|
+
| **Purpose** | Create/configure instances | Contain business logic |
|
|
113
|
+
| **Pattern** | Factory pattern | Business logic layer |
|
|
114
|
+
| **Method** | `value(container)` returns factory | Business methods (CRUD, etc.) |
|
|
115
|
+
| **Lifecycle** | Creates instances on-demand | Single instance per DI scope |
|
|
116
|
+
| **Dependencies** | Produces configured instances | Uses repositories/other services |
|
|
117
|
+
| **Example** | `MailTransportProvider` | `UserService` |
|
|
118
|
+
| **Returns** | Values, instances, or functions | Business data/results |
|
|
119
|
+
|
|
120
|
+
### When to Use Providers
|
|
121
|
+
|
|
122
|
+
Use providers when you need:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// ✅ Multiple implementations to choose from
|
|
126
|
+
class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
127
|
+
value(container: Container): TGetMailTransportFn {
|
|
128
|
+
return (options) => {
|
|
129
|
+
switch (options.provider) {
|
|
130
|
+
case 'nodemailer': return new NodemailerTransport(options);
|
|
131
|
+
case 'mailgun': return new MailgunTransport(options);
|
|
132
|
+
case 'sendgrid': return new SendGridTransport(options);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ✅ Configuration-based instance creation
|
|
139
|
+
class DatabaseProvider extends BaseProvider<Database> {
|
|
140
|
+
value(container: Container): Database {
|
|
141
|
+
const config = container.get(ConfigService);
|
|
142
|
+
return new Database({
|
|
143
|
+
host: config.get('DB_HOST'),
|
|
144
|
+
port: config.get('DB_PORT'),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ✅ Runtime factory functions
|
|
150
|
+
class CacheProvider extends BaseProvider<(key: string) => Cache> {
|
|
151
|
+
value(container: Container): (key: string) => Cache {
|
|
152
|
+
return (key: string) => new Cache({ namespace: key });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### When to Use Services
|
|
158
|
+
|
|
159
|
+
Use services when you need:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// ✅ Business logic
|
|
163
|
+
class UserService extends BaseService {
|
|
164
|
+
async createUser(data: CreateUserDto) {
|
|
165
|
+
// Validation, transformation, business rules
|
|
166
|
+
const user = await this.userRepository.create(data);
|
|
167
|
+
await this.emailService.sendWelcome(user.email);
|
|
168
|
+
return user;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ✅ Orchestration between repositories
|
|
173
|
+
class OrderService extends BaseService {
|
|
174
|
+
async createOrder(items: CartItem[]) {
|
|
175
|
+
const order = await this.orderRepository.create(items);
|
|
176
|
+
await this.inventoryRepository.decrementStock(items);
|
|
177
|
+
await this.paymentService.charge(order.total);
|
|
178
|
+
return order;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
:::tip Quick Decision
|
|
184
|
+
- **Need to produce different implementations?** → Use a Provider
|
|
185
|
+
- **Need to implement business logic?** → Use a Service
|
|
186
|
+
:::
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Creating Custom Providers
|
|
191
|
+
|
|
192
|
+
### Basic Provider
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { BaseProvider } from '@venizia/ignis';
|
|
196
|
+
import { Container } from '@venizia/ignis-inversion';
|
|
197
|
+
import { injectable } from '@venizia/ignis-inversion';
|
|
198
|
+
|
|
199
|
+
interface ILogger {
|
|
200
|
+
log(message: string): void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class ConsoleLogger implements ILogger {
|
|
204
|
+
log(message: string) {
|
|
205
|
+
console.log(message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
class FileLogger implements ILogger {
|
|
210
|
+
constructor(private filePath: string) {}
|
|
211
|
+
|
|
212
|
+
log(message: string) {
|
|
213
|
+
// Write to file
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@injectable()
|
|
218
|
+
export class LoggerProvider extends BaseProvider<ILogger> {
|
|
219
|
+
constructor() {
|
|
220
|
+
super({ scope: LoggerProvider.name });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
value(container: Container): ILogger {
|
|
224
|
+
const env = process.env.NODE_ENV;
|
|
225
|
+
|
|
226
|
+
if (env === 'production') {
|
|
227
|
+
this.logger.info('[value] Creating FileLogger for production');
|
|
228
|
+
return new FileLogger('/var/log/app.log');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.logger.info('[value] Creating ConsoleLogger for development');
|
|
232
|
+
return new ConsoleLogger();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Factory Function Provider
|
|
238
|
+
|
|
239
|
+
Providers can return factory functions for deferred instantiation:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
type TGetMailTransportFn = (options: MailOptions) => IMailTransport;
|
|
243
|
+
|
|
244
|
+
@injectable()
|
|
245
|
+
export class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
246
|
+
constructor() {
|
|
247
|
+
super({ scope: MailTransportProvider.name });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
value(container: Container): TGetMailTransportFn {
|
|
251
|
+
// Return a factory function
|
|
252
|
+
return (options: MailOptions) => {
|
|
253
|
+
this.logger.info('[value] Creating mail transport: %s', options.provider);
|
|
254
|
+
|
|
255
|
+
switch (options.provider) {
|
|
256
|
+
case 'nodemailer':
|
|
257
|
+
return new NodemailerTransport(options.config);
|
|
258
|
+
case 'mailgun':
|
|
259
|
+
return new MailgunTransport(options.config);
|
|
260
|
+
default:
|
|
261
|
+
throw new Error(`Unknown provider: ${options.provider}`);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Usage
|
|
268
|
+
const getTransport = app.get(MailTransportProvider).value(container);
|
|
269
|
+
const transport = getTransport({ provider: 'nodemailer', config: {...} });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Provider with Dependency Injection
|
|
273
|
+
|
|
274
|
+
Access other dependencies through the container:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
@injectable()
|
|
278
|
+
export class DatabaseProvider extends BaseProvider<Database> {
|
|
279
|
+
constructor() {
|
|
280
|
+
super({ scope: DatabaseProvider.name });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
value(container: Container): Database {
|
|
284
|
+
// Resolve dependencies from container
|
|
285
|
+
const config = container.get(ConfigService);
|
|
286
|
+
const logger = container.get(LoggerService);
|
|
287
|
+
|
|
288
|
+
const database = new Database({
|
|
289
|
+
host: config.get('DB_HOST'),
|
|
290
|
+
port: config.get('DB_PORT'),
|
|
291
|
+
logger: logger,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this.logger.info('[value] Database instance created');
|
|
295
|
+
return database;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Provider Lifecycle
|
|
303
|
+
|
|
304
|
+
Understanding the provider lifecycle helps you use them effectively.
|
|
305
|
+
|
|
306
|
+
### Lifecycle Stages
|
|
307
|
+
|
|
308
|
+
```mermaid
|
|
309
|
+
graph TD
|
|
310
|
+
A[Application Start] --> B[DI Container Scans Providers]
|
|
311
|
+
B --> C[Provider Instance Created]
|
|
312
|
+
C --> D[Provider Registered in Container]
|
|
313
|
+
D --> E[Application Calls provider.value]
|
|
314
|
+
E --> F[value Returns Factory/Instance]
|
|
315
|
+
F --> G[Consumer Uses Returned Value]
|
|
316
|
+
G --> H{Need Another Instance?}
|
|
317
|
+
H -->|Yes| E
|
|
318
|
+
H -->|No| I[End]
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Key Points
|
|
322
|
+
|
|
323
|
+
1. **Provider Instance Created Once**: The provider class itself is instantiated once by the DI container
|
|
324
|
+
2. **`value()` Called When Needed**: The `value(container)` method is called when the application needs the produced value
|
|
325
|
+
3. **Factory vs Instance**: Providers can return:
|
|
326
|
+
- Direct instances (created each time `value()` is called)
|
|
327
|
+
- Factory functions (deferred creation)
|
|
328
|
+
- Singleton instances (same instance each time)
|
|
329
|
+
|
|
330
|
+
### Example: Singleton vs Factory
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// Singleton: Same instance every time
|
|
334
|
+
@injectable()
|
|
335
|
+
export class SingletonDatabaseProvider extends BaseProvider<Database> {
|
|
336
|
+
private instance?: Database;
|
|
337
|
+
|
|
338
|
+
value(container: Container): Database {
|
|
339
|
+
if (!this.instance) {
|
|
340
|
+
this.instance = new Database({...});
|
|
341
|
+
this.logger.info('[value] Database singleton created');
|
|
342
|
+
}
|
|
343
|
+
return this.instance;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Factory: New instance every time
|
|
348
|
+
@injectable()
|
|
349
|
+
export class FactoryDatabaseProvider extends BaseProvider<Database> {
|
|
350
|
+
value(container: Container): Database {
|
|
351
|
+
this.logger.info('[value] Creating new Database instance');
|
|
352
|
+
return new Database({...});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Real-World Examples
|
|
360
|
+
|
|
361
|
+
### Example 1: Mail Transport Provider
|
|
362
|
+
|
|
363
|
+
From `packages/core/src/components/mail/providers/mail-transporter.provider.ts`:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
type TGetMailTransportFn = (options: TMailOptions) => IMailTransport;
|
|
367
|
+
|
|
368
|
+
@injectable()
|
|
369
|
+
export class MailTransportProvider extends BaseProvider<TGetMailTransportFn> {
|
|
370
|
+
constructor() {
|
|
371
|
+
super({ scope: MailTransportProvider.name });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
value(_container: Container): TGetMailTransportFn {
|
|
375
|
+
return (options: TMailOptions) => {
|
|
376
|
+
this.logger.info('[value] Creating mail transport: %s', options.provider);
|
|
377
|
+
|
|
378
|
+
switch (options.provider) {
|
|
379
|
+
case MailProviders.NODEMAILER:
|
|
380
|
+
return this.createNodemailerTransport(options);
|
|
381
|
+
|
|
382
|
+
case MailProviders.MAILGUN:
|
|
383
|
+
return this.createMailgunTransport(options);
|
|
384
|
+
|
|
385
|
+
case MailProviders.CUSTOM:
|
|
386
|
+
return this.createCustomTransport(options);
|
|
387
|
+
|
|
388
|
+
default:
|
|
389
|
+
throw new Error(`Unsupported provider: ${options.provider}`);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private createNodemailerTransport(options: INodemailerMailOptions) {
|
|
395
|
+
this.logger.info('[createNodemailerTransport] Initializing');
|
|
396
|
+
return new NodemailerTransportHelper(options.config);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private createMailgunTransport(options: IMailgunMailOptions) {
|
|
400
|
+
this.logger.info('[createMailgunTransport] Initializing');
|
|
401
|
+
return new MailgunTransportHelper(options.config);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private createCustomTransport(options: ICustomMailOptions) {
|
|
405
|
+
this.logger.info('[createCustomTransport] Using custom transport');
|
|
406
|
+
return options.config; // Already implements IMailTransport
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Usage:**
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// In your service or application setup
|
|
415
|
+
const getMailTransport = app.get(MailTransportProvider).value(container);
|
|
416
|
+
|
|
417
|
+
// Create Nodemailer transport
|
|
418
|
+
const nodemailerTransport = getMailTransport({
|
|
419
|
+
provider: MailProviders.NODEMAILER,
|
|
420
|
+
config: { /* nodemailer config */ }
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Create Mailgun transport
|
|
424
|
+
const mailgunTransport = getMailTransport({
|
|
425
|
+
provider: MailProviders.MAILGUN,
|
|
426
|
+
config: { /* mailgun config */ }
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Example 2: Queue Executor Provider
|
|
431
|
+
|
|
432
|
+
From `packages/core/src/components/mail/providers/mail-queue-executor.provider.ts`:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
type TGetMailQueueExecutorFn = (config: IMailQueueExecutorConfig) => IMailQueueExecutor;
|
|
436
|
+
|
|
437
|
+
@injectable()
|
|
438
|
+
export class MailQueueExecutorProvider extends BaseProvider<TGetMailQueueExecutorFn> {
|
|
439
|
+
constructor() {
|
|
440
|
+
super({ scope: MailQueueExecutorProvider.name });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
value(_container: Container): TGetMailQueueExecutorFn {
|
|
444
|
+
return (config: IMailQueueExecutorConfig) => {
|
|
445
|
+
this.logger.info('[value] Creating executor: %s', config.type);
|
|
446
|
+
|
|
447
|
+
switch (config.type) {
|
|
448
|
+
case MailQueueExecutorTypes.DIRECT:
|
|
449
|
+
return new DirectMailExecutorHelper();
|
|
450
|
+
|
|
451
|
+
case MailQueueExecutorTypes.INTERNAL_QUEUE:
|
|
452
|
+
return new InternalQueueMailExecutorHelper({
|
|
453
|
+
identifier: config.internalQueue.identifier,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
case MailQueueExecutorTypes.BULLMQ:
|
|
457
|
+
return new BullMQMailExecutorHelper(config.bullmq);
|
|
458
|
+
|
|
459
|
+
default:
|
|
460
|
+
throw new Error(`Unknown type: ${config.type}`);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Example 3: Middleware Provider
|
|
468
|
+
|
|
469
|
+
Providers can also produce middleware:
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { RequestSpyMiddleware } from '@venizia/ignis';
|
|
473
|
+
|
|
474
|
+
// RequestSpyMiddleware is a provider that produces Hono middleware
|
|
475
|
+
@injectable()
|
|
476
|
+
export class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
|
|
477
|
+
static readonly REQUEST_ID_KEY = 'requestId';
|
|
478
|
+
|
|
479
|
+
constructor() {
|
|
480
|
+
super({ scope: RequestSpyMiddleware.name });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
value() {
|
|
484
|
+
return createMiddleware(async (context, next) => {
|
|
485
|
+
const requestId = context.get(RequestSpyMiddleware.REQUEST_ID_KEY);
|
|
486
|
+
|
|
487
|
+
this.logger.info('[spy][%s] START | path: %s', requestId, context.req.path);
|
|
488
|
+
|
|
489
|
+
await next();
|
|
490
|
+
|
|
491
|
+
this.logger.info('[spy][%s] DONE | path: %s', requestId, context.req.path);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Usage
|
|
497
|
+
const requestSpy = new RequestSpyMiddleware();
|
|
498
|
+
app.use(requestSpy.value());
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Common Patterns
|
|
504
|
+
|
|
505
|
+
### Pattern 1: Configuration Validator
|
|
506
|
+
|
|
507
|
+
Validate configuration before creating instances:
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
@injectable()
|
|
511
|
+
export class S3StorageProvider extends BaseProvider<S3Storage> {
|
|
512
|
+
constructor() {
|
|
513
|
+
super({ scope: S3StorageProvider.name });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
value(container: Container): S3Storage {
|
|
517
|
+
const config = container.get(ConfigService);
|
|
518
|
+
|
|
519
|
+
const accessKey = config.get('AWS_ACCESS_KEY');
|
|
520
|
+
const secretKey = config.get('AWS_SECRET_KEY');
|
|
521
|
+
const bucket = config.get('AWS_S3_BUCKET');
|
|
522
|
+
|
|
523
|
+
// Validate configuration
|
|
524
|
+
if (!accessKey || !secretKey || !bucket) {
|
|
525
|
+
throw new Error('S3 configuration incomplete');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
this.logger.info('[value] Creating S3 storage for bucket: %s', bucket);
|
|
529
|
+
|
|
530
|
+
return new S3Storage({
|
|
531
|
+
accessKeyId: accessKey,
|
|
532
|
+
secretAccessKey: secretKey,
|
|
533
|
+
bucket: bucket,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Pattern 2: Lazy Singleton
|
|
540
|
+
|
|
541
|
+
Create instance only once, lazily:
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
@injectable()
|
|
545
|
+
export class DatabaseConnectionProvider extends BaseProvider<DatabaseConnection> {
|
|
546
|
+
private connection?: DatabaseConnection;
|
|
547
|
+
|
|
548
|
+
value(container: Container): DatabaseConnection {
|
|
549
|
+
if (!this.connection) {
|
|
550
|
+
this.logger.info('[value] Creating database connection');
|
|
551
|
+
const config = container.get(ConfigService);
|
|
552
|
+
this.connection = new DatabaseConnection(config.get('DATABASE_URL'));
|
|
553
|
+
} else {
|
|
554
|
+
this.logger.debug('[value] Reusing existing connection');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return this.connection;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Pattern 3: Environment-Based Strategy
|
|
563
|
+
|
|
564
|
+
Select implementation based on environment:
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
@injectable()
|
|
568
|
+
export class CacheProvider extends BaseProvider<ICache> {
|
|
569
|
+
value(container: Container): ICache {
|
|
570
|
+
const env = process.env.NODE_ENV;
|
|
571
|
+
|
|
572
|
+
if (env === 'test') {
|
|
573
|
+
this.logger.info('[value] Using InMemoryCache for testing');
|
|
574
|
+
return new InMemoryCache();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (env === 'production') {
|
|
578
|
+
this.logger.info('[value] Using RedisCache for production');
|
|
579
|
+
const config = container.get(ConfigService);
|
|
580
|
+
return new RedisCache({
|
|
581
|
+
host: config.get('REDIS_HOST'),
|
|
582
|
+
port: config.get('REDIS_PORT'),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
this.logger.info('[value] Using InMemoryCache for development');
|
|
587
|
+
return new InMemoryCache();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Common Pitfalls
|
|
595
|
+
|
|
596
|
+
### Pitfall 1: Forgetting to Call `value()`
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
// ❌ Wrong: Getting the provider instance
|
|
600
|
+
const provider = app.get(MailTransportProvider);
|
|
601
|
+
const transport = provider({ provider: 'nodemailer' }); // Error!
|
|
602
|
+
|
|
603
|
+
// ✅ Correct: Call value() first
|
|
604
|
+
const provider = app.get(MailTransportProvider);
|
|
605
|
+
const getTransport = provider.value(container);
|
|
606
|
+
const transport = getTransport({ provider: 'nodemailer' });
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Pitfall 2: Creating Instances in Constructor
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
// ❌ Wrong: Creating instances in constructor
|
|
613
|
+
@injectable()
|
|
614
|
+
export class BadProvider extends BaseProvider<Database> {
|
|
615
|
+
private db: Database;
|
|
616
|
+
|
|
617
|
+
constructor() {
|
|
618
|
+
super({ scope: BadProvider.name });
|
|
619
|
+
this.db = new Database(); // Too early! Config might not be ready
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
value(container: Container): Database {
|
|
623
|
+
return this.db;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// ✅ Correct: Create in value() method
|
|
628
|
+
@injectable()
|
|
629
|
+
export class GoodProvider extends BaseProvider<Database> {
|
|
630
|
+
constructor() {
|
|
631
|
+
super({ scope: GoodProvider.name });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
value(container: Container): Database {
|
|
635
|
+
const config = container.get(ConfigService);
|
|
636
|
+
return new Database(config.get('DATABASE_URL'));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Pitfall 3: Not Handling Errors
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
// ❌ Wrong: No error handling
|
|
645
|
+
value(container: Container): IMailTransport {
|
|
646
|
+
return new MailTransport(config.get('MAIL_CONFIG')); // Might throw
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ✅ Correct: Validate and handle errors
|
|
650
|
+
value(container: Container): IMailTransport {
|
|
651
|
+
const config = container.get(ConfigService);
|
|
652
|
+
const mailConfig = config.get('MAIL_CONFIG');
|
|
653
|
+
|
|
654
|
+
if (!mailConfig) {
|
|
655
|
+
throw new Error('Mail configuration is missing');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
try {
|
|
659
|
+
return new MailTransport(mailConfig);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
this.logger.error('[value] Failed to create mail transport', error);
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## Performance Considerations
|
|
670
|
+
|
|
671
|
+
### Factory Functions vs Direct Instances
|
|
672
|
+
|
|
673
|
+
**Factory Functions** (Recommended for multiple instances):
|
|
674
|
+
```typescript
|
|
675
|
+
// Returns a factory - no instance created until called
|
|
676
|
+
value(container: Container): () => Service {
|
|
677
|
+
return () => new Service();
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**Direct Instances** (Use for singletons):
|
|
682
|
+
```typescript
|
|
683
|
+
// Creates instance immediately
|
|
684
|
+
value(container: Container): Service {
|
|
685
|
+
return new Service();
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Caching Strategies
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
// Cache expensive operations
|
|
693
|
+
@injectable()
|
|
694
|
+
export class ConfigProvider extends BaseProvider<Config> {
|
|
695
|
+
private cachedConfig?: Config;
|
|
696
|
+
|
|
697
|
+
value(container: Container): Config {
|
|
698
|
+
if (this.cachedConfig) {
|
|
699
|
+
return this.cachedConfig;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Expensive operation: read files, parse, validate
|
|
703
|
+
this.cachedConfig = this.loadAndValidateConfig();
|
|
704
|
+
return this.cachedConfig;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private loadAndValidateConfig(): Config {
|
|
708
|
+
// ... expensive operations
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## See Also
|
|
716
|
+
|
|
717
|
+
- **Related References:**
|
|
718
|
+
- [Services](./services.md) - Business logic layer
|
|
719
|
+
- [Dependency Injection](./dependency-injection.md) - DI container and injection
|
|
720
|
+
- [Middlewares](./middlewares.md) - Middleware providers
|
|
721
|
+
|
|
722
|
+
- **Guides:**
|
|
723
|
+
- [Dependency Injection Guide](/guides/core-concepts/dependency-injection.md)
|
|
724
|
+
- [Building Services](/guides/core-concepts/services.md)
|
|
725
|
+
|
|
726
|
+
- **Best Practices:**
|
|
727
|
+
- [Architectural Patterns](/best-practices/architectural-patterns)
|
|
728
|
+
|
|
729
|
+
- **External Resources:**
|
|
730
|
+
- [Factory Pattern](https://refactoring.guru/design-patterns/factory-method)
|
|
731
|
+
- [Dependency Injection Pattern](https://en.wikipedia.org/wiki/Dependency_injection)
|