@venizia/ignis-docs 0.0.4-1 → 0.0.4-2
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/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +1 -0
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
- package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
- package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
- package/wiki/best-practices/code-style-standards/documentation.md +221 -0
- package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
- package/wiki/best-practices/code-style-standards/index.md +110 -0
- package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
- package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
- package/wiki/best-practices/code-style-standards/tooling.md +155 -0
- package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
- package/wiki/best-practices/common-pitfalls.md +164 -3
- package/wiki/best-practices/contribution-workflow.md +1 -1
- package/wiki/best-practices/data-modeling.md +102 -2
- package/wiki/best-practices/error-handling.md +468 -0
- package/wiki/best-practices/index.md +204 -21
- package/wiki/best-practices/performance-optimization.md +180 -0
- package/wiki/best-practices/security-guidelines.md +249 -0
- package/wiki/best-practices/testing-strategies.md +620 -0
- package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
- package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
- package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
- package/wiki/changelogs/index.md +3 -0
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/persistent/models.md +10 -0
- package/wiki/guides/tutorials/complete-installation.md +1 -1
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/components.md +47 -29
- package/wiki/references/base/controllers.md +215 -18
- package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
- package/wiki/references/base/middlewares.md +33 -1
- package/wiki/references/base/models.md +40 -2
- package/wiki/references/base/repositories/index.md +2 -0
- package/wiki/references/components/authentication.md +261 -247
- package/wiki/references/helpers/index.md +1 -1
- package/wiki/references/src-details/core.md +1 -1
- package/wiki/best-practices/code-style-standards.md +0 -1193
package/package.json
CHANGED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Advanced Patterns
|
|
2
|
+
|
|
3
|
+
Advanced TypeScript patterns used throughout the Ignis framework.
|
|
4
|
+
|
|
5
|
+
## Mixin Pattern
|
|
6
|
+
|
|
7
|
+
Create reusable class extensions without deep inheritance:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { TMixinTarget } from '@venizia/ignis';
|
|
11
|
+
|
|
12
|
+
export const LoggableMixin = <BaseClass extends TMixinTarget<Base>>(
|
|
13
|
+
baseClass: BaseClass,
|
|
14
|
+
) => {
|
|
15
|
+
return class extends baseClass {
|
|
16
|
+
protected logger = LoggerFactory.getLogger(this.constructor.name);
|
|
17
|
+
|
|
18
|
+
log(message: string): void {
|
|
19
|
+
this.logger.info(message);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Usage
|
|
25
|
+
class MyService extends LoggableMixin(BaseService) {
|
|
26
|
+
doWork(): void {
|
|
27
|
+
this.log('Work started'); // Method from mixin
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Multiple Mixins
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
class MyRepository extends LoggableMixin(CacheableMixin(BaseRepository)) {
|
|
36
|
+
// Has both logging and caching capabilities
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Typed Mixin with Constraints
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
type TWithId = { id: string };
|
|
44
|
+
|
|
45
|
+
export const TimestampMixin = <
|
|
46
|
+
BaseClass extends TMixinTarget<TWithId>
|
|
47
|
+
>(baseClass: BaseClass) => {
|
|
48
|
+
return class extends baseClass {
|
|
49
|
+
createdAt: Date = new Date();
|
|
50
|
+
updatedAt: Date = new Date();
|
|
51
|
+
|
|
52
|
+
touch(): void {
|
|
53
|
+
this.updatedAt = new Date();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Factory Pattern with Dynamic Class
|
|
60
|
+
|
|
61
|
+
Generate classes dynamically with configuration:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
class ControllerFactory {
|
|
65
|
+
static defineCrudController<Schema extends TTableSchemaWithId>(
|
|
66
|
+
opts: ICrudControllerOptions<Schema>,
|
|
67
|
+
) {
|
|
68
|
+
return class extends BaseController {
|
|
69
|
+
constructor(repository: AbstractRepository<Schema>) {
|
|
70
|
+
super({ scope: opts.controller.name });
|
|
71
|
+
this.repository = repository;
|
|
72
|
+
this.setupRoutes();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private setupRoutes(): void {
|
|
76
|
+
// Dynamically bind CRUD routes
|
|
77
|
+
this.defineRoute({
|
|
78
|
+
configs: { method: 'get', path: '/' },
|
|
79
|
+
handler: (c) => this.list(c),
|
|
80
|
+
});
|
|
81
|
+
this.defineRoute({
|
|
82
|
+
configs: { method: 'get', path: '/:id' },
|
|
83
|
+
handler: (c) => this.getById(c),
|
|
84
|
+
});
|
|
85
|
+
// ... more routes
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async list(c: Context) {
|
|
89
|
+
const data = await this.repository.find({});
|
|
90
|
+
return c.json(data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getById(c: Context) {
|
|
94
|
+
const { id } = c.req.param();
|
|
95
|
+
const data = await this.repository.findById({ id });
|
|
96
|
+
return c.json(data);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Usage
|
|
103
|
+
const UserCrudController = ControllerFactory.defineCrudController({
|
|
104
|
+
controller: { name: 'UserController', basePath: '/users' },
|
|
105
|
+
repository: { name: UserRepository.name },
|
|
106
|
+
entity: () => User,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
@controller({ path: '/users' })
|
|
110
|
+
export class UserController extends UserCrudController {
|
|
111
|
+
// Additional custom routes
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Value Resolver Pattern
|
|
116
|
+
|
|
117
|
+
Support multiple input types that resolve to a single value:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Type definitions
|
|
121
|
+
export type TResolver<T> = () => T;
|
|
122
|
+
export type TConstructor<T> = new (...args: any[]) => T;
|
|
123
|
+
export type TValueOrResolver<T> = T | TResolver<T> | TConstructor<T>;
|
|
124
|
+
|
|
125
|
+
// Resolver function
|
|
126
|
+
export const resolveValue = <T>(valueOrResolver: TValueOrResolver<T>): T => {
|
|
127
|
+
if (typeof valueOrResolver !== 'function') {
|
|
128
|
+
return valueOrResolver; // Direct value
|
|
129
|
+
}
|
|
130
|
+
if (isClassConstructor(valueOrResolver)) {
|
|
131
|
+
return valueOrResolver as T; // Class constructor (return as-is)
|
|
132
|
+
}
|
|
133
|
+
return (valueOrResolver as TResolver<T>)(); // Function resolver
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Helper to detect class constructors
|
|
137
|
+
function isClassConstructor(fn: Function): boolean {
|
|
138
|
+
return fn.toString().startsWith('class ');
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Usage
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface IOptions {
|
|
146
|
+
entity: TValueOrResolver<typeof User>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// All valid:
|
|
150
|
+
const opts1: IOptions = { entity: User }; // Direct class
|
|
151
|
+
const opts2: IOptions = { entity: () => User }; // Resolver function (for lazy loading)
|
|
152
|
+
|
|
153
|
+
// In consumer code
|
|
154
|
+
const EntityClass = resolveValue(opts.entity);
|
|
155
|
+
const instance = new EntityClass();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Why Use Value Resolvers?
|
|
159
|
+
|
|
160
|
+
1. **Circular Dependency Prevention**: Lazy loading via resolver functions breaks cycles
|
|
161
|
+
2. **Lazy Initialization**: Defer expensive imports until needed
|
|
162
|
+
3. **Testing**: Easy to swap implementations via resolvers
|
|
163
|
+
4. **Flexibility**: Single API accepts multiple input types
|
|
164
|
+
|
|
165
|
+
## Builder Pattern
|
|
166
|
+
|
|
167
|
+
For constructing complex objects step-by-step:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
class QueryBuilder<T> {
|
|
171
|
+
private _where: Record<string, any> = {};
|
|
172
|
+
private _orderBy: string[] = [];
|
|
173
|
+
private _limit?: number;
|
|
174
|
+
private _offset?: number;
|
|
175
|
+
|
|
176
|
+
where(conditions: Record<string, any>): this {
|
|
177
|
+
this._where = { ...this._where, ...conditions };
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): this {
|
|
182
|
+
this._orderBy.push(`${field} ${direction.toUpperCase()}`);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
limit(n: number): this {
|
|
187
|
+
this._limit = n;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
offset(n: number): this {
|
|
192
|
+
this._offset = n;
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
build(): TQueryOptions {
|
|
197
|
+
return {
|
|
198
|
+
where: this._where,
|
|
199
|
+
order: this._orderBy,
|
|
200
|
+
limit: this._limit,
|
|
201
|
+
offset: this._offset,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Usage
|
|
207
|
+
const query = new QueryBuilder()
|
|
208
|
+
.where({ status: 'active' })
|
|
209
|
+
.orderBy('createdAt', 'desc')
|
|
210
|
+
.limit(10)
|
|
211
|
+
.offset(0)
|
|
212
|
+
.build();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Registry Pattern
|
|
216
|
+
|
|
217
|
+
Centralized registration of components:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
class StrategyRegistry<T> {
|
|
221
|
+
private strategies = new Map<string, T>();
|
|
222
|
+
|
|
223
|
+
register(name: string, strategy: T): void {
|
|
224
|
+
if (this.strategies.has(name)) {
|
|
225
|
+
throw new Error(`Strategy '${name}' already registered`);
|
|
226
|
+
}
|
|
227
|
+
this.strategies.set(name, strategy);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get(name: string): T {
|
|
231
|
+
const strategy = this.strategies.get(name);
|
|
232
|
+
if (!strategy) {
|
|
233
|
+
throw new Error(`Strategy '${name}' not found`);
|
|
234
|
+
}
|
|
235
|
+
return strategy;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
has(name: string): boolean {
|
|
239
|
+
return this.strategies.has(name);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
all(): Map<string, T> {
|
|
243
|
+
return new Map(this.strategies);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Usage
|
|
248
|
+
const authRegistry = new StrategyRegistry<IAuthStrategy>();
|
|
249
|
+
authRegistry.register('jwt', new JWTStrategy());
|
|
250
|
+
authRegistry.register('basic', new BasicStrategy());
|
|
251
|
+
|
|
252
|
+
const strategy = authRegistry.get('jwt');
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## See Also
|
|
256
|
+
|
|
257
|
+
- [Type Safety](./type-safety) - Generic type patterns
|
|
258
|
+
- [Repositories Reference](../../references/base/repositories/) - Mixin usage
|
|
259
|
+
- [Architectural Patterns](../architectural-patterns) - High-level patterns
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Constants & Configuration
|
|
2
|
+
|
|
3
|
+
Best practices for defining constants and managing configuration.
|
|
4
|
+
|
|
5
|
+
## Constants Pattern
|
|
6
|
+
|
|
7
|
+
**Prefer static classes over enums** for better tree-shaking and extensibility.
|
|
8
|
+
|
|
9
|
+
### Basic Constants
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
export class Authentication {
|
|
13
|
+
static readonly STRATEGY_BASIC = 'basic';
|
|
14
|
+
static readonly STRATEGY_JWT = 'jwt';
|
|
15
|
+
static readonly TYPE_BEARER = 'Bearer';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class HealthCheckRestPaths {
|
|
19
|
+
static readonly ROOT = '/';
|
|
20
|
+
static readonly PING = '/ping';
|
|
21
|
+
static readonly METRICS = '/metrics';
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Typed Constants with Validation
|
|
26
|
+
|
|
27
|
+
For constants that need type extraction and runtime validation, use this pattern:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { TConstValue } from '@venizia/ignis-helpers';
|
|
31
|
+
|
|
32
|
+
export class DocumentUITypes {
|
|
33
|
+
// 1. Define static readonly values
|
|
34
|
+
static readonly SWAGGER = 'swagger';
|
|
35
|
+
static readonly SCALAR = 'scalar';
|
|
36
|
+
|
|
37
|
+
// 2. Create a Set for O(1) validation lookup
|
|
38
|
+
static readonly SCHEME_SET = new Set([this.SWAGGER, this.SCALAR]);
|
|
39
|
+
|
|
40
|
+
// 3. Validation helper method
|
|
41
|
+
static isValid(value: string): boolean {
|
|
42
|
+
return this.SCHEME_SET.has(value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 4. Extract union type from class values
|
|
47
|
+
export type TDocumentUIType = TConstValue<typeof DocumentUITypes>;
|
|
48
|
+
// Result: 'swagger' | 'scalar'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Full Example with Usage
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { TConstValue } from '@venizia/ignis-helpers';
|
|
55
|
+
|
|
56
|
+
export class UserStatuses {
|
|
57
|
+
static readonly ACTIVE = 'active';
|
|
58
|
+
static readonly INACTIVE = 'inactive';
|
|
59
|
+
static readonly PENDING = 'pending';
|
|
60
|
+
static readonly BANNED = 'banned';
|
|
61
|
+
|
|
62
|
+
static readonly SCHEME_SET = new Set([
|
|
63
|
+
this.ACTIVE,
|
|
64
|
+
this.INACTIVE,
|
|
65
|
+
this.PENDING,
|
|
66
|
+
this.BANNED,
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
static isValid(value: string): boolean {
|
|
70
|
+
return this.SCHEME_SET.has(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Optional: get all values as array
|
|
74
|
+
static values(): string[] {
|
|
75
|
+
return [...this.SCHEME_SET];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Type-safe union type
|
|
80
|
+
export type TUserStatus = TConstValue<typeof UserStatuses>;
|
|
81
|
+
// Result: 'active' | 'inactive' | 'pending' | 'banned'
|
|
82
|
+
|
|
83
|
+
// Usage in interfaces
|
|
84
|
+
interface IUser {
|
|
85
|
+
id: string;
|
|
86
|
+
status: TUserStatus; // Type-safe!
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Usage with validation
|
|
90
|
+
function updateUserStatus(userId: string, status: string) {
|
|
91
|
+
if (!UserStatuses.isValid(status)) {
|
|
92
|
+
throw getError({
|
|
93
|
+
statusCode: HTTP.ResultCodes.RS_4.BadRequest,
|
|
94
|
+
message: `Invalid status: ${status}. Valid: ${UserStatuses.values().join(', ')}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// status is validated at runtime
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Enum vs Static Class Comparison
|
|
102
|
+
|
|
103
|
+
| Aspect | Static Class | TypeScript Enum |
|
|
104
|
+
|--------|--------------|-----------------|
|
|
105
|
+
| Tree-shaking | Full support | Partial (IIFE blocks it) |
|
|
106
|
+
| Bundle size | Minimal | Larger (IIFE wrapper) |
|
|
107
|
+
| Runtime validation | O(1) with `Set` | O(n) with `Object.values()` |
|
|
108
|
+
| Type extraction | `TConstValue<typeof X>` → values | `keyof typeof X` → keys (not values!) |
|
|
109
|
+
| Add methods | Yes | Not possible |
|
|
110
|
+
| Compiled output | Clean class | IIFE wrapper |
|
|
111
|
+
|
|
112
|
+
**Compiled JavaScript:**
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Enum compiles to IIFE (not tree-shakable)
|
|
116
|
+
var UserStatus;
|
|
117
|
+
(function (UserStatus) {
|
|
118
|
+
UserStatus["ACTIVE"] = "active";
|
|
119
|
+
})(UserStatus || (UserStatus = {}));
|
|
120
|
+
|
|
121
|
+
// Static class compiles cleanly
|
|
122
|
+
class UserStatuses { }
|
|
123
|
+
UserStatuses.ACTIVE = 'active';
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Type Extraction Difference:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Enum - extracts KEYS
|
|
130
|
+
type T = keyof typeof UserStatus; // 'ACTIVE' | 'INACTIVE'
|
|
131
|
+
|
|
132
|
+
// Static Class - extracts VALUES
|
|
133
|
+
type T = TConstValue<typeof UserStatuses>; // 'active' | 'inactive'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**When to use `const enum`:** Only for numeric flags with no iteration needed (values are inlined, zero runtime). But doesn't work with `--isolatedModules`.
|
|
137
|
+
|
|
138
|
+
**Verdict:** Use Static Class for 90% of cases - better tree-shaking, easy validation, type-safe values, extensible with methods.
|
|
139
|
+
|
|
140
|
+
## Configuration Patterns
|
|
141
|
+
|
|
142
|
+
### Default Options
|
|
143
|
+
|
|
144
|
+
Every configurable class should define `DEFAULT_OPTIONS`:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const DEFAULT_OPTIONS: IHealthCheckOptions = {
|
|
148
|
+
restOptions: { path: '/health' },
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const DEFAULT_SERVER_OPTIONS: Partial<IServerOptions> = {
|
|
152
|
+
identifier: 'SOCKET_IO_SERVER',
|
|
153
|
+
path: '/io',
|
|
154
|
+
cors: {
|
|
155
|
+
origin: '*',
|
|
156
|
+
methods: ['GET', 'POST'],
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Option Merging
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// In component constructor or binding
|
|
165
|
+
const extraOptions = this.application.get<Partial<IServerOptions>>({
|
|
166
|
+
key: BindingKeys.SERVER_OPTIONS,
|
|
167
|
+
isOptional: true,
|
|
168
|
+
}) ?? {};
|
|
169
|
+
|
|
170
|
+
this.options = Object.assign({}, DEFAULT_OPTIONS, extraOptions);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Constructor Validation
|
|
174
|
+
|
|
175
|
+
Validate required options in the constructor:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
constructor(options: IJWTTokenServiceOptions) {
|
|
179
|
+
super({ scope: JWTTokenService.name });
|
|
180
|
+
|
|
181
|
+
if (!options.jwtSecret) {
|
|
182
|
+
throw getError({
|
|
183
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
184
|
+
message: '[JWTTokenService] Invalid jwtSecret',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!options.applicationSecret) {
|
|
189
|
+
throw getError({
|
|
190
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
191
|
+
message: '[JWTTokenService] Invalid applicationSecret',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.options = options;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Environment Variables Management
|
|
200
|
+
|
|
201
|
+
Avoid using `process.env` directly in your business logic. Instead, use the `applicationEnvironment` helper and define your keys as constants.
|
|
202
|
+
|
|
203
|
+
**Define Keys (`src/common/environments.ts`):**
|
|
204
|
+
```typescript
|
|
205
|
+
export class EnvironmentKeys {
|
|
206
|
+
static readonly APP_ENV_STRIPE_KEY = 'APP_ENV_STRIPE_KEY';
|
|
207
|
+
static readonly APP_ENV_MAX_RETRIES = 'APP_ENV_MAX_RETRIES';
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Usage:**
|
|
212
|
+
```typescript
|
|
213
|
+
import { applicationEnvironment } from '@venizia/ignis';
|
|
214
|
+
import { EnvironmentKeys } from '@/common/environments';
|
|
215
|
+
|
|
216
|
+
// Correct usage
|
|
217
|
+
const stripeKey = applicationEnvironment.get<string>(EnvironmentKeys.APP_ENV_STRIPE_KEY);
|
|
218
|
+
const retries = applicationEnvironment.get<number>(EnvironmentKeys.APP_ENV_MAX_RETRIES);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## See Also
|
|
222
|
+
|
|
223
|
+
- [Naming Conventions](./naming-conventions) - Constant naming
|
|
224
|
+
- [Type Safety](./type-safety) - Type extraction patterns
|
|
225
|
+
- [Configuration Reference](../../references/configuration/) - Environment variables
|