@venizia/ignis-docs 0.0.2 → 0.0.4-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/wiki/best-practices/api-usage-examples.md +591 -0
  4. package/wiki/best-practices/architectural-patterns.md +415 -0
  5. package/wiki/best-practices/architecture-decisions.md +488 -0
  6. package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +647 -182
  7. package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
  8. package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
  9. package/wiki/best-practices/data-modeling.md +376 -0
  10. package/wiki/best-practices/deployment-strategies.md +698 -0
  11. package/wiki/best-practices/index.md +27 -0
  12. package/wiki/best-practices/performance-optimization.md +196 -0
  13. package/wiki/best-practices/security-guidelines.md +218 -0
  14. package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
  15. package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
  16. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
  17. package/wiki/changelogs/2025-12-17-refactor.md +1 -1
  18. package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
  19. package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
  20. package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +86 -0
  21. package/wiki/changelogs/2025-12-26-transaction-support.md +57 -0
  22. package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
  23. package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
  24. package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
  25. package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
  26. package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
  27. package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
  28. package/wiki/changelogs/index.md +8 -1
  29. package/wiki/changelogs/planned-schema-migrator.md +2 -10
  30. package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
  31. package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
  32. package/wiki/guides/core-concepts/components-guide.md +509 -0
  33. package/wiki/guides/core-concepts/components.md +122 -0
  34. package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
  35. package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
  36. package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
  37. package/wiki/guides/core-concepts/persistent/index.md +119 -0
  38. package/wiki/guides/core-concepts/persistent/models.md +241 -0
  39. package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
  40. package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
  41. package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
  42. package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
  43. package/wiki/guides/get-started/philosophy.md +682 -0
  44. package/wiki/guides/get-started/setup.md +157 -0
  45. package/wiki/guides/index.md +89 -0
  46. package/wiki/guides/reference/glossary.md +243 -0
  47. package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
  48. package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
  49. package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
  50. package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
  51. package/wiki/guides/tutorials/realtime-chat.md +1261 -0
  52. package/wiki/guides/tutorials/testing.md +723 -0
  53. package/wiki/index.md +176 -37
  54. package/wiki/references/base/application.md +27 -0
  55. package/wiki/references/base/bootstrapping.md +30 -26
  56. package/wiki/references/base/components.md +532 -31
  57. package/wiki/references/base/controllers.md +136 -38
  58. package/wiki/references/base/datasources.md +108 -5
  59. package/wiki/references/base/dependency-injection.md +39 -3
  60. package/wiki/references/base/filter-system/application-usage.md +224 -0
  61. package/wiki/references/base/filter-system/array-operators.md +132 -0
  62. package/wiki/references/base/filter-system/comparison-operators.md +109 -0
  63. package/wiki/references/base/filter-system/default-filter.md +428 -0
  64. package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
  65. package/wiki/references/base/filter-system/index.md +127 -0
  66. package/wiki/references/base/filter-system/json-filtering.md +197 -0
  67. package/wiki/references/base/filter-system/list-operators.md +71 -0
  68. package/wiki/references/base/filter-system/logical-operators.md +156 -0
  69. package/wiki/references/base/filter-system/null-operators.md +58 -0
  70. package/wiki/references/base/filter-system/pattern-matching.md +108 -0
  71. package/wiki/references/base/filter-system/quick-reference.md +431 -0
  72. package/wiki/references/base/filter-system/range-operators.md +63 -0
  73. package/wiki/references/base/filter-system/tips.md +190 -0
  74. package/wiki/references/base/filter-system/use-cases.md +452 -0
  75. package/wiki/references/base/index.md +90 -0
  76. package/wiki/references/base/middlewares.md +602 -0
  77. package/wiki/references/base/models.md +215 -23
  78. package/wiki/references/base/providers.md +732 -0
  79. package/wiki/references/base/repositories/advanced.md +555 -0
  80. package/wiki/references/base/repositories/index.md +228 -0
  81. package/wiki/references/base/repositories/mixins.md +331 -0
  82. package/wiki/references/base/repositories/relations.md +486 -0
  83. package/wiki/references/base/repositories.md +40 -549
  84. package/wiki/references/base/services.md +28 -4
  85. package/wiki/references/components/authentication.md +22 -2
  86. package/wiki/references/components/health-check.md +12 -0
  87. package/wiki/references/components/index.md +23 -0
  88. package/wiki/references/components/mail.md +687 -0
  89. package/wiki/references/components/request-tracker.md +16 -0
  90. package/wiki/references/components/socket-io.md +18 -0
  91. package/wiki/references/components/static-asset.md +14 -26
  92. package/wiki/references/components/swagger.md +17 -0
  93. package/wiki/references/configuration/environment-variables.md +427 -0
  94. package/wiki/references/configuration/index.md +73 -0
  95. package/wiki/references/helpers/cron.md +14 -0
  96. package/wiki/references/helpers/crypto.md +15 -0
  97. package/wiki/references/helpers/env.md +16 -0
  98. package/wiki/references/helpers/error.md +17 -0
  99. package/wiki/references/helpers/index.md +15 -0
  100. package/wiki/references/helpers/inversion.md +24 -4
  101. package/wiki/references/helpers/logger.md +19 -0
  102. package/wiki/references/helpers/network.md +11 -0
  103. package/wiki/references/helpers/queue.md +19 -0
  104. package/wiki/references/helpers/redis.md +21 -0
  105. package/wiki/references/helpers/socket-io.md +24 -5
  106. package/wiki/references/helpers/storage.md +18 -10
  107. package/wiki/references/helpers/testing.md +18 -0
  108. package/wiki/references/helpers/types.md +167 -0
  109. package/wiki/references/helpers/uid.md +167 -0
  110. package/wiki/references/helpers/worker-thread.md +16 -0
  111. package/wiki/references/index.md +177 -0
  112. package/wiki/references/quick-reference.md +634 -0
  113. package/wiki/references/src-details/boot.md +3 -3
  114. package/wiki/references/src-details/dev-configs.md +0 -4
  115. package/wiki/references/src-details/docs.md +2 -2
  116. package/wiki/references/src-details/index.md +86 -0
  117. package/wiki/references/src-details/inversion.md +1 -6
  118. package/wiki/references/src-details/mcp-server.md +3 -15
  119. package/wiki/references/utilities/index.md +86 -10
  120. package/wiki/references/utilities/jsx.md +577 -0
  121. package/wiki/references/utilities/request.md +0 -2
  122. package/wiki/references/utilities/statuses.md +740 -0
  123. package/wiki/changelogs/planned-transaction-support.md +0 -216
  124. package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
  125. package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
  126. package/wiki/get-started/best-practices/data-modeling.md +0 -177
  127. package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
  128. package/wiki/get-started/best-practices/performance-optimization.md +0 -88
  129. package/wiki/get-started/best-practices/security-guidelines.md +0 -99
  130. package/wiki/get-started/core-concepts/components.md +0 -98
  131. package/wiki/get-started/core-concepts/persistent.md +0 -543
  132. package/wiki/get-started/index.md +0 -65
  133. package/wiki/get-started/philosophy.md +0 -296
  134. package/wiki/get-started/prerequisites.md +0 -113
@@ -115,7 +115,56 @@ Use the centralized TypeScript configs:
115
115
  - `strict: true` - Strict type checking with selective relaxations
116
116
  - `skipLibCheck: true` - Faster compilation
117
117
 
118
- See [`@venizia/dev-configs` documentation](../../references/src-details/dev-configs.md) for full details.
118
+ See [`@venizia/dev-configs` documentation](../references/src-details/dev-configs.md) for full details.
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
+ ```
119
168
 
120
169
  ## Naming Conventions
121
170
 
@@ -184,53 +233,357 @@ export class SocketIOBindingKeys {
184
233
  }
185
234
  ```
186
235
 
187
- ## Directory Structure
236
+ ## Private Field Naming Convention
188
237
 
189
- ### Component Organization
238
+ Use underscore prefix (`_`) for private and protected class fields to distinguish them from public fields and method parameters.
190
239
 
240
+ ```typescript
241
+ class MyRepository extends BaseRepository {
242
+ // Private fields with underscore prefix
243
+ private _dataSource: IDataSource;
244
+ private _entity: BaseEntity;
245
+ private _hiddenProperties: Set<string> | null = null;
246
+
247
+ // Protected fields also use underscore prefix
248
+ protected _schemaFactory?: ReturnType<typeof createSchemaFactory>;
249
+
250
+ constructor(dataSource: IDataSource) {
251
+ // 'dataSource' (param) vs '_dataSource' (field)
252
+ this._dataSource = dataSource;
253
+ }
254
+ }
191
255
  ```
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
256
+
257
+ **Benefits:**
258
+ - Clear distinction between fields and parameters
259
+ - Avoids naming conflicts in constructors
260
+ - Consistent with TypeScript community conventions
261
+
262
+ ## Sentinel Value Pattern for Caching
263
+
264
+ Use `null` to distinguish "not computed" from "computed as undefined" for lazy-initialized cached values.
265
+
266
+ ```typescript
267
+ class Repository {
268
+ // null = not computed yet, undefined = computed but no value
269
+ private _visibleProperties: Record<string, any> | null | undefined = null;
270
+
271
+ get visibleProperties(): Record<string, any> | undefined {
272
+ if (this._visibleProperties !== null) {
273
+ return this._visibleProperties;
274
+ }
275
+ // Compute once and cache (may be undefined)
276
+ this._visibleProperties = this.computeVisibleProperties();
277
+ return this._visibleProperties;
278
+ }
279
+ }
201
280
  ```
202
281
 
203
- ### Complex Component (with multiple features)
282
+ **Why not just `undefined`?**
283
+ - `undefined` can be a valid computed result
284
+ - `null` clearly indicates "never computed"
285
+ - Prevents redundant re-computation
286
+
287
+ ## Type Safety
288
+
289
+ To ensure long-term maintainability and catch errors at compile-time, Ignis enforces strict type safety.
290
+
291
+ ### Avoid `any` and `unknown`
292
+
293
+ **Never use `any` or `unknown` as much as possible.** You must specify clear, descriptive types for all variables, parameters, and return values.
294
+
295
+ - **`any`**: Bypasses all type checking and leads to "runtime surprises". It is strictly discouraged.
296
+ - **`unknown`**: While safer than `any`, it still forces consumers to perform manual type checking. Prefer using generics or specific interfaces.
204
297
 
298
+ **Why?**
299
+ - **Maintenance**: Developers reading your code in the future will know exactly what the data structure is.
300
+ - **Refactoring**: Changing an interface automatically highlights all broken code across the monorepo.
301
+ - **Documentation**: Types act as a self-documenting contract for your APIs and services.
302
+
303
+ ## Type Definitions
304
+
305
+ ### Explicit Return Types
306
+ Always define explicit return types for **public methods** and **API handlers**.
307
+
308
+ **Why?**
309
+ - **Compiler Performance:** Speeds up TypeScript type checking in large projects.
310
+ - **Safety:** Prevents accidental exposure of internal types or sensitive data.
311
+
312
+ ```typescript
313
+ // ✅ GOOD
314
+ public async findUser(id: string): Promise<User | null> { ... }
315
+
316
+ // ❌ BAD (Implicit inference)
317
+ public async findUser(id: string) { ... }
205
318
  ```
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
319
+
320
+ ## Type Inference Patterns
321
+
322
+ ### Zod Schema to Type
323
+
324
+ ```typescript
325
+ // Define schema
326
+ export const SignInRequestSchema = z.object({
327
+ email: z.string().email(),
328
+ password: z.string().min(8),
329
+ });
330
+
331
+ // Infer type from schema
332
+ export type TSignInRequest = z.infer<typeof SignInRequestSchema>;
218
333
  ```
219
334
 
220
- ### Barrel Exports
335
+ ### Const Assertion for Literal Types
221
336
 
222
- Every folder should have an `index.ts` that re-exports its contents:
337
+ ```typescript
338
+ const RouteConfigs = {
339
+ GET_USERS: { method: 'GET', path: '/users' },
340
+ GET_USER_BY_ID: { method: 'GET', path: '/users/:id' },
341
+ } as const;
342
+
343
+ // Type is now narrowed to literal values
344
+ type RouteKey = keyof typeof RouteConfigs; // 'GET_USERS' | 'GET_USER_BY_ID'
345
+ ```
346
+
347
+ ### Generic Type Constraints
223
348
 
224
349
  ```typescript
225
- // components/health-check/index.ts
226
- export * from './common';
227
- export * from './component';
228
- export * from './controller';
350
+ export class DefaultCRUDRepository<
351
+ Schema extends TTableSchemaWithId = TTableSchemaWithId
352
+ > {
353
+ // Schema is constrained to have an 'id' column
354
+ }
229
355
 
230
- // components/health-check/common/index.ts
231
- export * from './keys';
232
- export * from './rest-paths';
233
- export * from './types';
356
+ export interface IAuthService<
357
+ SIRQ extends TSignInRequest = TSignInRequest,
358
+ SIRS = AnyObject,
359
+ > {
360
+ signIn(context: Context, opts: SIRQ): Promise<SIRS>;
361
+ }
362
+ ```
363
+
364
+ ### Method Overloading for Conditional Returns
365
+
366
+ Use TypeScript method overloads when return types depend on input options:
367
+
368
+ ```typescript
369
+ class Repository<T, R> {
370
+ // Overload 1: shouldReturn: false → data is null
371
+ create(opts: { data: T; options: { shouldReturn: false } }): Promise<{ count: number; data: null }>;
372
+ // Overload 2: shouldReturn: true (default) → data is R
373
+ create(opts: { data: T; options?: { shouldReturn?: true } }): Promise<{ count: number; data: R }>;
374
+ // Implementation signature
375
+ create(opts: { data: T; options?: { shouldReturn?: boolean } }): Promise<{ count: number; data: R | null }> {
376
+ // implementation
377
+ }
378
+ }
379
+
380
+ // Usage
381
+ const result1 = await repo.create({ data: user, options: { shouldReturn: false } });
382
+ // result1.data is typed as null
383
+
384
+ const result2 = await repo.create({ data: user });
385
+ // result2.data is typed as R (the entity type)
386
+ ```
387
+
388
+ **When to use:**
389
+ - Return type varies based on boolean flag
390
+ - API with optional "return data" behavior
391
+ - Methods with conditional processing
392
+
393
+ ## Module Exports
394
+
395
+ ### Prefer Named Exports
396
+ 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.
397
+
398
+ **Why?**
399
+ - **Refactoring:** Renaming a symbol automatically updates imports across the monorepo.
400
+ - **Consistency:** Enforces consistent naming across all files importing the module.
401
+
402
+ ```typescript
403
+ // ✅ GOOD
404
+ export class UserController { ... }
405
+
406
+ // ❌ BAD
407
+ export default class UserController { ... }
408
+ ```
409
+
410
+ ## Function Signatures
411
+
412
+ ### The Options Object Pattern
413
+ Prefer using a single object parameter (`opts`) over multiple positional arguments, especially for constructors and public methods with more than 2 arguments.
414
+
415
+ **Why?**
416
+ - **Extensibility:** You can add new properties without breaking existing calls.
417
+ - **Readability:** Named keys act as documentation at the call site.
418
+
419
+ ```typescript
420
+ // ✅ GOOD
421
+ class UserService {
422
+ createUser(opts: { name: string; email: string; role?: string }) { ... }
423
+ }
424
+ // Usage: service.createUser({ name: 'John', email: 'john@example.com' });
425
+
426
+ // ❌ BAD
427
+ class UserService {
428
+ createUser(name: string, email: string, role?: string) { ... }
429
+ }
430
+ // Usage: service.createUser('John', 'john@example.com');
431
+ ```
432
+
433
+ ## Function Naming Conventions
434
+
435
+ Use consistent prefixes based on function purpose:
436
+
437
+ | Prefix | Purpose | Examples |
438
+ |--------|---------|----------|
439
+ | `generate*` | Create column definitions / schemas | `generateIdColumnDefs()`, `generateTzColumnDefs()` |
440
+ | `build*` | Construct complex objects | `buildPrimitiveCondition()`, `buildJsonOrderBy()` |
441
+ | `to*` | Convert/transform data | `toCamel()`, `toBoolean()`, `toStringDecimal()` |
442
+ | `is*` | Boolean validation/check | `isWeekday()`, `isInt()`, `isFloat()`, `isPromiseLike()` |
443
+ | `extract*` | Pull out specific parts | `extractTimestamp()`, `extractWorkerId()`, `extractSequence()` |
444
+ | `enrich*` | Enhance with additional data | `enrichUserAudit()`, `enrichWithMetadata()` |
445
+ | `get*` | Retrieve/fetch data | `getSchema()`, `getConnector()`, `getError()` |
446
+ | `resolve*` | Determine/compute value | `resolveValue()`, `resolvePath()` |
447
+
448
+ **Examples:**
449
+
450
+ ```typescript
451
+ // Generators - create schema definitions
452
+ const idCols = generateIdColumnDefs({ id: { dataType: 'string' } });
453
+ const tzCols = generateTzColumnDefs();
454
+
455
+ // Builders - construct complex query objects
456
+ const condition = buildPrimitiveCondition(column, operator, value);
457
+ const orderBy = buildJsonOrderBy(schema, path, direction);
458
+
459
+ // Converters - transform data types
460
+ const camelCase = toCamel('snake_case');
461
+ const bool = toBoolean('true');
462
+ const decimal = toStringDecimal(123.456, 2);
463
+
464
+ // Validators - boolean checks
465
+ if (isWeekday(date)) { /* ... */ }
466
+ if (isInt(value)) { /* ... */ }
467
+ if (isPromiseLike(result)) { /* ... */ }
468
+
469
+ // Extractors - pull specific data
470
+ const timestamp = extractTimestamp(snowflakeId);
471
+ const workerId = extractWorkerId(snowflakeId);
472
+ ```
473
+
474
+ ## Route Definition Patterns
475
+
476
+ Ignis supports three methods for defining routes. Choose based on your needs:
477
+
478
+ ### Method 1: Config-Driven Routes
479
+
480
+ Define route configurations as constants with UPPER_CASE names:
481
+
482
+ ```typescript
483
+ // common/rest-paths.ts
484
+ export class UserRestPaths {
485
+ static readonly ROOT = '/';
486
+ static readonly BY_ID = '/:id';
487
+ static readonly PROFILE = '/profile';
488
+ }
489
+
490
+ // common/route-configs.ts
491
+ export const RouteConfigs = {
492
+ GET_USERS: {
493
+ method: HTTP.Methods.GET,
494
+ path: UserRestPaths.ROOT,
495
+ responses: jsonResponse({
496
+ [HTTP.ResultCodes.RS_2.Ok]: UserListSchema,
497
+ }),
498
+ },
499
+ GET_USER_BY_ID: {
500
+ method: HTTP.Methods.GET,
501
+ path: UserRestPaths.BY_ID,
502
+ request: {
503
+ params: z.object({ id: z.string() }),
504
+ },
505
+ responses: jsonResponse({
506
+ [HTTP.ResultCodes.RS_2.Ok]: UserSchema,
507
+ [HTTP.ResultCodes.RS_4.NotFound]: ErrorSchema,
508
+ }),
509
+ },
510
+ } as const;
511
+ ```
512
+
513
+ ### Method 2: Using `@api` Decorator
514
+
515
+ ```typescript
516
+ @controller({ path: '/users' })
517
+ export class UserController extends BaseController {
518
+
519
+ @api({ configs: RouteConfigs.GET_USERS })
520
+ list(context: TRouteContext<typeof RouteConfigs.GET_USERS>) {
521
+ return context.json({ users: [] }, HTTP.ResultCodes.RS_2.Ok);
522
+ }
523
+
524
+ @api({ configs: RouteConfigs.GET_USER_BY_ID })
525
+ getById(context: TRouteContext<typeof RouteConfigs.GET_USER_BY_ID>) {
526
+ const { id } = context.req.valid('param');
527
+ return context.json({ id, name: 'User' }, HTTP.ResultCodes.RS_2.Ok);
528
+ }
529
+ }
530
+ ```
531
+
532
+ ### Method 3: Using `bindRoute` (Programmatic)
533
+
534
+ ```typescript
535
+ @controller({ path: '/health' })
536
+ export class HealthCheckController extends BaseController {
537
+ constructor() {
538
+ super({ scope: HealthCheckController.name });
539
+
540
+ this.bindRoute({ configs: RouteConfigs.GET_HEALTH }).to({
541
+ handler: context => context.json({ status: 'ok' }),
542
+ });
543
+ }
544
+ }
545
+ ```
546
+
547
+ ### Method 4: Using `defineRoute` (Inline)
548
+
549
+ ```typescript
550
+ @controller({ path: '/health' })
551
+ export class HealthCheckController extends BaseController {
552
+ constructor() {
553
+ super({ scope: HealthCheckController.name });
554
+
555
+ this.defineRoute({
556
+ configs: RouteConfigs.POST_PING,
557
+ handler: context => {
558
+ const { message } = context.req.valid('json');
559
+ return context.json({ echo: message }, HTTP.ResultCodes.RS_2.Ok);
560
+ },
561
+ });
562
+ }
563
+ }
564
+ ```
565
+
566
+ ### OpenAPI Schema Integration
567
+
568
+ Use Zod with `.openapi()` for automatic documentation:
569
+
570
+ ```typescript
571
+ const CreateUserSchema = z.object({
572
+ email: z.string().email(),
573
+ name: z.string().min(1).max(100),
574
+ }).openapi({
575
+ description: 'Create user request body',
576
+ example: { email: 'user@example.com', name: 'John Doe' },
577
+ });
578
+
579
+ const UserSchema = z.object({
580
+ id: z.string().uuid(),
581
+ email: z.string().email(),
582
+ name: z.string(),
583
+ createdAt: z.string().datetime(),
584
+ }).openapi({
585
+ description: 'User response',
586
+ });
234
587
  ```
235
588
 
236
589
  ## Constants Pattern
@@ -427,6 +780,28 @@ constructor(options: IJWTTokenServiceOptions) {
427
780
  }
428
781
  ```
429
782
 
783
+ ## Environment Variables Management
784
+
785
+ 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.
786
+
787
+ **Define Keys (`src/common/environments.ts`):**
788
+ ```typescript
789
+ export class EnvironmentKeys {
790
+ static readonly APP_ENV_STRIPE_KEY = 'APP_ENV_STRIPE_KEY';
791
+ static readonly APP_ENV_MAX_RETRIES = 'APP_ENV_MAX_RETRIES';
792
+ }
793
+ ```
794
+
795
+ **Usage:**
796
+ ```typescript
797
+ import { applicationEnvironment } from '@venizia/ignis';
798
+ import { EnvironmentKeys } from '@/common/environments';
799
+
800
+ // Correct usage
801
+ const stripeKey = applicationEnvironment.get<string>(EnvironmentKeys.APP_ENV_STRIPE_KEY);
802
+ const retries = applicationEnvironment.get<number>(EnvironmentKeys.APP_ENV_MAX_RETRIES);
803
+ ```
804
+
430
805
  ## Logging Patterns
431
806
 
432
807
  ### Method Context Prefix
@@ -451,46 +826,6 @@ this.logger.info('[create] User created | ID: %s | Email: %s', user.id, user.ema
451
826
  this.logger.debug('[config] Server options: %j', this.serverOptions);
452
827
  ```
453
828
 
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
829
  ## Standardized Error Handling
495
830
 
496
831
  Use the `getError` helper and `HTTP` constants to throw consistent, formatted exceptions that the framework's error handler can process correctly.
@@ -551,163 +886,285 @@ constructor(options: IServiceOptions) {
551
886
  | Not Found | `HTTP.ResultCodes.RS_4.NotFound` | Resource not found |
552
887
  | Internal Error | `HTTP.ResultCodes.RS_5.InternalServerError` | Server errors |
553
888
 
554
- ## Route Definition Patterns
555
-
556
- Ignis supports three methods for defining routes. Choose based on your needs:
889
+ ## Control Flow Patterns
557
890
 
558
- ### Method 1: Config-Driven Routes
891
+ ### Mandatory Braces
559
892
 
560
- Define route configurations as constants:
893
+ **Always use braces for `if`, `for`, `while`, and `do-while` statements**, even for single-line bodies. Never use inline statements.
561
894
 
562
895
  ```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';
896
+ // ✅ GOOD - Always use braces
897
+ if (condition) {
898
+ doSomething();
568
899
  }
569
900
 
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;
901
+ for (const item of items) {
902
+ process(item);
903
+ }
904
+
905
+ while (running) {
906
+ tick();
907
+ }
908
+
909
+ do {
910
+ attempt();
911
+ } while (retrying);
912
+
913
+ // ❌ BAD - Never inline without braces
914
+ if (condition) doSomething();
915
+ for (const item of items) process(item);
916
+ while (running) tick();
591
917
  ```
592
918
 
593
- ### Method 2: Using `@api` Decorator
919
+ **Why braces are mandatory:**
920
+ - Prevents bugs when adding statements later
921
+ - Clearer code structure at a glance
922
+ - Consistent formatting across codebase
594
923
 
595
- ```typescript
596
- @controller({ path: '/users' })
597
- export class UserController extends BaseController {
924
+ ### Switch Statement Requirements
598
925
 
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
- }
926
+ **All switch statements must:**
927
+ 1. Use braces `{}` for each case block
928
+ 2. Include a `default` case (even if it throws)
603
929
 
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);
930
+ ```typescript
931
+ // GOOD - Braces and default case
932
+ switch (status) {
933
+ case 'active': {
934
+ activateUser();
935
+ break;
936
+ }
937
+ case 'inactive': {
938
+ deactivateUser();
939
+ break;
940
+ }
941
+ case 'pending': {
942
+ notifyAdmin();
943
+ break;
944
+ }
945
+ default: {
946
+ throw getError({
947
+ statusCode: HTTP.ResultCodes.RS_4.BadRequest,
948
+ message: `Unknown status: ${status}`,
949
+ });
608
950
  }
609
951
  }
952
+
953
+ // ❌ BAD - Missing braces and default case
954
+ switch (status) {
955
+ case 'active':
956
+ activateUser();
957
+ break;
958
+ case 'inactive':
959
+ deactivateUser();
960
+ break;
961
+ // Missing default case!
962
+ }
610
963
  ```
611
964
 
612
- ### Method 3: Using `bindRoute` (Programmatic)
965
+ **Why these rules:**
966
+ - Braces prevent variable scoping issues between cases
967
+ - Default case ensures all values are handled
968
+ - Throwing in default catches unexpected values early
969
+
970
+ ## Scope Naming
971
+
972
+ Every class extending a base class should set its scope using `ClassName.name`:
613
973
 
614
974
  ```typescript
615
- @controller({ path: '/health' })
616
- export class HealthCheckController extends BaseController {
975
+ export class JWTTokenService extends BaseService {
617
976
  constructor() {
618
- super({ scope: HealthCheckController.name });
977
+ super({ scope: JWTTokenService.name });
978
+ }
979
+ }
619
980
 
620
- this.bindRoute({ configs: ROUTE_CONFIGS['/'] }).to({
621
- handler: context => context.json({ status: 'ok' }),
622
- });
981
+ export class UserController extends BaseController {
982
+ constructor() {
983
+ super({ scope: UserController.name });
623
984
  }
624
985
  }
625
986
  ```
626
987
 
627
- ### Method 4: Using `defineRoute` (Inline)
988
+ ## Code Organization
989
+
990
+ ### Section Separator Comments
991
+
992
+ Use visual separators for major code sections in long files:
628
993
 
629
994
  ```typescript
630
- @controller({ path: '/health' })
631
- export class HealthCheckController extends BaseController {
632
- constructor() {
633
- super({ scope: HealthCheckController.name });
995
+ // ---------------------------------------------------------------------------
996
+ // Type Definitions
997
+ // ---------------------------------------------------------------------------
634
998
 
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
- }
999
+ type TMyType = { /* ... */ };
1000
+
1001
+ // ---------------------------------------------------------------------------
1002
+ // Constants
1003
+ // ---------------------------------------------------------------------------
1004
+
1005
+ const DEFAULT_OPTIONS = { /* ... */ };
1006
+
1007
+ // ---------------------------------------------------------------------------
1008
+ // Main Implementation
1009
+ // ---------------------------------------------------------------------------
1010
+
1011
+ export class MyClass {
1012
+ // ...
643
1013
  }
644
1014
  ```
645
1015
 
646
- ### OpenAPI Schema Integration
1016
+ **Guidelines:**
1017
+ - Use for files > 200 lines with distinct sections
1018
+ - Use 75-character wide separator lines
1019
+ - Descriptive section names (2-4 words)
647
1020
 
648
- Use Zod with `.openapi()` for automatic documentation:
1021
+ ### Import Organization Order
1022
+
1023
+ Organize imports in this order:
649
1024
 
650
1025
  ```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
- });
1026
+ // 1. Node built-ins (with 'node:' prefix)
1027
+ import fs from 'node:fs';
1028
+ import path from 'node:path';
1029
+
1030
+ // 2. Third-party packages (alphabetical)
1031
+ import { z } from '@hono/zod-openapi';
1032
+ import dayjs from 'dayjs';
1033
+
1034
+ // 3. Internal absolute imports (by domain/package)
1035
+ import { getError } from '@venizia/ignis-helpers';
1036
+ import { BaseEntity } from '@/base/models';
1037
+ import { UserService } from '@/services';
1038
+
1039
+ // 4. Relative imports (same feature) - LAST
1040
+ import { AbstractRepository } from './base';
1041
+ import { QueryBuilder } from '../query';
1042
+ ```
658
1043
 
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',
1044
+ **Rules:**
1045
+ - Blank line between each group
1046
+ - Alphabetical within each group
1047
+ - `node:` prefix for Node.js built-ins
1048
+ - Relative imports only for same feature/module
1049
+
1050
+ ## Performance Logging Pattern
1051
+
1052
+ Use `performance.now()` for timing critical operations:
1053
+
1054
+ ```typescript
1055
+ const t = performance.now();
1056
+
1057
+ // ... operation to measure ...
1058
+
1059
+ this.logger.info('[methodName] DONE | Took: %s (ms)', performance.now() - t);
1060
+ ```
1061
+
1062
+ **With the helper utility:**
1063
+
1064
+ ```typescript
1065
+ import { executeWithPerformanceMeasure } from '@venizia/ignis';
1066
+
1067
+ await executeWithPerformanceMeasure({
1068
+ logger: this.logger,
1069
+ scope: 'DataSync',
1070
+ description: 'Sync user records',
1071
+ task: async () => {
1072
+ await syncAllUsers();
1073
+ },
666
1074
  });
1075
+ // Logs: [DataSync] Sync user records | Took: 1234.56 (ms)
667
1076
  ```
668
1077
 
669
- ## Type Inference Patterns
1078
+ ## Advanced Patterns
670
1079
 
671
- ### Zod Schema to Type
1080
+ ### Mixin Pattern
1081
+
1082
+ Create reusable class extensions without deep inheritance:
672
1083
 
673
1084
  ```typescript
674
- // Define schema
675
- export const SignInRequestSchema = z.object({
676
- email: z.string().email(),
677
- password: z.string().min(8),
678
- });
1085
+ import { TMixinTarget } from '@venizia/ignis';
679
1086
 
680
- // Infer type from schema
681
- export type TSignInRequest = z.infer<typeof SignInRequestSchema>;
1087
+ export const LoggableMixin = <BaseClass extends TMixinTarget<Base>>(
1088
+ baseClass: BaseClass,
1089
+ ) => {
1090
+ return class extends baseClass {
1091
+ protected logger = LoggerFactory.getLogger(this.constructor.name);
1092
+
1093
+ log(message: string): void {
1094
+ this.logger.info(message);
1095
+ }
1096
+ };
1097
+ };
1098
+
1099
+ // Usage
1100
+ class MyService extends LoggableMixin(BaseService) {
1101
+ doWork(): void {
1102
+ this.log('Work started'); // Method from mixin
1103
+ }
1104
+ }
682
1105
  ```
683
1106
 
684
- ### Const Assertion for Literal Types
1107
+ ### Factory Pattern with Dynamic Class
1108
+
1109
+ Generate classes dynamically with configuration:
685
1110
 
686
1111
  ```typescript
687
- const ROUTE_CONFIGS = {
688
- '/users': { method: 'GET', path: '/users' },
689
- '/users/:id': { method: 'GET', path: '/users/:id' },
690
- } as const;
1112
+ class ControllerFactory {
1113
+ static defineCrudController<Schema extends TTableSchemaWithId>(
1114
+ opts: ICrudControllerOptions<Schema>,
1115
+ ) {
1116
+ return class extends BaseController {
1117
+ constructor(repository: AbstractRepository<Schema>) {
1118
+ super({ scope: opts.controller.name });
1119
+ this.repository = repository;
1120
+ this.setupRoutes();
1121
+ }
1122
+
1123
+ private setupRoutes(): void {
1124
+ // Dynamically bind CRUD routes
1125
+ }
1126
+ };
1127
+ }
1128
+ }
691
1129
 
692
- // Type is now narrowed to literal values
693
- type RouteKey = keyof typeof ROUTE_CONFIGS; // '/users' | '/users/:id'
1130
+ // Usage
1131
+ const UserCrudController = ControllerFactory.defineCrudController({
1132
+ controller: { name: 'UserController', basePath: '/users' },
1133
+ repository: { name: UserRepository.name },
1134
+ entity: () => User,
1135
+ });
1136
+
1137
+ @controller({ path: '/users' })
1138
+ export class UserController extends UserCrudController {
1139
+ // Additional custom routes
1140
+ }
694
1141
  ```
695
1142
 
696
- ### Generic Type Constraints
1143
+ ### Value Resolver Pattern
1144
+
1145
+ Support multiple input types that resolve to a single value:
697
1146
 
698
1147
  ```typescript
699
- export class DefaultCRUDRepository<
700
- Schema extends TTableSchemaWithId = TTableSchemaWithId
701
- > {
702
- // Schema is constrained to have an 'id' column
703
- }
1148
+ export type TValueOrResolver<T> = T | TResolver<T> | TConstructor<T>;
704
1149
 
705
- export interface IAuthService<
706
- SIRQ extends TSignInRequest = TSignInRequest,
707
- SIRS = AnyObject,
708
- > {
709
- signIn(context: Context, opts: SIRQ): Promise<SIRS>;
1150
+ export const resolveValue = <T>(valueOrResolver: TValueOrResolver<T>): T => {
1151
+ if (typeof valueOrResolver !== 'function') {
1152
+ return valueOrResolver; // Direct value
1153
+ }
1154
+ if (isClassConstructor(valueOrResolver)) {
1155
+ return valueOrResolver as T; // Class constructor (return as-is)
1156
+ }
1157
+ return (valueOrResolver as TResolver<T>)(); // Function resolver
1158
+ };
1159
+
1160
+ // Usage
1161
+ interface IOptions {
1162
+ entity: TValueOrResolver<typeof User>;
710
1163
  }
1164
+
1165
+ // All valid:
1166
+ const opts1: IOptions = { entity: User }; // Direct class
1167
+ const opts2: IOptions = { entity: () => User }; // Resolver function
711
1168
  ```
712
1169
 
713
1170
  ## Summary Table
@@ -718,11 +1175,19 @@ export interface IAuthService<
718
1175
  | Type alias prefix | `T` (e.g., `TUserRequest`) |
719
1176
  | Class naming | PascalCase with suffix (e.g., `UserController`) |
720
1177
  | File naming | kebab-case (e.g., `user.controller.ts`) |
1178
+ | Private fields | Underscore prefix (`_dataSource`) |
721
1179
  | Binding keys | `@app/[component]/[feature]` |
722
1180
  | Constants | Static readonly class (not enums) |
723
1181
  | Barrel exports | `index.ts` at every folder level |
724
1182
  | Error format | `[ClassName][method] Message` |
725
1183
  | Logging format | `[method] Message \| Key: %s` |
726
1184
  | Default options | `DEFAULT_OPTIONS` constant |
1185
+ | Type safety | No `any` or `unknown` allowed |
727
1186
  | Scope naming | `ClassName.name` |
728
-
1187
+ | Arguments | Options object (`opts`) |
1188
+ | Exports | Named exports only |
1189
+ | Return types | Explicitly defined |
1190
+ | Control flow | Always use braces (`{}`) |
1191
+ | Switch statements | Braces + default case required |
1192
+ | Imports | Node → Third-party → Internal → Relative |
1193
+ | Function naming | `generate*`, `build*`, `to*`, `is*`, `extract*` |