@venizia/ignis-docs 0.0.2 → 0.0.3

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.
@@ -117,6 +117,55 @@ Use the centralized TypeScript configs:
117
117
 
118
118
  See [`@venizia/dev-configs` documentation](../../references/src-details/dev-configs.md) for full details.
119
119
 
120
+ ## Directory Structure
121
+
122
+ ### Component Organization
123
+
124
+ ```
125
+ src/components/[feature]/
126
+ ├── index.ts # Barrel exports
127
+ ├── component.ts # IoC binding setup
128
+ ├── controller.ts # Route handlers
129
+ └── common/
130
+ ├── index.ts # Barrel exports
131
+ ├── keys.ts # Binding key constants
132
+ ├── types.ts # Interfaces and types
133
+ └── rest-paths.ts # Route path constants
134
+ ```
135
+
136
+ ### Complex Component (with multiple features)
137
+
138
+ ```
139
+ src/components/auth/
140
+ ├── index.ts
141
+ ├── authenticate/
142
+ │ ├── index.ts
143
+ │ ├── component.ts
144
+ │ ├── common/
145
+ │ ├── controllers/
146
+ │ ├── services/
147
+ │ └── strategies/
148
+ └── models/
149
+ ├── entities/ # Database models
150
+ └── requests/ # Request schemas
151
+ ```
152
+
153
+ ### Barrel Exports
154
+
155
+ Every folder should have an `index.ts` that re-exports its contents:
156
+
157
+ ```typescript
158
+ // components/health-check/index.ts
159
+ export * from './common';
160
+ export * from './component';
161
+ export * from './controller';
162
+
163
+ // components/health-check/common/index.ts
164
+ export * from './keys';
165
+ export * from './rest-paths';
166
+ export * from './types';
167
+ ```
168
+
120
169
  ## Naming Conventions
121
170
 
122
171
  ### Class Names
@@ -184,53 +233,236 @@ export class SocketIOBindingKeys {
184
233
  }
185
234
  ```
186
235
 
187
- ## Directory Structure
236
+ ## Type Safety
188
237
 
189
- ### Component Organization
238
+ To ensure long-term maintainability and catch errors at compile-time, Ignis enforces strict type safety.
239
+
240
+ ### Avoid `any` and `unknown`
241
+
242
+ **Never use `any` or `unknown` as much as possible.** You must specify clear, descriptive types for all variables, parameters, and return values.
243
+
244
+ - **`any`**: Bypasses all type checking and leads to "runtime surprises". It is strictly discouraged.
245
+ - **`unknown`**: While safer than `any`, it still forces consumers to perform manual type checking. Prefer using generics or specific interfaces.
246
+
247
+ **Why?**
248
+ - **Maintenance**: Developers reading your code in the future will know exactly what the data structure is.
249
+ - **Refactoring**: Changing an interface automatically highlights all broken code across the monorepo.
250
+ - **Documentation**: Types act as a self-documenting contract for your APIs and services.
251
+
252
+ ## Type Definitions
253
+
254
+ ### Explicit Return Types
255
+ Always define explicit return types for **public methods** and **API handlers**.
256
+
257
+ **Why?**
258
+ - **Compiler Performance:** Speeds up TypeScript type checking in large projects.
259
+ - **Safety:** Prevents accidental exposure of internal types or sensitive data.
260
+
261
+ ```typescript
262
+ // ✅ GOOD
263
+ public async findUser(id: string): Promise<User | null> { ... }
190
264
 
265
+ // ❌ BAD (Implicit inference)
266
+ public async findUser(id: string) { ... }
191
267
  ```
192
- src/components/[feature]/
193
- ├── index.ts # Barrel exports
194
- ├── component.ts # IoC binding setup
195
- ├── controller.ts # Route handlers
196
- └── common/
197
- ├── index.ts # Barrel exports
198
- ├── keys.ts # Binding key constants
199
- ├── types.ts # Interfaces and types
200
- └── rest-paths.ts # Route path constants
268
+
269
+ ## Type Inference Patterns
270
+
271
+ ### Zod Schema to Type
272
+
273
+ ```typescript
274
+ // Define schema
275
+ export const SignInRequestSchema = z.object({
276
+ email: z.string().email(),
277
+ password: z.string().min(8),
278
+ });
279
+
280
+ // Infer type from schema
281
+ export type TSignInRequest = z.infer<typeof SignInRequestSchema>;
201
282
  ```
202
283
 
203
- ### Complex Component (with multiple features)
284
+ ### Const Assertion for Literal Types
204
285
 
286
+ ```typescript
287
+ const ROUTE_CONFIGS = {
288
+ '/users': { method: 'GET', path: '/users' },
289
+ '/users/:id': { method: 'GET', path: '/users/:id' },
290
+ } as const;
291
+
292
+ // Type is now narrowed to literal values
293
+ type RouteKey = keyof typeof ROUTE_CONFIGS; // '/users' | '/users/:id'
205
294
  ```
206
- src/components/auth/
207
- ├── index.ts
208
- ├── authenticate/
209
- │ ├── index.ts
210
- │ ├── component.ts
211
- │ ├── common/
212
- │ ├── controllers/
213
- │ ├── services/
214
- │ └── strategies/
215
- └── models/
216
- ├── entities/ # Database models
217
- └── requests/ # Request schemas
295
+
296
+ ### Generic Type Constraints
297
+
298
+ ```typescript
299
+ export class DefaultCRUDRepository<
300
+ Schema extends TTableSchemaWithId = TTableSchemaWithId
301
+ > {
302
+ // Schema is constrained to have an 'id' column
303
+ }
304
+
305
+ export interface IAuthService<
306
+ SIRQ extends TSignInRequest = TSignInRequest,
307
+ SIRS = AnyObject,
308
+ > {
309
+ signIn(context: Context, opts: SIRQ): Promise<SIRS>;
310
+ }
218
311
  ```
219
312
 
220
- ### Barrel Exports
313
+ ## Module Exports
221
314
 
222
- Every folder should have an `index.ts` that re-exports its contents:
315
+ ### Prefer Named Exports
316
+ Avoid `export default` except for configuration files (e.g., `eslint.config.mjs`) or lazy-loaded components. Use named exports for all classes, functions, and constants.
317
+
318
+ **Why?**
319
+ - **Refactoring:** Renaming a symbol automatically updates imports across the monorepo.
320
+ - **Consistency:** Enforces consistent naming across all files importing the module.
223
321
 
224
322
  ```typescript
225
- // components/health-check/index.ts
226
- export * from './common';
227
- export * from './component';
228
- export * from './controller';
323
+ // ✅ GOOD
324
+ export class UserController { ... }
229
325
 
230
- // components/health-check/common/index.ts
231
- export * from './keys';
232
- export * from './rest-paths';
233
- export * from './types';
326
+ // ❌ BAD
327
+ export default class UserController { ... }
328
+ ```
329
+
330
+ ## Function Signatures
331
+
332
+ ### The Options Object Pattern
333
+ Prefer using a single object parameter (`opts`) over multiple positional arguments, especially for constructors and public methods with more than 2 arguments.
334
+
335
+ **Why?**
336
+ - **Extensibility:** You can add new properties without breaking existing calls.
337
+ - **Readability:** Named keys act as documentation at the call site.
338
+
339
+ ```typescript
340
+ // ✅ GOOD
341
+ class UserService {
342
+ createUser(opts: { name: string; email: string; role?: string }) { ... }
343
+ }
344
+ // Usage: service.createUser({ name: 'John', email: 'john@example.com' });
345
+
346
+ // ❌ BAD
347
+ class UserService {
348
+ createUser(name: string, email: string, role?: string) { ... }
349
+ }
350
+ // Usage: service.createUser('John', 'john@example.com');
351
+ ```
352
+
353
+ ## Route Definition Patterns
354
+
355
+ Ignis supports three methods for defining routes. Choose based on your needs:
356
+
357
+ ### Method 1: Config-Driven Routes
358
+
359
+ Define route configurations as constants:
360
+
361
+ ```typescript
362
+ // common/rest-paths.ts
363
+ export class UserRestPaths {
364
+ static readonly ROOT = '/';
365
+ static readonly BY_ID = '/:id';
366
+ static readonly PROFILE = '/profile';
367
+ }
368
+
369
+ // common/route-configs.ts
370
+ export const ROUTE_CONFIGS = {
371
+ [UserRestPaths.ROOT]: {
372
+ method: HTTP.Methods.GET,
373
+ path: UserRestPaths.ROOT,
374
+ responses: jsonResponse({
375
+ [HTTP.ResultCodes.RS_2.Ok]: UserListSchema,
376
+ }),
377
+ },
378
+ [UserRestPaths.BY_ID]: {
379
+ method: HTTP.Methods.GET,
380
+ path: UserRestPaths.BY_ID,
381
+ request: {
382
+ params: z.object({ id: z.string().uuid() }),
383
+ },
384
+ responses: jsonResponse({
385
+ [HTTP.ResultCodes.RS_2.Ok]: UserSchema,
386
+ [HTTP.ResultCodes.RS_4.NotFound]: ErrorSchema,
387
+ }),
388
+ },
389
+ } as const;
390
+ ```
391
+
392
+ ### Method 2: Using `@api` Decorator
393
+
394
+ ```typescript
395
+ @controller({ path: '/users' })
396
+ export class UserController extends BaseController {
397
+
398
+ @api({ configs: ROUTE_CONFIGS[UserRestPaths.ROOT] })
399
+ list(context: TRouteContext<typeof ROUTE_CONFIGS[typeof UserRestPaths.ROOT]>) {
400
+ return context.json({ users: [] }, HTTP.ResultCodes.RS_2.Ok);
401
+ }
402
+
403
+ @api({ configs: ROUTE_CONFIGS[UserRestPaths.BY_ID] })
404
+ getById(context: TRouteContext<typeof ROUTE_CONFIGS[typeof UserRestPaths.BY_ID]>) {
405
+ const { id } = context.req.valid('param');
406
+ return context.json({ id, name: 'User' }, HTTP.ResultCodes.RS_2.Ok);
407
+ }
408
+ }
409
+ ```
410
+
411
+ ### Method 3: Using `bindRoute` (Programmatic)
412
+
413
+ ```typescript
414
+ @controller({ path: '/health' })
415
+ export class HealthCheckController extends BaseController {
416
+ constructor() {
417
+ super({ scope: HealthCheckController.name });
418
+
419
+ this.bindRoute({ configs: ROUTE_CONFIGS['/'] }).to({
420
+ handler: context => context.json({ status: 'ok' }),
421
+ });
422
+ }
423
+ }
424
+ ```
425
+
426
+ ### Method 4: Using `defineRoute` (Inline)
427
+
428
+ ```typescript
429
+ @controller({ path: '/health' })
430
+ export class HealthCheckController extends BaseController {
431
+ constructor() {
432
+ super({ scope: HealthCheckController.name });
433
+
434
+ this.defineRoute({
435
+ configs: ROUTE_CONFIGS['/ping'],
436
+ handler: context => {
437
+ const { message } = context.req.valid('json');
438
+ return context.json({ echo: message }, HTTP.ResultCodes.RS_2.Ok);
439
+ },
440
+ });
441
+ }
442
+ }
443
+ ```
444
+
445
+ ### OpenAPI Schema Integration
446
+
447
+ Use Zod with `.openapi()` for automatic documentation:
448
+
449
+ ```typescript
450
+ const CreateUserSchema = z.object({
451
+ email: z.string().email(),
452
+ name: z.string().min(1).max(100),
453
+ }).openapi({
454
+ description: 'Create user request body',
455
+ example: { email: 'user@example.com', name: 'John Doe' },
456
+ });
457
+
458
+ const UserSchema = z.object({
459
+ id: z.string().uuid(),
460
+ email: z.string().email(),
461
+ name: z.string(),
462
+ createdAt: z.string().datetime(),
463
+ }).openapi({
464
+ description: 'User response',
465
+ });
234
466
  ```
235
467
 
236
468
  ## Constants Pattern
@@ -427,6 +659,28 @@ constructor(options: IJWTTokenServiceOptions) {
427
659
  }
428
660
  ```
429
661
 
662
+ ## Environment Variables Management
663
+
664
+ Avoid using `process.env` directly in your business logic. Instead, use the `applicationEnvironment` helper and define your keys as constants. This ensures type safety and centralized management.
665
+
666
+ **Define Keys (`src/common/environments.ts`):**
667
+ ```typescript
668
+ export class EnvironmentKeys {
669
+ static readonly APP_ENV_STRIPE_KEY = 'APP_ENV_STRIPE_KEY';
670
+ static readonly APP_ENV_MAX_RETRIES = 'APP_ENV_MAX_RETRIES';
671
+ }
672
+ ```
673
+
674
+ **Usage:**
675
+ ```typescript
676
+ import { applicationEnvironment } from '@venizia/ignis';
677
+ import { EnvironmentKeys } from '@/common/environments';
678
+
679
+ // Correct usage
680
+ const stripeKey = applicationEnvironment.get<string>(EnvironmentKeys.APP_ENV_STRIPE_KEY);
681
+ const retries = applicationEnvironment.get<number>(EnvironmentKeys.APP_ENV_MAX_RETRIES);
682
+ ```
683
+
430
684
  ## Logging Patterns
431
685
 
432
686
  ### Method Context Prefix
@@ -451,46 +705,6 @@ this.logger.info('[create] User created | ID: %s | Email: %s', user.id, user.ema
451
705
  this.logger.debug('[config] Server options: %j', this.serverOptions);
452
706
  ```
453
707
 
454
- ## Scope Naming
455
-
456
- Every class extending a base class should set its scope using `ClassName.name`:
457
-
458
- ```typescript
459
- export class JWTTokenService extends BaseService {
460
- constructor() {
461
- super({ scope: JWTTokenService.name });
462
- }
463
- }
464
-
465
- export class UserController extends BaseController {
466
- constructor() {
467
- super({ scope: UserController.name });
468
- }
469
- }
470
- ```
471
-
472
- ## Environment Variables Management
473
-
474
- Avoid using `process.env` directly in your business logic. Instead, use the `applicationEnvironment` helper and define your keys as constants. This ensures type safety and centralized management.
475
-
476
- **Define Keys (`src/common/environments.ts`):**
477
- ```typescript
478
- export class EnvironmentKeys {
479
- static readonly APP_ENV_STRIPE_KEY = 'APP_ENV_STRIPE_KEY';
480
- static readonly APP_ENV_MAX_RETRIES = 'APP_ENV_MAX_RETRIES';
481
- }
482
- ```
483
-
484
- **Usage:**
485
- ```typescript
486
- import { applicationEnvironment } from '@venizia/ignis';
487
- import { EnvironmentKeys } from '@/common/environments';
488
-
489
- // Correct usage
490
- const stripeKey = applicationEnvironment.get<string>(EnvironmentKeys.APP_ENV_STRIPE_KEY);
491
- const retries = applicationEnvironment.get<number>(EnvironmentKeys.APP_ENV_MAX_RETRIES);
492
- ```
493
-
494
708
  ## Standardized Error Handling
495
709
 
496
710
  Use the `getError` helper and `HTTP` constants to throw consistent, formatted exceptions that the framework's error handler can process correctly.
@@ -551,165 +765,24 @@ constructor(options: IServiceOptions) {
551
765
  | Not Found | `HTTP.ResultCodes.RS_4.NotFound` | Resource not found |
552
766
  | Internal Error | `HTTP.ResultCodes.RS_5.InternalServerError` | Server errors |
553
767
 
554
- ## Route Definition Patterns
555
-
556
- Ignis supports three methods for defining routes. Choose based on your needs:
557
-
558
- ### Method 1: Config-Driven Routes
559
-
560
- Define route configurations as constants:
561
-
562
- ```typescript
563
- // common/rest-paths.ts
564
- export class UserRestPaths {
565
- static readonly ROOT = '/';
566
- static readonly BY_ID = '/:id';
567
- static readonly PROFILE = '/profile';
568
- }
569
-
570
- // common/route-configs.ts
571
- export const ROUTE_CONFIGS = {
572
- [UserRestPaths.ROOT]: {
573
- method: HTTP.Methods.GET,
574
- path: UserRestPaths.ROOT,
575
- responses: jsonResponse({
576
- [HTTP.ResultCodes.RS_2.Ok]: UserListSchema,
577
- }),
578
- },
579
- [UserRestPaths.BY_ID]: {
580
- method: HTTP.Methods.GET,
581
- path: UserRestPaths.BY_ID,
582
- request: {
583
- params: z.object({ id: z.string().uuid() }),
584
- },
585
- responses: jsonResponse({
586
- [HTTP.ResultCodes.RS_2.Ok]: UserSchema,
587
- [HTTP.ResultCodes.RS_4.NotFound]: ErrorSchema,
588
- }),
589
- },
590
- } as const;
591
- ```
592
-
593
- ### Method 2: Using `@api` Decorator
594
-
595
- ```typescript
596
- @controller({ path: '/users' })
597
- export class UserController extends BaseController {
598
-
599
- @api({ configs: ROUTE_CONFIGS[UserRestPaths.ROOT] })
600
- list(context: TRouteContext<typeof ROUTE_CONFIGS[typeof UserRestPaths.ROOT]>) {
601
- return context.json({ users: [] }, HTTP.ResultCodes.RS_2.Ok);
602
- }
603
-
604
- @api({ configs: ROUTE_CONFIGS[UserRestPaths.BY_ID] })
605
- getById(context: TRouteContext<typeof ROUTE_CONFIGS[typeof UserRestPaths.BY_ID]>) {
606
- const { id } = context.req.valid('param');
607
- return context.json({ id, name: 'User' }, HTTP.ResultCodes.RS_2.Ok);
608
- }
609
- }
610
- ```
768
+ ## Scope Naming
611
769
 
612
- ### Method 3: Using `bindRoute` (Programmatic)
770
+ Every class extending a base class should set its scope using `ClassName.name`:
613
771
 
614
772
  ```typescript
615
- @controller({ path: '/health' })
616
- export class HealthCheckController extends BaseController {
773
+ export class JWTTokenService extends BaseService {
617
774
  constructor() {
618
- super({ scope: HealthCheckController.name });
619
-
620
- this.bindRoute({ configs: ROUTE_CONFIGS['/'] }).to({
621
- handler: context => context.json({ status: 'ok' }),
622
- });
775
+ super({ scope: JWTTokenService.name });
623
776
  }
624
777
  }
625
- ```
626
778
 
627
- ### Method 4: Using `defineRoute` (Inline)
628
-
629
- ```typescript
630
- @controller({ path: '/health' })
631
- export class HealthCheckController extends BaseController {
779
+ export class UserController extends BaseController {
632
780
  constructor() {
633
- super({ scope: HealthCheckController.name });
634
-
635
- this.defineRoute({
636
- configs: ROUTE_CONFIGS['/ping'],
637
- handler: context => {
638
- const { message } = context.req.valid('json');
639
- return context.json({ echo: message }, HTTP.ResultCodes.RS_2.Ok);
640
- },
641
- });
781
+ super({ scope: UserController.name });
642
782
  }
643
783
  }
644
784
  ```
645
785
 
646
- ### OpenAPI Schema Integration
647
-
648
- Use Zod with `.openapi()` for automatic documentation:
649
-
650
- ```typescript
651
- const CreateUserSchema = z.object({
652
- email: z.string().email(),
653
- name: z.string().min(1).max(100),
654
- }).openapi({
655
- description: 'Create user request body',
656
- example: { email: 'user@example.com', name: 'John Doe' },
657
- });
658
-
659
- const UserSchema = z.object({
660
- id: z.string().uuid(),
661
- email: z.string().email(),
662
- name: z.string(),
663
- createdAt: z.string().datetime(),
664
- }).openapi({
665
- description: 'User response',
666
- });
667
- ```
668
-
669
- ## Type Inference Patterns
670
-
671
- ### Zod Schema to Type
672
-
673
- ```typescript
674
- // Define schema
675
- export const SignInRequestSchema = z.object({
676
- email: z.string().email(),
677
- password: z.string().min(8),
678
- });
679
-
680
- // Infer type from schema
681
- export type TSignInRequest = z.infer<typeof SignInRequestSchema>;
682
- ```
683
-
684
- ### Const Assertion for Literal Types
685
-
686
- ```typescript
687
- const ROUTE_CONFIGS = {
688
- '/users': { method: 'GET', path: '/users' },
689
- '/users/:id': { method: 'GET', path: '/users/:id' },
690
- } as const;
691
-
692
- // Type is now narrowed to literal values
693
- type RouteKey = keyof typeof ROUTE_CONFIGS; // '/users' | '/users/:id'
694
- ```
695
-
696
- ### Generic Type Constraints
697
-
698
- ```typescript
699
- export class DefaultCRUDRepository<
700
- Schema extends TTableSchemaWithId = TTableSchemaWithId
701
- > {
702
- // Schema is constrained to have an 'id' column
703
- }
704
-
705
- export interface IAuthService<
706
- SIRQ extends TSignInRequest = TSignInRequest,
707
- SIRS = AnyObject,
708
- > {
709
- signIn(context: Context, opts: SIRQ): Promise<SIRS>;
710
- }
711
- ```
712
-
713
786
  ## Summary Table
714
787
 
715
788
  | Aspect | Standard |
@@ -724,5 +797,8 @@ export interface IAuthService<
724
797
  | Error format | `[ClassName][method] Message` |
725
798
  | Logging format | `[method] Message \| Key: %s` |
726
799
  | Default options | `DEFAULT_OPTIONS` constant |
800
+ | Type safety | No `any` or `unknown` allowed |
727
801
  | Scope naming | `ClassName.name` |
728
-
802
+ | Arguments | Options object (`opts`) |
803
+ | Exports | Named exports only |
804
+ | Return types | Explicitly defined |
@@ -40,17 +40,26 @@ Prevent blocking the event loop with Worker Threads:
40
40
  |-----------|---------|--------|
41
41
  | **Select specific fields** | `fields: { id: true, name: true }` | Reduce data transfer |
42
42
  | **Use indexes** | Create indexes on WHERE/JOIN columns | 10-100x faster queries |
43
+ | **Mandatory Limit** | `limit: 20` | Prevent fetching massive datasets |
43
44
  | **Paginate results** | `limit: 20, offset: 0` | Prevent memory overflow |
44
45
  | **Eager load relations** | `include: [{ relation: 'creator' }]` | Solve N+1 problem |
45
46
 
47
+ > [!TIP]
48
+ > **Avoid Deep Nesting:** While Ignis supports deeply nested `include` filters, each level adds significant overhead to query construction and result mapping. We strongly recommend a **maximum of 2 levels** (e.g., `User -> Orders -> Items`). For more complex data fetching, consider separate queries.
49
+
46
50
  **Example:**
47
51
  ```typescript
48
52
  await userRepository.find({
49
53
  filter: {
50
54
  fields: { id: true, name: true, email: true }, // ✅ Specific fields
51
55
  where: { status: 'ACTIVE' },
52
- limit: 20, // ✅ Pagination
53
- include: [{ relation: 'profile' }], // ✅ Eager load
56
+ limit: 20, // ✅ Mandatory limit
57
+ include: [{
58
+ relation: 'orders',
59
+ scope: {
60
+ include: [{ relation: 'items' }] // ✅ Level 2 (Recommended limit)
61
+ }
62
+ }],
54
63
  },
55
64
  });
56
65
  ```