@venizia/ignis-docs 0.0.1-9 → 0.0.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.
Files changed (34) hide show
  1. package/LICENSE.md +1 -0
  2. package/package.json +2 -2
  3. package/wiki/changelogs/{v0.0.1-7-initial-architecture.md → 2025-12-16-initial-architecture.md} +20 -12
  4. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +300 -0
  5. package/wiki/changelogs/2025-12-17-refactor.md +80 -12
  6. package/wiki/changelogs/2025-12-18-performance-optimizations.md +28 -90
  7. package/wiki/changelogs/2025-12-18-repository-validation-security.md +101 -297
  8. package/wiki/changelogs/index.md +20 -8
  9. package/wiki/changelogs/planned-schema-migrator.md +561 -0
  10. package/wiki/changelogs/planned-transaction-support.md +216 -0
  11. package/wiki/changelogs/template.md +123 -0
  12. package/wiki/get-started/best-practices/api-usage-examples.md +0 -2
  13. package/wiki/get-started/best-practices/architectural-patterns.md +2 -2
  14. package/wiki/get-started/best-practices/code-style-standards.md +575 -10
  15. package/wiki/get-started/best-practices/common-pitfalls.md +5 -3
  16. package/wiki/get-started/best-practices/contribution-workflow.md +2 -0
  17. package/wiki/get-started/best-practices/data-modeling.md +91 -34
  18. package/wiki/get-started/best-practices/security-guidelines.md +3 -1
  19. package/wiki/get-started/building-a-crud-api.md +3 -3
  20. package/wiki/get-started/core-concepts/application.md +72 -3
  21. package/wiki/get-started/core-concepts/bootstrapping.md +566 -0
  22. package/wiki/get-started/core-concepts/components.md +4 -2
  23. package/wiki/get-started/core-concepts/persistent.md +350 -378
  24. package/wiki/get-started/core-concepts/services.md +21 -27
  25. package/wiki/references/base/bootstrapping.md +789 -0
  26. package/wiki/references/base/components.md +1 -1
  27. package/wiki/references/base/dependency-injection.md +95 -2
  28. package/wiki/references/base/services.md +2 -2
  29. package/wiki/references/components/authentication.md +4 -3
  30. package/wiki/references/components/index.md +1 -1
  31. package/wiki/references/helpers/error.md +2 -2
  32. package/wiki/references/src-details/boot.md +379 -0
  33. package/wiki/references/src-details/core.md +2 -2
  34. package/wiki/changelogs/v0.0.1-8-model-repo-datasource-refactor.md +0 -278
@@ -15,7 +15,7 @@ This package provides:
15
15
  - **Prettier settings** - Consistent formatting across all Ignis projects
16
16
  - **TypeScript configs** - Shared base and common configurations
17
17
 
18
- ## Prettier Configuration
18
+ ### Prettier Configuration
19
19
 
20
20
  Automatic code formatting eliminates style debates.
21
21
 
@@ -50,9 +50,7 @@ bun run prettier:cli # Check formatting
50
50
  bun run prettier:fix # Auto-fix
51
51
  ```
52
52
 
53
- **IDE Integration:** Configure your editor to format on save.
54
-
55
- ## ESLint Configuration
53
+ ### ESLint Configuration
56
54
 
57
55
  Prevents common errors and enforces best practices.
58
56
 
@@ -88,9 +86,7 @@ bun run eslint --fix # Auto-fix issues
88
86
  bun run lint:fix # Run both ESLint + Prettier
89
87
  ```
90
88
 
91
- **Pre-commit Hook:** Add ESLint to your pre-commit hooks to catch issues before committing.
92
-
93
- ## TypeScript Configuration
89
+ ### TypeScript Configuration
94
90
 
95
91
  Use the centralized TypeScript configs:
96
92
 
@@ -121,6 +117,358 @@ Use the centralized TypeScript configs:
121
117
 
122
118
  See [`@venizia/dev-configs` documentation](../../references/src-details/dev-configs.md) for full details.
123
119
 
120
+ ## Naming Conventions
121
+
122
+ ### Class Names
123
+
124
+ | Type | Pattern | Example |
125
+ |------|---------|---------|
126
+ | Components | `[Feature]Component` | `HealthCheckComponent`, `AuthComponent` |
127
+ | Controllers | `[Feature]Controller` | `UserController`, `AuthController` |
128
+ | Services | `[Feature]Service` | `JWTTokenService`, `PaymentService` |
129
+ | Repositories | `[Feature]Repository` | `UserRepository`, `OrderRepository` |
130
+ | Strategies | `[Feature]Strategy` | `JWTAuthenticationStrategy` |
131
+ | Factories | `[Feature]Factory` | `UIProviderFactory` |
132
+
133
+ ### File Names
134
+
135
+ Both styles are acceptable: `[type].ts` or `[name].[type].ts`
136
+
137
+ | Type | Single File | Multiple Files |
138
+ |------|-------------|----------------|
139
+ | Components | `component.ts` | `auth.component.ts` |
140
+ | Controllers | `controller.ts` | `user.controller.ts` |
141
+ | Services | `service.ts` | `jwt-token.service.ts` |
142
+ | Repositories | `repository.ts` | `user.repository.ts` |
143
+ | Types/Interfaces | `types.ts` | `user.types.ts` |
144
+ | Constants | `constants.ts` | `keys.ts`, `rest-paths.ts` |
145
+ | Schemas | `schema.ts` | `sign-in.schema.ts` |
146
+
147
+ **Guidelines:**
148
+ - Use `[type].ts` when there's only one file of that type in the folder
149
+ - Use `[name].[type].ts` when there are multiple files of the same type
150
+ - Use kebab-case for multi-word names: `jwt-token.service.ts`
151
+
152
+ ### Type and Interface Prefixes
153
+
154
+ ```typescript
155
+ // Interfaces use 'I' prefix
156
+ interface IHealthCheckOptions {
157
+ restOptions: { path: string };
158
+ }
159
+
160
+ interface IAuthService {
161
+ signIn(context: Context): Promise<void>;
162
+ }
163
+
164
+ // Type aliases use 'T' prefix
165
+ type TSignInRequest = z.infer<typeof SignInRequestSchema>;
166
+ type TRouteContext = Context<Env, Path, Input>;
167
+
168
+ // Generic constraints
169
+ type TTableSchemaWithId = { id: PgColumn };
170
+ ```
171
+
172
+ ### Binding Keys
173
+
174
+ Use static class with `@app/[component]/[feature]` format:
175
+
176
+ ```typescript
177
+ export class HealthCheckBindingKeys {
178
+ static readonly HEALTH_CHECK_OPTIONS = '@app/health-check/options';
179
+ }
180
+
181
+ export class SocketIOBindingKeys {
182
+ static readonly SOCKET_IO_INSTANCE = '@app/socket-io/instance';
183
+ static readonly SERVER_OPTIONS = '@app/socket-io/server-options';
184
+ }
185
+ ```
186
+
187
+ ## Directory Structure
188
+
189
+ ### Component Organization
190
+
191
+ ```
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
201
+ ```
202
+
203
+ ### Complex Component (with multiple features)
204
+
205
+ ```
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
218
+ ```
219
+
220
+ ### Barrel Exports
221
+
222
+ Every folder should have an `index.ts` that re-exports its contents:
223
+
224
+ ```typescript
225
+ // components/health-check/index.ts
226
+ export * from './common';
227
+ export * from './component';
228
+ export * from './controller';
229
+
230
+ // components/health-check/common/index.ts
231
+ export * from './keys';
232
+ export * from './rest-paths';
233
+ export * from './types';
234
+ ```
235
+
236
+ ## Constants Pattern
237
+
238
+ **Prefer static classes over enums** for better tree-shaking and extensibility.
239
+
240
+ ### Basic Constants
241
+
242
+ ```typescript
243
+ export class Authentication {
244
+ static readonly STRATEGY_BASIC = 'basic';
245
+ static readonly STRATEGY_JWT = 'jwt';
246
+ static readonly TYPE_BEARER = 'Bearer';
247
+ }
248
+
249
+ export class HealthCheckRestPaths {
250
+ static readonly ROOT = '/';
251
+ static readonly PING = '/ping';
252
+ static readonly METRICS = '/metrics';
253
+ }
254
+ ```
255
+
256
+ ### Typed Constants with Validation
257
+
258
+ For constants that need type extraction and runtime validation, use this pattern:
259
+
260
+ ```typescript
261
+ import { TConstValue } from '@venizia/ignis-helpers';
262
+
263
+ export class DocumentUITypes {
264
+ // 1. Define static readonly values
265
+ static readonly SWAGGER = 'swagger';
266
+ static readonly SCALAR = 'scalar';
267
+
268
+ // 2. Create a Set for O(1) validation lookup
269
+ static readonly SCHEME_SET = new Set([this.SWAGGER, this.SCALAR]);
270
+
271
+ // 3. Validation helper method
272
+ static isValid(value: string): boolean {
273
+ return this.SCHEME_SET.has(value);
274
+ }
275
+ }
276
+
277
+ // 4. Extract union type from class values
278
+ export type TDocumentUIType = TConstValue<typeof DocumentUITypes>;
279
+ // Result: 'swagger' | 'scalar'
280
+ ```
281
+
282
+ **Full Example with Usage:**
283
+
284
+ ```typescript
285
+ import { TConstValue } from '@venizia/ignis-helpers';
286
+
287
+ export class UserStatuses {
288
+ static readonly ACTIVE = 'active';
289
+ static readonly INACTIVE = 'inactive';
290
+ static readonly PENDING = 'pending';
291
+ static readonly BANNED = 'banned';
292
+
293
+ static readonly SCHEME_SET = new Set([
294
+ this.ACTIVE,
295
+ this.INACTIVE,
296
+ this.PENDING,
297
+ this.BANNED,
298
+ ]);
299
+
300
+ static isValid(value: string): boolean {
301
+ return this.SCHEME_SET.has(value);
302
+ }
303
+
304
+ // Optional: get all values as array
305
+ static values(): string[] {
306
+ return [...this.SCHEME_SET];
307
+ }
308
+ }
309
+
310
+ // Type-safe union type
311
+ export type TUserStatus = TConstValue<typeof UserStatuses>;
312
+ // Result: 'active' | 'inactive' | 'pending' | 'banned'
313
+
314
+ // Usage in interfaces
315
+ interface IUser {
316
+ id: string;
317
+ status: TUserStatus; // Type-safe!
318
+ }
319
+
320
+ // Usage with validation
321
+ function updateUserStatus(userId: string, status: string) {
322
+ if (!UserStatuses.isValid(status)) {
323
+ throw getError({
324
+ statusCode: HTTP.ResultCodes.RS_4.BadRequest,
325
+ message: `Invalid status: ${status}. Valid: ${UserStatuses.values().join(', ')}`,
326
+ });
327
+ }
328
+ // status is validated at runtime
329
+ }
330
+ ```
331
+
332
+ ### Enum vs Static Class Comparison
333
+
334
+ | Aspect | Static Class | TypeScript Enum |
335
+ |--------|--------------|-----------------|
336
+ | Tree-shaking | Full support | Partial (IIFE blocks it) |
337
+ | Bundle size | Minimal | Larger (IIFE wrapper) |
338
+ | Runtime validation | O(1) with `Set` | O(n) with `Object.values()` |
339
+ | Type extraction | `TConstValue<typeof X>` → values | `keyof typeof X` → keys (not values!) |
340
+ | Add methods | Yes | Not possible |
341
+ | Compiled output | Clean class | IIFE wrapper |
342
+
343
+ **Compiled JavaScript:**
344
+
345
+ ```typescript
346
+ // Enum compiles to IIFE (not tree-shakable)
347
+ var UserStatus;
348
+ (function (UserStatus) {
349
+ UserStatus["ACTIVE"] = "active";
350
+ })(UserStatus || (UserStatus = {}));
351
+
352
+ // Static class compiles cleanly
353
+ class UserStatuses { }
354
+ UserStatuses.ACTIVE = 'active';
355
+ ```
356
+
357
+ **Type Extraction Difference:**
358
+
359
+ ```typescript
360
+ // Enum - extracts KEYS
361
+ type T = keyof typeof UserStatus; // 'ACTIVE' | 'INACTIVE'
362
+
363
+ // Static Class - extracts VALUES
364
+ type T = TConstValue<typeof UserStatuses>; // 'active' | 'inactive'
365
+ ```
366
+
367
+ **When to use `const enum`:** Only for numeric flags with no iteration needed (values are inlined, zero runtime). But doesn't work with `--isolatedModules`.
368
+
369
+ **Verdict:** Use Static Class for 90% of cases - better tree-shaking, easy validation, type-safe values, extensible with methods.
370
+
371
+ ## Configuration Patterns
372
+
373
+ ### Default Options
374
+
375
+ Every configurable class should define `DEFAULT_OPTIONS`:
376
+
377
+ ```typescript
378
+ const DEFAULT_OPTIONS: IHealthCheckOptions = {
379
+ restOptions: { path: '/health' },
380
+ };
381
+
382
+ const DEFAULT_SERVER_OPTIONS: Partial<IServerOptions> = {
383
+ identifier: 'SOCKET_IO_SERVER',
384
+ path: '/io',
385
+ cors: {
386
+ origin: '*',
387
+ methods: ['GET', 'POST'],
388
+ },
389
+ };
390
+ ```
391
+
392
+ ### Option Merging
393
+
394
+ ```typescript
395
+ // In component constructor or binding
396
+ const extraOptions = this.application.get<Partial<IServerOptions>>({
397
+ key: BindingKeys.SERVER_OPTIONS,
398
+ isOptional: true,
399
+ }) ?? {};
400
+
401
+ this.options = Object.assign({}, DEFAULT_OPTIONS, extraOptions);
402
+ ```
403
+
404
+ ### Constructor Validation
405
+
406
+ Validate required options in the constructor:
407
+
408
+ ```typescript
409
+ constructor(options: IJWTTokenServiceOptions) {
410
+ super({ scope: JWTTokenService.name });
411
+
412
+ if (!options.jwtSecret) {
413
+ throw getError({
414
+ statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
415
+ message: '[JWTTokenService] Invalid jwtSecret',
416
+ });
417
+ }
418
+
419
+ if (!options.applicationSecret) {
420
+ throw getError({
421
+ statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
422
+ message: '[JWTTokenService] Invalid applicationSecret',
423
+ });
424
+ }
425
+
426
+ this.options = options;
427
+ }
428
+ ```
429
+
430
+ ## Logging Patterns
431
+
432
+ ### Method Context Prefix
433
+
434
+ Always include class and method context in log messages:
435
+
436
+ ```typescript
437
+ // Format: [ClassName][methodName] Message with %s placeholders
438
+ this.logger.info('[binding] Asset storage bound | Key: %s | Type: %s', key, storageType);
439
+ this.logger.debug('[authenticate] Token validated | User: %s', userId);
440
+ this.logger.warn('[register] Skipping duplicate registration | Type: %s', opts.type);
441
+ this.logger.error('[generate] Token generation failed | Error: %s', error.message);
442
+ ```
443
+
444
+ ### Structured Data
445
+
446
+ Use format specifiers for structured logging:
447
+
448
+ ```typescript
449
+ // %s - string, %d - number, %j - JSON object
450
+ this.logger.info('[create] User created | ID: %s | Email: %s', user.id, user.email);
451
+ this.logger.debug('[config] Server options: %j', this.serverOptions);
452
+ ```
453
+
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
+
124
472
  ## Environment Variables Management
125
473
 
126
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.
@@ -147,7 +495,8 @@ const retries = applicationEnvironment.get<number>(EnvironmentKeys.APP_ENV_MAX_R
147
495
 
148
496
  Use the `getError` helper and `HTTP` constants to throw consistent, formatted exceptions that the framework's error handler can process correctly.
149
497
 
150
- **Example:**
498
+ ### Basic Error
499
+
151
500
  ```typescript
152
501
  import { getError, HTTP } from '@venizia/ignis';
153
502
 
@@ -155,9 +504,225 @@ if (!record) {
155
504
  throw getError({
156
505
  statusCode: HTTP.ResultCodes.RS_4.NotFound,
157
506
  message: 'Record not found',
158
- // Optional details
159
- details: { id: requestedId }
507
+ details: { id: requestedId },
160
508
  });
161
509
  }
162
510
  ```
163
511
 
512
+ ### Error with Context
513
+
514
+ Include class/method context in error messages:
515
+
516
+ ```typescript
517
+ // Format: [ClassName][methodName] Descriptive message
518
+ throw getError({
519
+ statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
520
+ message: '[JWTTokenService][generate] Failed to generate token',
521
+ });
522
+
523
+ throw getError({
524
+ statusCode: HTTP.ResultCodes.RS_4.Unauthorized,
525
+ message: '[AuthMiddleware][authenticate] Missing authorization header',
526
+ });
527
+ ```
528
+
529
+ ### Validation Errors
530
+
531
+ ```typescript
532
+ constructor(options: IServiceOptions) {
533
+ if (!options.apiKey) {
534
+ throw getError({
535
+ statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
536
+ message: '[PaymentService] Missing required apiKey configuration',
537
+ });
538
+ }
539
+ }
540
+ ```
541
+
542
+ ### HTTP Status Code Categories
543
+
544
+ | Category | Constant | Use Case |
545
+ |----------|----------|----------|
546
+ | Success | `HTTP.ResultCodes.RS_2.Ok` | Successful response |
547
+ | Created | `HTTP.ResultCodes.RS_2.Created` | Resource created |
548
+ | Bad Request | `HTTP.ResultCodes.RS_4.BadRequest` | Invalid input |
549
+ | Unauthorized | `HTTP.ResultCodes.RS_4.Unauthorized` | Missing/invalid auth |
550
+ | Forbidden | `HTTP.ResultCodes.RS_4.Forbidden` | Insufficient permissions |
551
+ | Not Found | `HTTP.ResultCodes.RS_4.NotFound` | Resource not found |
552
+ | Internal Error | `HTTP.ResultCodes.RS_5.InternalServerError` | Server errors |
553
+
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
+ ```
611
+
612
+ ### Method 3: Using `bindRoute` (Programmatic)
613
+
614
+ ```typescript
615
+ @controller({ path: '/health' })
616
+ export class HealthCheckController extends BaseController {
617
+ constructor() {
618
+ super({ scope: HealthCheckController.name });
619
+
620
+ this.bindRoute({ configs: ROUTE_CONFIGS['/'] }).to({
621
+ handler: context => context.json({ status: 'ok' }),
622
+ });
623
+ }
624
+ }
625
+ ```
626
+
627
+ ### Method 4: Using `defineRoute` (Inline)
628
+
629
+ ```typescript
630
+ @controller({ path: '/health' })
631
+ export class HealthCheckController extends BaseController {
632
+ 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
+ });
642
+ }
643
+ }
644
+ ```
645
+
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
+ ## Summary Table
714
+
715
+ | Aspect | Standard |
716
+ |--------|----------|
717
+ | Interface prefix | `I` (e.g., `IUserService`) |
718
+ | Type alias prefix | `T` (e.g., `TUserRequest`) |
719
+ | Class naming | PascalCase with suffix (e.g., `UserController`) |
720
+ | File naming | kebab-case (e.g., `user.controller.ts`) |
721
+ | Binding keys | `@app/[component]/[feature]` |
722
+ | Constants | Static readonly class (not enums) |
723
+ | Barrel exports | `index.ts` at every folder level |
724
+ | Error format | `[ClassName][method] Message` |
725
+ | Logging format | `[method] Message \| Key: %s` |
726
+ | Default options | `DEFAULT_OPTIONS` constant |
727
+ | Scope naming | `ClassName.name` |
728
+
@@ -66,6 +66,8 @@ export class Application extends BaseApplication {
66
66
 
67
67
  - **Bad:**
68
68
  ```typescript
69
+ import { ApplicationError, getError } from '@venizia/ignis';
70
+
69
71
  // In a Controller
70
72
  async createUser(c: Context) {
71
73
  const { name, email, companyName } = c.req.valid('json');
@@ -73,7 +75,7 @@ export class Application extends BaseApplication {
73
75
  // Complex logic inside the controller
74
76
  const existingUser = await this.userRepository.findByEmail(email);
75
77
  if (existingUser) {
76
- throw new ApplicationError({ message: 'Email already exists' });
78
+ throw getError({ message: 'Email already exists' });
77
79
  }
78
80
 
79
81
  const company = await this.companyRepository.findOrCreate(companyName);
@@ -101,7 +103,7 @@ export class Application extends BaseApplication {
101
103
  }
102
104
  ```
103
105
 
104
- ### 4. Missing Environment Variables
106
+ ## 4. Missing Environment Variables
105
107
 
106
108
  **Pitfall:** The application fails to start or behaves unexpectedly because required environment variables are not defined in your `.env` file. The framework validates variables prefixed with `APP_ENV_` by default.
107
109
 
@@ -121,7 +123,7 @@ APP_ENV_POSTGRES_PASSWORD=password
121
123
  APP_ENV_POSTGRES_DATABASE=db
122
124
  ```
123
125
 
124
- ### 5. Not Using `as const` for Route Definitions
126
+ ## 5. Not Using `as const` for Route Definitions
125
127
 
126
128
  **Pitfall:** When using the decorator-based routing with a shared `ROUTE_CONFIGS` object, you forget to add `as const` to the object definition. TypeScript will infer the types too broadly, and you will lose the benefits of type-safe contexts (`TRouteContext`).
127
129
 
@@ -56,6 +56,7 @@ The project uses a Makefile for common development tasks:
56
56
  **Individual package builds:**
57
57
  ```bash
58
58
  make core # Build @venizia/ignis (after dependencies)
59
+ make boot # Build @venizia/ignis-boot
59
60
  make helpers # Build @venizia/ignis-helpers
60
61
  make inversion # Build @venizia/ignis-inversion
61
62
  make dev-configs # Build @venizia/dev-configs
@@ -65,6 +66,7 @@ make docs # Build documentation
65
66
  **Force update individual packages:**
66
67
  ```bash
67
68
  make update-core
69
+ make update-boot
68
70
  make update-helpers
69
71
  make update-inversion
70
72
  make update-dev-configs